{"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":"02960ff0-2506-4336-9591-0308ffa10a18","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"3e8cf701d0e6312bde54cb8844909004dbc4002b71d7660886d68e9b531ce487","dependsOn":["build_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":"accepted"},{"acceptedSubmissionHash":"632fa0de2123842c9f752b561e1899404ada3f31c52e1d9dae5f4b28fc56b369","dependsOn":["build_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":"accepted"},{"acceptedSubmissionHash":"b6a3866187301822261cd605ec5c6f235ef400aabfe75711b6a3fa5ffd54a275","dependsOn":["build_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":"accepted"},{"acceptedSubmissionHash":"f715d56c1c114f7da11496db850bd9c79e9748c29153635a83495716b777fb2e","dependsOn":["build_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":"accepted"},{"acceptedSubmissionHash":"af7494d68bc3f3321107f4c2e4d8ace159852b626ec3f623cb5299353f58f339","dependsOn":["build_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":"accepted"},{"acceptedSubmissionHash":"50e29daea377520dcfa66076858da6b79741e89e5b4526cdeb8ba5f19c51c59a","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"58d26ac998ea8aad9657a4db78710160ee942bdeebddd8ac390ea5169cd57372","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"48b09143614c9faedc76c9c6505cd1387b04f9bc43876a6a109b69a8852c3f4e","dependsOn":["build_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":"accepted"}],"objective":"# BLOOM — Mainnet Smart Contracts V2.4\n\nBuild, test, verify and deploy BLOOM DeFi farming smart contracts on Ethereum Mainnet (Chain ID 1). CONTRACTS ONLY. Do not create a website, token or liquidity pool.\n\n**TOKEN:** I will launch ERC-20 BLOOM independently. Do not mint or create any token. BLOOM is the farming reward token. Use its verified address when available; never substitute a fake token.\n\n**OWNER:** The authenticated IMD-paying requester wallet must own Factory, Treasury and all admin roles, not the Swarm worker. Resolve ownership from verified launch context. Support irreversible selective renouncement of non-Treasury roles.\n\n**ARCHITECTURE:** Deploy FarmFactory and Treasury now. Support independent sIMD, native ETH and BLOOM farms: one pool = one contract, with separate deposits, internal shares, fees, reward reserves and schedules. Owner can add new farms later. No IMD deposit farm.\n\n**SHARES:** Use non-transferable 18-decimal internal shares: stakedSIMD, stakedETH and stakedBLOOM. Shares represent proportional underlying ownership. Use full-precision mulDiv, accounted assets, minSharesOut, zero-share rejection and donation/inflation protection. Handle rounding dust safely.\n\n**WITHDRAWAL — MODEL B:** Deposit fee 0%. Withdrawal fee 15% of the full compounded share-backed position. User receives 85%; 14% stays in the same farm and increases assets per remaining share; 1% goes to Treasury in the underlying asset. Burn withdrawn shares before redistribution. Partial withdrawers may benefit from remaining shares; this is intentional. If no shares remain, all 15% goes to Treasury. No eligibility delay. Shares cannot transfer or bypass withdrawal fees.\n\n**ACCOUNTING:** Track underlying principal/compounded assets separately from BLOOM reward reserves and accrued liabilities, including in the BLOOM farm where both use the same token. Never count reward funding as user assets. Enforce asset solvency, precise fee conservation and safe rounding. When a pool becomes empty, isolate residual dust without affecting outstanding rewards or future depositors.\n\n**FARM REWARDS:** BLOOM emissions are separate, immediately claimable without withdrawal fees. Each farm has independently funded rewards. Owner sets reward amounts and distribution duration in days, boosts emissions, and selects Continue Existing Epoch or Restart New Epoch. Funding alone never starts emissions. Settle accrued rewards before changes; only unvested plus newly allocated rewards may be rescheduled. Prevent over-allocation and return zero-staker emissions to reserve.\n\n**ETH FARM:** Accept native ETH deposits and withdrawals, wrapping through WETH9 internally if needed. Support a secure WETH withdrawal fallback if ETH transfer fails. Prevent reentrancy.\n\n**sIMD FARM:** Use the verified official sIMD token. Keep integration compatible with official IMD staking for future website use. Do not create a replacement token or assume 1:1 conversion.\n\n**TREASURY:** Receives 1% of gross withdrawals, or 15% when no shares remain. No access to user assets or reward liabilities. Future trading-fee allocation and buyback will be separate contracts; do not implement them now.\n\n**ADMIN:** Owner can create farms, whitelist assets, fund rewards, set amount/days, Continue/Restart epochs, boost rates, pause deposits and selectively renounce roles. Withdrawals and claims remain available. Prevent accidental renouncement before essential setup and prohibit hidden role recovery.\n\n**SECURITY:** Use established Solidity libraries. Update reward indices before every deposit, withdrawal, claim or emission change. Define safe fee rounding and prevent fee avoidance through tiny repeated withdrawals. Test immediate deposits, fee sniping, multi-wallet splitting, 5–1,000 users, partial/full withdrawals, last depositor, share rounding, donation attacks, ETH failures, reward solvency, epoch changes, reentrancy and permissions. Report actual tests and residual risks.\n\n**DEPLOYMENT:** Deploy and verify Factory, Treasury and safely configurable infrastructure on Ethereum Mainnet now. Deploy farms only with verified real underlying and BLOOM reward-token addresses. If BLOOM is unavailable, preserve owner-controlled creation later without redeploying Factory/Treasury. Do not activate deposits or emissions prematurely.\n\n**DELIVER:** Verified contract addresses, complete Solidity source, ABIs, deployment scripts, actual test results, security findings and owner instructions. Future website integration must be supported. Do not silently change economic rules.","parentJobId":null,"planHash":"12b8272d6e047d9d08f343747d3af7d1046ffe8cf98fe9fcb5bf61298bd73713","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"02960ff0-2506-4336-9591-0308ffa10a18","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2282-bloom-mainnet"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51315","feedbackHash":"d5da52bf5153cc2ec25a1f797f3bc8522ca564bd3887e1f5a80768db4c647b72","nodeKey":"audit_economics","submissionHash":"3e8cf701d0e6312bde54cb8844909004dbc4002b71d7660886d68e9b531ce487","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52222","feedbackHash":"ebb7207210f9e3114ac961bb4650dff8e595ec20f198c661f34554147ffce521","nodeKey":"audit_flow","submissionHash":"632fa0de2123842c9f752b561e1899404ada3f31c52e1d9dae5f4b28fc56b369","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51154","feedbackHash":"d0244a1fd5bb348067dc66231e3a04d19ab91002c8f97c00aa3bdeea34355656","nodeKey":"audit_judge","submissionHash":"b6a3866187301822261cd605ec5c6f235ef400aabfe75711b6a3fa5ffd54a275","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50907","feedbackHash":"06cb8965cd6e44ebf07ecd922a39d0e24424dbce551e0e75da23467b49ad9106","nodeKey":"audit_judge","submissionHash":"019b7a948a51584a3cbd9e8106bd61f90641e0e45ad895476a6498b9e8281ab8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52613","feedbackHash":"013a4440b1070ff66889c840acc88c7b63581ffecd134febc85be6f644de6ec1","nodeKey":"audit_math","submissionHash":"f715d56c1c114f7da11496db850bd9c79e9748c29153635a83495716b777fb2e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52182","feedbackHash":"ebf4a00c921031100d1000a65c04ce99853396379dbdae628c74bf53ff4fc586","nodeKey":"audit_permissions","submissionHash":"af7494d68bc3f3321107f4c2e4d8ace159852b626ec3f623cb5299353f58f339","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51250","feedbackHash":"afc9f89ea504981a63c833ace34cf135d1a797ccdc4a44086f6d2df537cb8430","nodeKey":"build_contract_project","submissionHash":"50e29daea377520dcfa66076858da6b79741e89e5b4526cdeb8ba5f19c51c59a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51456","feedbackHash":"30dcc93bbc3595e3b966db0f5157e6afbdd97f2b6fd70850ba9e20e68763dd09","nodeKey":"build_contract_project","submissionHash":"591676e0e55a4bbbf31b1900cfd144c4347ce983dc0821c7b6aabc4190de80b2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50973","feedbackHash":"0e8754666601d249a2f624ae62febd3cd5dc2c697e20a86d922066e99cd934f3","nodeKey":"manifest","submissionHash":"021ef6ae0ed6c61a368692192618506c2e2bfa26bdd0d5083a3e46d3169dfc84","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51128","feedbackHash":"5d61cc93c6ef5494d10a63c1bd82ec164490db3014b92fbbf25d21ec90ca0eba","nodeKey":"manifest","submissionHash":"58d26ac998ea8aad9657a4db78710160ee942bdeebddd8ac390ea5169cd57372","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"54310","feedbackHash":"a885fceb2f8fba93bbd252bb70b547980f5cd3fb3592151af587d794e9a64901","nodeKey":"write_foundry_tests","submissionHash":"f34c0b006e460880dd8a5ed17865f5e6359aff64560485a1eebe083d7766b604","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52614","feedbackHash":"6896ffc8534f70bf01a28d2327b2cadea65bcdbde87dbe23b3c92061dcbb4af2","nodeKey":"write_foundry_tests","submissionHash":"48b09143614c9faedc76c9c6505cd1387b04f9bc43876a6a109b69a8852c3f4e","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"70e908d6e7001a7763d0df445a6e58c513123f33ba6290928ee14c7988f418a0","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d5e40d018a0986f3","findings":[{"citation":"resolved","description":"Merged from audit_math, audit_permissions, audit_flow and audit_economics (same root cause, four reports). Donation protection only covers raw token transfers: previewDeposit divides by accountedAssets, not balanceOf. The Model B retention is a second inflation channel with no bound. withdraw() burns the exiting shares and credits retained (14% of gross) to accountedAssets regardless of how many shares remain (line 214), and an 18-decimal farm mints exactly 1 share for a 1-wei first deposit (initialShareScale = 1, line 141). There is no virtual share/asset offset and no minimum live share balance. An unprivileged first depositor therefore sets the price of one share to 0.14 * X for any X they route through a second wallet, paying only the 1% Treasury part (the other 14% lands on their own single share). accountedAssets/totalShares never decreases (deposits mint at the current ratio with floor rounding, withdrawals retain), so the state is permanent while the attacker keeps the share; the owner has no reset path short of creating a new farm. Consequences: previewDeposit(amount) returns 0 for every amount below the share price so deposit() reverts ZeroShares (line 184) forever for those amounts; every larger deposit loses (assets mod sharePrice) to the existing holder on entry, and minSharesOut does not help a victim who is not front-run because previewDeposit honestly reports the rounded-down count. Affects the ETH farm (WETH, 18 dec) and any 18-decimal sIMD/BLOOM farm. The same state also arises without malice when a sole depositor withdraws all but one share. The brief requires donation/inflation protection and safe rounding dust; docs/SECURITY.md E3 only says minSharesOut matters if redistribution raises one share's value and does not disclose the deliberate attack or the deposit DoS. Suggested fix that preserves Model B: ERC-4626-style virtual offset (e.g. +1e6 virtual shares and +1 virtual asset in previewDeposit/previewWithdraw, with the virtual share's residual isolated like reward dust when the pool empties), or lock a minimum number of dead shares on the first deposit so one share can never carry a material amount.","line":214,"path":"src/BloomFarm.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {BloomFarm} from \"src/BloomFarm.sol\";\nimport {FarmFactory} from \"src/FarmFactory.sol\";\nimport {Treasury} from \"src/Treasury.sol\";\n\n/// @dev Minimal exact-transfer 18-decimal ERC-20, test-only.\ncontract ProofToken is IERC20Metadata {\n    string public name = \"T\";\n    string public symbol = \"T\";\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n        totalSupply += amount;\n        emit Transfer(address(0), to, amount);\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        emit Approval(msg.sender, spender, amount);\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(msg.sender, to, amount);\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(from, to, amount);\n        return true;\n    }\n}\n\n/// @notice Share-price inflation through the farm's own 14% retention, not through a donation.\n/// An attacker who is the first depositor mints 1 share for 1 wei, then deposits and withdraws\n/// 1,000 tokens from a second wallet. The 14% retained fee (140 tokens) now backs that single share.\n/// Every later depositor loses up to one share price (140 tokens) to rounding, and any deposit\n/// below 140 tokens reverts with ZeroShares. The attacker's only cost is the 1% Treasury fee.\ncontract InflationViaRetentionTest is Test {\n    address internal owner = makeAddr(\"owner\");\n    address internal attackerA = makeAddr(\"attackerA\");\n    address internal attackerB = makeAddr(\"attackerB\");\n    address internal victim = makeAddr(\"victim\");\n\n    ProofToken internal simd;\n    ProofToken internal bloom;\n    ProofToken internal weth;\n    Treasury internal treasury;\n    FarmFactory internal factory;\n    BloomFarm internal farm;\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        simd = new ProofToken();\n        bloom = new ProofToken();\n        weth = new ProofToken();\n        treasury = new Treasury(owner);\n        factory = new FarmFactory(owner, address(treasury));\n        vm.startPrank(owner);\n        factory.configureToken(BloomFarm.Kind.SIMD, address(simd), keccak256(\"simd\"));\n        factory.configureToken(BloomFarm.Kind.BLOOM, address(bloom), keccak256(\"bloom\"));\n        factory.configureToken(BloomFarm.Kind.ETH, address(weth), keccak256(\"weth\"));\n        factory.setWhitelisted(address(simd), true);\n        factory.setWhitelisted(address(bloom), true);\n        factory.setWhitelisted(address(weth), true);\n        farm = factory.createFarm(BloomFarm.Kind.SIMD);\n        farm.setDepositsPaused(false);\n        vm.stopPrank();\n    }\n\n    function _deposit(address who, uint256 amount) internal returns (uint256 shares) {\n        simd.mint(who, amount);\n        vm.startPrank(who);\n        simd.approve(address(farm), amount);\n        // Honest slippage bound: exactly what the public preview promises at send time.\n        shares = farm.deposit(amount, farm.previewDeposit(amount));\n        vm.stopPrank();\n    }\n\n    function _inflate() internal {\n        // Step 1: first depositor mints exactly one share for one wei.\n        _deposit(attackerA, 1);\n        assertEq(farm.totalShares(), 1);\n        // Step 2: second wallet deposits 1,000 tokens and withdraws everything at once.\n        uint256 sharesB = _deposit(attackerB, 1_000 ether);\n        vm.prank(attackerB);\n        farm.withdraw(sharesB, 0, payable(attackerB), false);\n        // 14% (140 tokens) stayed in the farm and now backs the attacker's single share.\n        assertEq(farm.totalShares(), 1);\n        assertEq(farm.accountedAssets(), 140 ether + 1);\n    }\n\n    function testSmallDepositsAreBrickedAfterRetentionInflation() public {\n        _inflate();\n        // Expected: a 100-token deposit into a farm mints a non-zero number of shares.\n        // Actual: previewDeposit returns 0 and deposit() reverts with ZeroShares forever,\n        // because accountedAssets/totalShares can only grow.\n        assertGt(farm.previewDeposit(100 ether), 0, \"100-token deposit mints zero shares\");\n    }\n\n    function testVictimLosesRoundingRemainderToAttacker() public {\n        _inflate();\n        uint256 victimDeposit = 280 ether + 1;\n        uint256 victimShares = _deposit(victim, victimDeposit);\n        // Victim's share-backed position right after depositing, before any fee.\n        (uint256 victimGross,,,) = farm.previewWithdraw(victimShares);\n        // Expected: a fresh depositor's position is worth (almost) exactly what they deposited.\n        // Actual: 1 share worth 210 tokens for a 280-token deposit; 70 tokens moved to attacker A.\n        assertGe(victimGross, victimDeposit * 99 / 100, \"victim lost more than 1% to share rounding\");\n\n        // Attacker closes out with a profit, net of the 15% fee on both withdrawals.\n        vm.prank(attackerA);\n        farm.withdraw(1, 0, payable(attackerA), false);\n        uint256 attackerOut = simd.balanceOf(attackerA) + simd.balanceOf(attackerB);\n        assertLe(attackerOut, 1_000 ether + 1, \"attacker extracted value from the victim\");\n    }\n}","reproduction":"Fresh 18-decimal SIMD farm, deposits unpaused (same for the ETH farm). (1) attackerA: deposit(1, 1) -> sharesOf 1, totalShares 1, accountedAssets 1. (2) attackerB: deposit(1000e18, previewDeposit) -> 1000e18 shares; withdraw(1000e18, 0, attackerB, false) -> gross 1000e18, net 850e18 paid, 10e18 to Treasury, retained 140e18. State: totalShares = 1, accountedAssets = 140e18 + 1. Expected: a pool with one tiny position where small deposits still mint proportional shares. Actual: previewDeposit(100e18) == 0 and deposit(100e18, 1) reverts ZeroShares forever. (3) victim: deposit(280e18+1, previewDeposit(280e18+1) = 1) succeeds with 1 share whose previewWithdraw gross is 210e18+1 (70e18 moved to attackerA's share at entry). (4) attackerA: withdraw(1, 0, attackerA, false) receives 178.5e18; attackers' total out 1028.5e18 vs 1000e18+1 in (+28.5e18 profit after paying every 15% fee), victim's exit nets 203.49e18 on 280e18 deposited. Verified: forge test --match-path test/scratch/Proof_1dd1fc1fd04c.t.sol fails both tests on the current code ('100-token deposit mints zero shares: 0 <= 0' and 'victim lost more than 1% to share rounding: 210000000000000000001 < 277200000000000000000'). The three other specialist proofs (Proof_bf1c9e4aa6bf, Proof_54eb7011497f, Proof_b5632552d99c) fail for the same reason at 1 wei, 1 token and 1000 token scales.","severity":"high","snippet":"        accountedAssets = accountedAssets - gross + retained;","title":"Share price can be inflated without bound through the retained 14% withdrawal fee: one surviving 1-wei share absorbs 14% of any round trip, so later deposits revert ZeroShares or lose up to one share-"},{"citation":"resolved","description":"From audit_economics. The only test for 'no shares remain' is shares == totalShares, and there is no minimum position size. Anyone who holds one 1-wei share in a farm guarantees the condition is never true for anyone else, so a sole real depositor's full exit sends 1% to the Treasury and 14% to the squatter instead of 15% to the Treasury. Partial-exit recapture (E2) is intentional per the brief, but this variant costs 1 wei once, persists, and its victim is the Treasury. Same root (no minimum share size) as finding 1 but a different consequence and a different fix: treat positions below a minimum share count or below one asset unit of backing as empty when deciding the Treasury share, or route retention to the Treasury when backing per remaining share would exceed a bound.","line":156,"path":"src/BloomFarm.sol","reproduction":"Fresh 18-decimal SIMD farm. (1) attacker: deposit(1, 1) -> 1 share. (2) victim: deposit(1000e18, 1) -> 1000e18 shares; withdraw(1000e18, 0, victim, false). Expected per brief (nothing economically left after the sole real depositor exits): 150e18 to Treasury. Actual: treasuryFee = 10e18, retained 140e18 credited to accountedAssets (140e18+1) backing the attacker's 1 share. (3) attacker: withdraw(1, 0, attacker, false) -> receives 119e18; Treasury received 21e18 more, total 31e18+1 instead of 150e18. Reproduced in test/scratch/Lows.t.sol testSquatterDefeatsFullTreasuryFee (logs: squatter net 119000000000000000000, treasury 31000000000000000001).","severity":"low","snippet":"            shares == totalShares ? fee : Math.mulDiv(gross, TREASURY_FEE_BPS, 10_000, Math.Rounding.Ceil);","title":"A permanent 1-wei squatter share defeats the 'all 15% to Treasury when no shares remain' rule for every other holder's last exit"},{"citation":"resolved","description":"From audit_permissions. The renouncement guard treats FUND_ROLE and EMISSION_ROLE symmetrically (both only need hasScheduled), but funding is useless without scheduling. After EMISSION_ROLE is renounced: fundRewards still succeeds and every token funded is unrecoverable; zero-staker emissions and recycled dust are returned to rewardReserve by _updateGlobal and _recycleEmptyRewardDust; rewardReserve is part of rewardBacking so sweepSurplus excludes it. The brief asked to prevent accidental renouncement before essential setup; the guard lets the owner keep a role whose only remaining effect is to lock tokens. Minimal fix preserving the design: require FUND_ROLE to be renounced together with or before EMISSION_ROLE, and reject renouncing EMISSION_ROLE while rewardReserve != 0 or an epoch is still vesting.","line":476,"path":"src/BloomFarm.sol","reproduction":"Owner: fundRewards(1000e18); setEpoch(1000e18, 10, Restart); renounceRoles(EMISSION_ROLE, renouncementHash(EMISSION_ROLE)) succeeds (hasActivated and hasScheduled true). Nobody stakes. warp +11 days; checkpoint() -> rewardReserve == 1000e18. setEpoch(1, 1, Restart) reverts RoleDisabled(2); fundRewards(1000e18) still succeeds and rewardReserve == 2000e18; sweepSurplus(bloom) reverts NoSurplus. Expected: renouncement blocked while reserve is reschedulable, or reserve recoverable. Actual: 2000e18 BLOOM permanently locked. Reproduced in test/scratch/Lows.t.sol testRenounceEmissionStrandsReserve.","severity":"low","snippet":"        if (roles & (FUND_ROLE | EMISSION_ROLE) != 0 && !hasScheduled) revert EssentialSetupIncomplete();","title":"Renouncing EMISSION_ROLE while FUND_ROLE stays active, or while reserve is non-zero, strands BLOOM in rewardReserve with no path to users or Treasury"},{"citation":"resolved","description":"From write_foundry_tests. With a 0-17 decimal underlying, after fee redistribution has raised backing per share above the deposit's share quantum, a one-base-unit deposit mints non-zero shares (so _prepareDeposit accepts it: shares != 0 and >= minSharesOut), but previewWithdraw of the entire position returns gross 0 and withdraw reverts ZeroAssets even with minAssetsOut = 0. The holder must add principal or wait for another exit; a deposit pause blocks topping up. Bounded to sub-unit dust, no theft. Fix: reject a deposit whose full position would redeem to zero before taking funds, or allow a full-position dust closure that burns the shares and forfeits the sub-unit remainder to the farm.","line":208,"path":"src/BloomFarm.sol","reproduction":"6-decimal exact-transfer SIMD asset, active farm. Alice and Bob each deposit 1000 base units -> 1e15 shares each. Alice withdraws 1e15 shares: accountedAssets = 1140, totalShares = 1e15. Carol deposit(1, 1): succeeds, mints 877192982456 shares, accountedAssets = 1141. Carol withdraw(877192982456, 0, carol, false): gross = floor(877192982456 * 1141 / 1000877192982456) = 0. Expected: the unclosable deposit rejected up front, or an accepted full position closable. Actual: revert ZeroAssets(), Carol's unit is held and her position stays open. Reproduced in test/scratch/Lows.t.sol testDustDepositCannotClose.","severity":"low","snippet":"        if (gross == 0) revert ZeroAssets();","title":"An accepted low-decimal dust deposit mints shares whose full redemption floors to zero, so the position cannot be closed (withdraw reverts ZeroAssets)"},{"citation":"resolved","description":"From audit_flow. claim() calls _assertSolvent() before and after paying rewards; for sIMD and ETH farms that check also requires asset.balanceOf(farm) >= accountedAssets, which has nothing to do with the BLOOM reward reserve being paid. If the underlying balance drops by one base unit (a negatively rebasing or slashing sIMD, an admin burn, a token upgrade), every claim() reverts even though rewardToken fully covers rewardBacking(). The brief says claims must remain available. Recovery needs someone to donate the shortfall in the underlying. Only reachable with a token outside the documented exact-transfer assumption, hence low. Minimal fix: in claim() assert only the reward side (rewardToken balance >= rewardBacking, plus accountedAssets when asset == rewardToken).","line":227,"path":"src/BloomFarm.sol","reproduction":"sIMD farm; alice deposit(100e18, 1); owner fundRewards(1000e18), setEpoch(1000e18, 10, Restart); warp +5 days (alice earned 500e18, farm holds 1000e18 BLOOM). Remove 1 wei of sIMD from the farm (MockToken.burn(farm, 1): asset balance 100e18-1 < accountedAssets 100e18). alice claim(alice): Expected 500e18 BLOOM paid since the reward token covers all liabilities. Actual: revert Insolvent. Reproduced in test/scratch/Lows.t.sol testAssetShortfallBlocksClaim.","severity":"low","snippet":"        _assertSolvent();","title":"claim() of BLOOM rewards reverts Insolvent on any shortfall of the unrelated underlying asset balance"},{"citation":"resolved","description":"From audit_permissions; documented as a trust assumption, not a permission bypass. The pause is required by the brief and the fee-sniping redistribution (SECURITY.md E1) is an accepted consequence of Model B. While deposits are paused only existing shareholders capture the 14% of every exit, and the owner alone chooses when deposits reopen. An owner holding shares can keep deposits paused during large exits, excluding competing snipers, then unpause for its own deposit. Record under the owner's DEPOSIT_ROLE powers until it is renounced.","line":298,"path":"src/BloomFarm.sol","reproduction":"Owner holds 100e18 shares beside Alice (10000e18 shares, 10000e18 tokens). Owner setDepositsPaused(true). Alice withdraw(10000e18, 0, alice, false): fee 1500e18, Treasury 100e18, retained 1400e18 credited to the owner's 100e18 surviving shares (now backed by 1500e18). Outside parties could not deposit to share in the 1400e18. Owner setDepositsPaused(false). Expected per brief: pause blocks only new deposits (it does); the economic effect is that the pauser alone collects the retained fee of exits made while paused.","severity":"info","snippet":"    function setDepositsPaused(bool paused) external onlyRole(DEPOSIT_ROLE) nonReentrant {","title":"Trust assumption: DEPOSIT_ROLE pause does not affect withdrawals, so a share-holding owner can monopolise the 14% retention of exits made while paused"}],"hash":"019b7a948a51584a3cbd9e8106bd61f90641e0e45ad895476a6498b9e8281ab8","nodeId":"c177786f-3d00-42ee-b7f9-18a80e2d869b","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the report. No source files were changed. The scratch tests live under `test/scratch/`, which is not kept.\n\n**Outcome**\n\nSix findings, one high and four low plus one documented trust assumption, and a coverage record answering all 20 entry points plus the manifest. The project's own suite passes in full.\n\n| Metric | Result |\n|---|---|\n| Project tests | 107 passed, 0 failed |\n| Specialist proofs run | 4 files, 6 tests, all fail on current code |\n| Coverage rows | 21 (20 entry points + launch manifest) |\n\n**Findings kept, after reproduction**\n\n1. **High, merged from four specialists.** The 14% retained fee is credited to `accountedAssets` after the withdrawn shares burn, with no virtual offset or minimum live share. A first depositor minting one share for 1 wei, followed by a 1,000-token round trip from a second wallet, leaves one share backed by 140 tokens at a 1% cost. Deposits below the share price then revert `ZeroShares` forever. Larger deposits lose up to one share price on entry, and an honest `minSharesOut` from `previewDeposit` does not catch it. The economics specialist's proof is attached. It fails on this code with the exact figures in the reproduction.\n2. **Low.** A permanent 1-wei squatter share makes `shares == totalShares` never true for others. A sole real depositor's full exit sends 1% to Treasury instead of 15%, and the squatter nets 119 of 140 tokens.\n3. **Low.** Renouncing EMISSION_ROLE while FUND_ROLE stays active, or while reserve is non-zero, strands BLOOM. Funding still succeeds, zero-staker emissions recycle into reserve, and `sweepSurplus` excludes it.\n4. **Low.** On a 6-decimal asset, a one-unit dust deposit mints shares whose full redemption floors to zero, so the position cannot be closed.\n5. **Low.** `claim()` asserts underlying solvency too. A 1-wei shortfall in sIMD blocks all BLOOM claims even though the reward token covers every liability.\n6. **Info.** A share-holding owner can keep deposits paused during large exits to monopolise retention. Trust assumption on DEPOSIT_ROLE, not a bypass.\n\n**Own pass and dropped leads**\n\nMy probes of the ETH round trip with a rejecting recipient, Continue-then-Restart budget conservation, zero-staker reserve return, and two-user fraction carry all held. Reward index arithmetic cannot over-distribute: the per-interval distribution is bounded by emitted amounts and the dropped remainder stays as liability until recycled. Every Slither and Aderyn line was a false positive on inspection. Exact-balance equalities are deliberate rebasing rejection, the modulo lines are remainder carries, `Treasury.payNative` does check zero, and the factory's flagged \"reentrancy\" is a constructor call. Deployed sizes are within limits. The manifest's two contracts match their constructors in order and count and make no external calls at construction.","treeHash":null,"usage":{"cachedInputTokens":1849720,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":32949,"runtime":"claude","turns":32,"wallClockMs":511106}},{"artifacts":[],"attempt":1,"bundleHash":"7e66c40128d2dac0cf20d7c9cff93b7f8d34d69fc60d0c0b34220d1229d98298","device":"2717ac8e1d58fd36","findings":[],"hash":"021ef6ae0ed6c61a368692192618506c2e2bfa26bdd0d5083a3e46d3169dfc84","nodeId":"7ed326be-5696-4f36-883f-3f73630dc8e5","outcome":"completed","summary":"Created [launch.json](/root/.identitymd/work/02960ff0-2506-4336-9591-0308ffa10a18/7ed326be-5696-4f36-883f-3f73630dc8e5/launch.json) with Treasury first, then FarmFactory, using `$owner` and the Treasury dependency.\n\nManifest structure and compiled constructor ABIs checked. `forge build` passed with existing lint warnings; `forge test` passed all 74 tests using the required seed.\n\nOnly `launch.json` changed outside scratch files. No Mainnet deployment was performed.","treeHash":"0b72530673367bfedf31fc925c96317e96ba117e","usage":{"cachedInputTokens":605184,"inputTokens":65037,"model":"gpt-6-astra","outputTokens":3472,"runtime":"codex","turns":6,"wallClockMs":264541}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e3a598aae0640402","findings":[{"citation":"resolved","description":"The brief requires donation/inflation protection. Donations are excluded (previewDeposit divides by accountedAssets, not the balance), but the Model B retention is a second inflation source that the code does not bound: withdraw() credits `retained` (14% of gross) to accountedAssets (line 214, `accountedAssets = accountedAssets - gross + retained;`) regardless of how many shares remain, and an 18-decimal farm mints exactly 1 share for a 1 wei first deposit (initialShareScale = 1). An attacker who is the first depositor can therefore set the price of one share to 0.14 * X tokens for any X they can route through a second wallet, paying only the 1% Treasury fee (0.01 * X); the other 14% lands on their own single share. accountedAssets/totalShares never decreases (deposits mint at the current ratio, withdrawals retain), so the state is permanent: previewDeposit(amount) returns 0 for every amount below the share price and deposit() reverts with ZeroShares, and every larger deposit loses the remainder below a whole share to the attacker. The author's own test testDepositSlippageZeroSharesAndRoundingDust exercises exactly this sequence at the wei scale (1, 1000, withdraw -> 141 per share) and treats the loss as dust; at the 1,000-token scale it is 140 tokens per share. minSharesOut does not help a victim who is not front-run: previewDeposit honestly reports 1 share and the deposit passes. Affects the ETH farm (WETH, 18 dec) and any 18-decimal sIMD/BLOOM farm; for a 6-decimal underlying the first deposit mints 1e12 shares so the effect is negligible. Fix: add an ERC-4626-style virtual offset (e.g. +1e6 virtual shares and +1 virtual asset in previewDeposit/previewWithdraw, with the virtual share's residual treated like reward dust when the pool empties), or lock a minimum number of dead shares on the first deposit so that one share can never be worth more than a few wei. The attached test passes with either fix.","line":141,"path":"src/BloomFarm.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {BloomFarm} from \"src/BloomFarm.sol\";\nimport {FarmFactory} from \"src/FarmFactory.sol\";\nimport {Treasury} from \"src/Treasury.sol\";\n\n/// @dev Minimal exact-transfer 18-decimal ERC-20, test-only.\ncontract ProofToken is IERC20Metadata {\n    string public name = \"T\";\n    string public symbol = \"T\";\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n        totalSupply += amount;\n        emit Transfer(address(0), to, amount);\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        emit Approval(msg.sender, spender, amount);\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(msg.sender, to, amount);\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(from, to, amount);\n        return true;\n    }\n}\n\n/// @notice Share-price inflation through the farm's own 14% retention, not through a donation.\n/// An attacker who is the first depositor mints 1 share for 1 wei, then deposits and withdraws\n/// 1,000 tokens from a second wallet. The 14% retained fee (140 tokens) now backs that single share.\n/// Every later depositor loses up to one share price (140 tokens) to rounding, and any deposit\n/// below 140 tokens reverts with ZeroShares. The attacker's only cost is the 1% Treasury fee.\ncontract InflationViaRetentionTest is Test {\n    address internal owner = makeAddr(\"owner\");\n    address internal attackerA = makeAddr(\"attackerA\");\n    address internal attackerB = makeAddr(\"attackerB\");\n    address internal victim = makeAddr(\"victim\");\n\n    ProofToken internal simd;\n    ProofToken internal bloom;\n    ProofToken internal weth;\n    Treasury internal treasury;\n    FarmFactory internal factory;\n    BloomFarm internal farm;\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        simd = new ProofToken();\n        bloom = new ProofToken();\n        weth = new ProofToken();\n        treasury = new Treasury(owner);\n        factory = new FarmFactory(owner, address(treasury));\n        vm.startPrank(owner);\n        factory.configureToken(BloomFarm.Kind.SIMD, address(simd), keccak256(\"simd\"));\n        factory.configureToken(BloomFarm.Kind.BLOOM, address(bloom), keccak256(\"bloom\"));\n        factory.configureToken(BloomFarm.Kind.ETH, address(weth), keccak256(\"weth\"));\n        factory.setWhitelisted(address(simd), true);\n        factory.setWhitelisted(address(bloom), true);\n        factory.setWhitelisted(address(weth), true);\n        farm = factory.createFarm(BloomFarm.Kind.SIMD);\n        farm.setDepositsPaused(false);\n        vm.stopPrank();\n    }\n\n    function _deposit(address who, uint256 amount) internal returns (uint256 shares) {\n        simd.mint(who, amount);\n        vm.startPrank(who);\n        simd.approve(address(farm), amount);\n        // Honest slippage bound: exactly what the public preview promises at send time.\n        shares = farm.deposit(amount, farm.previewDeposit(amount));\n        vm.stopPrank();\n    }\n\n    function _inflate() internal {\n        // Step 1: first depositor mints exactly one share for one wei.\n        _deposit(attackerA, 1);\n        assertEq(farm.totalShares(), 1);\n        // Step 2: second wallet deposits 1,000 tokens and withdraws everything at once.\n        uint256 sharesB = _deposit(attackerB, 1_000 ether);\n        vm.prank(attackerB);\n        farm.withdraw(sharesB, 0, payable(attackerB), false);\n        // 14% (140 tokens) stayed in the farm and now backs the attacker's single share.\n        assertEq(farm.totalShares(), 1);\n        assertEq(farm.accountedAssets(), 140 ether + 1);\n    }\n\n    function testSmallDepositsAreBrickedAfterRetentionInflation() public {\n        _inflate();\n        // Expected: a 100-token deposit into a farm mints a non-zero number of shares.\n        // Actual: previewDeposit returns 0 and deposit() reverts with ZeroShares forever,\n        // because accountedAssets/totalShares can only grow.\n        assertGt(farm.previewDeposit(100 ether), 0, \"100-token deposit mints zero shares\");\n    }\n\n    function testVictimLosesRoundingRemainderToAttacker() public {\n        _inflate();\n        uint256 victimDeposit = 280 ether + 1;\n        uint256 victimShares = _deposit(victim, victimDeposit);\n        // Victim's share-backed position right after depositing, before any fee.\n        (uint256 victimGross,,,) = farm.previewWithdraw(victimShares);\n        // Expected: a fresh depositor's position is worth (almost) exactly what they deposited.\n        // Actual: 1 share worth 210 tokens for a 280-token deposit; 70 tokens moved to attacker A.\n        assertGe(victimGross, victimDeposit * 99 / 100, \"victim lost more than 1% to share rounding\");\n\n        // Attacker closes out with a profit, net of the 15% fee on both withdrawals.\n        vm.prank(attackerA);\n        farm.withdraw(1, 0, payable(attackerA), false);\n        uint256 attackerOut = simd.balanceOf(attackerA) + simd.balanceOf(attackerB);\n        assertLe(attackerOut, 1_000 ether + 1, \"attacker extracted value from the victim\");\n    }\n}","reproduction":"Fresh 18-decimal SIMD farm, deposits unpaused. (1) attackerA: deposit(1, 1) -> sharesOf = 1, totalShares = 1, accountedAssets = 1. (2) attackerB: deposit(1000e18, 1000e18) -> 1000e18 shares; withdraw(1000e18, 0, attackerB, false) -> gross 1000e18, net 850e18 paid, 10e18 to Treasury, retained 140e18. State: totalShares = 1, accountedAssets = 140e18+1. Expected: an empty-ish pool where small deposits still mint shares proportionally. Actual: previewDeposit(100e18) == 0 and deposit(100e18, 1) reverts ZeroShares forever; victim deposit(280e18+1, previewDeposit(280e18+1) = 1) succeeds with 1 share whose previewWithdraw gross is 210e18+1 (70e18 moved to attackerA's share). attackerA then withdraw(1, 0, attackerA, false) receives 178.5e18; attacker total out 1028.5e18 vs 1000e18+1 in (+28.5e18 profit), Treasury 12.1e18, victim's net exit 203.49e18 for 280e18 deposited. Run: forge test --match-path test/scratch/InflationViaRetention.t.sol (both tests fail on current code).","severity":"high","snippet":"        if (totalShares == 0) return Math.mulDiv(assets, initialShareScale, 1);","title":"Share price can be inflated through the farm's own 14% retention: first depositor mints 1 share for 1 wei, then a 1,000-token round trip from a second wallet leaves one share backed by 140 tokens, bri"},{"citation":"resolved","description":"Model B's retention is paid to whatever shares remain, with no minimum size. Because the only test for 'no shares remain' is shares == totalShares, an attacker who holds one 1 wei share in each farm guarantees the condition is never true for anyone else. The documented fee-sniping case E1 requires the sniper to deposit just before the exit; this variant is cheaper (1 wei once) and persistent, and its victim is the Treasury rather than another user: a 1,000-token sole depositor's full exit sends 10 tokens to the Treasury instead of 150 and 140 tokens to the squatter, who then withdraws them for 119 net. Partial-exit recapture (E2) is stated as intentional in the brief, so this is reported as an unstated economic consequence rather than a rule violation; if the requester wants the 15% rule to be meaningful, treat positions below a minimum share count (or below one asset unit of backing) as empty, or sweep retention to the Treasury when the remaining backing per share would exceed a bound.","line":156,"path":"src/BloomFarm.sol","reproduction":"Fresh 18-dec SIMD farm. (1) attacker: deposit(1, 1) -> 1 share. (2) victim: deposit(1000e18, ...) -> 1000e18 shares; withdraw(1000e18, 0, victim, false). Expected per brief (sole real depositor exiting, nothing economically left): 150e18 to Treasury. Actual: previewWithdraw sees shares (1000e18) != totalShares (1000e18+1) so treasuryFee = 10e18, retained 140e18 is credited to accountedAssets backing the attacker's 1 share (accountedAssets = 140e18+1, totalShares = 1). (3) attacker: withdraw(1, 0, attacker, false) -> receives 119e18, Treasury receives 21e18 (total 31e18 instead of 150e18).","severity":"low","snippet":"            shares == totalShares ? fee : Math.mulDiv(gross, TREASURY_FEE_BPS, 10_000, Math.Rounding.Ceil);","title":"The 'all 15% to Treasury when no shares remain' rule is defeated by a 1 wei squatter position: a permanent dust share converts every other holder's last exit from 15% Treasury revenue into 1% Treasury"}],"hash":"3e8cf701d0e6312bde54cb8844909004dbc4002b71d7660886d68e9b531ce487","nodeId":"e3758755-d2ab-4f51-91bc-cca6ed76b2f4","outcome":"completed","summary":"Review complete. The findings file is written and validated against the tree; no repository files were changed (only the ignored `.imd-findings.json` and `test/scratch/`).\n\n**Outcome:** one substantiated high finding with a failing Foundry proof, one low finding, and coverage rows for all 20 entry points plus four invariants.\n\n**Finding 1, high: share-price inflation through the farm's own 14% retention.** `src/BloomFarm.sol:141` mints exactly 1 share for a 1 wei first deposit on an 18-decimal farm, and `withdraw` credits the retained 14% to `accountedAssets` no matter how few shares remain. An attacker deposits 1 wei from wallet A, then round-trips 1,000 tokens from wallet B. The pool is left with 1 share backed by 140 tokens, and the ratio can never fall. Consequences, all reproduced:\n\n- Any deposit under 140 tokens reverts with `ZeroShares`, permanently.\n- A victim depositing 280 tokens with an honest `minSharesOut` from `previewDeposit` gets 1 share worth 210 tokens.\n- Attacker in: 1,000 tokens plus 1 wei. Attacker out: 1,028.5 tokens. Treasury: 12.1. Victim's net exit: 203.49.\n\nThe brief requires inflation protection; donations are excluded but this vector is the protocol's own feature at 1% cost. The author's `testDepositSlippageZeroSharesAndRoundingDust` runs this exact sequence at wei scale and calls it dust. The proof test `test/scratch/InflationViaRetention.t.sol` fails twice on current code and passes with either a virtual-offset or dead-shares fix.\n\n**Finding 2, low: a 1 wei squatter defeats the \"15% to Treasury when no shares remain\" rule.** With a permanent 1 wei position in each farm, no other holder's last exit ever satisfies `shares == totalShares`. A 1,000-token sole depositor's exit pays the Treasury 10 instead of 150, and the squatter collects 119 net. Reported as an unstated economic consequence since partial recapture is intentional per brief.\n\n**What I traced and found holding:** reward conservation across fund, epoch set, checkpoint, claim and dust recycle; the no-over-claim bound on the index carry; asset solvency on every path; fee rounding that makes tiny withdrawals overpay rather than avoid fees; Continue/Restart/boost rate math checked numerically; the ETH farm against a WETH9-shaped token with a 2,300-gas `transfer` into the farm's `receive`. Slither's weak-PRNG, strict-equality and timestamp leads were checked and are not defects. Documented risks E1 and E2 were not re-reported.\n\n**Not reached:** access-control and governance entry points were only sanity-checked, not traced in depth, since they are outside the assigned economics area; they are marked `holds` with that reason.","treeHash":null,"usage":{"cachedInputTokens":1611159,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":49728,"runtime":"claude","turns":27,"wallClockMs":928993}},{"artifacts":[],"attempt":1,"bundleHash":"71aedcb34e3522619a104d447689b8a1100800bb6bac252fbc945492fcc77af9","device":"9506ecec995bdbf9","findings":[{"description":"previewWithdraw prices every exit against (accountedAssets + virtualAssets) / (totalShares + virtualShares) with virtualShares = 1e6 for 18-decimal assets. The 14% retained by an exit is therefore shared between the real remaining shares and the 1e6 virtual shares, and the virtual part is paid to the Treasury on the last exit (EmptyPoolResidualToTreasury). The brief's Model B says the 14% 'stays in the same farm and increases assets per remaining share' and the Treasury receives 1% (15% only when no shares remain). The diverted fraction is virtualShares / (remainingRealShares + virtualShares): negligible (<= 1e-6) for positions of at least 1e12 wei, which testFuzz_retentionReachesRemainingHoldersExceptVirtualFraction asserts, but up to one half or more when the remaining position is around 1e6 wei. docs/SECURITY.md E4 gives the formula but describes the cost only for a 100-token position. This is a documented side effect of the inflation fix rather than an exploit; no user can capture the diverted value, only the owner-controlled Treasury receives it.","line":171,"path":"src/BloomFarm.sol","reproduction":"sIMD farm (18 decimals). Alice deposits 1e6 wei (1e6 shares). Bob deposits 100e18 and withdraws all his shares: 14e18 is retained and accountedAssets = 14000000000001000000. Expected under Model B: Alice, now the only holder, has gross 14e18 and on her full exit nets 11.9e18, and the Treasury receives 1e18 + 2.1e18 = 3.1e18. Actual: previewWithdraw(1e6) gross = 7000000000001000000, Alice nets 5950000000000850000, and the Treasury ends with 9050000000000150000 (about 5.95e18 more than Model B). Suggested direction, without changing the inflation protection: document the minimum economically meaningful position, or reject deposits whose resulting position is below a floor much greater than virtualShares.","severity":"low","title":"Virtual offset diverts most of the 14% retention from dust-sized remaining holders to the Treasury"}],"hash":"48b09143614c9faedc76c9c6505cd1387b04f9bc43876a6a109b69a8852c3f4e","nodeId":"763a0f81-c201-4702-b83b-39838960b79e","outcome":"completed","summary":"The implementation had changed since my last round and three of my tests no longer matched it. I've updated them to the new rules and added tests for the new code. The full suite now passes with the verifier seed: **119 passed, 0 failed, 0 skipped** across 14 suites. `forge build` is clean apart from lint warnings already in the source.\n\n**Why my tests were failing:** the new version prices shares with a fixed buffer of 1e6 \"virtual\" shares and wei that belong to nobody (the ERC-4626 virtual-offset defence), so my old exact formulas no longer applied. It also forbids giving up the emission role while the funding role is still held. None of the three failures was a contract bug.\n\n**What I changed:**\n- **`test/NativeCustodyInvariants.t.sol`:** the random-sequence handler now works out the new pricing itself. Each deposit must mint exactly the rounded-down quote, or be rejected when that quote is zero. A partial exit worth zero must be rejected. On the last exit the Treasury gets everything the user doesn't receive. No exit may ever lower the value of a remaining share.\n- **`test/BoundaryProperties.t.sol`:** the role-renouncement fuzz test now expects `RenouncementOrder` when the emission role would go before the funding role. It checks the rejected call removed nothing, then gives up the funding role first and carries on.\n- **New `test/VirtualOffsetAndRecovery.t.sol`**, which checks that:\n  - holders who stay keep the 14% retained from other users' exits, except at most one millionth, for any position of at least 1e12 wei;\n  - fees add up exactly, the last exit leaves nothing behind for future depositors, and an empty farm restarts at its starting price;\n  - share value never falls across random deposits and exits;\n  - every disallowed renouncement order is rejected;\n  - `recoverStrandedReserve` on the BLOOM farm, called by anyone at any time, takes only the unallocated reserve, never scheduled rewards, earned rewards or deposits. This matters because deposits and rewards there are the same token.\n- **`test/TESTING.md`:** updated with these results.\n\n**One finding, reported in `.imd-findings.json` (low severity):** because retention is now shared with the 1e6 virtual shares, a very small remaining holder loses much of it to the Treasury.\n- **Reproduction:** Alice keeps a 1e6-wei position (a millionth of a millionth of a token) while Bob exits with 100 tokens. Alice can then redeem only 7 of the 14 retained tokens, not all 14.\n- **Effect:** when Alice exits, the Treasury ends up with 9.05 tokens. The brief's withdrawal rules would give it 3.1.\n- **Scope:** for positions of 1e12 wei or more, the loss is at most one millionth, and the tests check that bound. I deliberately didn't write a test that treats the small-position case as correct. The security notes give the formula but describe the cost only for a 100-token position. Only the owner's Treasury gains, so no user can exploit it.\n\nThe previous round's finding (a tiny deposit in a low-decimal farm that could never be withdrawn) is fixed in this version: such deposits are now rejected, or the whole position can be closed. The existing tests cover both. All tests run offline against stand-in tokens, so the real BLOOM, sIMD and WETH contracts still need checking against live mainnet before farms are switched on. I haven't committed the changes.","treeHash":"dd2812ecb4f91fe3db330d7c0e602db4126b2cde","usage":{"cachedInputTokens":1397035,"inputTokens":44,"model":"claude-opus-5-5","outputTokens":21714,"runtime":"claude","turns":23,"wallClockMs":620148}},{"artifacts":[],"attempt":1,"bundleHash":"90e2903d7cc924cfd6b0e78a7f0736c9eab312aa07f7463abc9cfdba7d94086b","device":"0cf632e317dfab7a","findings":[],"hash":"50e29daea377520dcfa66076858da6b79741e89e5b4526cdeb8ba5f19c51c59a","nodeId":"6bebacc8-6130-4f0e-b22f-dd9e1e426c4b","outcome":"completed","summary":"The revision is complete. All six findings are answered in `.imd-responses.json`, the reviewer's proof passes, and the full suite, formatter check and deploy-script simulation pass.\n\n**What reproduced and what changed**\n\n- **High, retention-driven share inflation (fixed).** The proof failed both tests on the starting tree. Share prices in `src/BloomFarm.sol` now use an ERC-4626-style virtual offset: one million virtual shares and matching virtual assets scaled so the empty-farm price is unchanged, so a 1 wei first deposit still mints one share and the proof's pinned state is preserved. One real share can hold at most about a millionth of the pool, so inflating the price to any value costs the attacker roughly a million times that value with 15% lost per round trip. The dust the virtual shares absorb is paid to the Treasury on the last real exit, leaving an emptied farm at zero accounted assets. Deposits also reject positions that could not be redeemed for one unit.\n- **Low, 1 wei squatter defeats the final 15% rule (fixed by the same offset).** The squatter's share now captures about a millionth of the retention and the rest reaches the Treasury.\n- **Low, renouncing EMISSION strands reserve (fixed).** EMISSION can only be renounced with or after FUND. Once EMISSION is gone, anyone may send the unallocated reserve to the Treasury through a new recovery function. I chose this over forbidding renouncement while reserve is non-zero, which would trap the owner in a loop with recycled dust.\n- **Low, unclosable low-decimal dust position (fixed).** Such deposits are refused up front, and a whole position that redeems to zero can still be closed.\n- **Low, claims blocked by an unrelated underlying shortfall (fixed).** Claims now check reward-side solvency only.\n- **Info, pause-holder retention (documented).** Recorded as a trust assumption in the security and operations docs with a reproducing test.\n\n**Verification run on this machine**\n\n| Check | Result |\n|---|---|\n| `forge build` | passed |\n| `forge test` (delivered suites) | 82 passed, 0 failed |\n| Reviewer proof under `test/scratch/` | 2 passed (both failed before) |\n| `forge fmt --check` | passed |\n| Deploy script simulation, chain id 1 | passed, no broadcast |\n\n**Test adjustments to disclose.** Five existing accounting assertions that pinned exact whole-pool exit figures now allow the sub-1e6 wei deviation the offset introduces and assert exact conservation instead. One renouncement test follows the new FUND-before-EMISSION order. The invariant handler accounts for the last-exit residual. Nine new tests cover the findings.\n\n**Residual risks.** Model B fee sniping and partial-exit recapture remain intentional and documented. No Slither or Mythril run was performed, and no Mainnet deployment or token verification is claimed. ABIs, build metadata, the validation record and the README, security, operations and integration docs are updated.","treeHash":"6164730ca0b2985dcd5c47db96604151b961608f","usage":{"cachedInputTokens":2017853,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":71405,"runtime":"claude","turns":72,"wallClockMs":1369308}},{"artifacts":[],"attempt":1,"bundleHash":"bdb23a8799c36b36a6ddaa64528c6f2e29537211bde526ea5ddadfe5f37b0129","device":"48cf2e19b6c54386","findings":[],"hash":"58d26ac998ea8aad9657a4db78710160ee942bdeebddd8ac390ea5169cd57372","nodeId":"7ed326be-5696-4f36-883f-3f73630dc8e5","outcome":"completed","summary":"The accepted launch.json already matches the accepted contracts, and this revision round came with no findings to act on, so I verified it rather than changing it. The working tree is unchanged apart from my scratch test, which git ignores.\n\n**What the manifest declares**\n\n- Treasury first, with a single constructor argument of `$owner`. This matches the constructor in `src/Treasury.sol:22`, which takes one owner address and rejects zero.\n- FarmFactory second, with `$owner` then `$contract:Treasury`. This matches the constructor in `src/FarmFactory.sol:39`, which takes the owner and the treasury address in that order and passes the owner to SelectiveRoles.\n- Neither constructor reads msg.sender, so the factory never becomes an owner. Neither calls another contract, so both deploy on an empty chain as the protected deployment probe requires.\n\n**Checks I ran**\n\n- Schema check in Python: only the keys kind, contracts and notes are present, names are unique ASCII under 32 characters, every argument is a string, and the one contract reference points backward. The notes are 909 characters.\n- Build with sizes: Treasury and FarmFactory runtimes are well under the EIP-170 limit, which the protected opcode test enforces. The compiler output contains only lint warnings.\n- Scratch rehearsal in test/scratch: deployed both contracts from a fake factory address in manifest order with the launch owner and confirmed both owner fields and the factory's treasury pointer resolve correctly.\n- Full suite with the verifier's fuzz seed: 83 tests passed, 0 failed, including the invariant suites.\n\nNo file other than launch.json is in scope, and launch.json needed no edit. The task's mention of findings \"below\" had none attached, so there is nothing further to repair. If a reviewer finding was meant to accompany this round, it did not reach this run.","treeHash":"613466b392f209c6d2c728aa1fcd188c918d2fa7","usage":{"cachedInputTokens":280982,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":3876,"runtime":"claude","turns":8,"wallClockMs":172420}},{"artifacts":[],"attempt":1,"bundleHash":"72f0301b451e53b93b7957944f064397e333b361abb6ab718577ead3effaf914","device":"f4cf051c8a9d8584","findings":[],"hash":"591676e0e55a4bbbf31b1900cfd144c4347ce983dc0821c7b6aabc4190de80b2","nodeId":"6bebacc8-6130-4f0e-b22f-dd9e1e426c4b","outcome":"completed","summary":"Implemented the contracts, ABIs, deployment scripts, tests, and [owner documentation](README.md).\n\n`forge build`, `forge test` (**74 passed**), and `forge fmt --check` pass. Model B fee-sniping and partial-withdrawal risks are documented.\n\nMainnet deployment and verified addresses remain pending the IMD attested launch. No transaction was broadcast here.","treeHash":"21c60803bc7fb6966852f6a44c8d7284ab55357b","usage":{"cachedInputTokens":3873792,"inputTokens":115217,"model":"gpt-6-astra","outputTokens":58352,"runtime":"codex","turns":10,"wallClockMs":1862185}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"357c46e3781993d4","findings":[{"citation":"resolved","description":"Share price is accountedAssets/totalShares with no virtual offset or locked minimum shares. withdraw() keeps 14% of gross inside the farm (line 214: accountedAssets = accountedAssets - gross + retained) while burning the withdrawn shares, so a holder who withdraws all shares but one leaves a single share backed by 0.14 * gross. previewDeposit() then floors assets*totalShares/accountedAssets, so every later deposit is quantised to multiples of that share value: deposits below it revert with ZeroShares and larger deposits lose up to one share's worth of underlying to the existing holder on entry. Assets-per-share can only rise (deposits floor, withdrawals retain), so the state is permanent while the attacker keeps the single share; the owner has no reset path other than creating a new farm. Attacker cost is 1% of the gross to Treasury plus the 15% exit fee on the retained 14% (about 0.22 of the resulting share value), and the attacker profits from every victim who sets a loose minSharesOut (the project's own fixtures use 1). docs/SECURITY.md E3 only notes that minSharesOut matters 'if redistribution raises the value of one share'; the brief asks for donation/inflation protection. Fix without changing the fee rules: add a virtual-share/virtual-asset offset in previewDeposit/previewWithdraw (ERC-4626 style decimalsOffset) or lock a minimum number of dead shares on the first deposit so one share can never be worth a material amount.","line":142,"path":"src/BloomFarm.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {BloomFarm} from \"src/BloomFarm.sol\";\nimport {FarmFactory} from \"src/FarmFactory.sol\";\nimport {Treasury} from \"src/Treasury.sol\";\n\n/// @dev Minimal exact-transfer ERC-20 for the proof. No project test helpers are imported.\ncontract ProofToken is IERC20Metadata {\n    string public constant name = \"T\";\n    string public constant symbol = \"T\";\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n        totalSupply += amount;\n        emit Transfer(address(0), to, amount);\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        emit Approval(msg.sender, spender, amount);\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(msg.sender, to, amount);\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        emit Transfer(from, to, amount);\n        return true;\n    }\n}\n\n/// @notice A first depositor inflates the value of one internal share through the 14% retention\n/// path (withdraw all but one share). Afterwards every later deposit is rounded down to multiples\n/// of that share value, the dust accrues to the attacker, and deposits below one share revert.\n/// Nothing can lower assets-per-share again while the attacker keeps the single share.\ncontract ShareInflationProofTest is Test {\n    address owner = makeAddr(\"owner\");\n    address attacker = makeAddr(\"attacker\");\n    address victim = makeAddr(\"victim\");\n    address small = makeAddr(\"small\");\n    ProofToken simd;\n    ProofToken bloom;\n    ProofToken weth;\n    Treasury treasury;\n    FarmFactory factory;\n    BloomFarm farm;\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        simd = new ProofToken();\n        bloom = new ProofToken();\n        weth = new ProofToken();\n        treasury = new Treasury(owner);\n        factory = new FarmFactory(owner, address(treasury));\n        vm.startPrank(owner);\n        factory.configureToken(BloomFarm.Kind.SIMD, address(simd), keccak256(\"simd\"));\n        factory.configureToken(BloomFarm.Kind.BLOOM, address(bloom), keccak256(\"bloom\"));\n        factory.configureToken(BloomFarm.Kind.ETH, address(weth), keccak256(\"weth\"));\n        factory.setWhitelisted(address(simd), true);\n        factory.setWhitelisted(address(bloom), true);\n        farm = factory.createFarm(BloomFarm.Kind.SIMD);\n        farm.setDepositsPaused(false);\n        vm.stopPrank();\n    }\n\n    function _deposit(address who, uint256 amount) internal returns (uint256 shares) {\n        simd.mint(who, amount);\n        vm.startPrank(who);\n        simd.approve(address(farm), amount);\n        shares = farm.deposit(amount, 1);\n        vm.stopPrank();\n    }\n\n    function testFirstDepositorCannotMakeOneShareWorthAMaterialAmount() public {\n        uint256 X = 100 ether;\n        // 1. Attacker deposits X and immediately withdraws all shares but one.\n        //    85% is paid back, 1% goes to Treasury and 14% is retained by the single remaining share.\n        uint256 attackerShares = _deposit(attacker, X);\n        vm.prank(attacker);\n        farm.withdraw(attackerShares - 1, 0, payable(attacker), false);\n        assertEq(farm.totalShares(), 1);\n\n        // 2. A deposit of 1 token (1% of X) now mints zero shares and is rejected forever,\n        //    because one share is backed by ~14 tokens.\n        simd.mint(small, 1 ether);\n        vm.startPrank(small);\n        simd.approve(address(farm), 1 ether);\n        (bool ok,) = address(farm).call(abi.encodeCall(farm.deposit, (1 ether, 1)));\n        vm.stopPrank();\n        assertTrue(ok, \"a 1-token deposit must still mint shares after a 100-token depositor left\");\n\n        // 3. A depositor of 2 shares' worth minus 1 wei receives a single share and loses ~1/3 of\n        //    the deposit to the attacker's share on entry (before any withdrawal fee).\n        uint256 V = 2 * farm.accountedAssets() - 1;\n        uint256 victimShares = _deposit(victim, V);\n        uint256 victimBacking = Math_mulDiv(victimShares, farm.accountedAssets(), farm.totalShares());\n        assertGe(victimBacking, V * 99 / 100, \"victim must keep at least 99% of their deposit on entry\");\n    }\n\n    function Math_mulDiv(uint256 a, uint256 b, uint256 d) internal pure returns (uint256) {\n        return a * b / d;\n    }\n}","reproduction":"sIMD farm, 18-decimal token, deposits active. (1) attacker: deposit(1e18, 1) -> 1e18 shares. (2) attacker: withdraw(1e18-1, 0, attacker, false): gross 1e18-1, net 8.5e17 paid, 1e16 to Treasury, retained 1.4e17; state totalShares=1, accountedAssets=1.4e17+1. (3) anyone: deposit(1e17, 1) reverts ZeroShares (expected: mints ~1e17 shares; before step 2 it did). (4) victim: deposit(2.8e17+1, 1) -> 1 share backed by 2.1e17+1 (expected ~2.8e17); 7e16 moves to the attacker's share on entry. (5) attacker: withdraw(1, 0, attacker, false) -> receives 1.785e17; attacker total 1.0285e18 on 1e18 in. (6) victim: withdraw(1, ...) -> 2.0349e17 instead of the 2.38e17 a sole 15% exit would pay. Measured with test/scratch/Probe.t.sol logs: attacker final 1028499999999999999, victim final 203490000000000000 on 280000000000000001 deposited.","severity":"high","snippet":"        return Math.mulDiv(assets, totalShares, accountedAssets);","title":"First depositor permanently inflates assets-per-share through the 14% retention path; later deposits round down to that granularity or revert"},{"citation":"resolved","description":"claim() calls _assertSolvent() before and after paying rewards. For sIMD and ETH farms _assertSolvent() also requires asset.balanceOf(farm) >= accountedAssets, which has nothing to do with the BLOOM reward reserve being paid. If the underlying balance ever drops by even one base unit (a negatively rebasing or slashing sIMD, an admin burn/confiscation, or any future token upgrade), every claim() reverts with Insolvent even though rewardToken fully covers rewardBacking(). withdraw() is also blocked, which is defensible for principal, but reward claims that the brief says must 'remain available' are coupled to the wrong balance. Recovery requires someone to donate the shortfall in the underlying. Minimal fix: in claim() assert only the reward side (rewardToken balance >= rewardBacking, plus accountedAssets when asset == rewardToken).","line":227,"path":"src/BloomFarm.sol","reproduction":"sIMD farm; victim deposit(100e18, 1); owner fundRewards(1000e18) then setEpoch(1000e18, 10, Restart); warp +5 days (victim earned ~500e18 BLOOM, farm holds 1000e18 BLOOM). Remove 1 wei of sIMD from the farm's balance (simulated with MockToken.burn(farm, 1), i.e. asset balance 100e18-1 < accountedAssets 100e18). victim calls claim(victim): reverts Insolvent. Expected: 500e18 BLOOM paid, since the reward token balance covers all liabilities. Reproduced in test/scratch/Probe.t.sol testAssetShortfallBlocksClaim.","severity":"low","snippet":"        _assertSolvent();","title":"claim() of BLOOM rewards is blocked by any shortfall of the unrelated underlying asset balance"}],"hash":"632fa0de2123842c9f752b561e1899404ada3f31c52e1d9dae5f4b28fc56b369","nodeId":"7366960b-1112-46e3-b937-765c785b0462","outcome":"completed","summary":"The findings file is written, both snippets match the tree exactly, and all 20 entry points plus three invariant rows are covered. Here is the recap.\n\n**Outcome:** two substantiated findings in `.imd-findings.json`, one high with a failing Foundry proof, one low. Everything else in the Execution Trace, Periphery and First Principles areas held under tracing.\n\n**Finding 1, high: first depositor permanently inflates assets per share.** `withdraw()` burns shares then keeps 14% of gross in `accountedAssets`, so a holder who withdraws all but one share leaves a single share backed by 0.14 of their deposit. There is no virtual offset or locked minimum, so from then on `previewDeposit` floors every deposit to multiples of that value. Reproduced with a 1 token attacker deposit: a later 0.1 token deposit reverts with ZeroShares, a 0.28 token depositor gets one share worth 0.21 and the difference moves to the attacker, who exits with 1.0285 tokens on 1 token in. Assets per share can never fall while the attacker holds the share, so the farm is permanently degraded for about a 0.22 unit cost per unit of granularity. Proof at `test/scratch/ShareInflation.t.sol` fails on the current code. The suggested fix is an ERC-4626 style virtual offset or dead shares on first deposit, which preserves the 85/14/1 rules.\n\n**Finding 2, low: BLOOM claims blocked by the underlying balance.** `claim()` runs the full solvency check, so a one wei shortfall of sIMD or WETH makes every reward claim revert with Insolvent even though the BLOOM balance fully covers liabilities. Recovery needs someone to donate the shortfall.\n\n**Held after tracing:** reward index arithmetic including remainder and fraction carry at the 2^128 caps, empty-pool isolation, epoch Continue and Restart accounting, ETH wrap and unwrap paths with the WETH fallback, role renouncement gates, factory one-shot configuration, and the Treasury. The documented E1 fee sniping and E2 two-step recapture are consequences of the brief and were not reported again. All Slither and Aderyn lines were checked and none became a finding.\n\n**Not reached:** no live token behaviour of the real BLOOM or sIMD contracts was tested, and the full project suite with invariants was not rerun in this session.","treeHash":null,"usage":{"cachedInputTokens":1034691,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":43348,"runtime":"claude","turns":26,"wallClockMs":692722}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d11ea2b5e05fa7a8","findings":[{"citation":"resolved","description":"Donation protection only covers raw token transfers (accountedAssets ignores balanceOf). It does not cover the Model B retention: when a withdrawer leaves 1 share behind, the 14% retained part of their own gross is credited to that single share. An unprivileged attacker can therefore raise assets-per-share without limit at a cost of only the 1% Treasury part (the 14% stays in their own surviving share). Deposits then mint floor(assets * totalShares / accountedAssets) shares (src/BloomFarm.sol:142), so any deposit smaller than one share-price reverts with ZeroShares, and larger deposits lose up to one share-price of value to rounding, which accrues pro rata to the attacker's share. minSharesOut does not help: previewDeposit already returns the rounded-down share count, so a user who sets minSharesOut = previewDeposit(assets) = 1 still loses the fraction. The brief required donation/inflation protection and safe rounding; this is the standard vault inflation attack reached through the fee-retention branch instead of a donation. Fix options that keep the economic rules: ERC-4626 style virtual shares/assets offset in previewDeposit/previewWithdraw (e.g. +1e6 virtual shares and +1 virtual asset, or a decimals offset), or a minimum permanent share floor (burn/lock the first N shares to a dead address) so that a single live share can never carry the whole pool.","line":214,"path":"src/BloomFarm.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {BloomFarm} from \"src/BloomFarm.sol\";\nimport {FarmFactory} from \"src/FarmFactory.sol\";\nimport {Treasury} from \"src/Treasury.sol\";\n\ncontract ScratchToken is IERC20Metadata {\n    string public name = \"T\";\n    string public symbol = \"T\";\n    uint8 public immutable decimals;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(uint8 d) {\n        decimals = d;\n    }\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n        totalSupply += amount;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @notice Share-price inflation through the retained 14% of the attacker's own withdrawal.\ncontract InflationScratchTest is Test {\n    address owner = makeAddr(\"owner\");\n    address attacker = makeAddr(\"attacker\");\n    address victim = makeAddr(\"victim\");\n    ScratchToken simd;\n    ScratchToken bloom;\n    ScratchToken weth;\n    BloomFarm farm;\n\n    function setUp() public {\n        vm.warp(1_000_000);\n        simd = new ScratchToken(18);\n        bloom = new ScratchToken(18);\n        weth = new ScratchToken(18);\n        Treasury treasury = new Treasury(owner);\n        FarmFactory factory = new FarmFactory(owner, address(treasury));\n        vm.startPrank(owner);\n        factory.configureToken(BloomFarm.Kind.SIMD, address(simd), keccak256(\"s\"));\n        factory.configureToken(BloomFarm.Kind.BLOOM, address(bloom), keccak256(\"b\"));\n        factory.configureToken(BloomFarm.Kind.ETH, address(weth), keccak256(\"w\"));\n        factory.setWhitelisted(address(simd), true);\n        factory.setWhitelisted(address(bloom), true);\n        factory.setWhitelisted(address(weth), true);\n        farm = factory.createFarm(BloomFarm.Kind.SIMD);\n        farm.setDepositsPaused(false);\n        vm.stopPrank();\n    }\n\n    function _deposit(address who, uint256 amount) internal returns (uint256) {\n        simd.mint(who, amount);\n        vm.startPrank(who);\n        simd.approve(address(farm), amount);\n        uint256 shares = farm.deposit(amount, 1);\n        vm.stopPrank();\n        return shares;\n    }\n\n    function testRetainedFeeInflatesSharePriceAndGriefsDepositors() public {\n        // Cycle 1: 1,000 tokens in, withdraw all but one share. 10 tokens go to Treasury,\n        // 140 tokens are \"retained\" into the attacker's single remaining share.\n        uint256 shares = _deposit(attacker, 1_000e18);\n        assertEq(shares, 1_000e18);\n        vm.prank(attacker);\n        farm.withdraw(shares - 1, 0, payable(attacker), false);\n        assertEq(farm.totalShares(), 1);\n        uint256 pricePerShare = farm.accountedAssets();\n        emit log_named_uint(\"assets backing the single share after cycle 1\", pricePerShare);\n        assertGt(pricePerShare, 140e18);\n\n        // Cycle 2: repeat with more capital; the single share now backs > 1,400 tokens.\n        shares = _deposit(attacker, 1_400e18 + 1_400e18);\n        uint256 held = farm.sharesOf(attacker);\n        vm.prank(attacker);\n        farm.withdraw(held - 1, 0, payable(attacker), false);\n        pricePerShare = farm.accountedAssets();\n        emit log_named_uint(\"assets backing the single share after cycle 2\", pricePerShare);\n\n        // Expected (protected design): a 100-token deposit mints shares. Actual: ZeroShares revert.\n        simd.mint(victim, 100e18);\n        vm.startPrank(victim);\n        simd.approve(address(farm), 100e18);\n        vm.expectRevert(BloomFarm.ZeroShares.selector);\n        farm.deposit(100e18, 1);\n        vm.stopPrank();\n\n        // A deposit just under two share-prices mints exactly one share and loses ~half to rounding.\n        uint256 victimDeposit = pricePerShare * 2 - 1;\n        uint256 victimShares = _deposit(victim, victimDeposit);\n        assertEq(victimShares, 1);\n        (uint256 victimGross,,,) = farm.previewWithdraw(1);\n        emit log_named_uint(\"victim deposited\", victimDeposit);\n        emit log_named_uint(\"victim position gross value\", victimGross);\n        uint256 lost = victimDeposit - victimGross;\n        emit log_named_uint(\"victim value lost to rounding\", lost);\n\n        // Attacker exits its single share and finishes with more tokens than it started with,\n        // even after paying 15% withdrawal fees on every exit.\n        uint256 attackerBefore = simd.balanceOf(attacker);\n        vm.prank(attacker);\n        uint256 net = farm.withdraw(1, 0, payable(attacker), false);\n        emit log_named_uint(\"attacker final exit net\", net);\n        uint256 attackerTotal = attackerBefore + net;\n        emit log_named_uint(\"attacker total received\", attackerTotal);\n        emit log_named_uint(\"attacker total deposited\", 1_000e18 + 2_800e18);\n\n        // Protected design expectation: the victim does not lose more than 1% of its deposit\n        // to share rounding at deposit time. Fails on current code (loses ~50%).\n        assertLe(lost, victimDeposit / 100, \"victim lost more than 1% to deposit rounding\");\n    }\n}","reproduction":"SIMD farm, 18-decimal asset, deposits unpaused. 1) attacker deposit(1000e18, 1) -> 1000e18 shares. 2) attacker withdraw(1000e18-1, 0, attacker, false): gross 1000e18-1, fee 150e18, Treasury 10e18, retained 140e18 -> totalShares = 1, accountedAssets = 140e18+1. 3) attacker deposit(2800e18,1) -> 19 shares; withdraw(19,...) -> totalShares = 1, accountedAssets = 538.02e18. 4) victim deposit(100e18, 1): expected shares minted; actual revert ZeroShares(). 5) victim deposit(1076.04e18, 1) -> 1 share whose previewWithdraw gross is 807.03e18: 269.01e18 (25%) lost at deposit time to rounding. 6) attacker withdraw(1, ...) nets 685.98e18; attacker total received 3910.03e18 versus 3800e18 deposited, a profit of 110e18 after paying all 15% fees and 38e18 to Treasury. Run: forge test --match-path test/scratch/Inflation.t.sol (fails on current code at the final assertLe).","severity":"high","snippet":"        accountedAssets = accountedAssets - gross + retained;","title":"Share price can be inflated through the retained 14% fee, griefing and skimming later depositors"},{"citation":"resolved","description":"The renouncement guard treats FUND_ROLE and EMISSION_ROLE symmetrically (both only need hasScheduled), but the roles are not symmetric: funding is useless without scheduling. After EMISSION_ROLE is renounced, (a) fundRewards still succeeds and every token funded is unrecoverable, (b) zero-staker emissions and recycled dust are returned to rewardReserve by _updateGlobal/_recycleEmptyRewardDust, and (c) rewardReserve is part of rewardBacking so sweepSurplus excludes it. No path can move it to users or Treasury. SECURITY.md discloses that finalizing the emission role can leave reserves unused, but the brief asked to prevent accidental renouncement before essential setup; the guard lets the owner keep a role (FUND) whose only remaining effect is to lock tokens. Suggested minimal fix preserving the design: require FUND_ROLE to be renounced together with or before EMISSION_ROLE (revert if roles includes EMISSION_ROLE while FUND_ROLE stays active), and reject renouncing EMISSION_ROLE while rewardReserve != 0 or an epoch is still vesting, so only an owner with nothing left to schedule can finalize.","line":476,"path":"src/BloomFarm.sol","reproduction":"Owner: fundRewards(1000e18); setEpoch(1000e18, 10, Restart); renounceRoles(EMISSION_ROLE, renouncementHash(EMISSION_ROLE)) succeeds (hasActivated and hasScheduled are true). No one stakes. warp +11 days; checkpoint() -> rewardReserve == 1000e18. setEpoch(...) now reverts RoleDisabled(2); fundRewards(1000e18) still succeeds and rewardReserve == 2000e18; sweepSurplus(bloom) reverts NoSurplus; 2000e18 BLOOM sit in the farm permanently. Expected: renouncement blocked or reserve recoverable. Verified with test/scratch/Renounce.t.sol (passes, demonstrating the stranded state).","severity":"low","snippet":"        if (roles & (FUND_ROLE | EMISSION_ROLE) != 0 && !hasScheduled) revert EssentialSetupIncomplete();","title":"Renouncing EMISSION_ROLE while FUND_ROLE or an unfinished epoch remains strands BLOOM forever in the reserve"},{"citation":"resolved","description":"Access x economics x asymmetry. The pause is intentional and documented, and the fee-sniping redistribution (14% of any exit to the remaining shares, SECURITY.md E1) is an accepted consequence of Model B. The seam is that the pause has no effect on withdrawals, so while deposits are paused only existing shareholders capture the 14% of every exit, and the owner is the only party who can choose when deposits reopen. An owner holding shares can keep deposits paused during large exits, excluding competing snipers, then unpause for its own deposit before another exit. Not a permission bypass; record as a trust assumption for the owner's DEPOSIT_ROLE until it is renounced.","line":298,"path":"src/BloomFarm.sol","reproduction":"Owner holds 100 shares alongside whale Alice (10,000 shares, 10,000 tokens). Owner setDepositsPaused(true). Alice withdraw(10000e18,...): fee 1500, Treasury 100, retained 1400 credited to the owner's 100 surviving shares (now worth 1500 tokens). Outside parties could not deposit to share in the 1400. Owner then setDepositsPaused(false). Expected per brief: pause only blocks new deposits (it does), but the economic effect is that the pauser alone collects the retained fee of exits made while paused.","severity":"info","snippet":"    function setDepositsPaused(bool paused) external onlyRole(DEPOSIT_ROLE) nonReentrant {","title":"Trust gap: DEPOSIT_ROLE pause lets the owner monopolise the Model B fee-sniping redistribution"}],"hash":"af7494d68bc3f3321107f4c2e4d8ace159852b626ec3f623cb5299353f58f339","nodeId":"f8c02c27-2e67-4227-af70-fc82e428ecca","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` with coverage rows for all 20 entry points plus two invariants. No source files were changed; the only writes are the findings file and two scratch tests under `test/scratch/` (both gitignored).\n\n**Findings**\n\n- **High. Share-price inflation through the retained 14% fee.** Donation protection only ignores raw token transfers. A withdrawer who leaves one share behind has their own 14% retained fee credited to that share, so an unprivileged attacker can push assets-per-share up without limit at a cost of just the 1% Treasury part. Reproduced in a scratch test: after cycling 3,800 tokens, one share backs 538 tokens, a 100-token deposit reverts with ZeroShares, a 1,076-token victim deposit loses 269 tokens at deposit time, and the attacker exits with a net profit of 110 tokens after all fees. The proof test fails on the current code at `src/BloomFarm.sol:214`. A virtual-share offset or a permanent share floor fixes it without changing the fee rules.\n- **Low. Emission-role renouncement strands BLOOM.** The renounce guard at `src/BloomFarm.sol:476` treats FUND_ROLE and EMISSION_ROLE symmetrically. After renouncing EMISSION_ROLE, funding still works, zero-staker emissions flow back to the reserve, and sweeps exclude the reserve, so those tokens are locked forever. Documented in SECURITY.md as a known consequence, reported as a guard asymmetry.\n- **Info. Trust gap on the deposit pause.** The pause blocks only deposits, so an owner who holds shares can keep competitors out while large exits redistribute their 14% to existing shareholders. Intended power, recorded as a trust assumption.\n\n**Coverage**\n\nAll 20 entry points are answered. The access model itself held: the owner is immutable, roles can only be cleared, confirmations are bound to chain, contract and role set, farms are owned by the factory's owner rather than the factory, and the Treasury has no path into farm backing. Sweeps correctly exclude accounted assets and the full reward backing. Reentrancy on the ETH payout path is blocked by the guard on every mutating entry.\n\n**Not reached**\n\nI could not verify whether official sIMD rebases. If it does, anyone can route its yield to the Treasury via sweepSurplus. The docs already say not to register a rebasing sIMD, so this stays an open item rather than a finding.","treeHash":null,"usage":{"cachedInputTokens":1105170,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":31156,"runtime":"claude","turns":25,"wallClockMs":684972}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"27168b1ff68f84d2","findings":[{"citation":"resolved","description":"Settlement of the prior round first. The high finding (1083e685, merged from audit_math bf1c9e4a, audit_permissions b5632552, audit_flow 54eb7011, audit_economics 1dd1fc1f) is FIXED: previewDeposit/previewWithdraw now price against totalShares+virtualShares and accountedAssets+virtualAssets (VIRTUAL_OFFSET 1e6, ratio equal to the initial scale), so one real share can hold at most 1/(1e6+1) of the pool. All five attached proofs copied to test/scratch now pass except Proof_b5632552d99c, which fails only because it encodes the old ZeroShares revert as an expectRevert in its attack setup (the deposit no longer reverts, which is the fix). The four lows are FIXED and re-verified by test/scratch/Settle.t.sol (6/6 pass): (a) squatter 19f9b515: 1 wei squatter then 1000e18 sole-depositor exit now leaves the squatter a net of 118,999,881,000,119 wei and the Treasury 149,999,881,000,118,999,882 wei with accountedAssets 0 after the squatter exits; (b) renounce order 1c441133: renounceRoles(EMISSION_ROLE) alone reverts RenouncementOrder (src/BloomFarm.sol:533), recoverStrandedReserve reverts RoleStillActive while the role is active and sends the 1000e18 zero-staker reserve to the Treasury after FUND|EMISSION are renounced together; (c) dust deposit 873bd161: on a 6-decimal farm after the 1140/1e15 state, deposit(1,1) now reverts ZeroAssets at src/BloomFarm.sol:214-218 before funds are taken; (d) claim 9fb64c2c: with the underlying 1 wei short, claim pays 500e18 while withdraw still reverts Insolvent (_assertRewardSolvent at line 478). The pause trust assumption (b1f8cb2c) is documented in SECURITY.md and OPERATIONS.md; no code change was expected. A 512-run fuzz of mixed deposits/withdrawals in Settle.t.sol confirms accountedAssets*virtualShares >= totalShares*virtualAssets after every step, so the last exit's accountedAssets - gross never underflows, and whole-pool drains always end at accountedAssets == 0 with exact conservation. The full project suite passes (119/119). Remaining item, from write_foundry_tests (3a23f1aa), reproduced and kept as information only: because gross is priced against totalShares + virtualShares, the 14% retained by an exit is split between the real remaining shares and the 1e6 virtual shares. A remaining holder with S real shares gets S/(S+1e6) of it; the remainder is attached to the virtual shares, is not minted to any later depositor (verified: Carol's later 50e18 deposit previews to 49,999,999,781,251,559,082 gross, i.e. she captures none of it), and is paid to the Treasury only when the last real share exits (EmptyPoolResidualToTreasury). For any position of at least 1e12 wei the deviation is below one millionth; it is material only when the sole remaining positions are dust, where the brief's 'increases assets per remaining share' rule is replaced by 'mostly waits for the Treasury', and a 1 wei squatter who never exits defers that Treasury payment indefinitely (140e18 held at accountedAssets while the squatter stays in the squatter scenario). This is the inherent cost of the virtual-offset fix this review recommended; SECURITY.md E4 states the formula and E5 the squatter case. Nothing to change unless the requester wants a minimum position floor; recorded so the Model B deviation for dust positions is explicit.","line":172,"path":"src/BloomFarm.sol","reproduction":"Fresh 18-decimal sIMD farm, deposits unpaused. (1) alice deposit(1e6, 1) -> 1e6 shares. (2) bob deposit(100e18, 1) -> 100e18 shares; bob withdraw(100e18, 0, bob, false): Treasury 1e18, retained 14e18, accountedAssets = 14,000,000,000,001,000,000, totalShares = 1e6. Expected under a literal Model B: previewWithdraw(1e6) gross = 14e18 (+1e6) for alice, the sole remaining holder. Actual: gross = 7,000,000,000,001,000,000, net = 5,950,000,000,000,850,000; the other 7e18 stays in accountedAssets attached to the virtual shares. (3) carol deposit(50e18, 1): previewWithdraw of her shares = 49,999,999,781,251,559,082 (she does not receive the residual). (4) alice withdraw(1e6) then carol withdraws all: Treasury ends at 15,819,298,328,959,740,677 and accountedAssets == 0, conservation exact. Run: forge test --match-path test/scratch/Settle.t.sol --match-test testDustHolderSharesRetentionWithVirtualShares -vv (passes; logs show the figures).","severity":"info","snippet":"        gross = Math.mulDiv(shares, accountedAssets + virtualAssets, totalShares + virtualShares);","title":"Virtual-offset residual: a dust-sized remaining holder receives only shares/(shares+1e6) of the 14% retention; the rest reaches the Treasury only when the pool fully empties (documented in SECURITY.md"}],"hash":"b6a3866187301822261cd605ec5c6f235ef400aabfe75711b6a3fa5ffd54a275","nodeId":"c177786f-3d00-42ee-b7f9-18a80e2d869b","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` and no tracked file changed.\n\n**Settlement of the prior round**\n\n- **High, share-price inflation through the 14% retention: fixed.** Share prices now use an ERC-4626-style virtual offset of 1e6 shares and matching virtual assets, so one real share holds at most 1/(1e6+1) of the pool. Four of the five attached proofs pass on the revised code. The fifth, Proof_b5632552d99c, fails only because its attack setup uses `expectRevert(ZeroShares)` on the victim deposit, and that deposit no longer reverts. That failure is the fix working.\n- **Low, 1-wei squatter defeats the final 15% rule: fixed.** Re-run: the squatter's single share nets 118,999,881,000,119 wei and the Treasury ends with 149,999,881,000,118,999,882 wei after a 1000-token sole-depositor exit.\n- **Low, renouncing EMISSION_ROLE strands reserve: fixed.** Renouncing EMISSION_ROLE while FUND_ROLE stays active reverts `RenouncementOrder`. The new `recoverStrandedReserve` refuses while the role is active and otherwise sends only `rewardReserve` to the Treasury. Verified 1000e18 recovered, then `NoSurplus`.\n- **Low, unclosable low-decimal dust deposit: fixed.** On a 6-decimal farm in the 1140/1e15 state, `deposit(1,1)` now reverts `ZeroAssets` before funds are pulled.\n- **Low, claim blocked by underlying shortfall: fixed.** With the sIMD balance 1 wei short, `claim` pays 500e18 and `withdraw` still reverts `Insolvent`.\n- **Info, pause-time retention capture:** documented as a trust assumption in SECURITY.md and OPERATIONS.md, as expected.\n\n**Additional checks this round**\n\n- A 512-run fuzz of mixed deposits and withdrawals held the invariant that the share price never drops below the initial price, so the last exit's residual subtraction cannot underflow, and every full drain ends at `accountedAssets == 0` with exact conservation.\n- The full project suite passes, 119 of 119.\n- Contract sizes are within the factory's deployment limits, and `launch.json` lists Treasury then FarmFactory with correct constructor arguments.\n- Every static-analysis lead was traced. The reentrancy, strict-equality, weak-PRNG and zero-check lines are either guarded by `nonReentrant` and exact-transfer checks or already handled in code.\n\n**One item kept, as information only.** The write_foundry_tests specialist's point reproduces: a dust-sized remaining holder gets only shares/(shares+1e6) of the retained 14%, and the virtual-share remainder reaches the Treasury only when the pool fully empties. For any position of 1e12 wei or more the deviation is below one millionth, later depositors never capture it, and SECURITY.md E4 and E5 already describe it. It is the inherent cost of the fix this review recommended and needs no code change.\n\nCoverage: all 21 entry points answered `holds`, plus three invariant rows.","treeHash":null,"usage":{"cachedInputTokens":1116445,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":26980,"runtime":"claude","turns":14,"wallClockMs":855468}},{"artifacts":[],"attempt":1,"bundleHash":"8cabba76e9ee692243361a1b07c2ac8e83b93ed4231b85e47319c231a78b40db","device":"2c7ded3d2e6abf8e","findings":[{"description":"With a 0-17 decimal underlying, fee redistribution can make a one-base-unit deposit mint nonzero shares whose entire gross redemption floors to zero. _prepareDeposit accepts the funds because it checks only nonzero shares and minSharesOut, but withdraw then rejects the complete position with ZeroAssets even when minAssetsOut is zero. This is a full-position dust liveness gap under the requested safe dust handling and available withdrawals, distinct from deliberately rejecting a tiny partial withdrawal. The holder must add more principal or wait for another shareholder to exit; a deposit pause prevents the holder from topping up. Impact is bounded to sub-unit redeemable dust, not a demonstrated theft of other users' principal or reward insolvency. Consider rejecting an unclosable deposit before taking funds, or providing a carefully specified full-position dust closure that preserves fee accounting.","line":208,"path":"src/BloomFarm.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {BloomFarm} from \"src/BloomFarm.sol\";\nimport {Treasury} from \"src/Treasury.sol\";\n\n// Local test-only token; no production token address or fork is needed.\ncontract DustExitToken {\n    uint8 public immutable decimals;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(uint8 decimals_) { decimals = decimals_; }\n    function mint(address to, uint256 amount) external { balanceOf[to] += amount; }\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\ncontract DustExitProof is Test {\n    function test_acceptedOneUnitDepositMustBeClosableOrRejectedBeforeTakingFunds() public {\n        address alice = makeAddr(\"dust test alice\");\n        address bob = makeAddr(\"dust test bob\");\n        address carol = makeAddr(\"dust test carol\");\n        DustExitToken underlying = new DustExitToken(6);\n        DustExitToken reward = new DustExitToken(18);\n        Treasury treasury = new Treasury(address(this));\n        BloomFarm farm = new BloomFarm(\n            address(this), address(treasury), address(underlying), address(reward), 6, BloomFarm.Kind.SIMD\n        );\n        farm.setDepositsPaused(false);\n        underlying.mint(alice, 1000);\n        underlying.mint(bob, 1000);\n        vm.startPrank(alice);\n        underlying.approve(address(farm), 1000);\n        farm.deposit(1000, 1);\n        vm.stopPrank();\n        vm.startPrank(bob);\n        underlying.approve(address(farm), 1000);\n        farm.deposit(1000, 1);\n        vm.stopPrank();\n        vm.prank(alice);\n        farm.withdraw(1e15, 0, payable(alice), false);\n        assertEq(farm.accountedAssets(), 1140);\n        assertEq(farm.totalShares(), 1e15);\n\n        underlying.mint(carol, 1);\n        vm.startPrank(carol);\n        underlying.approve(address(farm), 1);\n        try farm.deposit(1, 1) returns (uint256 shares) {\n            assertGt(shares, 0);\n            // On current code, shares = 877192982456 and this full exit reverts ZeroAssets().\n            // A remedy may instead reject the deposit before taking the indivisible underlying unit.\n            farm.withdraw(shares, 0, payable(carol), false);\n            assertEq(farm.sharesOf(carol), 0, \"accepted full position must be closable\");\n        } catch {\n            assertEq(underlying.balanceOf(carol), 1, \"rejected deposit must preserve funds\");\n            assertEq(farm.sharesOf(carol), 0);\n        }\n        vm.stopPrank();\n    }\n}","reproduction":"Deploy an exact-transfer 6-decimal test asset, a distinct reward token, Treasury and an active SIMD farm. Alice and Bob each deposit 1000 underlying base units, receiving 1000000000000000 shares each. Alice withdraws all her shares: Bob now owns 1140 accounted asset units with 1000000000000000 shares. Carol deposits 1 base unit with minSharesOut=1. The deposit succeeds and mints 877192982456 shares; accountedAssets becomes 1141. Carol withdraws all 877192982456 shares with minAssetsOut=0. Expected: either the unclosable deposit is rejected without taking funds, or an accepted full position can be closed safely. Actual: the deposit takes her unit, and the full withdrawal reverts ZeroAssets(), leaving the position open. Confirmed by running forge test --match-path test/scratch/DustExitProof.t.sol -vv: 0 passed, 1 failed, ZeroAssets(). The embedded self-contained proof was executed before being removed from the passing default suite.","severity":"low","title":"An accepted low-decimal dust deposit cannot immediately close its full share position"}],"hash":"f34c0b006e460880dd8a5ed17865f5e6359aff64560485a1eebe083d7766b604","nodeId":"763a0f81-c201-4702-b83b-39838960b79e","outcome":"completed","summary":"Added 31 tests and two invariant campaigns.\n\n- `forge build` passed.\n- `forge test`: **107 passed, 0 failed**, using the required seed.\n- Invariants exercised **65,536 calls**.\n\nReported one low-severity dust-withdrawal defect with an executed proof in [.imd-findings.json](.imd-findings.json). Coverage and remaining integration risks are documented in [test/TESTING.md](test/TESTING.md).","treeHash":"ddfaa646ba91f9469f2a9afe3c2644e7144aa68b","usage":{"cachedInputTokens":3625856,"inputTokens":133556,"model":"gpt-6-astra","outputTokens":34794,"runtime":"codex","turns":10,"wallClockMs":1290777}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7eaf1cd0fb3df712","findings":[{"citation":"resolved","description":"Donation protection only covers external transfers, because accountedAssets ignores raw balances. The withdrawal path is an internal donation channel open to every depositor: `accountedAssets = accountedAssets - gross + retained;` (line 214) adds 14% of every non-final exit to the remaining shares. The first deposit mints `assets * initialShareScale` shares (1:1 for 18-decimal sIMD/BLOOM/WETH). There is no virtual share/asset offset and no minimum share balance, so a pool whose only remaining position is 1 share can have that share's value pushed arbitrarily high. Afterwards previewDeposit = floor(assets*totalShares/accountedAssets) truncates. Any deposit smaller than one share's value reverts ZeroShares. Larger deposits lose (assets mod sharePrice) to existing holders, and minSharesOut does not catch this, because the loss is already included in the preview the user sets it from. The attacker's net cost is only the 1% Treasury fee, since the 14% stays inside their own share. The same state also arises without an attacker when a sole depositor withdraws 'max minus 1 wei' of shares (the remaining 1 share then holds 14% of the position). Precision x invariant seam: share issuance is floor-rounded against a share price that the retained-fee path can inflate without bound, which breaks the brief's 'donation/inflation protection' and 'handle rounding dust safely' guarantees. Note: docs/SECURITY.md E3 mentions that one share's value can rise; it does not disclose this cheap, deliberate attack or the deposit DoS. Suggested fix that keeps Model B: require a minimum live share balance (e.g. a first deposit and every remaining account balance either 0 or >= 1e9 shares), or use an OZ-style virtual share offset, which caps the reachable per-share price so truncation only loses dust.","line":141,"path":"src/BloomFarm.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {BloomFarm} from \"src/BloomFarm.sol\";\nimport {Treasury} from \"src/Treasury.sol\";\n\ncontract PlainToken {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    uint8 public immutable decimals;\n\n    constructor(uint8 d) {\n        decimals = d;\n    }\n\n    function mint(address to, uint256 a) external {\n        balanceOf[to] += a;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\ncontract InflationGriefTest is Test {\n    address owner = makeAddr(\"owner\");\n    address attackerA = makeAddr(\"attackerA\");\n    address attackerB = makeAddr(\"attackerB\");\n    address victim = makeAddr(\"victim\");\n    PlainToken simd;\n    PlainToken bloom;\n    Treasury treasury;\n    BloomFarm farm;\n\n    function setUp() public {\n        simd = new PlainToken(18);\n        bloom = new PlainToken(18);\n        treasury = new Treasury(owner);\n        farm = new BloomFarm(owner, address(treasury), address(simd), address(bloom), 18, BloomFarm.Kind.SIMD);\n        vm.prank(owner);\n        farm.setDepositsPaused(false);\n    }\n\n    function _deposit(address who, uint256 amount, uint256 minShares) internal returns (uint256) {\n        simd.mint(who, amount);\n        vm.startPrank(who);\n        simd.approve(address(farm), amount);\n        uint256 s = farm.deposit(amount, minShares);\n        vm.stopPrank();\n        return s;\n    }\n\n    /// Retained 14% withdrawal fee is an internal donation channel: one 1-wei share\n    /// can be made worth 14% of any cycled amount for a 1% cost, after which ordinary\n    /// deposits revert ZeroShares or silently lose up to one share's value.\n    function attackStep(address who, uint256 amount, bool cycle) external {\n        require(msg.sender == address(this));\n        uint256 shares = _deposit(who, amount, 1);\n        if (cycle) {\n            vm.prank(who);\n            farm.withdraw(shares, 0, payable(who), false);\n        }\n    }\n\n    function testRetainedFeeInflatesSingleWeiShare() public {\n        uint256 treasuryBefore = simd.balanceOf(address(treasury));\n        // Attacker wallet A takes the first, only share with 1 wei; wallet B cycles 100 tokens once.\n        // (If a fix rejects either step, the attack is prevented and the victim checks below still apply.)\n        try this.attackStep(attackerA, 1, false) {} catch {}\n        try this.attackStep(attackerB, 100e18, true) {} catch {}\n        // Attacker's net cost is only the 1% Treasury fee: 14% stays inside A's single share.\n        assertLe(simd.balanceOf(address(treasury)) - treasuryBefore, 1e18);\n\n        // A victim's 10-token deposit must still mint shares.\n        simd.mint(victim, 10e18);\n        vm.startPrank(victim);\n        simd.approve(address(farm), 10e18);\n        bool smallDepositOk;\n        try farm.deposit(10e18, 1) returns (uint256) {\n            smallDepositOk = true;\n        } catch {}\n        vm.stopPrank();\n        assertTrue(smallDepositOk, \"10-token deposit reverted: 1 share inflated to 14 tokens for a 1-token cost\");\n    }\n\n    function testInflatedShareSilentlyTakesDepositValue() public {\n        try this.attackStep(attackerA, 1, false) {} catch {}\n        try this.attackStep(attackerB, 100e18, true) {} catch {}\n        // previewDeposit(20e18) == 1 share, so minSharesOut = preview passes,\n        // yet that share is worth ~17 tokens before any withdrawal fee.\n        uint256 preview = farm.previewDeposit(20e18);\n        uint256 vShares = _deposit(victim, 20e18, preview == 0 ? 1 : preview);\n        uint256 victimValue = vShares * farm.accountedAssets() / farm.totalShares();\n        assertGe(victimValue, 20e18 * 9999 / 10_000, \"deposit lost value to share-price rounding\");\n    }\n}","reproduction":"sIMD farm (18 decimals), deposits unpaused, empty pool. (1) Attacker wallet A: deposit(1, 1) -> 1 share. (2) Attacker wallet B: deposit(100e18, 1) -> 100e18 shares, then withdraw(100e18, 0, B, false): gross=100e18, net=85e18, Treasury=1e18, retained=14e18. State: totalShares=1, accountedAssets=14e18+1. Attacker's net cost = 1e18 (1%). (3) Victim deposit(10e18, 1) reverts ZeroShares (floor(10e18*1/(14e18+1)) = 0). Expected: a 10-token deposit mints shares. (4) Victim deposit(20e18, previewDeposit(20e18)=1) succeeds with 1 of 2 shares, worth floor((34e18+1)/2) = 17e18 before any fee: an immediate 3e18 (15%) loss, invisible to minSharesOut. Repeating step 2 with 1000e18 raises the minimum depositable amount to ~140e18 for a 10e18 cost. Proof: test/scratch/InflationGrief.t.sol. Both tests fail now: 'deposit reverted' and '17000000000000000000 < 19998000000000000000'.","severity":"medium","snippet":"        if (totalShares == 0) return Math.mulDiv(assets, initialShareScale, 1);\n        return Math.mulDiv(assets, totalShares, accountedAssets);","title":"Retained 14% withdrawal fee lets one 1-wei share be inflated cheaply: later deposits revert ZeroShares or lose up to one share's value despite minSharesOut"}],"hash":"f715d56c1c114f7da11496db850bd9c79e9748c29153635a83495716b777fb2e","nodeId":"8c9b79ae-f694-41ff-bae7-838052860d4c","outcome":"completed","summary":"I found one defect in the math area (medium severity) and wrote it to `.imd-findings.json` along with a coverage row for all 20 entry points and 4 invariant rows. I changed no repository files: the temporary `test/scratch/` proof was run, then deleted, and its full source is in the finding's `proof` field.\n\n**Finding 1 (medium): any depositor can cheaply inflate the share price through the retained 14% withdrawal fee** (`src/BloomFarm.sol:141`, with the retention at line 214)\n- **Cause:** the donation protection only blocks direct token transfers. Every withdrawal that isn't the last one adds 14% to the remaining shares, which works like an internal donation. The first deposit mints shares 1:1 for 18-decimal tokens. There is no virtual share offset and no minimum share balance, so a pool down to a single share can have that share's value pushed up without limit.\n- **Reproduction:**\n  1. Wallet A deposits 1 wei and gets 1 share.\n  2. Wallet B deposits 100e18 and withdraws it all. The pool is left with 1 share backed by 14e18 + 1 assets. The attacker's net cost is just the 1e18 Treasury fee, since the 14e18 stays in their own share.\n  3. A victim's `deposit(10e18, 1)` now reverts with `ZeroShares`.\n  4. `deposit(20e18, previewDeposit=1)` succeeds, but the victim's share is worth 17e18. That is an immediate 3e18 loss, and `minSharesOut` can't catch it because the loss is already in the preview.\n- **Scale:** one 1000e18 cycle, costing about 10e18, blocks every deposit under about 140e18.\n- **Without an attacker:** a sole depositor who withdraws all but 1 wei of shares leaves the same state behind.\n- **Proof:** both proof tests fail on the current code for exactly this reason (`17000000000000000000 < 19998000000000000000`, and the 10-token deposit reverting).\n- **Fix that keeps Model B:** require a minimum live share balance, or add a virtual share offset.\n\n**What held up:**\n- **Withdrawal fees:** the gross amount rounds down, while the total fee and the 1% Treasury fee round up. The retained part is never negative, and the last exit sends the full 15% to Treasury.\n- **Assets vs. shares:** `accountedAssets` is zero exactly when `totalShares` is zero.\n- **Rewards:** the reward index can't overflow. Index rounding and remainder resets can only under-credit, so user credits never exceed `rewardLiability`.\n- **Epochs:** vesting is conserved across Continue and Restart, and a segment's end is always after its start, so there is no zero divisor.\n- **Factory:** token decimals are capped at 18, so the share-scale exponent can't underflow.\n\n**Leads I dropped, and why:**\n- A sub-unit share that can't be withdrawn (`ZeroAssets`) on assets with fewer than 18 decimals. It is worth less than one base unit and becomes withdrawable once the share price rises.\n- Index remainder lost on every deposit or withdrawal. At most about 340 base units, and already disclosed in the security doc (E3).\n- Fee sniping and wallet splitting. These are disclosed as E1/E2, follow from the brief's own rules, and are outside the math area.\n- The Slither weak-PRNG and strict-equality hits are deliberate modulo and exact-transfer checks, not defects.\n\n**Not covered:** I didn't run the full existing test suite, and I spent only light passes on non-math areas.","treeHash":null,"usage":{"cachedInputTokens":741812,"inputTokens":22,"model":"claude-opus-5-5","outputTokens":29541,"runtime":"claude","turns":15,"wallClockMs":331160}}],"verification":[{"checks":[{"durationMs":30609,"exitCode":0,"name":"build","output":"Compiling 47 files with Solc 0.8.26\nSolc 0.8.26 finished in 30.53s\nCompiler run successful!\n","passed":true},{"durationMs":18315,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testConstructorsDoNotCallDependenciesOnEmptyChain() (gas: 5942196)\n[PASS] testInitCodeAndRuntimeFitMainnetLimits() (gas: 12316)\n[PASS] testScriptRehearsalUsesAuthenticatedRequesterAndLeavesTokensUnset() (gas: 16269144)\n[PASS] testScriptRejectsWrongChainAndWorkerOwnership() (gas: 4036865)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 26.17ms (46.67ms CPU time)\n\nRan 10 tests for test/NativeETH.t.sol:NativeETHTest\n[PASS] testExplicitWETHWithdrawalHasIdenticalFees() (gas: 463586)\n[PASS] testForcedETHDonationsAreNeverShareBacking() (gas: 476573)\n[PASS] testGasExhaustionDoesNotBlockWETHFallback() (gas: 632684)\n[PASS] testNativeDepositWrapsAndWithdrawalPaysETHAndTreasury() (gas: 463858)\n[PASS] testNativeRedistributionRemainsWrappedInSameFarm() (gas: 586962)\n[PASS] testReentrantETHRecipientCannotClaimOrWithdrawDuringPayout() (gas: 647048)\n[PASS] testRejectingRecipientAutomaticallyGetsWETH() (gas: 593421)\n[PASS] testRejectingTreasuryGetsWETHWithoutBlockingUser() (gas: 651642)\n[PASS] testReturnDataBombDoesNotBlockWETHFallback() (gas: 617425)\n[PASS] testWrongEntryPointsAndUnsolicitedETHRejected() (gas: 68854)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 53.90ms (5.60ms CPU time)\n\nRan 14 tests for test/Admin.t.sol:AdminTest\n[PASS] testAllOwnerPermissionsRejectOtherWallets() (gas: 265965)\n[PASS] testFactoryRenouncementRequiresSetupAndCannotBeRecovered() (gas: 194644)\n[PASS] testFarmRenouncementRequiresActivationScheduleAndUnpausedFinalDeposits() (gas: 2771840)\n[PASS] testOwnershipAlwaysExplicitNotDeployer() (gas: 75677)\n[PASS] testPauseLeavesWithdrawalsAndClaimsAvailable() (gas: 1606513)\n[PASS] testRenouncementConfirmationCannotBeReplayedAcrossContractsOrRoles() (gas: 174391)\n[PASS] testRenouncingAllFarmRolesDoesNotDisableUsersOrReenableViaUnknownCalls() (gas: 1778339)\n[PASS] testSelectiveFarmRenouncementIsPermanentAndTreasuryUnaffected() (gas: 1648462)\n[PASS] testTokenConfigurationIsSingleUseAndRequiresEvidenceAndCode() (gas: 1114112)\n[PASS] testTreasuryCannotReachFarmPrincipalOrRewardLiabilities() (gas: 826142)\n[PASS] testUnavailableBloomDoesNotBlockInfrastructureDeployment() (gas: 2849905)\n[PASS] testUnconfiguredUnderlyingFailsAndIMDCannotBeWhitelisted() (gas: 1066536)\n[PASS] testWhitelistDoesNotRetroactivelyDisableExistingFarms() (gas: 520358)\n[PASS] testWrongTreasuryOwnerRejectedOnFirstUse() (gas: 45805)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 62.25ms (5.55ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:BLOOMInvariants\n[PASS]\nBLOOMInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n BLOOMInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 101\n  Bound result 79\n  Bound result 6\n  Bound result 1000000000000000000\n  Bound result 255\n  Bound result 6903\n  Bound result 6585\n  Bound result 1\n  Bound result 1580\n  Bound result 30\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 7000000000000000000\n  Bound result 5127\n  Bound result 1926\n  Bound result 30\n  Bound result 7000000000000000000\n  Bound result 1500\n  Bound result 0\n  Bound result 4\n  Bound result 2571\n  Bound result 512853\n  Bound result 1480\n  Bound result 72039838646999738124\n  Bound result 1\n  Bound result 19\n  Bound result 7252\n  Bound result 19\n  Bound result 2027565564887957005\n  Bound result 3691\n  Bound result 3456000\n  Bound result 18\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.51s (3.51s CPU time)\n\nRan 1 test for test/Invariants.t.sol:SIMDInvariants\n[PASS]\nSIMDInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n SIMDInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 240\n  Bound result 100\n  Bound result 6\n  Bound result 1000000000000000000\n  Bound result 255\n  Bound result 719206527\n  Bound result 5611\n  Bound result 1\n  Bound result 860\n  Bound result 1\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 7000000000000000000\n  Bound result 2281\n  Bound result 30\n  Bound result 7000000000000000000\n  Bound result 1500\n  Bound result 0\n  Bound result 4\n  Bound result 3519\n  Bound result 9171\n  Bound result 512853\n  Bound result 2488\n  Bound result 72021413758680140299\n  Bound result 1\n  Bound result 19\n  Bound result 6165\n  Bound result 19\n  Bound result 8847409862856312640\n  Bound result 100006\n  Bound result 3456000\n  Bound result 18\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.69s (3.69s CPU time)\n\nRan 12 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] testDepositAndFundingCapsRejectWithoutOverflow() (gas: 764181)\n[PASS] testFalseReturnTokenRevertsSafely() (gas: 447518)\n[PASS] testFarmRejectsInvalidRecipientsAndConstructorValues() (gas: 368854)\n[PASS] testMaximumAccountingBoundsAndFullPrecision() (gas: 1192366)\n[PASS] testNegativeRebaseCannotSpendRewardBackingAsPrincipal() (gas: 3261543)\n[PASS] testReentrantTokenCannotDepositWithdrawClaimOrSweep() (gas: 1995032)\n[PASS] testRewardTokenCallbackCannotClaimTwice() (gas: 1262290)\n[PASS] testTaxedDepositRevertsWithoutIssuingShares() (gas: 380916)\n[PASS] testTaxedRewardFundingRevertsWithoutCreatingUnbackedReserve() (gas: 331291)\n[PASS] testTaxedWithdrawalRollsBackBurnAndTreasuryPayment() (gas: 502788)\n[PASS] testTokenWithNoReturnValueSupported() (gas: 797483)\n[PASS] testTreasuryNativeFailureAndRecipientValidation() (gas: 237914)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 17.61s (10.75ms CPU time)\n\nRan 16 tests for test/Rewards.t.sol:RewardsTest\n[PASS] testBloomFarmSeparatesPrincipalRewardsAndDonations() (gas: 3998198)\n[PASS] testContinueAndBoostPreserveFinishAndAccruedClaims() (gas: 1074960)\n[PASS] testDurationModeAndReserveFailures() (gas: 893871)\n[PASS] testEmptyPoolRecyclesOnlyDustAndPreservesOldClaims() (gas: 2106106)\n[PASS] testFundingNeverStartsEmissions() (gas: 700205)\n[PASS] testFuzzEpochReconfigurationConservesFundedRewards(uint128,uint32,uint16,bool) (runs: 512, μ: 1192293, ~: 1185973)\nLogs:\n  Bound result 864000\n  Bound result 11011\n\n[PASS] testGlobalRemainderPreventsCheckpointFrequencyGrief() (gas: 6560512)\n[PASS] testNewAllocationCannotReuseLiabilities() (gas: 1163228)\n[PASS] testOneThousandUsersCanExitAndClaimWithoutLeakingRewards() (gas: 548871712)\n[PASS] testPerAccountFractionsSurviveFrequentClaims() (gas: 1888882)\n[PASS] testRestartOnlyReschedulesUnvestedPlusNewAllocation() (gas: 1202226)\n[PASS] testRewardsAccrueOnlyDuringOwnStakeAndClaimHasNoFee() (gas: 1471313)\n[PASS] testSameTimestampEntryExitCannotCapturePreviouslyAccruedRewards() (gas: 1399717)\n[PASS] testSeparateFarmsNeverShareAssetsRewardsOrSchedules() (gas: 3951020)\n[PASS] testSmallBudgetVestsExactlyWithoutZeroRateLoss() (gas: 1048312)\n[PASS] testZeroStakerEmissionsReturnToReserveAndDoNotRewardNextDepositor() (gas: 1103656)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 17.61s (2.43s CPU time)\n\nRan 16 tests for test/Accounting.t.sol:AccountingTest\n[PASS] testDepositSlippageZeroSharesAndRoundingDust() (gas: 1178896)\n[PASS] testDonationsBeforeFirstDepositAndAfterEmptyRemainIsolated() (gas: 1083512)\n[PASS] testDonationsDoNotInflateSharesOrBecomeRewards() (gas: 805066)\n[PASS] testFeeSnipingIsPossibleUnderImmediateEligibility() (gas: 920049)\n[PASS] testFuzzFeeConservation(uint128,uint128,uint128) (runs: 512, μ: 838830, ~: 841354)\nLogs:\n  Bound result 1000000000000\n  Bound result 30\n  Bound result 1000000000000\n\n[PASS] testFuzzFiveToOneThousandUsers(uint16) (runs: 512, μ: 102837282, ~: 52270187)\nLogs:\n  Bound result 7\n\n[PASS] testImmediateSingleDepositorFullWithdrawal() (gas: 589553)\n[PASS] testLastDepositorDoesNotInheritPriorRoundingOrRewards() (gas: 960304)\n[PASS] testMinimumsAndWithdrawBounds() (gas: 506283)\n[PASS] testMultiWalletSplittingMatchesIntentionalPartialWithdrawalBenefit() (gas: 937927)\n[PASS] testPartialWithdrawerIntentionallyBenefits() (gas: 716375)\n[PASS] testRedistributionAndFullCompoundedPositionFee() (gas: 961086)\n[PASS] testRepeatedPartialWithdrawalsRetainTheModelBRecaptureBenefit() (gas: 4596832)\n[PASS] testSharesAreNonTransferableAndNoAllowanceExists() (gas: 386262)\n[PASS] testSixDecimalUnderlyingStillHas18DecimalShares() (gas: 1486873)\n[PASS] testTinyWithdrawalsCannotAvoidFee() (gas: 13017533)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 18.22s (18.30s CPU time)\n\nRan 8 test suites in 18.22s (60.78s CPU time): 74 tests passed, 0 failed, 0 skipped (74 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BloomFarm.boost(uint256)\",\"BloomFarm.checkpoint()\",\"BloomFarm.claim(address)\",\"BloomFarm.deposit(uint256,uint256)\",\"BloomFarm.depositETH(uint256)\",\"BloomFarm.fundRewards(uint256)\",\"BloomFarm.receive()\",\"BloomFarm.renounceRoles(uint256,bytes32)\",\"BloomFarm.setDepositsPaused(bool)\",\"BloomFarm.setEpoch(uint256,uint256,uint8)\",\"BloomFarm.sweepNativeDonations()\",\"BloomFarm.sweepSurplus(address)\",\"BloomFarm.withdraw(uint256,uint256,address,bool)\",\"FarmFactory.configureToken(uint8,address,bytes32)\",\"FarmFactory.createFarm(uint8)\",\"FarmFactory.renounceRoles(uint256,bytes32)\",\"FarmFactory.setWhitelisted(address,bool)\",\"Treasury.payNative(address,uint256)\",\"Treasury.payToken(address,address,uint256)\",\"Treasury.receive()\"],\"files\":{\".gitignore\":4,\"README.md\":209,\"abi/BloomFarm.json\":1305,\"abi/FarmFactory.json\":482,\"abi/Treasury.json\":151,\"docs/DEPLOYMENT.md\":66,\"docs/INTEGRATION.md\":61,\"docs/OPERATIONS.md\":95,\"docs/SECURITY.md\":130,\"docs/VALIDATION.md\":78,\"docs/build-metadata.json\":88,\"docs/validation-results.txt\":161,\"foundry.toml\":28,\"launch.json\":19,\"script/Deploy.s.sol\":28,\"src/BloomFarm.sol\":479,\"src/FarmFactory.sol\":118,\"src/SelectiveRoles.sol\":50,\"src/Treasury.sol\":51,\"src/interfaces/IWETH9.sol\":9,\"test/Accounting.t.sol\":230,\"test/Admin.t.sol\":258,\"test/Adversarial.t.sol\":178,\"test/Deployment.t.sol\":70,\"test/Invariants.t.sol\":238,\"test/NativeETH.t.sol\":127,\"test/Rewards.t.sol\":303,\"test/helpers/FarmFixture.sol\":79,\"test/helpers/Mocks.sol\":161,\"tools/export_artifacts.py\":54},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"021ef6ae0ed6c61a368692192618506c2e2bfa26bdd0d5083a3e46d3169dfc84","verifiedTreeHash":"0b72530673367bfedf31fc925c96317e96ba117e","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":61311,"exitCode":0,"name":"build","output":"Compiling 52 files with Solc 0.8.26\nSolc 0.8.26 finished in 61.22s\nCompiler run successful!\n","passed":true},{"durationMs":19139,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 17 tests for test/Admin.t.sol:AdminTest\n[PASS] testAllOwnerPermissionsRejectOtherWallets() (gas: 266251)\n[PASS] testClaimsSurviveAnUnderlyingShortfallWhileWithdrawalsRejectIt() (gas: 1117511)\n[PASS] testFactoryRenouncementRequiresSetupAndCannotBeRecovered() (gas: 194688)\n[PASS] testFarmRenouncementRequiresActivationScheduleAndUnpausedFinalDeposits() (gas: 2942091)\n[PASS] testFundRoleMayGoFirstAndStrandedReserveIsRecoverableOnlyAfterEmissionRenouncement() (gas: 1597571)\n[PASS] testOwnershipAlwaysExplicitNotDeployer() (gas: 75765)\n[PASS] testPauseHolderCapturesRetentionOfExitsMadeWhilePaused() (gas: 941509)\n[PASS] testPauseLeavesWithdrawalsAndClaimsAvailable() (gas: 1609162)\n[PASS] testRenouncementConfirmationCannotBeReplayedAcrossContractsOrRoles() (gas: 175096)\n[PASS] testRenouncingAllFarmRolesDoesNotDisableUsersOrReenableViaUnknownCalls() (gas: 1781259)\n[PASS] testSelectiveFarmRenouncementIsPermanentAndTreasuryUnaffected() (gas: 1502697)\n[PASS] testTokenConfigurationIsSingleUseAndRequiresEvidenceAndCode() (gas: 1114178)\n[PASS] testTreasuryCannotReachFarmPrincipalOrRewardLiabilities() (gas: 834449)\n[PASS] testUnavailableBloomDoesNotBlockInfrastructureDeployment() (gas: 3019691)\n[PASS] testUnconfiguredUnderlyingFailsAndIMDCannotBeWhitelisted() (gas: 1066558)\n[PASS] testWhitelistDoesNotRetroactivelyDisableExistingFarms() (gas: 521694)\n[PASS] testWrongTreasuryOwnerRejectedOnFirstUse() (gas: 45827)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 5.56ms (7.34ms CPU time)\n\nRan 10 tests for test/NativeETH.t.sol:NativeETHTest\n[PASS] testExplicitWETHWithdrawalHasIdenticalFees() (gas: 471775)\n[PASS] testForcedETHDonationsAreNeverShareBacking() (gas: 484894)\n[PASS] testGasExhaustionDoesNotBlockWETHFallback() (gas: 640873)\n[PASS] testNativeDepositWrapsAndWithdrawalPaysETHAndTreasury() (gas: 472046)\n[PASS] testNativeRedistributionRemainsWrappedInSameFarm() (gas: 595931)\n[PASS] testReentrantETHRecipientCannotClaimOrWithdrawDuringPayout() (gas: 655258)\n[PASS] testRejectingRecipientAutomaticallyGetsWETH() (gas: 601610)\n[PASS] testRejectingTreasuryGetsWETHWithoutBlockingUser() (gas: 659920)\n[PASS] testReturnDataBombDoesNotBlockWETHFallback() (gas: 625614)\n[PASS] testWrongEntryPointsAndUnsolicitedETHRejected() (gas: 68964)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 142.08ms (3.13ms CPU time)\n\nRan 13 tests for test/FailureAtomicity.t.sol:FailureAtomicityTest\n[PASS] test_failedDepositCannotAdvanceRewardIndexOrConsumeAllowance() (gas: 2031831)\n[PASS] test_failedEpochAndSlippageLeaveEveryAccountingBucketUnchanged() (gas: 2206234)\n[PASS] test_failedFallbackRewrapRollsBackTheSuccessfulTreasuryPayment() (gas: 2075487)\n[PASS] test_failedFundingRollsBackSettlementAsWellAsReserve() (gas: 1921566)\n[PASS] test_failedNativeDonationSweepDoesNotBlockUserWETHExit() (gas: 603966)\n[PASS] test_failedUserPaymentRollsBackAnAlreadySuccessfulTreasuryTransferAndRewardCheckpoint() (gas: 2264174)\n[PASS] test_falseReturnClaimPreservesEntitlementAndCanBeRetried() (gas: 1921941)\n[PASS] test_rewardDeficitBlocksPrincipalSpendUntilBackingIsActuallyRestored() (gas: 2077661)\n[PASS] test_shortWETHMintRejectsDepositWithoutLosingNativeETH() (gas: 1608529)\n[PASS] test_shortWETHUnwrapRollsBackBurnAndFees() (gas: 1858958)\n[PASS] test_taxedRewardClaimCannotConsumeAccruedLiability() (gas: 1890810)\n[PASS] test_tokenCallbacksFailSpecificallyAtTheReentrancyGuard() (gas: 2820164)\n[PASS] test_treasuryOwnerCannotReenterNativeOrTokenPayment() (gas: 824232)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 305.70ms (11.17ms CPU time)\n\nRan 4 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testConstructorsDoNotCallDependenciesOnEmptyChain() (gas: 6155754)\n[PASS] testInitCodeAndRuntimeFitMainnetLimits() (gas: 12935)\n[PASS] testScriptRehearsalUsesAuthenticatedRequesterAndLeavesTokensUnset() (gas: 17006062)\n[PASS] testScriptRejectsWrongChainAndWorkerOwnership() (gas: 4250107)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 307.79ms (54.41ms CPU time)\n\nRan 4 tests for test/VirtualOffsetAndRecovery.t.sol:VirtualOffsetAndRecoveryTest\n[PASS] testFuzz_retentionReachesRemainingHoldersExceptVirtualFraction(uint256,uint256) (runs: 1000, μ: 1334447, ~: 1335245)\nLogs:\n  Bound result 988788399350311864820404277\n  Bound result 215387\n\n[PASS] testFuzz_sharePriceIsMonotoneAcrossDepositsAndExits(uint256[6],uint256) (runs: 500, μ: 1689682, ~: 1692938)\nLogs:\n  Bound result 3\n  Bound result 598423461911565323525340\n  Bound result 33435362168824384\n  Bound result 3\n  Bound result 152840001041845995443178\n  Bound result 12035184956961856\n\n[PASS] testFuzz_strandedRecoveryNeverReachesScheduledEarnedOrPrincipal(uint256,uint256,uint256,uint256,uint256,address) (runs: 500, μ: 4601995, ~: 4615223)\nLogs:\n  Bound result 312237668460563970469246666\n  Bound result 1\n  Bound result 1000000000002\n  Bound result 87056\n  Bound result 679059\n\n[PASS] test_emissionRenouncementRequiresFundGoneFirstOrTogether() (gas: 1013320)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 567.59ms (1.44s CPU time)\n\nRan 1 test for test/Invariants.t.sol:BLOOMInvariants\n[PASS]\nBLOOMInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n BLOOMInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1383378920\n  Bound result 392\n  Bound result 30\n  Bound result 123\n  Bound result 3456000\n  Bound result 197635845\n  Bound result 7048\n  Bound result 3\n  Bound result 1\n  Bound result 880\n  Bound result 28\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 9\n  Bound result 10361\n  Bound result 17\n  Bound result 3456000\n  Bound result 10000\n  Bound result 0\n  Bound result 2\n  Bound result 9646\n  Bound result 512853\n  Bound result 1026\n  Bound result 59913429742876687445\n  Bound result 1\n  Bound result 32\n  Bound result 8454\n  Bound result 1001\n  Bound result 9791772042893090061\n  Bound result 858\n  Bound result 150000000000000000\n  Bound result 1000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.70s (3.70s CPU time)\n\nRan 1 test for test/Invariants.t.sol:SIMDInvariants\n[PASS]\nSIMDInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n SIMDInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 149088551278651741427575\n  Bound result 579\n  Bound result 30\n  Bound result 123\n  Bound result 3456000\n  Bound result 1725540768\n  Bound result 6934\n  Bound result 3\n  Bound result 1\n  Bound result 3418\n  Bound result 26\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 9\n  Bound result 3393\n  Bound result 17\n  Bound result 3456000\n  Bound result 10000\n  Bound result 0\n  Bound result 2\n  Bound result 3238\n  Bound result 49152\n  Bound result 512853\n  Bound result 1377211532\n  Bound result 126574812206491701142431\n  Bound result 1\n  Bound result 32\n  Bound result 7241\n  Bound result 1001\n  Bound result 21469961853263659107516\n  Bound result 5722\n  Bound result 150000000000000000\n  Bound result 1000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.75s (3.75s CPU time)\n\nRan 2 tests for test/RewardOracle.t.sol:RewardOracleInvariants\n[PASS] invariant_independentOracleMatchesEveryUser() (runs: 256, calls: 24576, reverts: 0)\n\n╭---------------------+---------------+-------+---------+----------╮\n| Contract            | Selector      | Calls | Reverts | Discards |\n+==================================================================+\n| RewardOracleHandler | advance       | 3517  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | checkpoint    | 3471  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | claim         | 3456  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | continueEpoch | 3555  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | fund          | 3512  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | pause         | 3499  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | restart       | 3566  | 0       | 0        |\n╰---------------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 7\n  Bound result 1000000000\n  Bound result 3342\n  Bound result 999999999999\n  Bound result 15000000000000000000\n  Bound result 3392\n  Bound result 151444470\n  Bound result 902797284704\n  Bound result 8052\n  Bound result 7154\n  Bound result 10532\n  Bound result 172800\n  Bound result 1000000000000\n  Bound result 172800\n  Bound result 2999\n  Bound result 159214899\n  Bound result 2043\n  Bound result 172800\n  Bound result 197\n  Bound result 227708292579\n  Bound result 728\n  Bound result 1000000000000\n  Bound result 3275\n  Bound result 1055\n  Bound result 3340\n  Bound result 12093\n  Bound result 296183841541050752651227\n  Bound result 73897971\n  Bound result 83492664\n  Bound result 6602\n  Bound result 8495\n  Bound result 188361092546\n  Bound result 32\n  Bound result 2450\n  Bound result 640273355138\n  Bound result 225499243\n  Bound result 823\n  Bound result 1000000000000\n  Bound result 143367504\n  Bound result 2372\n  Bound result 789184\n  Bound result 5524\n  Bound result 49152\n  Bound result 183104753\n  Bound result 478601422963\n  Bound result 11220\n  Bound result 500\n  Bound result 57\n  Bound result 1000000000\n  Bound result 312077963799358235507525\n  Bound result 3648\n  Bound result 8451560574\n  Bound result 5730\n  Bound result 1250\n  Bound result 3650\n  Bound result 1000000000000\n  Bound result 342\n  Bound result 2\n  Bound result 8500000000000000000\n  Bound result 4064\n  Bound result 2544\n  Bound result 95\n  Bound result 23764931400\n  Bound result 9519\n  Bound result 75722235\n  Bound result 3650\n  Bound result 659\n  Bound result 2000\n  Bound result 3431\n  Bound result 1000000000000\n  Bound result 11698\n  Bound result 5553\n  Bound result 3401\n  Bound result 331336654795\n\n[PASS] test_oracleExercisesContinueRestartBoostAndExpiredFailure() (gas: 2172425)\nLogs:\n  Bound result 7\n  Bound result 1000000000\n  Bound result 86400\n  Bound result 123\n  Bound result 86400\n  Bound result 456\n  Bound result 1\n  Bound result 789\n  Bound result 86400\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.31s (6.31s CPU time)\n\nRan 2 tests for test/NativeCustodyInvariants.t.sol:NativeCustodyInvariants\n[PASS]\nNativeCustodyInvariants invariants:\n[PASS] invariant_nativeAndWrappedPrincipalConservation\n[PASS] invariant_rewardsAndLiabilitiesRemainSeparatelyBacked\n[PASS] invariant_treasuryOnlySpendsActualReceipts\n NativeCustodyInvariants invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭----------------------+-------------+-------+---------+----------╮\n| Contract             | Selector    | Calls | Reverts | Discards |\n+=================================================================+\n| NativeCustodyHandler | advance     | 2226  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | checkpoint  | 2199  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | claim       | 2288  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | deposit     | 2181  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | donate      | 2236  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | fund        | 2227  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | pause       | 2261  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | schedule    | 2266  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | sweep       | 2250  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | treasuryPay | 2207  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | withdraw    | 2235  | 0       | 0        |\n╰----------------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 4000000000000000000\n  Bound result 5000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 1\n  Bound result 500\n  Bound result 18239\n  Bound result 4\n  Bound result 16386\n  Bound result 181901895\n  Bound result 921142626222592204894\n  Bound result 7\n  Bound result 9197\n  Bound result 20773\n  Bound result 851865712330941829818\n  Bound result 6\n  Bound result 1722224614322037026407\n  Bound result 1791\n  Bound result 127500000000000000000\n  Bound result 4110\n  Bound result 16396\n  Bound result 8519\n  Bound result 863999\n  Bound result 863999\n  Bound result 3051\n  Bound result 151487265212489326075\n  Bound result 3551\n  Bound result 3473\n  Bound result 1500\n  Bound result 14999999999999999995\n  Bound result 3510\n  Bound result 3614\n  Bound result 1729\n  Bound result 2445\n  Bound result 1000000000000000000000\n  Bound result 281\n  Bound result 7063\n  Bound result 49152\n  Bound result 66022348\n  Bound result 86400\n  Bound result 4499\n  Bound result 15642\n  Bound result 7824922835343959110\n  Bound result 922156471576910357434\n  Bound result 181901895\n  Bound result 10000000000\n  Bound result 253741028\n  Bound result 8500000000000000000\n  Bound result 31134313\n  Bound result 1\n  Bound result 3053160834504674549\n  Bound result 1000\n  Bound result 96900000000000000000\n  Bound result 14139999999999999560\n  Bound result 88551278651741427575\n  Bound result 2000000000000000016\n  Bound result 7143\n  Bound result 16357\n  Bound result 10000000000\n  Bound result 89912202\n  Bound result 4497\n  Bound result 151444470\n  Bound result 193691195\n  Bound result 7\n  Bound result 863999\n  Bound result 783\n  Bound result 1213\n\n[PASS] test_handlerExercisesFallbackRewardsAndTreasuryPayouts() (gas: 3866654)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 4000000000000000000\n  Bound result 5000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 1\n  Bound result 86400\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.15s (11.15s CPU time)\n\nRan 12 tests for test/BoundaryProperties.t.sol:BoundaryPropertiesTest\n[PASS] testFuzz_allSupportedDecimalsKeepRewardsAndDonationsOutOfTheSharePrice(uint8,uint256,uint256,bool) (runs: 1000, μ: 2234721, ~: 2267977)\nLogs:\n  Bound result 7\n  Bound result 1001\n  Bound result 2735\n\n[PASS] testFuzz_eighteenDecimalRepeatedRoundTripsCannotCreateUnderlying(uint256,uint8) (runs: 1000, μ: 3075016, ~: 2548490)\nLogs:\n  Bound result 10373\n  Bound result 13\n\n[PASS] testFuzz_everySelectiveFarmRoleMaskIsIrreversibleAndLeavesClaimsAvailable(uint8) (runs: 1000, μ: 2004258, ~: 1999718)\nLogs:\n  Bound result 5\n\n[PASS] testFuzz_subUnitWithdrawalCannotBurnLowDecimalShares(uint8,uint256) (runs: 1000, μ: 1426675, ~: 1426648)\nLogs:\n  Bound result 7\n  Bound result 8774\n\n[PASS] test_activationRejectsMissingDependenciesWithoutUnlockingRenouncement() (gas: 102803)\n[PASS] test_creationRenouncementCannotBeUndoneByWhitelistChanges() (gas: 608956)\n[PASS] test_factoryConfigRenouncementStillPermitsLaterFarms() (gas: 8464255)\n[PASS] test_missingTokenCodeAtCreationDoesNotAppendAPhantomFarm() (gas: 62846)\n[PASS] test_missingTreasuryIsRejectedOnFirstUseRatherThanDuringConstruction() (gas: 90957)\n[PASS] test_nativeTokenDecimalsMustBeEighteenAndConstructorsRejectUnsupportedDecimals() (gas: 857601)\n[PASS] test_renouncementConfirmationCannotBeReplayedOnAnotherChain() (gas: 100046)\n[PASS] test_unknownRoleBitsAndUnauthorizedFactoryRenouncementAreRejected() (gas: 102952)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 18.50s (55.95s CPU time)\n\nRan 4 tests for test/RewardOracle.t.sol:RewardBoundaryTest\n[PASS] testFuzz_checkpointAndClaimFrequencyCannotChangeRewards(uint256,uint256) (runs: 1000, μ: 4369903, ~: 4458082)\nLogs:\n  Bound result 27076448266725759942161919633931761373\n  Bound result 215387\n\n[PASS] testFuzz_staggeredEntryAndExitUsesSharesRatherThanCompoundedAssetBalances(uint256,uint256) (runs: 1000, μ: 2346179, ~: 2346145)\nLogs:\n  Bound result 3260183558\n  Bound result 200\n\n[PASS] test_epochStartEndAndOneSecondOutsideAreExact() (gas: 1222811)\n[PASS] test_expiredEmptyEpochCanAllocateTheJustRecycledReserve() (gas: 1166691)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 18.50s (34.00s CPU time)\n\nRan 16 tests for test/Rewards.t.sol:RewardsTest\n[PASS] testBloomFarmSeparatesPrincipalRewardsAndDonations() (gas: 4178010)\n[PASS] testContinueAndBoostPreserveFinishAndAccruedClaims() (gas: 1083553)\n[PASS] testDurationModeAndReserveFailures() (gas: 894815)\n[PASS] testEmptyPoolRecyclesOnlyDustAndPreservesOldClaims() (gas: 2099299)\n[PASS] testFundingNeverStartsEmissions() (gas: 708486)\n[PASS] testFuzzEpochReconfigurationConservesFundedRewards(uint128,uint32,uint16,bool) (runs: 512, μ: 1194469, ~: 1187283)\nLogs:\n  Bound result 1465539\n  Bound result 38111\n  Bound result 3123\n\n[PASS] testGlobalRemainderPreventsCheckpointFrequencyGrief() (gas: 6568222)\n[PASS] testNewAllocationCannotReuseLiabilities() (gas: 1164450)\n[PASS] testOneThousandUsersCanExitAndClaimWithoutLeakingRewards() (gas: 541423282)\n[PASS] testPerAccountFractionsSurviveFrequentClaims() (gas: 1850475)\n[PASS] testRestartOnlyReschedulesUnvestedPlusNewAllocation() (gas: 1203602)\n[PASS] testRewardsAccrueOnlyDuringOwnStakeAndClaimHasNoFee() (gas: 1473370)\n[PASS] testSameTimestampEntryExitCannotCapturePreviouslyAccruedRewards() (gas: 1400389)\n[PASS] testSeparateFarmsNeverShareAssetsRewardsOrSchedules() (gas: 4130515)\n[PASS] testSmallBudgetVestsExactlyWithoutZeroRateLoss() (gas: 1049554)\n[PASS] testZeroStakerEmissionsReturnToReserveAndDoNotRewardNextDepositor() (gas: 1104876)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 18.50s (3.20s CPU time)\n\nRan 12 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] testDepositAndFundingCapsRejectWithoutOverflow() (gas: 766015)\n[PASS] testFalseReturnTokenRevertsSafely() (gas: 448874)\n[PASS] testFarmRejectsInvalidRecipientsAndConstructorValues() (gas: 369917)\n[PASS] testMaximumAccountingBoundsAndFullPrecision() (gas: 1194473)\n[PASS] testNegativeRebaseCannotSpendRewardBackingAsPrincipal() (gas: 3432998)\n[PASS] testReentrantTokenCannotDepositWithdrawClaimOrSweep() (gas: 2007172)\n[PASS] testRewardTokenCallbackCannotClaimTwice() (gas: 1254665)\n[PASS] testTaxedDepositRevertsWithoutIssuingShares() (gas: 381955)\n[PASS] testTaxedRewardFundingRevertsWithoutCreatingUnbackedReserve() (gas: 331557)\n[PASS] testTaxedWithdrawalRollsBackBurnAndTreasuryPayment() (gas: 504166)\n[PASS] testTokenWithNoReturnValueSupported() (gas: 805837)\n[PASS] testTreasuryNativeFailureAndRecipientValidation() (gas: 237914)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 19.03s (5.43ms CPU time)\n\nRan 21 tests for test/Accounting.t.sol:AccountingTest\n[PASS] testDepositSlippageZeroSharesAndRoundingDust() (gas: 1150158)\n[PASS] testDonationsBeforeFirstDepositAndAfterEmptyRemainIsolated() (gas: 1093445)\n[PASS] testDonationsDoNotInflateSharesOrBecomeRewards() (gas: 814240)\n[PASS] testEmptyPoolResidualGoesToTreasuryAndNeverToFutureDepositors() (gas: 1344451)\n[PASS] testFeeSnipingIsPossibleUnderImmediateEligibility() (gas: 938870)\n[PASS] testFuzzFeeConservation(uint128,uint128,uint128) (runs: 512, μ: 848519, ~: 850799)\nLogs:\n  Bound result 2300062452855446488207696837\n  Bound result 7451\n  Bound result 85296979066547179116977\n\n[PASS] testFuzzFiveToOneThousandUsers(uint16) (runs: 512, μ: 101572910, ~: 45608228)\nLogs:\n  Bound result 668\n\n[PASS] testImmediateSingleDepositorFullWithdrawal() (gas: 597989)\n[PASS] testLastDepositorDoesNotInheritPriorRoundingOrRewards() (gas: 969926)\n[PASS] testLowDecimalDustDepositIsRejectedOrClosableWhole() (gas: 2162352)\n[PASS] testMinimumsAndWithdrawBounds() (gas: 507983)\n[PASS] testMultiWalletSplittingMatchesIntentionalPartialWithdrawalBenefit() (gas: 959615)\n[PASS] testPartialWithdrawerIntentionallyBenefits() (gas: 732427)\n[PASS] testRedistributionAndFullCompoundedPositionFee() (gas: 983966)\n[PASS] testRepeatedPartialWithdrawalsRetainTheModelBRecaptureBenefit() (gas: 4620837)\n[PASS] testRetentionCannotInflateSharePriceAgainstLaterDepositors() (gas: 1228360)\n[PASS] testSharesAreNonTransferableAndNoAllowanceExists() (gas: 387721)\n[PASS] testSixDecimalUnderlyingStillHas18DecimalShares() (gas: 1495992)\n[PASS] testSquatterShareCannotCaptureTheFinalFifteenPercent() (gas: 968971)\n[PASS] testTinyWithdrawalsCannotAvoidFee() (gas: 13080897)\n[PASS] testZeroSharesOnlyBelowTheVirtuallyBoundedSharePrice() (gas: 1128670)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 19.03s (19.05s CPU time)\n\nRan 14 test suites in 19.04s (119.83s CPU time): 119 tests passed, 0 failed, 0 skipped (119 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BloomFarm.boost(uint256)\",\"BloomFarm.checkpoint()\",\"BloomFarm.claim(address)\",\"BloomFarm.deposit(uint256,uint256)\",\"BloomFarm.depositETH(uint256)\",\"BloomFarm.fundRewards(uint256)\",\"BloomFarm.receive()\",\"BloomFarm.recoverStrandedReserve()\",\"BloomFarm.renounceRoles(uint256,bytes32)\",\"BloomFarm.setDepositsPaused(bool)\",\"BloomFarm.setEpoch(uint256,uint256,uint8)\",\"BloomFarm.sweepNativeDonations()\",\"BloomFarm.sweepSurplus(address)\",\"BloomFarm.withdraw(uint256,uint256,address,bool)\",\"FarmFactory.configureToken(uint8,address,bytes32)\",\"FarmFactory.createFarm(uint8)\",\"FarmFactory.renounceRoles(uint256,bytes32)\",\"FarmFactory.setWhitelisted(address,bool)\",\"Treasury.payNative(address,uint256)\",\"Treasury.payToken(address,address,uint256)\",\"Treasury.receive()\"],\"files\":{\".gitignore\":4,\"README.md\":247,\"abi/BloomFarm.json\":1406,\"abi/FarmFactory.json\":482,\"abi/Treasury.json\":151,\"docs/DEPLOYMENT.md\":66,\"docs/INTEGRATION.md\":70,\"docs/OPERATIONS.md\":106,\"docs/SECURITY.md\":179,\"docs/VALIDATION.md\":84,\"docs/build-metadata.json\":88,\"docs/validation-results.txt\":169,\"foundry.toml\":28,\"script/Deploy.s.sol\":28,\"src/BloomFarm.sol\":538,\"src/FarmFactory.sol\":118,\"src/SelectiveRoles.sol\":50,\"src/Treasury.sol\":51,\"src/interfaces/IWETH9.sol\":9,\"test/Accounting.t.sol\":347,\"test/Admin.t.sol\":334,\"test/Adversarial.t.sol\":178,\"test/BoundaryProperties.t.sol\":310,\"test/Deployment.t.sol\":70,\"test/FailureAtomicity.t.sol\":392,\"test/Invariants.t.sol\":246,\"test/NativeCustodyInvariants.t.sol\":437,\"test/NativeETH.t.sol\":127,\"test/RewardOracle.t.sol\":321,\"test/Rewards.t.sol\":303,\"test/TESTING.md\":61,\"test/VirtualOffsetAndRecovery.t.sol\":219,\"test/helpers/FarmFixture.sol\":79,\"test/helpers/Mocks.sol\":161,\"tools/export_artifacts.py\":54},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"48b09143614c9faedc76c9c6505cd1387b04f9bc43876a6a109b69a8852c3f4e","verifiedTreeHash":"dd2812ecb4f91fe3db330d7c0e602db4126b2cde","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":58539,"exitCode":0,"name":"build","output":"Compiling 47 files with Solc 0.8.26\nSolc 0.8.26 finished in 58.40s\nCompiler run successful!\n","passed":true},{"durationMs":75623,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testConstructorsDoNotCallDependenciesOnEmptyChain() (gas: 6155754)\n[PASS] testInitCodeAndRuntimeFitMainnetLimits() (gas: 12935)\n[PASS] testScriptRehearsalUsesAuthenticatedRequesterAndLeavesTokensUnset() (gas: 17006062)\n[PASS] testScriptRejectsWrongChainAndWorkerOwnership() (gas: 4250107)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 28.82ms (47.75ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:BLOOMInvariants\n[PASS]\nBLOOMInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n BLOOMInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1383378920\n  Bound result 392\n  Bound result 6\n  Bound result 50000000000000000000\n  Bound result 244\n  Bound result 197635845\n  Bound result 7048\n  Bound result 1\n  Bound result 879\n  Bound result 9\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 244\n  Bound result 10361\n  Bound result 30\n  Bound result 95\n  Bound result 1000000\n  Bound result 0\n  Bound result 2\n  Bound result 9646\n  Bound result 512853\n  Bound result 880\n  Bound result 94758728128114254281\n  Bound result 1\n  Bound result 988380000000000000000\n  Bound result 8455\n  Bound result 988370000000000000000\n  Bound result 566169294606724377258\n  Bound result 858\n  Bound result 96\n  Bound result 1500\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.74s (6.73s CPU time)\n\nRan 1 test for test/Invariants.t.sol:SIMDInvariants\n[PASS]\nSIMDInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n SIMDInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 149088551278651741427575\n  Bound result 579\n  Bound result 6\n  Bound result 50000000000000000000\n  Bound result 244\n  Bound result 1725540768\n  Bound result 6934\n  Bound result 1\n  Bound result 3418\n  Bound result 26\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 244\n  Bound result 3393\n  Bound result 30\n  Bound result 95\n  Bound result 1000000\n  Bound result 0\n  Bound result 2\n  Bound result 3238\n  Bound result 16\n  Bound result 512853\n  Bound result 3417\n  Bound result 61318848832448917361507\n  Bound result 1\n  Bound result 988380000000000000000\n  Bound result 7260\n  Bound result 988370000000000000000\n  Bound result 2165140561815711834445\n  Bound result 511918235\n  Bound result 96\n  Bound result 1500\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.91s (6.91s CPU time)\n\nRan 16 tests for test/Rewards.t.sol:RewardsTest\n[PASS] testBloomFarmSeparatesPrincipalRewardsAndDonations() (gas: 4178010)\n[PASS] testContinueAndBoostPreserveFinishAndAccruedClaims() (gas: 1083553)\n[PASS] testDurationModeAndReserveFailures() (gas: 894815)\n[PASS] testEmptyPoolRecyclesOnlyDustAndPreservesOldClaims() (gas: 2099299)\n[PASS] testFundingNeverStartsEmissions() (gas: 708486)\n[PASS] testFuzzEpochReconfigurationConservesFundedRewards(uint128,uint32,uint16,bool) (runs: 512, μ: 1193946, ~: 1187283)\nLogs:\n  Bound result 12738042934819107231\n  Bound result 7525\n\n[PASS] testGlobalRemainderPreventsCheckpointFrequencyGrief() (gas: 6568222)\n[PASS] testNewAllocationCannotReuseLiabilities() (gas: 1164450)\n[PASS] testOneThousandUsersCanExitAndClaimWithoutLeakingRewards() (gas: 541423282)\n[PASS] testPerAccountFractionsSurviveFrequentClaims() (gas: 1850475)\n[PASS] testRestartOnlyReschedulesUnvestedPlusNewAllocation() (gas: 1203602)\n[PASS] testRewardsAccrueOnlyDuringOwnStakeAndClaimHasNoFee() (gas: 1473370)\n[PASS] testSameTimestampEntryExitCannotCapturePreviouslyAccruedRewards() (gas: 1400389)\n[PASS] testSeparateFarmsNeverShareAssetsRewardsOrSchedules() (gas: 4130515)\n[PASS] testSmallBudgetVestsExactlyWithoutZeroRateLoss() (gas: 1049554)\n[PASS] testZeroStakerEmissionsReturnToReserveAndDoNotRewardNextDepositor() (gas: 1104876)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 74.53s (13.00s CPU time)\n\nRan 10 tests for test/NativeETH.t.sol:NativeETHTest\n[PASS] testExplicitWETHWithdrawalHasIdenticalFees() (gas: 471775)\n[PASS] testForcedETHDonationsAreNeverShareBacking() (gas: 484894)\n[PASS] testGasExhaustionDoesNotBlockWETHFallback() (gas: 640873)\n[PASS] testNativeDepositWrapsAndWithdrawalPaysETHAndTreasury() (gas: 472046)\n[PASS] testNativeRedistributionRemainsWrappedInSameFarm() (gas: 595931)\n[PASS] testReentrantETHRecipientCannotClaimOrWithdrawDuringPayout() (gas: 655258)\n[PASS] testRejectingRecipientAutomaticallyGetsWETH() (gas: 601610)\n[PASS] testRejectingTreasuryGetsWETHWithoutBlockingUser() (gas: 659920)\n[PASS] testReturnDataBombDoesNotBlockWETHFallback() (gas: 625614)\n[PASS] testWrongEntryPointsAndUnsolicitedETHRejected() (gas: 68964)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 74.53s (5.08ms CPU time)\n\nRan 12 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] testDepositAndFundingCapsRejectWithoutOverflow() (gas: 766015)\n[PASS] testFalseReturnTokenRevertsSafely() (gas: 448874)\n[PASS] testFarmRejectsInvalidRecipientsAndConstructorValues() (gas: 369917)\n[PASS] testMaximumAccountingBoundsAndFullPrecision() (gas: 1194473)\n[PASS] testNegativeRebaseCannotSpendRewardBackingAsPrincipal() (gas: 3432998)\n[PASS] testReentrantTokenCannotDepositWithdrawClaimOrSweep() (gas: 2007172)\n[PASS] testRewardTokenCallbackCannotClaimTwice() (gas: 1254665)\n[PASS] testTaxedDepositRevertsWithoutIssuingShares() (gas: 381955)\n[PASS] testTaxedRewardFundingRevertsWithoutCreatingUnbackedReserve() (gas: 331557)\n[PASS] testTaxedWithdrawalRollsBackBurnAndTreasuryPayment() (gas: 504166)\n[PASS] testTokenWithNoReturnValueSupported() (gas: 805837)\n[PASS] testTreasuryNativeFailureAndRecipientValidation() (gas: 237914)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 75.47s (9.48ms CPU time)\n\nRan 21 tests for test/Accounting.t.sol:AccountingTest\n[PASS] testDepositSlippageZeroSharesAndRoundingDust() (gas: 1150158)\n[PASS] testDonationsBeforeFirstDepositAndAfterEmptyRemainIsolated() (gas: 1093445)\n[PASS] testDonationsDoNotInflateSharesOrBecomeRewards() (gas: 814240)\n[PASS] testEmptyPoolResidualGoesToTreasuryAndNeverToFutureDepositors() (gas: 1344451)\n[PASS] testFeeSnipingIsPossibleUnderImmediateEligibility() (gas: 938870)\n[PASS] testFuzzFeeConservation(uint128,uint128,uint128) (runs: 512, μ: 848326, ~: 850799)\nLogs:\n  Bound result 2293\n  Bound result 1000000000000000000000000000000\n  Bound result 10\n\n[PASS] testFuzzFiveToOneThousandUsers(uint16) (runs: 512, μ: 100560673, ~: 45793308)\nLogs:\n  Bound result 657\n\n[PASS] testImmediateSingleDepositorFullWithdrawal() (gas: 597989)\n[PASS] testLastDepositorDoesNotInheritPriorRoundingOrRewards() (gas: 969926)\n[PASS] testLowDecimalDustDepositIsRejectedOrClosableWhole() (gas: 2162352)\n[PASS] testMinimumsAndWithdrawBounds() (gas: 507983)\n[PASS] testMultiWalletSplittingMatchesIntentionalPartialWithdrawalBenefit() (gas: 959615)\n[PASS] testPartialWithdrawerIntentionallyBenefits() (gas: 732427)\n[PASS] testRedistributionAndFullCompoundedPositionFee() (gas: 983966)\n[PASS] testRepeatedPartialWithdrawalsRetainTheModelBRecaptureBenefit() (gas: 4620837)\n[PASS] testRetentionCannotInflateSharePriceAgainstLaterDepositors() (gas: 1228360)\n[PASS] testSharesAreNonTransferableAndNoAllowanceExists() (gas: 387721)\n[PASS] testSixDecimalUnderlyingStillHas18DecimalShares() (gas: 1495992)\n[PASS] testSquatterShareCannotCaptureTheFinalFifteenPercent() (gas: 968971)\n[PASS] testTinyWithdrawalsCannotAvoidFee() (gas: 13080897)\n[PASS] testZeroSharesOnlyBelowTheVirtuallyBoundedSharePrice() (gas: 1128670)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 75.48s (75.65s CPU time)\n\nRan 17 tests for test/Admin.t.sol:AdminTest\n[PASS] testAllOwnerPermissionsRejectOtherWallets() (gas: 266251)\n[PASS] testClaimsSurviveAnUnderlyingShortfallWhileWithdrawalsRejectIt() (gas: 1117511)\n[PASS] testFactoryRenouncementRequiresSetupAndCannotBeRecovered() (gas: 194688)\n[PASS] testFarmRenouncementRequiresActivationScheduleAndUnpausedFinalDeposits() (gas: 2942091)\n[PASS] testFundRoleMayGoFirstAndStrandedReserveIsRecoverableOnlyAfterEmissionRenouncement() (gas: 1597571)\n[PASS] testOwnershipAlwaysExplicitNotDeployer() (gas: 75765)\n[PASS] testPauseHolderCapturesRetentionOfExitsMadeWhilePaused() (gas: 941509)\n[PASS] testPauseLeavesWithdrawalsAndClaimsAvailable() (gas: 1609162)\n[PASS] testRenouncementConfirmationCannotBeReplayedAcrossContractsOrRoles() (gas: 175096)\n[PASS] testRenouncingAllFarmRolesDoesNotDisableUsersOrReenableViaUnknownCalls() (gas: 1781259)\n[PASS] testSelectiveFarmRenouncementIsPermanentAndTreasuryUnaffected() (gas: 1502697)\n[PASS] testTokenConfigurationIsSingleUseAndRequiresEvidenceAndCode() (gas: 1114178)\n[PASS] testTreasuryCannotReachFarmPrincipalOrRewardLiabilities() (gas: 834449)\n[PASS] testUnavailableBloomDoesNotBlockInfrastructureDeployment() (gas: 3019691)\n[PASS] testUnconfiguredUnderlyingFailsAndIMDCannotBeWhitelisted() (gas: 1066558)\n[PASS] testWhitelistDoesNotRetroactivelyDisableExistingFarms() (gas: 521694)\n[PASS] testWrongTreasuryOwnerRejectedOnFirstUse() (gas: 45827)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 75.48s (19.26ms CPU time)\n\nRan 8 test suites in 75.48s (389.16s CPU time): 82 tests passed, 0 failed, 0 skipped (82 total tests)\n","passed":true},{"durationMs":95,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BloomFarm.boost(uint256)\",\"BloomFarm.checkpoint()\",\"BloomFarm.claim(address)\",\"BloomFarm.deposit(uint256,uint256)\",\"BloomFarm.depositETH(uint256)\",\"BloomFarm.fundRewards(uint256)\",\"BloomFarm.receive()\",\"BloomFarm.recoverStrandedReserve()\",\"BloomFarm.renounceRoles(uint256,bytes32)\",\"BloomFarm.setDepositsPaused(bool)\",\"BloomFarm.setEpoch(uint256,uint256,uint8)\",\"BloomFarm.sweepNativeDonations()\",\"BloomFarm.sweepSurplus(address)\",\"BloomFarm.withdraw(uint256,uint256,address,bool)\",\"FarmFactory.configureToken(uint8,address,bytes32)\",\"FarmFactory.createFarm(uint8)\",\"FarmFactory.renounceRoles(uint256,bytes32)\",\"FarmFactory.setWhitelisted(address,bool)\",\"Treasury.payNative(address,uint256)\",\"Treasury.payToken(address,address,uint256)\",\"Treasury.receive()\"],\"files\":{\".gitignore\":4,\"README.md\":247,\"abi/BloomFarm.json\":1406,\"abi/FarmFactory.json\":482,\"abi/Treasury.json\":151,\"docs/DEPLOYMENT.md\":66,\"docs/INTEGRATION.md\":70,\"docs/OPERATIONS.md\":106,\"docs/SECURITY.md\":179,\"docs/VALIDATION.md\":84,\"docs/build-metadata.json\":88,\"docs/validation-results.txt\":169,\"foundry.toml\":28,\"script/Deploy.s.sol\":28,\"src/BloomFarm.sol\":538,\"src/FarmFactory.sol\":118,\"src/SelectiveRoles.sol\":50,\"src/Treasury.sol\":51,\"src/interfaces/IWETH9.sol\":9,\"test/Accounting.t.sol\":347,\"test/Admin.t.sol\":334,\"test/Adversarial.t.sol\":178,\"test/Deployment.t.sol\":70,\"test/Invariants.t.sol\":246,\"test/NativeETH.t.sol\":127,\"test/Rewards.t.sol\":303,\"test/helpers/FarmFixture.sol\":79,\"test/helpers/Mocks.sol\":161,\"tools/export_artifacts.py\":54},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6206,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/BloomFarm.sol:410: BloomFarm._checkpoint(address) (src/BloomFarm.sol#410-420) uses a weak PRNG: \"rewardFraction[account] = fraction % PRECISION (src/BloomFarm.sol#416)\"\n[high/medium] weak-prng at src/BloomFarm.sol:387: BloomFarm._indexIncrease(uint256) (src/BloomFarm.sol#387-392) uses a weak PRNG: \"remainder %= totalShares (src/BloomFarm.sol#391)\"\n[high/medium] reentrancy-balance at src/BloomFarm.sol:484: Reentrancy in BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#484-506):\n[high/medium] reentrancy-balance at src/BloomFarm.sol:484: Reentrancy in BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#484-506):\n[high/medium] reentrancy-balance at src/BloomFarm.sol:192: Reentrancy in BloomFarm.depositETH(uint256) (src/BloomFarm.sol#192-200):\n[high/medium] reentrancy-balance at src/BloomFarm.sol:484: Reentrancy in BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#484-506):\n[medium/medium] divide-before-multiply at src/BloomFarm.sol:122: BloomFarm.constructor(address,address,address,address,uint8,BloomFarm.Kind) (src/BloomFarm.sol#122-146) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/BloomFarm.sol:514: BloomFarm._sendExact(IERC20,address,uint256) (src/BloomFarm.sol#514-523) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:394: BloomFarm._updateGlobal() (src/BloomFarm.sol#394-408) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:461: BloomFarm.sweepNativeDonations() (src/BloomFarm.sol#461-467) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:310: BloomFarm._setEpoch(uint256,uint256,BloomFarm.EpochMode) (src/BloomFarm.sol#310-336) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:381: BloomFarm._vested() (src/BloomFarm.sol#381-385) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:433: BloomFarm.recoverStrandedReserve() (src/BloomFarm.sol#433-444) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/BloomFarm.sol:449: BloomFarm.sweepSurplus(IERC20).reserved (src/BloomFarm.sol#449) is a local variable never initialized\n[low/medium] missing-zero-check at src/Treasury.sol:39: Treasury.payNative(address,uint256).recipient (src/Treasury.sol#39) lacks a zero-check on :\n[low/medium] return-bomb at src/BloomFarm.sol:484: BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#484-506) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/BloomFarm.sol:381: BloomFarm._vested() (src/BloomFarm.sol#381-385) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:363: BloomFarm.earned(address) (src/BloomFarm.sol#363-374) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:394: BloomFarm._updateGlobal() (src/BloomFarm.sol#394-408) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:310: BloomFarm._setEpoch(uint256,uint256,BloomFarm.EpochMode) (src/BloomFarm.sol#310-336) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:433: BloomFarm.recoverStrandedReserve() (src/BloomFarm.sol#433-444) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:478: BloomFarm._assertRewardSolvent() (src/BloomFarm.sol#478-482) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:514: BloomFarm._sendExact(IERC20,address,uint256) (src/BloomFarm.sol#514-523) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:447: BloomFarm.sweepSurplus(IERC20) (src/BloomFarm.sol#447-458) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:265: BloomFarm.claim(address) (src/BloomFarm.sol#265-279) uses timestamp for comparisons","passed":true},{"durationMs":613,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/BloomFarm.sol:461: ETH transferred without address checks\n[high] reentrancy-state-change at src/FarmFactory.sol:54: Reentrancy: State change after external call\n[high] strict-equality-contract-balance at src/BloomFarm.sol:495: Dangerous strict equality checks on contract balances\n[low] centralization-risk at src/BloomFarm.sol:282: Centralization Risk (8 places)\n[low] large-numeric-literal at src/BloomFarm.sol:32: Large Numeric Literal (3 places)\n[low] literal-instead-of-constant at src/BloomFarm.sol:133: Literal Instead of Constant (7 places)\n[low] modifier-used-only-once at src/SelectiveRoles.sol:24: Modifier Invoked Only Once\n[low] non-reentrant-not-first at src/BloomFarm.sol:282: `nonReentrant` is Not the First Modifier (6 places)","passed":true},{"durationMs":8618,"exitCode":0,"name":"proof 1083e6856077","output":"Compiling 40 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.75s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-7c296389/Proof_1083e6856077.t.sol:InflationViaRetentionTest\n[PASS] testSmallDepositsAreBrickedAfterRetentionInflation() (gas: 716379)\n[PASS] testVictimLosesRoundingRemainderToAttacker() (gas: 1094036)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.46ms (1.54ms CPU time)\n\nRan 1 test suite in 5.07ms (2.46ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"50e29daea377520dcfa66076858da6b79741e89e5b4526cdeb8ba5f19c51c59a","verifiedTreeHash":"6164730ca0b2985dcd5c47db96604151b961608f","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":59929,"exitCode":0,"name":"build","output":"Compiling 47 files with Solc 0.8.26\nSolc 0.8.26 finished in 59.75s\nCompiler run successful!\n","passed":true},{"durationMs":70727,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testConstructorsDoNotCallDependenciesOnEmptyChain() (gas: 6155754)\n[PASS] testInitCodeAndRuntimeFitMainnetLimits() (gas: 12935)\n[PASS] testScriptRehearsalUsesAuthenticatedRequesterAndLeavesTokensUnset() (gas: 17006062)\n[PASS] testScriptRejectsWrongChainAndWorkerOwnership() (gas: 4250107)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 28.55ms (54.30ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:BLOOMInvariants\n[PASS]\nBLOOMInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n BLOOMInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1383378920\n  Bound result 392\n  Bound result 6\n  Bound result 50000000000000000000\n  Bound result 244\n  Bound result 197635845\n  Bound result 7048\n  Bound result 1\n  Bound result 879\n  Bound result 9\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 244\n  Bound result 10361\n  Bound result 30\n  Bound result 95\n  Bound result 1000000\n  Bound result 0\n  Bound result 2\n  Bound result 9646\n  Bound result 512853\n  Bound result 880\n  Bound result 94758728128114254281\n  Bound result 1\n  Bound result 988380000000000000000\n  Bound result 8455\n  Bound result 988370000000000000000\n  Bound result 566169294606724377258\n  Bound result 858\n  Bound result 96\n  Bound result 1500\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.05s (6.04s CPU time)\n\nRan 1 test for test/Invariants.t.sol:SIMDInvariants\n[PASS]\nSIMDInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n SIMDInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 149088551278651741427575\n  Bound result 579\n  Bound result 6\n  Bound result 50000000000000000000\n  Bound result 244\n  Bound result 1725540768\n  Bound result 6934\n  Bound result 1\n  Bound result 3418\n  Bound result 26\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 244\n  Bound result 3393\n  Bound result 30\n  Bound result 95\n  Bound result 1000000\n  Bound result 0\n  Bound result 2\n  Bound result 3238\n  Bound result 16\n  Bound result 512853\n  Bound result 3417\n  Bound result 61318848832448917361507\n  Bound result 1\n  Bound result 988380000000000000000\n  Bound result 7260\n  Bound result 988370000000000000000\n  Bound result 2165140561815711834445\n  Bound result 511918235\n  Bound result 96\n  Bound result 1500\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.09s (6.08s CPU time)\n\nRan 10 tests for test/NativeETH.t.sol:NativeETHTest\n[PASS] testExplicitWETHWithdrawalHasIdenticalFees() (gas: 471775)\n[PASS] testForcedETHDonationsAreNeverShareBacking() (gas: 484894)\n[PASS] testGasExhaustionDoesNotBlockWETHFallback() (gas: 640873)\n[PASS] testNativeDepositWrapsAndWithdrawalPaysETHAndTreasury() (gas: 472046)\n[PASS] testNativeRedistributionRemainsWrappedInSameFarm() (gas: 595931)\n[PASS] testReentrantETHRecipientCannotClaimOrWithdrawDuringPayout() (gas: 655258)\n[PASS] testRejectingRecipientAutomaticallyGetsWETH() (gas: 601610)\n[PASS] testRejectingTreasuryGetsWETHWithoutBlockingUser() (gas: 659920)\n[PASS] testReturnDataBombDoesNotBlockWETHFallback() (gas: 625614)\n[PASS] testWrongEntryPointsAndUnsolicitedETHRejected() (gas: 68964)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 69.20s (5.24ms CPU time)\n\nRan 17 tests for test/Admin.t.sol:AdminTest\n[PASS] testAllOwnerPermissionsRejectOtherWallets() (gas: 266251)\n[PASS] testClaimsSurviveAnUnderlyingShortfallWhileWithdrawalsRejectIt() (gas: 1117511)\n[PASS] testFactoryRenouncementRequiresSetupAndCannotBeRecovered() (gas: 194688)\n[PASS] testFarmRenouncementRequiresActivationScheduleAndUnpausedFinalDeposits() (gas: 2942091)\n[PASS] testFundRoleMayGoFirstAndStrandedReserveIsRecoverableOnlyAfterEmissionRenouncement() (gas: 1597571)\n[PASS] testOwnershipAlwaysExplicitNotDeployer() (gas: 75765)\n[PASS] testPauseHolderCapturesRetentionOfExitsMadeWhilePaused() (gas: 941509)\n[PASS] testPauseLeavesWithdrawalsAndClaimsAvailable() (gas: 1609162)\n[PASS] testRenouncementConfirmationCannotBeReplayedAcrossContractsOrRoles() (gas: 175096)\n[PASS] testRenouncingAllFarmRolesDoesNotDisableUsersOrReenableViaUnknownCalls() (gas: 1781259)\n[PASS] testSelectiveFarmRenouncementIsPermanentAndTreasuryUnaffected() (gas: 1502697)\n[PASS] testTokenConfigurationIsSingleUseAndRequiresEvidenceAndCode() (gas: 1114178)\n[PASS] testTreasuryCannotReachFarmPrincipalOrRewardLiabilities() (gas: 834449)\n[PASS] testUnavailableBloomDoesNotBlockInfrastructureDeployment() (gas: 3019691)\n[PASS] testUnconfiguredUnderlyingFailsAndIMDCannotBeWhitelisted() (gas: 1066558)\n[PASS] testWhitelistDoesNotRetroactivelyDisableExistingFarms() (gas: 521694)\n[PASS] testWrongTreasuryOwnerRejectedOnFirstUse() (gas: 45827)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 69.20s (15.56ms CPU time)\n\nRan 16 tests for test/Rewards.t.sol:RewardsTest\n[PASS] testBloomFarmSeparatesPrincipalRewardsAndDonations() (gas: 4178010)\n[PASS] testContinueAndBoostPreserveFinishAndAccruedClaims() (gas: 1083553)\n[PASS] testDurationModeAndReserveFailures() (gas: 894815)\n[PASS] testEmptyPoolRecyclesOnlyDustAndPreservesOldClaims() (gas: 2099299)\n[PASS] testFundingNeverStartsEmissions() (gas: 708486)\n[PASS] testFuzzEpochReconfigurationConservesFundedRewards(uint128,uint32,uint16,bool) (runs: 512, μ: 1193946, ~: 1187283)\nLogs:\n  Bound result 12738042934819107231\n  Bound result 7525\n\n[PASS] testGlobalRemainderPreventsCheckpointFrequencyGrief() (gas: 6568222)\n[PASS] testNewAllocationCannotReuseLiabilities() (gas: 1164450)\n[PASS] testOneThousandUsersCanExitAndClaimWithoutLeakingRewards() (gas: 541423282)\n[PASS] testPerAccountFractionsSurviveFrequentClaims() (gas: 1850475)\n[PASS] testRestartOnlyReschedulesUnvestedPlusNewAllocation() (gas: 1203602)\n[PASS] testRewardsAccrueOnlyDuringOwnStakeAndClaimHasNoFee() (gas: 1473370)\n[PASS] testSameTimestampEntryExitCannotCapturePreviouslyAccruedRewards() (gas: 1400389)\n[PASS] testSeparateFarmsNeverShareAssetsRewardsOrSchedules() (gas: 4130515)\n[PASS] testSmallBudgetVestsExactlyWithoutZeroRateLoss() (gas: 1049554)\n[PASS] testZeroStakerEmissionsReturnToReserveAndDoNotRewardNextDepositor() (gas: 1104876)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 70.56s (11.35s CPU time)\n\nRan 12 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] testDepositAndFundingCapsRejectWithoutOverflow() (gas: 766015)\n[PASS] testFalseReturnTokenRevertsSafely() (gas: 448874)\n[PASS] testFarmRejectsInvalidRecipientsAndConstructorValues() (gas: 369917)\n[PASS] testMaximumAccountingBoundsAndFullPrecision() (gas: 1194473)\n[PASS] testNegativeRebaseCannotSpendRewardBackingAsPrincipal() (gas: 3432998)\n[PASS] testReentrantTokenCannotDepositWithdrawClaimOrSweep() (gas: 2007172)\n[PASS] testRewardTokenCallbackCannotClaimTwice() (gas: 1254665)\n[PASS] testTaxedDepositRevertsWithoutIssuingShares() (gas: 381955)\n[PASS] testTaxedRewardFundingRevertsWithoutCreatingUnbackedReserve() (gas: 331557)\n[PASS] testTaxedWithdrawalRollsBackBurnAndTreasuryPayment() (gas: 504166)\n[PASS] testTokenWithNoReturnValueSupported() (gas: 805837)\n[PASS] testTreasuryNativeFailureAndRecipientValidation() (gas: 237914)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 70.56s (9.18ms CPU time)\n\nRan 21 tests for test/Accounting.t.sol:AccountingTest\n[PASS] testDepositSlippageZeroSharesAndRoundingDust() (gas: 1150158)\n[PASS] testDonationsBeforeFirstDepositAndAfterEmptyRemainIsolated() (gas: 1093445)\n[PASS] testDonationsDoNotInflateSharesOrBecomeRewards() (gas: 814240)\n[PASS] testEmptyPoolResidualGoesToTreasuryAndNeverToFutureDepositors() (gas: 1344451)\n[PASS] testFeeSnipingIsPossibleUnderImmediateEligibility() (gas: 938870)\n[PASS] testFuzzFeeConservation(uint128,uint128,uint128) (runs: 512, μ: 848326, ~: 850799)\nLogs:\n  Bound result 1918\n  Bound result 585\n  Bound result 198\n\n[PASS] testFuzzFiveToOneThousandUsers(uint16) (runs: 512, μ: 100560673, ~: 45793308)\nLogs:\n  Bound result 657\n\n[PASS] testImmediateSingleDepositorFullWithdrawal() (gas: 597989)\n[PASS] testLastDepositorDoesNotInheritPriorRoundingOrRewards() (gas: 969926)\n[PASS] testLowDecimalDustDepositIsRejectedOrClosableWhole() (gas: 2162352)\n[PASS] testMinimumsAndWithdrawBounds() (gas: 507983)\n[PASS] testMultiWalletSplittingMatchesIntentionalPartialWithdrawalBenefit() (gas: 959615)\n[PASS] testPartialWithdrawerIntentionallyBenefits() (gas: 732427)\n[PASS] testRedistributionAndFullCompoundedPositionFee() (gas: 983966)\n[PASS] testRepeatedPartialWithdrawalsRetainTheModelBRecaptureBenefit() (gas: 4620837)\n[PASS] testRetentionCannotInflateSharePriceAgainstLaterDepositors() (gas: 1228360)\n[PASS] testSharesAreNonTransferableAndNoAllowanceExists() (gas: 387721)\n[PASS] testSixDecimalUnderlyingStillHas18DecimalShares() (gas: 1495992)\n[PASS] testSquatterShareCannotCaptureTheFinalFifteenPercent() (gas: 968971)\n[PASS] testTinyWithdrawalsCannotAvoidFee() (gas: 13080897)\n[PASS] testZeroSharesOnlyBelowTheVirtuallyBoundedSharePrice() (gas: 1128670)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 70.56s (70.72s CPU time)\n\nRan 8 test suites in 70.56s (362.23s CPU time): 82 tests passed, 0 failed, 0 skipped (82 total tests)\n","passed":true},{"durationMs":106,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BloomFarm.boost(uint256)\",\"BloomFarm.checkpoint()\",\"BloomFarm.claim(address)\",\"BloomFarm.deposit(uint256,uint256)\",\"BloomFarm.depositETH(uint256)\",\"BloomFarm.fundRewards(uint256)\",\"BloomFarm.receive()\",\"BloomFarm.recoverStrandedReserve()\",\"BloomFarm.renounceRoles(uint256,bytes32)\",\"BloomFarm.setDepositsPaused(bool)\",\"BloomFarm.setEpoch(uint256,uint256,uint8)\",\"BloomFarm.sweepNativeDonations()\",\"BloomFarm.sweepSurplus(address)\",\"BloomFarm.withdraw(uint256,uint256,address,bool)\",\"FarmFactory.configureToken(uint8,address,bytes32)\",\"FarmFactory.createFarm(uint8)\",\"FarmFactory.renounceRoles(uint256,bytes32)\",\"FarmFactory.setWhitelisted(address,bool)\",\"Treasury.payNative(address,uint256)\",\"Treasury.payToken(address,address,uint256)\",\"Treasury.receive()\"],\"files\":{\".gitignore\":4,\"README.md\":247,\"abi/BloomFarm.json\":1406,\"abi/FarmFactory.json\":482,\"abi/Treasury.json\":151,\"docs/DEPLOYMENT.md\":66,\"docs/INTEGRATION.md\":70,\"docs/OPERATIONS.md\":106,\"docs/SECURITY.md\":179,\"docs/VALIDATION.md\":84,\"docs/build-metadata.json\":88,\"docs/validation-results.txt\":169,\"foundry.toml\":28,\"launch.json\":19,\"script/Deploy.s.sol\":28,\"src/BloomFarm.sol\":538,\"src/FarmFactory.sol\":118,\"src/SelectiveRoles.sol\":50,\"src/Treasury.sol\":51,\"src/interfaces/IWETH9.sol\":9,\"test/Accounting.t.sol\":347,\"test/Admin.t.sol\":334,\"test/Adversarial.t.sol\":178,\"test/Deployment.t.sol\":70,\"test/Invariants.t.sol\":246,\"test/NativeETH.t.sol\":127,\"test/Rewards.t.sol\":303,\"test/helpers/FarmFixture.sol\":79,\"test/helpers/Mocks.sol\":161,\"tools/export_artifacts.py\":54},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"58d26ac998ea8aad9657a4db78710160ee942bdeebddd8ac390ea5169cd57372","verifiedTreeHash":"613466b392f209c6d2c728aa1fcd188c918d2fa7","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":52108,"exitCode":0,"name":"build","output":"Compiling 47 files with Solc 0.8.26\nSolc 0.8.26 finished in 51.90s\nCompiler run successful!\n","passed":true},{"durationMs":70903,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testConstructorsDoNotCallDependenciesOnEmptyChain() (gas: 5942196)\n[PASS] testInitCodeAndRuntimeFitMainnetLimits() (gas: 12316)\n[PASS] testScriptRehearsalUsesAuthenticatedRequesterAndLeavesTokensUnset() (gas: 16269144)\n[PASS] testScriptRejectsWrongChainAndWorkerOwnership() (gas: 4036865)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 38.67ms (67.42ms CPU time)\n\nRan 14 tests for test/Admin.t.sol:AdminTest\n[PASS] testAllOwnerPermissionsRejectOtherWallets() (gas: 265965)\n[PASS] testFactoryRenouncementRequiresSetupAndCannotBeRecovered() (gas: 194644)\n[PASS] testFarmRenouncementRequiresActivationScheduleAndUnpausedFinalDeposits() (gas: 2771840)\n[PASS] testOwnershipAlwaysExplicitNotDeployer() (gas: 75677)\n[PASS] testPauseLeavesWithdrawalsAndClaimsAvailable() (gas: 1606513)\n[PASS] testRenouncementConfirmationCannotBeReplayedAcrossContractsOrRoles() (gas: 174391)\n[PASS] testRenouncingAllFarmRolesDoesNotDisableUsersOrReenableViaUnknownCalls() (gas: 1778339)\n[PASS] testSelectiveFarmRenouncementIsPermanentAndTreasuryUnaffected() (gas: 1648462)\n[PASS] testTokenConfigurationIsSingleUseAndRequiresEvidenceAndCode() (gas: 1114112)\n[PASS] testTreasuryCannotReachFarmPrincipalOrRewardLiabilities() (gas: 826142)\n[PASS] testUnavailableBloomDoesNotBlockInfrastructureDeployment() (gas: 2849905)\n[PASS] testUnconfiguredUnderlyingFailsAndIMDCannotBeWhitelisted() (gas: 1066536)\n[PASS] testWhitelistDoesNotRetroactivelyDisableExistingFarms() (gas: 520358)\n[PASS] testWrongTreasuryOwnerRejectedOnFirstUse() (gas: 45805)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 125.86ms (9.13ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:BLOOMInvariants\n[PASS]\nBLOOMInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n BLOOMInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 101\n  Bound result 79\n  Bound result 6\n  Bound result 1000000000000000000\n  Bound result 255\n  Bound result 6903\n  Bound result 6585\n  Bound result 1\n  Bound result 1580\n  Bound result 30\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 7000000000000000000\n  Bound result 5127\n  Bound result 1926\n  Bound result 30\n  Bound result 7000000000000000000\n  Bound result 1500\n  Bound result 0\n  Bound result 4\n  Bound result 2571\n  Bound result 512853\n  Bound result 1480\n  Bound result 72039838646999738124\n  Bound result 1\n  Bound result 19\n  Bound result 7252\n  Bound result 19\n  Bound result 2027565564887957005\n  Bound result 3691\n  Bound result 3456000\n  Bound result 18\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.90s (5.87s CPU time)\n\nRan 1 test for test/Invariants.t.sol:SIMDInvariants\n[PASS]\nSIMDInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n SIMDInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 240\n  Bound result 100\n  Bound result 6\n  Bound result 1000000000000000000\n  Bound result 255\n  Bound result 719206527\n  Bound result 5611\n  Bound result 1\n  Bound result 860\n  Bound result 1\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 7000000000000000000\n  Bound result 2281\n  Bound result 30\n  Bound result 7000000000000000000\n  Bound result 1500\n  Bound result 0\n  Bound result 4\n  Bound result 3519\n  Bound result 9171\n  Bound result 512853\n  Bound result 2488\n  Bound result 72021413758680140299\n  Bound result 1\n  Bound result 19\n  Bound result 6165\n  Bound result 19\n  Bound result 8847409862856312640\n  Bound result 100006\n  Bound result 3456000\n  Bound result 18\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.04s (6.01s CPU time)\n\nRan 16 tests for test/Rewards.t.sol:RewardsTest\n[PASS] testBloomFarmSeparatesPrincipalRewardsAndDonations() (gas: 3998198)\n[PASS] testContinueAndBoostPreserveFinishAndAccruedClaims() (gas: 1074960)\n[PASS] testDurationModeAndReserveFailures() (gas: 893871)\n[PASS] testEmptyPoolRecyclesOnlyDustAndPreservesOldClaims() (gas: 2106106)\n[PASS] testFundingNeverStartsEmissions() (gas: 700205)\n[PASS] testFuzzEpochReconfigurationConservesFundedRewards(uint128,uint32,uint16,bool) (runs: 512, μ: 1192293, ~: 1185973)\nLogs:\n  Bound result 27534242394911123377601\n  Bound result 92\n  Bound result 1\n\n[PASS] testGlobalRemainderPreventsCheckpointFrequencyGrief() (gas: 6560512)\n[PASS] testNewAllocationCannotReuseLiabilities() (gas: 1163228)\n[PASS] testOneThousandUsersCanExitAndClaimWithoutLeakingRewards() (gas: 548871712)\n[PASS] testPerAccountFractionsSurviveFrequentClaims() (gas: 1888882)\n[PASS] testRestartOnlyReschedulesUnvestedPlusNewAllocation() (gas: 1202226)\n[PASS] testRewardsAccrueOnlyDuringOwnStakeAndClaimHasNoFee() (gas: 1471313)\n[PASS] testSameTimestampEntryExitCannotCapturePreviouslyAccruedRewards() (gas: 1399717)\n[PASS] testSeparateFarmsNeverShareAssetsRewardsOrSchedules() (gas: 3951020)\n[PASS] testSmallBudgetVestsExactlyWithoutZeroRateLoss() (gas: 1048312)\n[PASS] testZeroStakerEmissionsReturnToReserveAndDoNotRewardNextDepositor() (gas: 1103656)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 9.27s (9.37s CPU time)\n\nRan 12 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] testDepositAndFundingCapsRejectWithoutOverflow() (gas: 764181)\n[PASS] testFalseReturnTokenRevertsSafely() (gas: 447518)\n[PASS] testFarmRejectsInvalidRecipientsAndConstructorValues() (gas: 368854)\n[PASS] testMaximumAccountingBoundsAndFullPrecision() (gas: 1192366)\n[PASS] testNegativeRebaseCannotSpendRewardBackingAsPrincipal() (gas: 3261543)\n[PASS] testReentrantTokenCannotDepositWithdrawClaimOrSweep() (gas: 1995032)\n[PASS] testRewardTokenCallbackCannotClaimTwice() (gas: 1262290)\n[PASS] testTaxedDepositRevertsWithoutIssuingShares() (gas: 380916)\n[PASS] testTaxedRewardFundingRevertsWithoutCreatingUnbackedReserve() (gas: 331291)\n[PASS] testTaxedWithdrawalRollsBackBurnAndTreasuryPayment() (gas: 502788)\n[PASS] testTokenWithNoReturnValueSupported() (gas: 797483)\n[PASS] testTreasuryNativeFailureAndRecipientValidation() (gas: 237914)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 68.74s (13.84ms CPU time)\n\nRan 10 tests for test/NativeETH.t.sol:NativeETHTest\n[PASS] testExplicitWETHWithdrawalHasIdenticalFees() (gas: 463586)\n[PASS] testForcedETHDonationsAreNeverShareBacking() (gas: 476573)\n[PASS] testGasExhaustionDoesNotBlockWETHFallback() (gas: 632684)\n[PASS] testNativeDepositWrapsAndWithdrawalPaysETHAndTreasury() (gas: 463858)\n[PASS] testNativeRedistributionRemainsWrappedInSameFarm() (gas: 586962)\n[PASS] testReentrantETHRecipientCannotClaimOrWithdrawDuringPayout() (gas: 647048)\n[PASS] testRejectingRecipientAutomaticallyGetsWETH() (gas: 593421)\n[PASS] testRejectingTreasuryGetsWETHWithoutBlockingUser() (gas: 651642)\n[PASS] testReturnDataBombDoesNotBlockWETHFallback() (gas: 617425)\n[PASS] testWrongEntryPointsAndUnsolicitedETHRejected() (gas: 68854)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 69.40s (6.81ms CPU time)\n\nRan 16 tests for test/Accounting.t.sol:AccountingTest\n[PASS] testDepositSlippageZeroSharesAndRoundingDust() (gas: 1178896)\n[PASS] testDonationsBeforeFirstDepositAndAfterEmptyRemainIsolated() (gas: 1083512)\n[PASS] testDonationsDoNotInflateSharesOrBecomeRewards() (gas: 805066)\n[PASS] testFeeSnipingIsPossibleUnderImmediateEligibility() (gas: 920049)\n[PASS] testFuzzFeeConservation(uint128,uint128,uint128) (runs: 512, μ: 838830, ~: 841354)\nLogs:\n  Bound result 242\n  Bound result 4408\n  Bound result 48\n\n[PASS] testFuzzFiveToOneThousandUsers(uint16) (runs: 512, μ: 102837282, ~: 52270187)\nLogs:\n  Bound result 7\n\n[PASS] testImmediateSingleDepositorFullWithdrawal() (gas: 589553)\n[PASS] testLastDepositorDoesNotInheritPriorRoundingOrRewards() (gas: 960304)\n[PASS] testMinimumsAndWithdrawBounds() (gas: 506283)\n[PASS] testMultiWalletSplittingMatchesIntentionalPartialWithdrawalBenefit() (gas: 937927)\n[PASS] testPartialWithdrawerIntentionallyBenefits() (gas: 716375)\n[PASS] testRedistributionAndFullCompoundedPositionFee() (gas: 961086)\n[PASS] testRepeatedPartialWithdrawalsRetainTheModelBRecaptureBenefit() (gas: 4596832)\n[PASS] testSharesAreNonTransferableAndNoAllowanceExists() (gas: 386262)\n[PASS] testSixDecimalUnderlyingStillHas18DecimalShares() (gas: 1486873)\n[PASS] testTinyWithdrawalsCannotAvoidFee() (gas: 13017533)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 70.74s (70.86s CPU time)\n\nRan 8 test suites in 70.74s (230.25s CPU time): 74 tests passed, 0 failed, 0 skipped (74 total tests)\n","passed":true},{"durationMs":98,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BloomFarm.boost(uint256)\",\"BloomFarm.checkpoint()\",\"BloomFarm.claim(address)\",\"BloomFarm.deposit(uint256,uint256)\",\"BloomFarm.depositETH(uint256)\",\"BloomFarm.fundRewards(uint256)\",\"BloomFarm.receive()\",\"BloomFarm.renounceRoles(uint256,bytes32)\",\"BloomFarm.setDepositsPaused(bool)\",\"BloomFarm.setEpoch(uint256,uint256,uint8)\",\"BloomFarm.sweepNativeDonations()\",\"BloomFarm.sweepSurplus(address)\",\"BloomFarm.withdraw(uint256,uint256,address,bool)\",\"FarmFactory.configureToken(uint8,address,bytes32)\",\"FarmFactory.createFarm(uint8)\",\"FarmFactory.renounceRoles(uint256,bytes32)\",\"FarmFactory.setWhitelisted(address,bool)\",\"Treasury.payNative(address,uint256)\",\"Treasury.payToken(address,address,uint256)\",\"Treasury.receive()\"],\"files\":{\".gitignore\":4,\"README.md\":209,\"abi/BloomFarm.json\":1305,\"abi/FarmFactory.json\":482,\"abi/Treasury.json\":151,\"docs/DEPLOYMENT.md\":66,\"docs/INTEGRATION.md\":61,\"docs/OPERATIONS.md\":95,\"docs/SECURITY.md\":130,\"docs/VALIDATION.md\":78,\"docs/build-metadata.json\":88,\"docs/validation-results.txt\":161,\"foundry.toml\":28,\"script/Deploy.s.sol\":28,\"src/BloomFarm.sol\":479,\"src/FarmFactory.sol\":118,\"src/SelectiveRoles.sol\":50,\"src/Treasury.sol\":51,\"src/interfaces/IWETH9.sol\":9,\"test/Accounting.t.sol\":230,\"test/Admin.t.sol\":258,\"test/Adversarial.t.sol\":178,\"test/Deployment.t.sol\":70,\"test/Invariants.t.sol\":238,\"test/NativeETH.t.sol\":127,\"test/Rewards.t.sol\":303,\"test/helpers/FarmFixture.sol\":79,\"test/helpers/Mocks.sol\":161,\"tools/export_artifacts.py\":54},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6631,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/BloomFarm.sol:347: BloomFarm._indexIncrease(uint256) (src/BloomFarm.sol#347-352) uses a weak PRNG: \"remainder %= totalShares (src/BloomFarm.sol#351)\"\n[high/medium] weak-prng at src/BloomFarm.sol:370: BloomFarm._checkpoint(address) (src/BloomFarm.sol#370-380) uses a weak PRNG: \"rewardFraction[account] = fraction % PRECISION (src/BloomFarm.sol#376)\"\n[high/medium] reentrancy-balance at src/BloomFarm.sol:429: Reentrancy in BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#429-451):\n[high/medium] reentrancy-balance at src/BloomFarm.sol:429: Reentrancy in BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#429-451):\n[high/medium] reentrancy-balance at src/BloomFarm.sol:168: Reentrancy in BloomFarm.depositETH(uint256) (src/BloomFarm.sol#168-176):\n[high/medium] reentrancy-balance at src/BloomFarm.sol:429: Reentrancy in BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#429-451):\n[medium/high] incorrect-equality at src/BloomFarm.sol:459: BloomFarm._sendExact(IERC20,address,uint256) (src/BloomFarm.sol#459-468) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:341: BloomFarm._vested() (src/BloomFarm.sol#341-345) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:354: BloomFarm._updateGlobal() (src/BloomFarm.sol#354-368) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:406: BloomFarm.sweepNativeDonations() (src/BloomFarm.sol#406-412) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/BloomFarm.sol:270: BloomFarm._setEpoch(uint256,uint256,BloomFarm.EpochMode) (src/BloomFarm.sol#270-296) uses a dangerous strict equality:\n[medium/medium] uninitialized-local at src/BloomFarm.sol:394: BloomFarm.sweepSurplus(IERC20).reserved (src/BloomFarm.sol#394) is a local variable never initialized\n[low/medium] missing-zero-check at src/Treasury.sol:39: Treasury.payNative(address,uint256).recipient (src/Treasury.sol#39) lacks a zero-check on :\n[low/medium] return-bomb at src/BloomFarm.sol:429: BloomFarm._payUnderlying(address,uint256,bool) (src/BloomFarm.sol#429-451) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/BloomFarm.sol:323: BloomFarm.earned(address) (src/BloomFarm.sol#323-334) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:270: BloomFarm._setEpoch(uint256,uint256,BloomFarm.EpochMode) (src/BloomFarm.sol#270-296) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:341: BloomFarm._vested() (src/BloomFarm.sol#341-345) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:459: BloomFarm._sendExact(IERC20,address,uint256) (src/BloomFarm.sol#459-468) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:418: BloomFarm._assertSolvent() (src/BloomFarm.sol#418-427) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:392: BloomFarm.sweepSurplus(IERC20) (src/BloomFarm.sol#392-403) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:354: BloomFarm._updateGlobal() (src/BloomFarm.sol#354-368) uses timestamp for comparisons\n[low/medium] timestamp at src/BloomFarm.sol:225: BloomFarm.claim(address) (src/BloomFarm.sol#225-239) uses timestamp for comparisons","passed":true},{"durationMs":1262,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/BloomFarm.sol:406: ETH transferred without address checks\n[high] reentrancy-state-change at src/FarmFactory.sol:54: Reentrancy: State change after external call\n[high] strict-equality-contract-balance at src/BloomFarm.sol:440: Dangerous strict equality checks on contract balances\n[low] centralization-risk at src/BloomFarm.sol:242: Centralization Risk (8 places)\n[low] large-numeric-literal at src/BloomFarm.sol:32: Large Numeric Literal (3 places)\n[low] literal-instead-of-constant at src/BloomFarm.sol:120: Literal Instead of Constant (7 places)\n[low] modifier-used-only-once at src/SelectiveRoles.sol:24: Modifier Invoked Only Once\n[low] non-reentrant-not-first at src/BloomFarm.sol:242: `nonReentrant` is Not the First Modifier (6 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"591676e0e55a4bbbf31b1900cfd144c4347ce983dc0821c7b6aabc4190de80b2","verifiedTreeHash":"21c60803bc7fb6966852f6a44c8d7284ab55357b","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":82745,"exitCode":0,"name":"build","output":"Compiling 51 files with Solc 0.8.26\nSolc 0.8.26 finished in 82.60s\nCompiler run successful!\n","passed":true},{"durationMs":69764,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/NativeETH.t.sol:NativeETHTest\n[PASS] testExplicitWETHWithdrawalHasIdenticalFees() (gas: 463586)\n[PASS] testForcedETHDonationsAreNeverShareBacking() (gas: 476573)\n[PASS] testGasExhaustionDoesNotBlockWETHFallback() (gas: 632684)\n[PASS] testNativeDepositWrapsAndWithdrawalPaysETHAndTreasury() (gas: 463858)\n[PASS] testNativeRedistributionRemainsWrappedInSameFarm() (gas: 586962)\n[PASS] testReentrantETHRecipientCannotClaimOrWithdrawDuringPayout() (gas: 647048)\n[PASS] testRejectingRecipientAutomaticallyGetsWETH() (gas: 593421)\n[PASS] testRejectingTreasuryGetsWETHWithoutBlockingUser() (gas: 651642)\n[PASS] testReturnDataBombDoesNotBlockWETHFallback() (gas: 617425)\n[PASS] testWrongEntryPointsAndUnsolicitedETHRejected() (gas: 68854)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 8.24ms (4.44ms CPU time)\n\nRan 12 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] testDepositAndFundingCapsRejectWithoutOverflow() (gas: 764181)\n[PASS] testFalseReturnTokenRevertsSafely() (gas: 447518)\n[PASS] testFarmRejectsInvalidRecipientsAndConstructorValues() (gas: 368854)\n[PASS] testMaximumAccountingBoundsAndFullPrecision() (gas: 1192366)\n[PASS] testNegativeRebaseCannotSpendRewardBackingAsPrincipal() (gas: 3261543)\n[PASS] testReentrantTokenCannotDepositWithdrawClaimOrSweep() (gas: 1995032)\n[PASS] testRewardTokenCallbackCannotClaimTwice() (gas: 1262290)\n[PASS] testTaxedDepositRevertsWithoutIssuingShares() (gas: 380916)\n[PASS] testTaxedRewardFundingRevertsWithoutCreatingUnbackedReserve() (gas: 331291)\n[PASS] testTaxedWithdrawalRollsBackBurnAndTreasuryPayment() (gas: 502788)\n[PASS] testTokenWithNoReturnValueSupported() (gas: 797483)\n[PASS] testTreasuryNativeFailureAndRecipientValidation() (gas: 237914)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 16.33ms (11.68ms CPU time)\n\nRan 4 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] testConstructorsDoNotCallDependenciesOnEmptyChain() (gas: 5942196)\n[PASS] testInitCodeAndRuntimeFitMainnetLimits() (gas: 12316)\n[PASS] testScriptRehearsalUsesAuthenticatedRequesterAndLeavesTokensUnset() (gas: 16269144)\n[PASS] testScriptRejectsWrongChainAndWorkerOwnership() (gas: 4036865)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 249.11ms (46.96ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SIMDInvariants\n[PASS]\nSIMDInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n SIMDInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 240\n  Bound result 100\n  Bound result 16\n  Bound result 48450000000000000000\n  Bound result 3456000\n  Bound result 719206527\n  Bound result 34382578364627542883\n  Bound result 3\n  Bound result 1\n  Bound result 937\n  Bound result 3\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 7000000000000000000\n  Bound result 2280\n  Bound result 1001\n  Bound result 7000000000000000000\n  Bound result 10\n  Bound result 0\n  Bound result 4\n  Bound result 3520\n  Bound result 512853\n  Bound result 2486\n  Bound result 10952573671382550419\n  Bound result 1\n  Bound result 244\n  Bound result 2859\n  Bound result 7019\n  Bound result 244\n  Bound result 45347630855424572394\n  Bound result 1074929750\n  Bound result 345600000\n  Bound result 100\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.35s (6.34s CPU time)\n\nRan 1 test for test/Invariants.t.sol:BLOOMInvariants\n[PASS]\nBLOOMInvariants invariants:\n[PASS] invariantPrincipalConservationAndTreasuryIsolation\n[PASS] invariantRewardConservationAndCoverage\n[PASS] invariantSharesAndEmptyPoolIsolation\n BLOOMInvariants invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| FarmHandler | advance    | 814   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | checkpoint | 786   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | claim      | 796   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | deposit    | 812   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | donate     | 800   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | epoch      | 816   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | fund       | 822   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | pause      | 788   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | sweep      | 904   | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| FarmHandler | withdraw   | 854   | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 101\n  Bound result 79\n  Bound result 16\n  Bound result 48450000000000000000\n  Bound result 3456000\n  Bound result 6903\n  Bound result 6633\n  Bound result 3\n  Bound result 1\n  Bound result 1581\n  Bound result 13\n  Bound result 89524362289589200156\n  Bound result 991853015393492926228310\n  Bound result 7000000000000000000\n  Bound result 1917\n  Bound result 1001\n  Bound result 7000000000000000000\n  Bound result 10\n  Bound result 0\n  Bound result 4\n  Bound result 9153\n  Bound result 512853\n  Bound result 1303\n  Bound result 120129309750433519859\n  Bound result 1\n  Bound result 244\n  Bound result 7252\n  Bound result 244\n  Bound result 7717155149911739143\n  Bound result 10581\n  Bound result 345600000\n  Bound result 100\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.49s (6.49s CPU time)\n\nRan 14 tests for test/Admin.t.sol:AdminTest\n[PASS] testAllOwnerPermissionsRejectOtherWallets() (gas: 265965)\n[PASS] testFactoryRenouncementRequiresSetupAndCannotBeRecovered() (gas: 194644)\n[PASS] testFarmRenouncementRequiresActivationScheduleAndUnpausedFinalDeposits() (gas: 2771840)\n[PASS] testOwnershipAlwaysExplicitNotDeployer() (gas: 75677)\n[PASS] testPauseLeavesWithdrawalsAndClaimsAvailable() (gas: 1606513)\n[PASS] testRenouncementConfirmationCannotBeReplayedAcrossContractsOrRoles() (gas: 174391)\n[PASS] testRenouncingAllFarmRolesDoesNotDisableUsersOrReenableViaUnknownCalls() (gas: 1778339)\n[PASS] testSelectiveFarmRenouncementIsPermanentAndTreasuryUnaffected() (gas: 1648462)\n[PASS] testTokenConfigurationIsSingleUseAndRequiresEvidenceAndCode() (gas: 1114112)\n[PASS] testTreasuryCannotReachFarmPrincipalOrRewardLiabilities() (gas: 826142)\n[PASS] testUnavailableBloomDoesNotBlockInfrastructureDeployment() (gas: 2849905)\n[PASS] testUnconfiguredUnderlyingFailsAndIMDCannotBeWhitelisted() (gas: 1066536)\n[PASS] testWhitelistDoesNotRetroactivelyDisableExistingFarms() (gas: 520358)\n[PASS] testWrongTreasuryOwnerRejectedOnFirstUse() (gas: 45805)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 10.49s (27.67ms CPU time)\n\nRan 16 tests for test/Rewards.t.sol:RewardsTest\n[PASS] testBloomFarmSeparatesPrincipalRewardsAndDonations() (gas: 3998198)\n[PASS] testContinueAndBoostPreserveFinishAndAccruedClaims() (gas: 1074960)\n[PASS] testDurationModeAndReserveFailures() (gas: 893871)\n[PASS] testEmptyPoolRecyclesOnlyDustAndPreservesOldClaims() (gas: 2106106)\n[PASS] testFundingNeverStartsEmissions() (gas: 700205)\n[PASS] testFuzzEpochReconfigurationConservesFundedRewards(uint128,uint32,uint16,bool) (runs: 512, μ: 1192810, ~: 1185973)\nLogs:\n  Bound result 1465539\n  Bound result 38111\n  Bound result 3123\n\n[PASS] testGlobalRemainderPreventsCheckpointFrequencyGrief() (gas: 6560512)\n[PASS] testNewAllocationCannotReuseLiabilities() (gas: 1163228)\n[PASS] testOneThousandUsersCanExitAndClaimWithoutLeakingRewards() (gas: 548871712)\n[PASS] testPerAccountFractionsSurviveFrequentClaims() (gas: 1888882)\n[PASS] testRestartOnlyReschedulesUnvestedPlusNewAllocation() (gas: 1202226)\n[PASS] testRewardsAccrueOnlyDuringOwnStakeAndClaimHasNoFee() (gas: 1471313)\n[PASS] testSameTimestampEntryExitCannotCapturePreviouslyAccruedRewards() (gas: 1399717)\n[PASS] testSeparateFarmsNeverShareAssetsRewardsOrSchedules() (gas: 3951020)\n[PASS] testSmallBudgetVestsExactlyWithoutZeroRateLoss() (gas: 1048312)\n[PASS] testZeroStakerEmissionsReturnToReserveAndDoNotRewardNextDepositor() (gas: 1103656)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 10.49s (11.60s CPU time)\n\nRan 2 tests for test/RewardOracle.t.sol:RewardOracleInvariants\n[PASS] invariant_independentOracleMatchesEveryUser() (runs: 256, calls: 24576, reverts: 0)\n\n╭---------------------+---------------+-------+---------+----------╮\n| Contract            | Selector      | Calls | Reverts | Discards |\n+==================================================================+\n| RewardOracleHandler | advance       | 3517  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | checkpoint    | 3471  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | claim         | 3456  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | continueEpoch | 3555  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | fund          | 3512  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | pause         | 3499  | 0       | 0        |\n|---------------------+---------------+-------+---------+----------|\n| RewardOracleHandler | restart       | 3566  | 0       | 0        |\n╰---------------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 7\n  Bound result 1000000000\n  Bound result 3342\n  Bound result 999999999999\n  Bound result 100000000000000000000\n  Bound result 3264\n  Bound result 456\n  Bound result 902797284704\n  Bound result 1256760758\n  Bound result 6744\n  Bound result 9556\n  Bound result 864000\n  Bound result 900\n  Bound result 864000\n  Bound result 3232440050\n  Bound result 85000000\n  Bound result 1484\n  Bound result 864000\n  Bound result 197\n  Bound result 227708292579\n  Bound result 18\n  Bound result 1000000000000\n  Bound result 1983\n  Bound result 1712\n  Bound result 2169\n  Bound result 11309\n  Bound result 296183841541050752651227\n  Bound result 50481490\n  Bound result 3456000\n  Bound result 3352\n  Bound result 7607\n  Bound result 188361092546\n  Bound result 242\n  Bound result 2450\n  Bound result 640273355138\n  Bound result 225499243\n  Bound result 494\n  Bound result 1000000000000\n  Bound result 143367504\n  Bound result 2372\n  Bound result 789184\n  Bound result 12246\n  Bound result 242\n  Bound result 19\n  Bound result 207461004448\n  Bound result 11025\n  Bound result 150000000000000000\n  Bound result 400000000001\n  Bound result 86399\n  Bound result 312077963799358235507525\n  Bound result 3648\n  Bound result 8451560574\n  Bound result 6045\n  Bound result 2600\n  Bound result 431768211455\n  Bound result 1000000000000\n  Bound result 722\n  Bound result 2\n  Bound result 9\n  Bound result 3123\n  Bound result 2731\n  Bound result 851\n  Bound result 23764931400\n  Bound result 10113\n  Bound result 9\n  Bound result 431768211455\n  Bound result 182\n  Bound result 5\n  Bound result 2465\n  Bound result 1000000000000\n  Bound result 11285\n  Bound result 5817\n  Bound result 3401\n  Bound result 331336654795\n\n[PASS] test_oracleExercisesContinueRestartBoostAndExpiredFailure() (gas: 2190545)\nLogs:\n  Bound result 7\n  Bound result 1000000000\n  Bound result 86400\n  Bound result 123\n  Bound result 86400\n  Bound result 456\n  Bound result 1\n  Bound result 789\n  Bound result 86400\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.09s (11.09s CPU time)\n\nRan 2 tests for test/NativeCustodyInvariants.t.sol:NativeCustodyInvariants\n[PASS]\nNativeCustodyInvariants invariants:\n[PASS] invariant_nativeAndWrappedPrincipalConservation\n[PASS] invariant_rewardsAndLiabilitiesRemainSeparatelyBacked\n[PASS] invariant_treasuryOnlySpendsActualReceipts\n NativeCustodyInvariants invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭----------------------+-------------+-------+---------+----------╮\n| Contract             | Selector    | Calls | Reverts | Discards |\n+=================================================================+\n| NativeCustodyHandler | advance     | 2226  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | checkpoint  | 2199  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | claim       | 2288  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | deposit     | 2181  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | donate      | 2236  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | fund        | 2227  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | pause       | 2261  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | schedule    | 2266  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | sweep       | 2250  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | treasuryPay | 2207  | 0       | 0        |\n|----------------------+-------------+-------+---------+----------|\n| NativeCustodyHandler | withdraw    | 2235  | 0       | 0        |\n╰----------------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 4000000000000000000\n  Bound result 5000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 1\n  Bound result 150000000000000000\n  Bound result 15659\n  Bound result 2\n  Bound result 36529508476024566968\n  Bound result 141\n  Bound result 921142626222592204894\n  Bound result 192\n  Bound result 19335\n  Bound result 851865712330941829818\n  Bound result 30\n  Bound result 127\n  Bound result 1000000000000\n  Bound result 3717\n  Bound result 14243\n  Bound result 8801\n  Bound result 65000000000000000000\n  Bound result 1085000000000000000000\n  Bound result 101\n  Bound result 155016727972342557212\n  Bound result 1734518806\n  Bound result 3124\n  Bound result 18\n  Bound result 6299999999999999999\n  Bound result 43\n  Bound result 41\n  Bound result 420\n  Bound result 2361\n  Bound result 1984370661749027096\n  Bound result 32\n  Bound result 13436\n  Bound result 153\n  Bound result 7412\n  Bound result 242\n  Bound result 209748986\n  Bound result 10000\n  Bound result 4122\n  Bound result 15330\n  Bound result 4\n  Bound result 2670\n  Bound result 922156471576910357434\n  Bound result 141\n  Bound result 49152\n  Bound result 7758\n  Bound result 3456000\n  Bound result 7\n  Bound result 127500000000000000000\n  Bound result 15000000000000000000\n  Bound result 8869090327086470680\n  Bound result 182\n  Bound result 393494873684510409485\n  Bound result 2755\n  Bound result 64\n  Bound result 119875525\n  Bound result 4314\n  Bound result 456\n  Bound result 193691195\n  Bound result 192\n  Bound result 65000000000000000000\n  Bound result 214\n  Bound result 7599\n\n[PASS] test_handlerExercisesFallbackRewardsAndTreasuryPayouts() (gas: 3819489)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 4000000000000000000\n  Bound result 5000000000000000000\n  Bound result 100000000000000000000\n  Bound result 100000000000000000000\n  Bound result 1\n  Bound result 86400\n  Bound result 1000000000000000000\n  Bound result 2000000000000000000\n  Bound result 3000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 18.55s (18.56s CPU time)\n\nRan 12 tests for test/BoundaryProperties.t.sol:BoundaryPropertiesTest\n[PASS] testFuzz_allSupportedDecimalsKeepRewardsAndDonationsOutOfTheSharePrice(uint8,uint256,uint256,bool) (runs: 1000, μ: 2222730, ~: 2256965)\nLogs:\n  Bound result 5\n  Bound result 11426559043015134675980651\n  Bound result 262459888462872991170786899492707016411\n\n[PASS] testFuzz_eighteenDecimalRepeatedRoundTripsCannotCreateUnderlying(uint256,uint8) (runs: 1000, μ: 3052214, ~: 2529328)\nLogs:\n  Bound result 20124797355152116510\n  Bound result 5\n\n[PASS] testFuzz_everySelectiveFarmRoleMaskIsIrreversibleAndLeavesClaimsAvailable(uint8) (runs: 1000, μ: 2013566, ~: 1993025)\nLogs:\n  Bound result 4\n\n[PASS] testFuzz_subUnitWithdrawalCannotBurnLowDecimalShares(uint8,uint256) (runs: 1000, μ: 1424582, ~: 1424549)\nLogs:\n  Bound result 12\n  Bound result 7029\n\n[PASS] test_activationRejectsMissingDependenciesWithoutUnlockingRenouncement() (gas: 102627)\n[PASS] test_creationRenouncementCannotBeUndoneByWhitelistChanges() (gas: 607642)\n[PASS] test_factoryConfigRenouncementStillPermitsLaterFarms() (gas: 7955170)\n[PASS] test_missingTokenCodeAtCreationDoesNotAppendAPhantomFarm() (gas: 62846)\n[PASS] test_missingTreasuryIsRejectedOnFirstUseRatherThanDuringConstruction() (gas: 90957)\n[PASS] test_nativeTokenDecimalsMustBeEighteenAndConstructorsRejectUnsupportedDecimals() (gas: 857601)\n[PASS] test_renouncementConfirmationCannotBeReplayedOnAnotherChain() (gas: 99715)\n[PASS] test_unknownRoleBitsAndUnauthorizedFactoryRenouncementAreRejected() (gas: 102798)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 66.39s (134.88s CPU time)\n\nRan 13 tests for test/FailureAtomicity.t.sol:FailureAtomicityTest\n[PASS] test_failedDepositCannotAdvanceRewardIndexOrConsumeAllowance() (gas: 2025481)\n[PASS] test_failedEpochAndSlippageLeaveEveryAccountingBucketUnchanged() (gas: 2198317)\n[PASS] test_failedFallbackRewrapRollsBackTheSuccessfulTreasuryPayment() (gas: 2071260)\n[PASS] test_failedFundingRollsBackSettlementAsWellAsReserve() (gas: 1916376)\n[PASS] test_failedNativeDonationSweepDoesNotBlockUserWETHExit() (gas: 602650)\n[PASS] test_failedUserPaymentRollsBackAnAlreadySuccessfulTreasuryTransferAndRewardCheckpoint() (gas: 2260613)\n[PASS] test_falseReturnClaimPreservesEntitlementAndCanBeRetried() (gas: 1942327)\n[PASS] test_rewardDeficitBlocksPrincipalSpendUntilBackingIsActuallyRestored() (gas: 2097185)\n[PASS] test_shortWETHMintRejectsDepositWithoutLosingNativeETH() (gas: 1604936)\n[PASS] test_shortWETHUnwrapRollsBackBurnAndFees() (gas: 1854731)\n[PASS] test_taxedRewardClaimCannotConsumeAccruedLiability() (gas: 1902422)\n[PASS] test_tokenCallbacksFailSpecificallyAtTheReentrancyGuard() (gas: 2814696)\n[PASS] test_treasuryOwnerCannotReenterNativeOrTokenPayment() (gas: 824232)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 69.60s (30.69ms CPU time)\n\nRan 4 tests for test/RewardOracle.t.sol:RewardBoundaryTest\n[PASS] testFuzz_checkpointAndClaimFrequencyCannotChangeRewards(uint256,uint256) (runs: 1000, μ: 4511589, ~: 4591765)\nLogs:\n  Bound result 165044517626820748388928954914344419170\n  Bound result 314968560786892166090448272444681595661\n\n[PASS] testFuzz_staggeredEntryAndExitUsesSharesRatherThanCompoundedAssetBalances(uint256,uint256) (runs: 1000, μ: 2358620, ~: 2358582)\nLogs:\n  Bound result 225436979891637728\n  Bound result 287\n\n[PASS] test_epochStartEndAndOneSecondOutsideAreExact() (gas: 1228473)\n[PASS] test_expiredEmptyEpochCanAllocateTheJustRecycledReserve() (gas: 1165421)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 69.60s (135.99s CPU time)\n\nRan 16 tests for test/Accounting.t.sol:AccountingTest\n[PASS] testDepositSlippageZeroSharesAndRoundingDust() (gas: 1178896)\n[PASS] testDonationsBeforeFirstDepositAndAfterEmptyRemainIsolated() (gas: 1083512)\n[PASS] testDonationsDoNotInflateSharesOrBecomeRewards() (gas: 805066)\n[PASS] testFeeSnipingIsPossibleUnderImmediateEligibility() (gas: 920049)\n[PASS] testFuzzFeeConservation(uint128,uint128,uint128) (runs: 512, μ: 838836, ~: 841354)\nLogs:\n  Bound result 96\n  Bound result 2000\n  Bound result 15\n\n[PASS] testFuzzFiveToOneThousandUsers(uint16) (runs: 512, μ: 103677210, ~: 51900905)\nLogs:\n  Bound result 7\n\n[PASS] testImmediateSingleDepositorFullWithdrawal() (gas: 589553)\n[PASS] testLastDepositorDoesNotInheritPriorRoundingOrRewards() (gas: 960304)\n[PASS] testMinimumsAndWithdrawBounds() (gas: 506283)\n[PASS] testMultiWalletSplittingMatchesIntentionalPartialWithdrawalBenefit() (gas: 937927)\n[PASS] testPartialWithdrawerIntentionallyBenefits() (gas: 716375)\n[PASS] testRedistributionAndFullCompoundedPositionFee() (gas: 961086)\n[PASS] testRepeatedPartialWithdrawalsRetainTheModelBRecaptureBenefit() (gas: 4596832)\n[PASS] testSharesAreNonTransferableAndNoAllowanceExists() (gas: 386262)\n[PASS] testSixDecimalUnderlyingStillHas18DecimalShares() (gas: 1486873)\n[PASS] testTinyWithdrawalsCannotAvoidFee() (gas: 13017533)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 69.60s (70.84s CPU time)\n\nRan 13 test suites in 69.61s (338.95s CPU time): 107 tests passed, 0 failed, 0 skipped (107 total tests)\n","passed":true},{"durationMs":85,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"BloomFarm.boost(uint256)\",\"BloomFarm.checkpoint()\",\"BloomFarm.claim(address)\",\"BloomFarm.deposit(uint256,uint256)\",\"BloomFarm.depositETH(uint256)\",\"BloomFarm.fundRewards(uint256)\",\"BloomFarm.receive()\",\"BloomFarm.renounceRoles(uint256,bytes32)\",\"BloomFarm.setDepositsPaused(bool)\",\"BloomFarm.setEpoch(uint256,uint256,uint8)\",\"BloomFarm.sweepNativeDonations()\",\"BloomFarm.sweepSurplus(address)\",\"BloomFarm.withdraw(uint256,uint256,address,bool)\",\"FarmFactory.configureToken(uint8,address,bytes32)\",\"FarmFactory.createFarm(uint8)\",\"FarmFactory.renounceRoles(uint256,bytes32)\",\"FarmFactory.setWhitelisted(address,bool)\",\"Treasury.payNative(address,uint256)\",\"Treasury.payToken(address,address,uint256)\",\"Treasury.receive()\"],\"files\":{\".gitignore\":4,\"README.md\":209,\"abi/BloomFarm.json\":1305,\"abi/FarmFactory.json\":482,\"abi/Treasury.json\":151,\"docs/DEPLOYMENT.md\":66,\"docs/INTEGRATION.md\":61,\"docs/OPERATIONS.md\":95,\"docs/SECURITY.md\":130,\"docs/VALIDATION.md\":78,\"docs/build-metadata.json\":88,\"docs/validation-results.txt\":161,\"foundry.toml\":28,\"script/Deploy.s.sol\":28,\"src/BloomFarm.sol\":479,\"src/FarmFactory.sol\":118,\"src/SelectiveRoles.sol\":50,\"src/Treasury.sol\":51,\"src/interfaces/IWETH9.sol\":9,\"test/Accounting.t.sol\":230,\"test/Admin.t.sol\":258,\"test/Adversarial.t.sol\":178,\"test/BoundaryProperties.t.sol\":293,\"test/Deployment.t.sol\":70,\"test/FailureAtomicity.t.sol\":392,\"test/Invariants.t.sol\":238,\"test/NativeCustodyInvariants.t.sol\":419,\"test/NativeETH.t.sol\":127,\"test/RewardOracle.t.sol\":321,\"test/Rewards.t.sol\":303,\"test/TESTING.md\":64,\"test/helpers/FarmFixture.sol\":79,\"test/helpers/Mocks.sol\":161,\"tools/export_artifacts.py\":54},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f34c0b006e460880dd8a5ed17865f5e6359aff64560485a1eebe083d7766b604","verifiedTreeHash":"ddfaa646ba91f9469f2a9afe3c2644e7144aa68b","verifierVersion":"0.1.0+6a374fcd"}]}