{"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":"5a737f9c-132f-4bbd-a556-4510d4dcce14","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"1b0cf42d11f06ccc91993950fb7fda1598128fb4b798563cef2f7b92c9c00372","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":"f16e3853940c6fc6041820d700bca6e85eb8f578e8dc5fb88a2dbe2133d93e4e","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":"b2a6af828db8fabf0209609315db85f92e85ae380322b48ca35b268735bab12a","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"743a3c5efc0591c8c54a7431784c549ec10ce5e368697fd5429da0b3ce021c40","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":"a93ea31ed9a3633b956173afe735d5fa26afe04ae28905747337b02410457e81","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":"865a0d41b73f45ba399c4dcded463761fc075683b7bed9c94d3d29b191fd9a99","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"956acb94fbeb7799ee21edd3d7be0d4d169840935bf4bf63f7ba40975eeb12af","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"07fb5b7f18cc4b330db1fd859b91c96e821af993aad68efc02dde2a3c6b78440","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"PLANT ORGANISM — one contract on robinhood chain, the body of a digital plant that lives somewhere real: its location's weather decides how it grows, holders decide where to place it. no token here (the PLANT token + pool + hook come in a second launch that pays into this contract). no admin, no upgrade, no pause; all numbers constant. constructor (static args): IMD = 0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127, signer = 0x5598aa9146215bc13eb26f2c692ad1461fd32982, FALLBACK_CELL = 10223579, deployer = $owner. cell packing: uint32 = uint16(int16 lat in quarter degrees) << 16 | uint16(int16 lon); lisbon (155, −37) = 0x009BFFDB = 10223579. day index = unix day (timestamp / 86400); lastSettledDay starts at the deployment day.\n\nBIND (the only privileged call, once): bind(hook, questionHash) by deployer, only while unbound: reads hook.organism() == this and hook.plant(); stores plant, hook and QUESTION_HASH (the keccak of the frozen oracle question; set once, never changed); emits Bound. after that the deployer has no powers. until bound, park/redeem revert and settle only advances lastSettledDay.\n\nMONEY: IMD arrives from the launch factory's fee distributor (1% of trades) and from the hook (0.75%); any IMD transferred in counts. pot = IMD held − backing − gardener IMD owed. backing is redeemable IMD.\n\nSTATE: location (cell = int16 lat + int16 lon, quarter degrees; 0 = nowhere), water 0..100 (starts 50), backing, parked[cell][holder], parkedTotal[cell], burned (PLANT held forever), lastSettledDay.\nHOURS (24 per utc day, inside settle): rain → water = min(100, water+3). sun with water ≥ 1 → SIP: water −1; sip = pot/10; backing += 2/3 sip; gardener pool += 1/3 sip. else nothing.\nSETTLE(attestation, sig), anyone may call: imd OracleAttestation v2 (EIP-712 domain name \"IdentityMD Oracle\", version \"2\", chainId 4663, verifyingContract = THIS contract; struct fields as published by imd: requestId, chainId, questionHash, answerType, answer, figure, fromBlock, toBlock, blockHash, panelJobId, panelSize, quorum, agreed, issuedAt, expiresAt), signed by the oracle signer. require attestation.chainId == 4663, attestation.questionHash == QUESTION_HASH, answerType == bytes32[] with exactly 3 words (so no other oracle request can feed this contract). word0: bits 0..23 sun mask, 24..47 rain mask, 48 challenger valid, 64..95 challenger cell, 96..127 day index; word1/2 hourly temp + wind, only emitted. require valid sig, issuedAt ≤ now ≤ expiresAt, day == lastSettledDay+1 (missed days in order); apply hours; READ; pay caller 1% of pot; emit Settled(day, words, sips, backing, water, location).\nREAD: C = current, L = challenger. if L ≠ C and valid and parkedTotal[L] > parkedTotal[C] and parkedTotal[L] ≥ 5% of PLANT supply → location = L (Moved); else stay. no minimum stay. the gardener pool goes pro rata to holders parked behind C as of the previous settle (reward-per-token accumulator per cell + holder checkpoints; tokens parked mid-day activate at the next settle; unparked tokens drop out); if nobody is parked behind C it goes to backing. claim() any time, in IMD.\nPARK/UNPARK(cell, amount): any time, any cell, incl. the current one. parking proposes; no list on chain.\nREDEEM(amount): floor = backing / (PLANT supply − burned); pays amount × floor × 90% in IMD from backing; 10% stays; PLANT is pulled in and held forever. unpark first. floor never decreases.\nBIRTH: location 0, no hours while 0; the first settle with a valid challenger ≥ 5% moves it; after 3 empty settles → FALLBACK_CELL (lisbon, (155, −37)).\nDEAD PLANT: no successful settle for 30 days → pot merges into backing and redeem pays full floor.\nSIGNER ROTATION (no governance, no votes): rotateSigner(newSigner, sig) is accepted only if sig is an EIP-712 message from the CURRENT signer naming this contract and the new address; the previous signer stays valid for 30 more days. holders cannot change the signer. nothing else is settable by anyone.\nSAFETY: reentrancy guards; no external calls beyond IMD/PLANT transfers; no delegatecall/selfdestruct/proxy/ownable/pausable/roles; uint256 math rounding toward backing; settle O(24), never O(holders); public reads of all state + floor + pot; events for every action.\nTESTS (foundry): fee inflow → pot; synthetic day from masks; missed-day ordering; fixture attestation accepted, expired/wrong-domain/wrong-signer/v1 rejected; park → settle → claim incl. pending→active; invariants: floor never decreases, pot + backing + owed == IMD held (minus bounties), burned never leaves; move, birth, fallback, dead plant, signer rotation (accepted only from the current signer), bind-once; settle ≤ 400k gas. one-page README: rules in plain words, how to call settle, addresses.","parentJobId":null,"planHash":"777b6a9b925db760c19b62438b3b93c4c81923d506bc31b7defa743ffe5e317c","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"5a737f9c-132f-4bbd-a556-4510d4dcce14","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-978-plant-organism-one"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52162","feedbackHash":"345dbcd299a1d5539c00f27ef02fa9d0351681a637c61bb9cb57bca302814a9f","nodeKey":"audit_economics","submissionHash":"1b0cf42d11f06ccc91993950fb7fda1598128fb4b798563cef2f7b92c9c00372","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52170","feedbackHash":"2714455fc4f06de78cdc3fef32cf8b6dde355ec6f07bc0174c7598911232b72a","nodeKey":"audit_flow","submissionHash":"f16e3853940c6fc6041820d700bca6e85eb8f578e8dc5fb88a2dbe2133d93e4e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51032","feedbackHash":"b9f27ab271e4f455aea7f25be85bd396a483840d2ad476639de8eb729640583b","nodeKey":"audit_judge","submissionHash":"b2a6af828db8fabf0209609315db85f92e85ae380322b48ca35b268735bab12a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51315","feedbackHash":"e023f7cbbd5a10603e562e7ff005848ac72cb9d2ae1e3d9cd3d180bb610db7ae","nodeKey":"audit_judge","submissionHash":"aba71d4113b757cc9196df5a3e186a182e81fbe8dc9d4872e1bc740f5adfa206","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52160","feedbackHash":"0c6887c5df27ab68c94efadf6021671c5eff115652c161913d2bf46e728ba8cc","nodeKey":"audit_math","submissionHash":"743a3c5efc0591c8c54a7431784c549ec10ce5e368697fd5429da0b3ce021c40","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52176","feedbackHash":"42b381409846b92b91591bbca5604f0a8ab7e6ff35a59f7ad44d4c71db55451c","nodeKey":"audit_permissions","submissionHash":"a93ea31ed9a3633b956173afe735d5fa26afe04ae28905747337b02410457e81","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52141","feedbackHash":"9d193931af26dc07d496daf180dbb9a65260774035f7d12a0b74a5a324fee5ad","nodeKey":"build_contract_project","submissionHash":"bdc501fbb87ed49f79311d4706ce7fe740e9405851031b8e92636ef865dcb89e","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52344","feedbackHash":"c2e14f9d6a7297f2ac2ed19c0f22da98cecf04d437970cf2d2a1a59f7eb09d79","nodeKey":"build_contract_project","submissionHash":"865a0d41b73f45ba399c4dcded463761fc075683b7bed9c94d3d29b191fd9a99","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51188","feedbackHash":"7cb615415863984c63158ce9fab9c75598a7bccd928b8d6997e9061c88144308","nodeKey":"manifest","submissionHash":"956acb94fbeb7799ee21edd3d7be0d4d169840935bf4bf63f7ba40975eeb12af","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51418","feedbackHash":"6077fed8b5600f665c134ced7308a65e515e88c1933a4c5e747dfe74b4defe9b","nodeKey":"manifest","submissionHash":"e31708eb7b9a95b528d7b1b1035a867d6ba2c2934ed03e672fd21add87f92f56","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52122","feedbackHash":"6ee6aba0c8ec96891277be4d488f38f9f919b88f0350dfd70ee9e69ef48b9520","nodeKey":"write_foundry_tests","submissionHash":"07fb5b7f18cc4b330db1fd859b91c96e821af993aad68efc02dde2a3c6b78440","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51179","feedbackHash":"9c4e35fe01ab9d1f7e7c6cee6b5816b3ce7ae1a342cc572b8265f6ff38160f39","nodeKey":"write_foundry_tests","submissionHash":"82b027f296a6719008f15c917dcf7c0a4c2b16a5afcb1bf67b27f1a14463b83d","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"f9d6f4940fbd43fce528b9dcd594b2930c927bef5eb690029bd2448fca131326","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"72a01333273126ca2e00084659edf544008fee1a319c35e03f14e93fdcfac9a0","device":"3592a9be44170f18","findings":[],"hash":"07fb5b7f18cc4b330db1fd859b91c96e821af993aad68efc02dde2a3c6b78440","nodeId":"d4e484f4-3f55-4726-a34d-22bddf9fd136","outcome":"completed","summary":"Updated tests for birth rules, external burns, quorum fixtures, and randomized accounting. Changes are confined to `test/`.\n\nValidation passed:\n\n- `forge build`\n- `forge test`: 81 passed, zero failures\n- Invariants: 49,152 randomized calls\n- Cold settlement: 375,394 gas, below 400,000","treeHash":"d95d9d23fb34a71b31491e3416f96f12564f9733","usage":{"cachedInputTokens":1669248,"inputTokens":116619,"model":"gpt-6-astra","outputTokens":8102,"runtime":"codex","turns":5,"wallClockMs":366673}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"cd2c3a8d55620566","findings":[],"hash":"15e36ca0e15dc8f0e3403691e9462943ee9546973bde07b49c7aa365edb1b5b5","nodeId":"3303df5c-c99b-42dc-ae65-cebae4457b0f","outcome":"failed","summary":"the task produced no changes; the agent's last message was: The workspace still blocks execution: every command fails with `bwrap: setting up uid map: Permission denied`. Writing `.imd-blocked.json` also failed.\n\nNo files changed. I couldn’t inspect or repair the contracts, write `.imd-responses.json`, or run Foundry checks.\n\nPlease restore workspace execution and write access, then rerun this revision.","treeHash":null,"usage":{"cachedInputTokens":147968,"inputTokens":44660,"model":"gpt-6-astra","outputTokens":1507,"runtime":"codex","turns":5,"wallClockMs":120861}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f9cb4fd544aa3c68","findings":[{"citation":"resolved","description":"_checkSupply() is called by park (178), redeem (233) and settle (273) and reverts whenever plant.totalSupply() differs from the value snapshotted at bind. Nothing in the organism's arithmetic uses the live supply (floor, threshold and redemption all use plantSupply), so the check adds no protection, but it turns any supply change of the external PLANT token into a permanent, unrecoverable freeze: redeem reverts, settle reverts, so 30 days later the plant is dead (isDead), and dead redeem also reverts because it calls _checkSupply first (line 233). Backing IMD, which the brief defines as 'redeemable IMD' and whose floor 'never decreases', can then never be redeemed by anyone; unpark and claim still work, so holders get their PLANT and already-checkpointed gardener credit back, but 100% of backing is stuck. Severity: if the second-launch PLANT token has a public burn (ERC20Burnable is the common launch-token shape; the PLANT token is not in this tree so it could not be verified), any holder can trigger this with 1 wei at near-zero cost and every other holder loses their entire redeemable backing. If PLANT turns out to be strictly fixed-supply with no burn, this remains a latent single-point dependency with no recovery path and should be treated as medium. Fix preserving the design: drop the live-supply comparison (keep plantSupply as the immutable denominator), or at minimum never apply it on redeem so backing stays withdrawable; a supply change cannot change what the organism owes.","line":387,"path":"src/PlantOrganism.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PlantOrganism} from \"src/PlantOrganism.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev A PLANT token with a public burn, as ERC20Burnable launch tokens commonly have.\ncontract BurnableToken {\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        totalSupply += amount;\n        balanceOf[to] += amount;\n    }\n\n    function burn(uint256 amount) external {\n        balanceOf[msg.sender] -= 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\ncontract ScratchHook {\n    address public immutable organism;\n    address public immutable plant;\n\n    constructor(address organism_, address plant_) {\n        organism = organism_;\n        plant = plant_;\n    }\n}\n\n/// @dev One wei of PLANT burned by anyone makes redeem and settle revert forever,\n/// locking every holder's backing IMD in the contract.\ncontract SupplyLockTest is Test {\n    uint256 internal constant KEY = 0xA11CE;\n    uint32 internal constant LISBON = 10223579;\n    bytes32 internal constant QUESTION = keccak256(\"frozen test weather question\");\n\n    BurnableToken internal imd;\n    BurnableToken internal token;\n    PlantOrganism internal body;\n    address internal alice = makeAddr(\"alice\");\n    address internal mallory = makeAddr(\"mallory\");\n\n    function setUp() public {\n        vm.chainId(4663);\n        vm.warp(20000 days + 12 hours);\n        imd = new BurnableToken();\n        token = new BurnableToken();\n        token.mint(alice, 999 ether);\n        token.mint(mallory, 1 ether);\n        body = new PlantOrganism(address(imd), vm.addr(KEY), LISBON, address(this));\n        ScratchHook hook = new ScratchHook(address(body), address(token));\n        body.bind(address(hook), QUESTION);\n        vm.prank(alice);\n        token.approve(address(body), type(uint256).max);\n    }\n\n    function attestation(uint256 day, uint24 sun) internal view returns (OracleAttestation.Attestation memory a) {\n        bytes32[] memory words = new bytes32[](3);\n        words[0] = bytes32(uint256(sun) | (uint256(1) << 48) | (uint256(LISBON) << 64) | (day << 96));\n        a = OracleAttestation.Attestation({\n            requestId: bytes32(day),\n            chainId: 4663,\n            questionHash: QUESTION,\n            answerType: 5,\n            answer: abi.encode(words),\n            figure: 0,\n            fromBlock: 100,\n            toBlock: 200,\n            blockHash: bytes32(uint256(7)),\n            panelJobId: bytes32(uint256(8)),\n            panelSize: 5,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: uint64(block.timestamp),\n            expiresAt: uint64(block.timestamp + 1 days)\n        });\n    }\n\n    function settle(uint24 sun) internal {\n        uint256 day = body.lastSettledDay() + 1;\n        if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n        OracleAttestation.Attestation memory a = attestation(day, sun);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(KEY, body.attestationDigest(a));\n        body.settle(a, abi.encodePacked(r, s, v));\n    }\n\n    function test_oneWeiBurnLocksAllBackingForever() public {\n        // Birth at Lisbon, then one sunny hour turns pot into backing.\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        settle(0);\n        imd.mint(address(body), 10_000 ether);\n        settle(1);\n        assertGt(body.backing(), 0);\n        uint256 backingBefore = body.backing();\n\n        // An unrelated holder burns one wei of PLANT.\n        vm.prank(mallory);\n        token.burn(1);\n\n        // Alice unparks (still allowed) and tries to redeem: the backing is hers pro rata.\n        vm.prank(alice);\n        body.unpark(LISBON, 100 ether);\n        vm.prank(alice);\n        uint256 paid = body.redeem(100 ether);\n        assertGt(paid, 0, \"holder cannot redeem any backing after a one-wei burn\");\n\n        // And the organism must keep living: the next day's settle must still be accepted.\n        settle(1);\n        assertGt(body.backing(), backingBefore);\n    }\n\n    function test_lockPersistsThroughDeathSoBackingIsNeverRecoverable() public {\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        settle(0);\n        imd.mint(address(body), 10_000 ether);\n        settle(1);\n        vm.prank(mallory);\n        token.burn(1);\n        // Settle is blocked, so 30 days later the plant is dead and redeem should pay the full floor.\n        vm.warp(block.timestamp + 30 days);\n        assertTrue(body.isDead());\n        vm.prank(alice);\n        body.unpark(LISBON, 100 ether);\n        vm.prank(alice);\n        uint256 paid = body.redeem(100 ether);\n        assertGt(paid, 0, \"dead-plant redemption is blocked: all IMD is locked forever\");\n    }\n}","reproduction":"State: bound organism, PLANT supply 1000e18, alice parks 100e18 at Lisbon, settle (birth), 10_000e18 IMD arrives, settle with one sunny hour -> backing > 0. Call sequence: mallory (holds 1e18 PLANT) calls PLANT.burn(1); alice calls unpark(LISBON, 100e18) (succeeds) then redeem(100e18). Expected: redeem pays floor(100e18 * backing / remaining) * 9/10 in IMD. Actual: redeem reverts SupplyChanged(). settle(nextDay) also reverts SupplyChanged(); after warp +30 days isDead()==true and redeem still reverts SupplyChanged(), so the full backing is locked forever. See test/scratch/SupplyLock.t.sol (both tests fail on the current code with SupplyChanged()).","severity":"high","snippet":"        if (plant.totalSupply() != plantSupply) revert SupplyChanged();","title":"One-wei PLANT supply change bricks redeem and settle forever, locking all backing IMD (precondition: PLANT exposes burn/mint)"},{"citation":"resolved","description":"lastSuccessfulSettle is set only in the constructor (line 127) and in a successful bound settle (line 322). Before bind a successful settle is impossible by construction (unbound settle only advances the cursor, lines 265-271) and bind (lines 144-148) does not touch lastSuccessfulSettle. So the brief's rule 'no successful settle for 30 days -> dead' is measured over a window in which no settle could have happened. The organism is deployed first and the second launch (PLANT token, pool, hook) must point at its address before bind is possible; if that second launch lands 30 or more days later, isDead() is true at the instant of bind: settle reverts Dead() forever, no weather is ever applied, the plant never moves or births, and redeem pays the full floor of all IMD that ever arrives. There is no admin, upgrade or revival path, so the only remedy is redeploying the organism and re-running the second launch against the new address. README documents 'the clock starts at deployment, including time waiting to bind', but the brief's BIRTH and DEAD PLANT rules only make sense if the 30 days are counted from when settling became possible. No existing test covers a bind after the deadline (test_deadClockTracksSuccessTimestampNotBacklogDay binds at deployment). Minimal fix preserving the design: set lastSuccessfulSettle = block.timestamp inside bind() (and optionally in the unbound settle branch), so the 30-day liveness window starts when the first real settle can occur.","line":159,"path":"src/PlantOrganism.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PlantOrganism} from \"src/PlantOrganism.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\ncontract ScratchToken {\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        totalSupply += amount;\n        balanceOf[to] += 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\ncontract ScratchHook {\n    address public immutable organism;\n    address public immutable plant;\n\n    constructor(address organism_, address plant_) {\n        organism = organism_;\n        plant = plant_;\n    }\n}\n\n/// @dev The death clock starts at deployment, before bind. A bind 30 days after deployment\n/// produces an organism that is dead before its first settle and can never be settled.\ncontract BornDeadTest is Test {\n    uint256 internal constant KEY = 0xA11CE;\n    uint32 internal constant LISBON = 10223579;\n    bytes32 internal constant QUESTION = keccak256(\"frozen test weather question\");\n\n    ScratchToken internal imd;\n    ScratchToken internal token;\n    PlantOrganism internal body;\n    address internal alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        vm.chainId(4663);\n        vm.warp(20000 days + 12 hours);\n        imd = new ScratchToken();\n        token = new ScratchToken();\n        token.mint(alice, 1000 ether);\n        body = new PlantOrganism(address(imd), vm.addr(KEY), LISBON, address(this));\n    }\n\n    function attestation(uint256 day) internal view returns (OracleAttestation.Attestation memory a) {\n        bytes32[] memory words = new bytes32[](3);\n        words[0] = bytes32((uint256(1) << 48) | (uint256(LISBON) << 64) | (day << 96));\n        a = OracleAttestation.Attestation({\n            requestId: bytes32(day),\n            chainId: 4663,\n            questionHash: QUESTION,\n            answerType: 5,\n            answer: abi.encode(words),\n            figure: 0,\n            fromBlock: 100,\n            toBlock: 200,\n            blockHash: bytes32(uint256(7)),\n            panelJobId: bytes32(uint256(8)),\n            panelSize: 5,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: uint64(block.timestamp),\n            expiresAt: uint64(block.timestamp + 1 days)\n        });\n    }\n\n    function test_bindThirtyDaysAfterDeploymentIsDeadBeforeFirstSettle() public {\n        // The second launch (PLANT + hook) lands 30 days after the organism was deployed.\n        vm.warp(block.timestamp + 30 days);\n        // Keep the day cursor current so the first bound settle is simply \"tomorrow\".\n        OracleAttestation.Attestation memory blank;\n        body.settle(blank, \"\");\n        ScratchHook hook = new ScratchHook(address(body), address(token));\n        body.bind(address(hook), QUESTION);\n        assertTrue(body.bound());\n\n        // Nothing ever succeeded or failed: no settle was possible before bind. The plant must be alive.\n        assertFalse(body.isDead(), \"organism is dead at the moment it is bound\");\n\n        // And the first settle, on the very next day, must be accepted.\n        vm.prank(alice);\n        token.approve(address(body), type(uint256).max);\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        vm.warp(block.timestamp + 1 days);\n        OracleAttestation.Attestation memory a = attestation(body.lastSettledDay() + 1);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(KEY, body.attestationDigest(a));\n        body.settle(a, abi.encodePacked(r, s, v));\n        assertEq(body.location(), LISBON);\n    }\n}","reproduction":"Deploy PlantOrganism(imd, signer, 10223579, deployer) at T0. Warp to T0 + 30 days. Optionally call settle(blank, '') unbound to advance the cursor. Deployer calls bind(hook, questionHash) with a hook whose organism()==this and a nonzero-supply PLANT. Expected: bound()==true, isDead()==false, and the next day's attestation (day == lastSettledDay+1, signed by signer) is accepted and births the plant. Actual: isDead()==true immediately after bind; every settle reverts Dead(); pot()==0 and backing()==IMD balance. See test/scratch/BornDead.t.sol, which fails on the current code with 'organism is dead at the moment it is bound'.","severity":"medium","snippet":"        return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;","title":"Death clock starts at deployment, so a bind 30+ days after deployment creates a permanently dead plant that can never settle"},{"citation":"resolved","description":"The brief defines cell 0 as 'nowhere' and says the plant births on 'the first settle with a valid challenger >= 5%'. validCell(0) returns true (line 155: lat 0, lon 0 are in range), and park only checks validCell, so holders can park at cell 0. While location == 0 the move test at line 314 compares parkedTotal[challenger] > parkedTotal[current] with current == 0, so PLANT parked at 'nowhere' blocks any challenger with less support even though cell 0 can never itself be chosen (challenger != 0 at line 313). A holder with the largest stake can therefore delay birth-by-vote for three settles and force the fallback cell, during which no weather hours run and no backing accrues for anyone; this also contradicts the constructor, which rejects fallbackCell_ == 0 as invalid. The existing test test_rejectsChangedSupplyInvalidCellsAndZeroAmounts asserts validCell(0)==true, so the suite enshrines the behaviour rather than catching it. Fix: reject cell == 0 in park (or in validCell, keeping the constructor check), or compare against 0 rather than parkedTotal[0] when current == 0.","line":180,"path":"src/PlantOrganism.sol","reproduction":"Bound organism, supply 1000e18, location 0. bob parks 100e18 (10%) at PARIS = (195<<16)|9; alice parks 101e18 at cell 0 (succeeds; expected InvalidCell). Oracle attests day lastSettledDay+1 with valid=1, challenger=PARIS. Expected: location == PARIS after settle. Actual: location stays 0 because parkedTotal[PARIS]=100e18 is not > parkedTotal[0]=101e18; emptySettles becomes 1, and after two more such settles the plant lands at FALLBACK_CELL instead of PARIS. See test/scratch/CellZero.t.sol, which fails on the current code with 'valid >=5% challenger did not move the plant off nowhere: 0 != 12779529'.","severity":"low","snippet":"        if (!validCell(cell)) revert InvalidCell();","title":"Cell 0 ('nowhere') is parkable and counts as the incumbent during birth, letting a holder veto the first valid challenger"}],"hash":"1b0cf42d11f06ccc91993950fb7fda1598128fb4b798563cef2f7b92c9c00372","nodeId":"51cc42a3-ffe0-4d60-bf80-d9127a2caec9","outcome":"completed","summary":"The CellZero test still fails on the current tree and now passes under either remedy. No tracked file was modified; the findings file and scratch tests are untracked, as the task requires.\n\n**Review complete. Three substantiated defects, all reproduced with failing Foundry tests, are in `.imd-findings.json` along with a 14-row coverage record (all 9 entry points plus 5 invariants).**\n\nFindings, most severe first:\n\n1. **High: any PLANT supply change permanently locks all backing.** The live-supply check at `src/PlantOrganism.sol:387` guards park, redeem and settle, yet no arithmetic depends on the live supply. If the second-launch PLANT token has a public burn, one wei burned by any holder makes redeem revert forever. Settle also reverts, so the plant dies after 30 days, and the dead redeem path still reverts. All backing IMD is then unrecoverable. The precondition (burnable PLANT) could not be verified since that token is not in this tree. Proof attached.\n\n2. **Medium: the death clock starts at deployment, so a bind 30 or more days later yields a plant that is dead on arrival.** The constructor sets the last-success timestamp and bind never resets it (`src/PlantOrganism.sol:159`). The second launch must target the organism's address, so a month-long gap is plausible. The organism can never settle, birth, or move, and there is no revival path. Proof attached.\n\n3. **Low: cell 0 is parkable and acts as the birth incumbent.** `validCell(0)` is true, so PLANT parked at \"nowhere\" blocks a valid challenger with less support during birth, forcing the fallback cell. The existing suite asserts this behavior rather than catching it. Reproduction attached as a scratch test.\n\nWhat I covered: all three guides in my area (Economic Security, Invariant, Flow Gap) against every entry point. I traced the sip split, bounty, pot/backing/owed conservation, the reward accumulator with exact ceiling reservation, pending-to-active activation, floor monotonicity across redeem and death, and signer rotation. Those hold. Oracle-protocol details (quorum checks, question-hash windows) are another specialist's area and were not pursued.","treeHash":null,"usage":{"cachedInputTokens":1064784,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":43539,"runtime":"claude","turns":27,"wallClockMs":624173}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4da6f73105193824","findings":[],"hash":"483cdc7abb7e7e76e9c521edfe5753a30ea77519805b9d56fa969c4fe8af088c","nodeId":"3303df5c-c99b-42dc-ae65-cebae4457b0f","outcome":"failed","summary":"the task produced no changes; the agent's last message was: Blocked by the workspace environment: every terminal command fails with `bwrap: setting up uid map: Permission denied`. File writes also fail, including `.imd-blocked.json`.\n\nNo files changed. I couldn’t reproduce the findings, apply repairs, write `.imd-responses.json`, or run Foundry checks. Please restore terminal and file-write access and rerun this revision.","treeHash":null,"usage":{"cachedInputTokens":301696,"inputTokens":58754,"model":"gpt-6-astra","outputTokens":1968,"runtime":"codex","turns":6,"wallClockMs":152068}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"cd8076510aad8b8e","findings":[{"citation":"resolved","description":"redeem(), park() and settle() all call _checkSupply(), which reverts unless plant.totalSupply() equals the supply snapshotted at bind. The PLANT token is not part of this project (it comes from the second launch), so its supply behaviour is an unverified external boundary. If PLANT exposes any burn (ERC20Burnable-style burn/burnFrom, which launch tokens commonly do) or any mint, a single ordinary holder changes totalSupply by 1 wei and every later redeem() reverts SupplyChanged. settle() reverts the same way, so the plant dies after 30 days, and redeem keeps reverting after death because _checkSupply runs before the dead branch. Only claim() and unpark() still work, so the entire backing (and, after death, the merged pot) is permanently unredeemable. The check buys nothing for redeem: redeem already divides by the stored plantSupply - burned, so a changed live supply cannot corrupt its math; it can only brick it. Boundary: plant.totalSupply() external read. Assumption: it is constant for the life of the organism. Actual: any supply-changing function on PLANT flips it permanently. Minimal fix: drop _checkSupply() from redeem() (keep it on park if desired), or compare against the stored snapshot only to refuse increases, so a burn by a third party can never block redemption of IMD already backing the remaining supply.","line":387,"path":"src/PlantOrganism.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PlantOrganism} from \"src/PlantOrganism.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev Minimal exact-transfer ERC-20 whose holders can burn their own tokens (ERC20Burnable-style).\ncontract BurnableToken {\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        totalSupply += amount;\n        balanceOf[to] += amount;\n    }\n\n    function burn(uint256 amount) external {\n        balanceOf[msg.sender] -= 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\ncontract StubHook {\n    address public organism;\n    address public plant;\n\n    constructor(address organism_, address plant_) {\n        organism = organism_;\n        plant = plant_;\n    }\n}\n\n/// @notice A single 1-wei burn by any PLANT holder makes redeem() revert forever, locking all backing IMD.\ncontract SupplyFreezeTest is Test {\n    uint256 constant KEY = 0xA11CE;\n    uint32 constant LISBON = 10223579;\n    bytes32 constant QUESTION = keccak256(\"frozen question\");\n    BurnableToken imd;\n    BurnableToken plant;\n    PlantOrganism body;\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    function setUp() public {\n        vm.chainId(4663);\n        vm.warp(20000 days + 12 hours);\n        imd = new BurnableToken();\n        plant = new BurnableToken();\n        plant.mint(alice, 600 ether);\n        plant.mint(bob, 400 ether);\n        body = new PlantOrganism(address(imd), vm.addr(KEY), LISBON, address(this));\n        body.bind(address(new StubHook(address(body), address(plant))), QUESTION);\n        vm.prank(alice);\n        plant.approve(address(body), type(uint256).max);\n        vm.prank(bob);\n        plant.approve(address(body), type(uint256).max);\n    }\n\n    function settleNext(uint24 sun, bool valid, uint32 cell) internal {\n        uint256 day = body.lastSettledDay() + 1;\n        vm.warp(day * 1 days + 12 hours);\n        bytes32[] memory words = new bytes32[](3);\n        words[0] = bytes32(uint256(sun) | (uint256(valid ? 1 : 0) << 48) | (uint256(cell) << 64) | (day << 96));\n        OracleAttestation.Attestation memory a;\n        a.requestId = bytes32(day);\n        a.chainId = 4663;\n        a.questionHash = QUESTION;\n        a.answerType = 5;\n        a.answer = abi.encode(words);\n        a.issuedAt = uint64(block.timestamp);\n        a.expiresAt = uint64(block.timestamp + 1 days);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(KEY, body.attestationDigest(a));\n        body.settle(a, abi.encodePacked(r, s, v));\n    }\n\n    function test_oneWeiBurnByAnyHolderPermanentlyBlocksRedemptionOfBacking() public {\n        // Birth at Lisbon, then one sunny hour so backing becomes nonzero.\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        settleNext(0, true, LISBON);\n        imd.mint(address(body), 1000 ether);\n        settleNext(1, false, 0);\n        uint256 backingBefore = body.backing();\n        assertGt(backingBefore, 0);\n\n        // Bob, an ordinary holder, burns 1 wei of his own PLANT.\n        vm.prank(bob);\n        plant.burn(1);\n\n        // Expected: holders can still redeem against the recorded backing.\n        // Actual on current code: redeem reverts SupplyChanged, forever (settle reverts the same way,\n        // so the plant dies in 30 days and redeem still reverts after death).\n        vm.prank(bob);\n        uint256 paid = body.redeem(100 ether);\n        assertGt(paid, 0, \"redeem paid nothing\");\n        assertEq(imd.balanceOf(bob), paid);\n    }\n}","reproduction":"State: bound to a PLANT token with a holder-callable burn(uint256); alice parked 100e18 at Lisbon; birth settled; 1000e18 IMD deposited; one sunny hour settled so backing() > 0. Call sequence: (1) bob (any PLANT holder) calls plant.burn(1); (2) bob calls body.redeem(100e18). Expected: payout = 100e18 * backing / (1000e18 - 0) * 9/10 in IMD. Actual: revert SupplyChanged(). (3) relay a valid attestation for lastSettledDay+1 -> revert SupplyChanged(). (4) warp 31 days: isDead() == true; body.redeem(1e18) -> still revert SupplyChanged(). All backing IMD is locked; only claim()/unpark() remain callable. Confirmed with test/scratch/Leads.t.sol:test_leadB_supplyBurnFreezesRedeemAndSettle and the attached proof.","severity":"medium","snippet":"    function _checkSupply() private view {\n        if (!bound()) revert Unbound();\n        if (plant.totalSupply() != plantSupply) revert SupplyChanged();\n    }","title":"Strict PLANT totalSupply equality in redeem/settle lets any holder's 1-wei burn lock all backing IMD forever"},{"citation":"resolved","description":"validCell(0) is true (lat 0, lon 0 are inside the ranges), so park(0, amount) is accepted at line 180 and increments parkedTotal[0]. While location == 0 (birth), the move test compares parkedTotal[challenger] > parkedTotal[current] with current == 0, so PLANT parked at the sentinel 'nowhere' cell counts as support for staying nowhere. The brief defines 0 as 'nowhere' and says the first settle with a valid challenger holding >= 5% moves the plant; with tokens parked at 0 that is no longer true. Anyone holding more PLANT than the largest challenger can pin the plant at nowhere for three settles and force FALLBACK_CELL, overriding the holders' placement vote. Tokens at cell 0 can never earn (cell 0 is never current with hours) and can never be moved to (challenger != 0), so the only effect of allowing them is this distortion. Seam: sentinel boundary (0) x invariant (only real cells carry support). Fix: reject cell == 0 in park() (validCell(cell) && cell != 0), or treat parkedTotal[current] as 0 when current == 0.","line":314,"path":"src/PlantOrganism.sol","reproduction":"State: freshly bound organism, location == 0, supply 1000e18. (1) bob: park(0, 100e18). (2) alice: park(PARIS=(195<<16)|9, 60e18) (6% >= 5% threshold). (3) settle day+1 with valid=1, challenger=PARIS. Expected per brief: location == PARIS. Actual: location stays 0 because parkedTotal[PARIS]=60e18 is not > parkedTotal[0]=100e18. (4),(5) two more settles with the same challenger: location becomes FALLBACK_CELL (Lisbon) via emptySettles == 3. Confirmed with test/scratch/Leads.t.sol:test_leadA_cellZeroBlocksBirth.","severity":"low","snippet":"        if (\n            valid && challenger != 0 && validCell(challenger) && challenger != current\n                && parkedTotal[challenger] > parkedTotal[current] && parkedTotal[challenger] >= threshold\n        ) {","title":"Cell 0 ('nowhere') is parkable and its parkedTotal is the incumbent's support during birth, so parking at 0 blocks challengers and forces the Lisbon fallback"},{"citation":"resolved","description":"lastSuccessfulSettle is set to block.timestamp in the constructor (line 127) and only updated by a successful bound settle. isDead() becomes true as soon as the organism is bound if 30 days have elapsed since deployment. bind() neither resets the clock nor refuses to bind a dead body. The second launch (PLANT, pool, hook) is a separate, later event the deployer does not fully control, so a delay of 30 days between the two launches yields a bound organism whose settle() reverts Dead on the very first call, forever; it can never be born, moved or watered, and every IMD the hook and fee distributor forward into it becomes dead backing. The README documents this clock start, but the brief's rule is 'no successful settle for 30 days', which an unbound body cannot have had. Seam: time boundary (30 days) x invariant (a bound plant can settle). Minimal fix preserving the design: set lastSuccessfulSettle = block.timestamp inside bind(), or revert bind() when block.timestamp >= lastSuccessfulSettle + DEATH_DELAY so the deployer learns the body is unusable instead of binding the hook to it.","line":159,"path":"src/PlantOrganism.sol","reproduction":"(1) Deploy PlantOrganism at T. (2) At T + 30 days, deployer calls bind(hook, questionHash) with a valid hook; it succeeds. (3) isDead() returns true immediately. (4) Relay a validly signed attestation for lastSettledDay + 1: reverts Dead(). No later call can ever settle; the plant never gets a location. Confirmed with test/scratch/Leads.t.sol:test_leadC_bornDead.","severity":"low","snippet":"    function isDead() public view returns (bool) {\n        return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    }","title":"Death clock runs from deployment, so a bind more than 30 days after deployment produces a plant that is dead before its first settle"}],"hash":"743a3c5efc0591c8c54a7431784c549ec10ce5e368697fd5429da0b3ce021c40","nodeId":"cc8a6568-0e32-4c25-80a3-e28794744a36","outcome":"completed","summary":"Review complete. The findings file is written and every snippet was verified against the tree; no tracked files were changed (scratch tests and the findings file are git-ignored).\n\n**What I covered.** The three assigned guides (Math Precision, Boundary, Numerical Gap) against every arithmetic path in `src/PlantOrganism.sol`: the 24-hour sip loop and its rounding toward backing, the reward-per-token accumulator with its exact reservation and fraction tracking, the pending-to-active activation timing, redemption and floor monotonicity (alive, dead and terminal), the ceiling 5% threshold, the bit-mask extraction of word 0, the time boundaries for windows, death and signer grace, and every external call and decode site. Beyond the shipped suite I ran a wider stateful fuzz with five actors, four cells, wei-scale parks, a 1e27-scale holder and tiny pots. Conservation, floor monotonicity, reservation coverage and dust-free exit all held. The core math is sound.\n\n**Findings, most severe first:**\n\n1. **Medium. Supply-equality check can lock all backing forever.** Any PLANT holder who can change total supply by 1 wei (a burn or mint on the second-launch token, whose implementation this project does not control) makes redeem and settle revert permanently. The plant then dies in 30 days and redeem still reverts after death. Only claim and unpark keep working, so the backing IMD is stranded. The check adds no safety to redeem, which already divides by the stored supply. A proof test that fails on the current code is attached.\n\n2. **Low. Cell 0 is parkable and counts as the incumbent's support at birth.** Parking more PLANT at the sentinel \"nowhere\" cell than any challenger holds pins the plant at nowhere for three settles and forces the Lisbon fallback, defeating the holders' first placement vote. Reproduced.\n\n3. **Low. The death clock starts at deployment, so a bind 30 or more days after deployment yields a body that is dead before its first settle.** This is documented in the README, but it contradicts the brief's rule and binds the hook to an unusable organism with no recovery. Reproduced.\n\n**Coverage.** All nine entry points have a row. Settle, redeem, park and bind carry findings. Claim, unpark, rotateSigner and syncDeath hold. Four invariant rows record the fuzz results. Access-control and signature-domain aspects of rotateSigner and bind are outside my area and were not reviewed in depth.","treeHash":null,"usage":{"cachedInputTokens":1593643,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":46723,"runtime":"claude","turns":30,"wallClockMs":723573}},{"artifacts":[],"attempt":1,"bundleHash":"efaff8b2c065aec990208f74c59fb6c1bab68cde968b295e9e98aa95c4c909c7","device":"ee235ca2c050b44d","findings":[],"hash":"82b027f296a6719008f15c917dcf7c0a4c2b16a5afcb1bf67b27f1a14463b83d","nodeId":"d4e484f4-3f55-4726-a34d-22bddf9fd136","outcome":"completed","summary":"Added adversarial tests, lifecycle invariants, gas checks, and a test README. Changes are confined to `test/`.\n\n- `forge build` passes.\n- All 67 tests pass.\n- Invariants exercised 49,152 random calls.\n- Cold settlement measured 375,215 gas, below 400k.\n\nNo reproducible defects found. Live-chain integrations remain unverified.","treeHash":"b46643008f0766092afa9e5dfa0491512f559a39","usage":{"cachedInputTokens":1731584,"inputTokens":117613,"model":"gpt-6-astra","outputTokens":19512,"runtime":"codex","turns":7,"wallClockMs":611023}},{"artifacts":[],"attempt":1,"bundleHash":"44192f383e304b292cd6055f2acd8b1ac5110686028a0bcf77ac30c2bea7808f","device":"ab846a7d68ed31f8","findings":[],"hash":"865a0d41b73f45ba399c4dcded463761fc075683b7bed9c94d3d29b191fd9a99","nodeId":"3303df5c-c99b-42dc-ae65-cebae4457b0f","outcome":"completed","summary":"Fixed supply-decrease lockups, the bind death clock, the birth veto, and quorum validation. Documented disputes for timestamp tolerance and signer rotation in `.imd-responses.json`.\n\nValidation passed: `forge build`, all 54 tests, both reviewer proofs, and `forge fmt --check`. Settlement gas: 354,018, below 400,000.","treeHash":"8cda6d417755900c47c487a65af39853267725b3","usage":{"cachedInputTokens":1476864,"inputTokens":101254,"model":"gpt-6-astra","outputTokens":12617,"runtime":"codex","turns":6,"wallClockMs":450999}},{"artifacts":[],"attempt":1,"bundleHash":"b7b5d0ca38bc40b3ec6b333c6bf7c12b750fe051bee5fa0e30a3e74975824295","device":"0b0761c9b10fe0bb","findings":[],"hash":"956acb94fbeb7799ee21edd3d7be0d4d169840935bf4bf63f7ba40975eeb12af","nodeId":"f9e2ca39-cc00-4d66-8af7-d2df478f6dac","outcome":"completed","summary":"Updated only [launch.json](/root/.identitymd/work/5a737f9c-132f-4bbd-a556-4510d4dcce14/f9e2ca39-cc00-4d66-8af7-d2df478f6dac/launch.json), aligning notes with accepted death-clock, burn, birth, and quorum behavior. Constructor arguments remain correct.\n\nValidation passed: supplied schema, compiled constructor ABI, `forge build`, and all 54 tests.","treeHash":"7364973cdda572ad151dc43eff5a57648221fbb7","usage":{"cachedInputTokens":197632,"inputTokens":35486,"model":"gpt-6-astra","outputTokens":3160,"runtime":"codex","turns":3,"wallClockMs":119039}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ee2cc12e7dfe571a","findings":[{"citation":"resolved","description":"isDead() (line 159: `return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;`) measures from lastSuccessfulSettle, which is set once in the constructor and only refreshed by a *bound* settle. Before bind(), a successful settle is impossible by design (the unbound branch returns after advancing lastSettledDay and never touches lastSuccessfulSettle), yet the 30-day death window is already running. bind() neither resets the clock nor refuses to bind a body that is already past the deadline. The brief's rule is 'no successful settle for 30 days'; the pre-bind window is one where success is impossible by construction, so counting it defeats the rule's purpose. The PLANT token, pool and hook are a separate second launch whose timing is not under this contract's control; if that launch lands >= 30 days after this one, the deployer's single privileged call binds a corpse: every bound settle reverts Dead, location stays 0 forever, water never changes, gardeners never earn, and the hook's 0.75% + factory's 1% fee flows all land as 100%-redeemable backing with no plant behaviour at all. The contract is immutable, so there is no recovery short of redeploying both launches. README documents this as intended ('The clock starts at deployment, including time waiting to bind') but that does not make it safe: it is a stillbirth trap gated purely on launch scheduling. Related trap in the same asymmetry: because bind() does not touch lastSettledDay either, binding N days after deployment without first calling the unbound settle() forces the oracle to produce N historical attestations (day == lastSettledDay+1 in order) before any real day can settle, all within the same 30-day window. Design-preserving fix: in bind(), set lastSuccessfulSettle = block.timestamp (the 'no successful settle for 30 days' clock cannot meaningfully start before success is possible) and optionally lastSettledDay = block.timestamp / DAY; or revert bind() when isDead() would be true immediately after it.","line":127,"path":"src/PlantOrganism.sol","reproduction":"chainId 4663, t0 = 20000 days + 12h. deploy PlantOrganism(imd, signer, 10223579, deployer); imd.mint(body, 1000e18); warp(t0 + 30 days); assert !body.isDead() (unbound). deployer.bind(hook, Q) succeeds. Expected: a freshly bound plant with 30 days to receive its first settle. Actual: body.isDead() == true in the same second; settle(validAttestation for day lastSettledDay+1, sig) reverts Dead; location()==0, backing()==1000e18 (entire pot is now full-floor redeemable), and no call sequence can ever change that. Scratch test test_lead1_bindAfter30DaysIsDeadOnArrival passes on the current code demonstrating exactly this state.","severity":"medium","snippet":"        lastSuccessfulSettle = block.timestamp;","title":"Death clock starts at deployment, so a bind 30+ days after deploy produces a permanently dead plant (asymmetry: unbound settle advances lastSettledDay but can never refresh lastSuccessfulSettle)"},{"citation":"resolved","description":"_checkSupply() (line 387: `if (plant.totalSupply() != plantSupply) revert SupplyChanged();`) is called at the top of park(), redeem() and settle(). redeem's own arithmetic never reads the live supply (it uses the frozen plantSupply - burned), so the check buys nothing for the payout maths; it only converts any supply drift into a permanent DoS of the one function that releases IMD backing to holders. If the second-launch PLANT token exposes any supply-changing path to an unprivileged party (ERC20Burnable-style burn(), a burn-on-transfer, a mintable hook/pool), a single 1-wei burn by any holder bricks redeem() and settle() forever: settle reverting means the plant dies after 30 days, death merges the whole pot into backing, and redeem *still* reverts SupplyChanged, so backing is unreachable by anyone, for all time (unpark and claim keep working, which shows the asymmetry: the custody path is tolerant, the value-release path is not). README states fixed supply as an assumption; the contract has no way to verify that assumption at bind() beyond totalSupply() != 0, and the assumption is about a token deployed later by a different launch. Severity is medium because the precondition is a property of an external token, but the consequence is total, permanent loss of redeemable IMD. Design-preserving fix: in redeem(), tolerate a supply *decrease* (payout = amount * backing / (plantSupply - burned) with plantSupply >= live supply only under-pays, it cannot drain) and only revert on an increase (the dilution risk), or at minimum skip _checkSupply() in redeem() once isDead() is true so the terminal state is always exitable.","line":233,"path":"src/PlantOrganism.sol","reproduction":"Bind with a PLANT mock that has a public burn(uint256). alice.park(LISBON,100e18); settle birth (valid challenger LISBON) -> location LISBON; imd.mint(body, 9000e18); settle sun mask 1 -> backing()==600e18. bob (any holder, 400e18) calls plant.burn(1). Then bob.redeem(1e18) reverts SupplyChanged (expected: pays 1e18*600e18/1000e18*0.9); next-day settle reverts SupplyChanged; warp +30 days -> isDead() true, syncDeath() merges pot (backing()>0); bob.redeem(1e18) still reverts SupplyChanged. alice.unpark(LISBON,100e18) succeeds. Scratch test test_lead2_supplyChangeLocksBacking demonstrates the full sequence on the current code.","severity":"medium","snippet":"        _checkSupply();","title":"redeem() hard-reverts on any PLANT totalSupply deviation, so one external burn/mint permanently locks all IMD backing (asymmetry: unpark/claim have no supply check, redeem/settle do)"},{"citation":"resolved","description":"rotateSigner(newSigner, sig) is permissionless to relay and accepts any nonzero newSigner != signer with no acknowledgement from newSigner and no deadline in the signed message (RotateSigner(organism,newSigner,nonce) carries only the nonce). The moment it lands, `signer = newSigner` and the previous key keeps only attestation authority for SIGNER_GRACE; it can no longer call rotateSigner (line 367 compares against the *current* signer). So a single wrong address in a signed rotation, or a rotation message that was signed for a candidate key and later leaked/relayed by anyone after the operator changed their mind (there is no expiry, and the same nonce stays valid until some rotation lands), hands the oracle to an address that may hold no key. With no admin and no governance by design, there is no recovery: after the 30-day grace nobody can produce an accepted attestation, settle() can never succeed, and the plant is permanently dead. This is the checklist's missing two-step handover applied to the only mutable authority in the contract. Design-preserving fix (still 'only the current signer can rotate, no governance'): also require a signature from newSigner over the same digest (proves the key exists and is controlled), and add a deadline field to the RotateSigner struct so unsubmitted authorisations expire.","line":367,"path":"src/PlantOrganism.sol","reproduction":"signer A current. Anyone relays rotateSigner(typoAddr, sigA(rotationDigest(typoAddr))) where typoAddr is an address with no known key. Expected (safe handover): the typo'd key must prove control before taking over, or A can undo. Actual: signer()==typoAddr immediately; rotateSigner(A, sigA(rotationDigest(A))) reverts BadSignature because A is no longer current; A may still settle until signerValidUntil[A], after which no attestation verifies and isDead() becomes true 30 days after the last success and stays true forever. Scratch test test_lead6_misrotationIsFatal demonstrates the sequence.","severity":"low","snippet":"        if (_recover(rotationDigest(newSigner), sig) != signer) revert BadSignature();","title":"rotateSigner is one-step and the retired key loses rotation authority at once: a mis-signed newSigner (typo, dead key, leaked pre-signed message) irrevocably hands over the oracle and kills the plant "},{"citation":"resolved","description":"settle pins chainId, questionHash, answerType, shape, window, signer and day, but never reads a.panelSize, a.quorum or a.agreed. The pinned oracle-consumer reference (the protocol definition this task is judged against) states: 'panelSize, quorum and agreed are signed. Require agreed >= quorum and that the panel and quorum are at least what the contract asked for', and the attestation library comment explains agreed < quorum is exactly the case where the panel split and a rerun, not the panel, chose the answer. Because the oracle signs such attestations as well, a weather/challenger answer that the panel did not agree on can still spend up to 24 sips of the pot, pay the bounty, and move the location. ORACLE.md explicitly declines to add the check ('no additional panel threshold is invented'); this finding records that the contract diverges from the reference's stated requirement so the author can decide knowingly. Design-preserving fix: `if (a.agreed < a.quorum || a.quorum < MIN_QUORUM) revert BadAttestation();` with MIN_QUORUM a constant matching the frozen question's requested panel.","line":281,"path":"src/PlantOrganism.sol","reproduction":"Bound body, alice.park(LISBON, 100e18). Build the day-(lastSettledDay+1) attestation with panelSize 5, quorum 4, agreed 0, valid challenger LISBON, signed by the current signer. Expected per reference: rejected. Actual: settle succeeds, location()==LISBON, bounty paid. Scratch test test_lead3_agreedBelowQuorumAccepted passes on the current code.","severity":"low","snippet":"        verifyAttestation(a, sig);","title":"settle() accepts attestations whose panel did not agree (agreed < quorum), contrary to the oracle-consumer reference's required check"},{"citation":"resolved","description":"The attestation and signature are plain calldata, and settle() accepts any msg.sender. Whoever requested the oracle answer (paying the 0.5 IMD request price and gas, per the oracle-consumer reference) broadcasts settle(a, sig); any observer can copy the two arguments into their own transaction and, if included first, collects 1% of the pot while the original transaction reverts WrongDay. The brief does make settle 'anyone may call' and the bounty 'pay caller', so this is a design property rather than a bypass, but it is the seam where the relayer's economic incentive (which the liveness of the plant depends on: no settle for 30 days = death) can be stripped by a party that bore none of the cost. On a FCFS sequencer the risk is lower than on a public mempool; it is non-zero as long as pending transactions are visible to anyone. Design-preserving options: let the attestation optionally bind a payee (a word the oracle signs, or an `address payee` parameter covered by an extra relayer signature), or keep the current behaviour and document that relayers should use a private submission path.","line":329,"path":"src/PlantOrganism.sol","reproduction":"Bound body with 1000e18 IMD in pot. alice obtains (a, sig) for day lastSettledDay+1 and submits settle(a, sig). bob observes alice's pending tx and submits settle(a, sig) with higher priority. Actual: bob receives 10e18 IMD (1%), alice's call reverts WrongDay. Expected: alice, who paid for the attestation, receives the bounty. Scratch test test_lead5_bountyFrontRun reproduces the outcome with ordered calls.","severity":"low","snippet":"        uint256 bounty = available / 100;\n        if (bounty != 0) _sendExact(IMD, msg.sender, bounty);","title":"Settle bounty pays whoever lands the transaction, not the party that paid for and relayed the oracle answer (access x economics: permissionless call + public calldata)"},{"citation":"resolved","description":"The brief defines cell 0 as 'nowhere' and the challenger path correctly refuses it (`challenger != 0`, line 313), but park() only requires validCell(cell), and lat 0 / lon 0 is inside the bounds. During birth the incumbent is cell 0, so parkedTotal[0] is the number a challenger must beat (line 314). A holder can therefore park at 0 - earning nothing, since location 0 has no hours - purely to veto all proposed cells until the third empty settle forces FALLBACK_CELL. Post-birth the parked tokens at 0 are inert. Impact is nil beyond forcing Lisbon (holders can already veto by parking at the current cell, and birth is bounded to three settles), so this is informational, but a cell that the brief calls 'nowhere' probably should not be parkable: `if (cell == 0 || !validCell(cell)) revert InvalidCell();` in park().","line":180,"path":"src/PlantOrganism.sol","reproduction":"Bound body, location 0. bob.park(0, 400e18); alice.park(LISBON, 300e18) (30% of supply). settle with valid challenger LISBON -> location stays 0 because 300e18 > 400e18 is false. Two more such settles -> location == FALLBACK_CELL. Expected: parking at 'nowhere' is rejected, so LISBON (sole real candidate, >= 5%) is born on the first settle. Scratch test test_lead4_parkAtNowhereBlocksBirth passes on the current code.","severity":"info","snippet":"        if (!validCell(cell)) revert InvalidCell();","title":"validCell(0) is true, so PLANT can be parked at 'nowhere' and out-vote every birth candidate"}],"hash":"a93ea31ed9a3633b956173afe735d5fa26afe04ae28905747337b02410457e81","nodeId":"2dce6111-3886-4b41-a412-45a0ef2c68d1","outcome":"completed","summary":"Review complete. `.imd-findings.json` is written with 6 findings and 14 coverage rows (all 9 entry points answered, plus 4 invariants and one honest `unreached`). Every snippet was checked against the tree at its cited line, and all six reproductions ran as passing scratch tests on the current code before I removed `test/scratch/`.\n\n## Summary\n\n**Area: Access Control, Trust Gap, Asymmetry** — the permission model itself is sound. There is no unguarded writer, no privilege chain, no confused deputy, no initializer race: `bind` is deployer‑only and one‑shot, `rotateSigner` is bound to the current key with a nonce and this contract's domain, every value‑moving path is reentrancy‑guarded with exact balance checks, and the existing 43 tests (including the eager‑model invariant suite) pass. The defects I found live at the *seams*, where the design's own rules interact badly:\n\n| # | Sev | Finding |\n|---|---|---|\n| 1 | medium | Death clock starts at **deployment**, not bind. Bind ≥30 days after deploy = plant is dead the same second, settle reverts `Dead` forever. README documents it as intended; I report it because the second launch's timing is outside this contract's control and the result is an immutable corpse. |\n| 2 | medium | `redeem()` hard‑gates on exact `PLANT.totalSupply()`. Any supply drift (e.g. a holder calling a `burn()` on the token) permanently locks all IMD backing — settle dies, death merges the pot, redeem *still* reverts. `unpark`/`claim` don't have the gate, exposing the asymmetry. |\n| 3 | low | `rotateSigner` is one‑step, no acceptance, no deadline; a mis‑signed address irrevocably hands over the oracle, and with no admin the plant dies 30 days later. |\n| 4 | low | `settle` never reads `agreed`/`quorum`; the pinned oracle‑consumer reference says to require `agreed >= quorum`. |\n| 5 | low | Bounty goes to whoever lands the tx with the public calldata, not the relayer who paid for the oracle answer. |\n| 6 | info | `validCell(0)` is true, so PLANT can be parked at \"nowhere\" and used to veto birth. |\n\nNo finding reached high, so no proof files were attached. Design‑preserving fixes are given for each (e.g. refresh `lastSuccessfulSettle` in `bind`; in `redeem`, revert only on supply *increase*).\n\n**Not reached:** independent re‑derivation of the `_reward`/`_checkpoint` ceiling‑and‑fraction dust arithmetic beyond the author's invariant suite, and live verification of the IMD token's transfer semantics on chain 4663 (no network).","treeHash":null,"usage":{"cachedInputTokens":1527909,"inputTokens":34,"model":"claude-fable-5-1","outputTokens":43072,"runtime":"claude","turns":18,"wallClockMs":885970}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e3a598aae0640402","findings":[{"citation":"resolved","description":"_checkSupply() runs first in park (L179), redeem (L233) and bound settle (L273) and reverts on ANY difference between the live PLANT totalSupply and the snapshot taken at bind (L146). Nothing can ever re-sync the snapshot: bind is once, there is no admin. Redeem's own arithmetic never reads the live supply (payout = amount * _backing / (plantSupply - burned), L234-239), so the check adds no protection against a supply DECREASE; it only turns one into a permanent denial of the sole function that releases backing. If the second-launch PLANT token exposes any supply-decreasing path to an unprivileged party (ERC20Burnable burn/burnFrom is the common launch-token shape; the token is not in this tree and its shape is unverified), a 1-wei burn by any holder makes redeem revert SupplyChanged forever; settle reverts the same way, so the plant dies 30 days later, and the dead-path redeem still reverts because _checkSupply runs before the dead branch (L233 before L236). Only unpark and claim keep working, so 100% of backing (and the merged dead pot) is unreachable by anyone, for all time. The brief defines backing as redeemable IMD whose floor never decreases; here it becomes unredeemable. A supply INCREASE is a genuine dilution risk (minted tokens could redeem against existing backing) and may keep reverting, but a decrease can only under-pay the burner and never harms other holders. Merged from audit_flow, audit_economics, audit_math and audit_permissions (same root cause). Severity: permanent loss of all redeemable value triggered by an unprivileged party at near-zero cost; downgrade to medium only if the PLANT token is proven to have no supply-decreasing path. Minimal fix preserving the design: compare with `>` (reject increases only) at L387, or at least skip the check in redeem (and in redeem once isDead()) so the terminal state is always exitable.","line":387,"path":"src/PlantOrganism.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PlantOrganism} from \"src/PlantOrganism.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev A PLANT token with a public burn, the ERC20Burnable shape launch tokens commonly expose.\ncontract BurnableToken {\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        totalSupply += amount;\n        balanceOf[to] += amount;\n    }\n\n    function burn(uint256 amount) external {\n        balanceOf[msg.sender] -= 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\ncontract ScratchHook {\n    address public immutable organism;\n    address public immutable plant;\n\n    constructor(address organism_, address plant_) {\n        organism = organism_;\n        plant = plant_;\n    }\n}\n\n/// @dev One wei of PLANT burned by any holder makes redeem revert SupplyChanged forever, alive or dead,\n/// so the IMD backing that the brief calls \"redeemable\" can never be redeemed by anyone.\ncontract SupplyDriftLocksBackingTest is Test {\n    uint256 internal constant KEY = 0xA11CE;\n    uint32 internal constant LISBON = 10223579;\n    bytes32 internal constant QUESTION = keccak256(\"frozen test weather question\");\n\n    BurnableToken internal imd;\n    BurnableToken internal token;\n    PlantOrganism internal body;\n    address internal alice = makeAddr(\"alice\");\n    address internal mallory = makeAddr(\"mallory\");\n\n    function setUp() public {\n        vm.chainId(4663);\n        vm.warp(20000 days + 12 hours);\n        imd = new BurnableToken();\n        token = new BurnableToken();\n        token.mint(alice, 999 ether);\n        token.mint(mallory, 1 ether);\n        body = new PlantOrganism(address(imd), vm.addr(KEY), LISBON, address(this));\n        ScratchHook hook = new ScratchHook(address(body), address(token));\n        body.bind(address(hook), QUESTION);\n        vm.prank(alice);\n        token.approve(address(body), type(uint256).max);\n\n        // Birth at Lisbon, then 10 000 IMD arrives and one sunny hour turns a tenth of it into backing.\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        settle(0);\n        imd.mint(address(body), 10_000 ether);\n        settle(1);\n        assertGt(body.backing(), 0);\n        vm.prank(alice);\n        body.unpark(LISBON, 100 ether);\n    }\n\n    function attestation(uint256 day, uint24 sun) internal view returns (OracleAttestation.Attestation memory a) {\n        bytes32[] memory words = new bytes32[](3);\n        words[0] = bytes32(uint256(sun) | (uint256(1) << 48) | (uint256(LISBON) << 64) | (day << 96));\n        a = OracleAttestation.Attestation({\n            requestId: bytes32(day),\n            chainId: 4663,\n            questionHash: QUESTION,\n            answerType: 5,\n            answer: abi.encode(words),\n            figure: 0,\n            fromBlock: 100,\n            toBlock: 200,\n            blockHash: bytes32(uint256(7)),\n            panelJobId: bytes32(uint256(8)),\n            panelSize: 5,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: uint64(block.timestamp),\n            expiresAt: uint64(block.timestamp + 1 days)\n        });\n    }\n\n    function settle(uint24 sun) internal {\n        uint256 day = body.lastSettledDay() + 1;\n        if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n        OracleAttestation.Attestation memory a = attestation(day, sun);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(KEY, body.attestationDigest(a));\n        body.settle(a, abi.encodePacked(r, s, v));\n    }\n\n    function test_oneWeiBurnByAnyHolderBlocksLiveRedemptionOfBacking() public {\n        uint256 backingBefore = body.backing();\n        uint256 expected = 100 ether * backingBefore / 1000 ether * 9 / 10;\n\n        // An unrelated holder burns one wei of PLANT. Nothing the organism owes has changed.\n        vm.prank(mallory);\n        token.burn(1);\n\n        vm.prank(alice);\n        uint256 paid = body.redeem(100 ether);\n        assertEq(paid, expected, \"live redemption must pay amount x backing / remaining x 90%\");\n    }\n\n    function test_oneWeiBurnLocksBackingEvenAfterDeath() public {\n        vm.prank(mallory);\n        token.burn(1);\n\n        // No settle can land, so the plant dies; death must still let holders exit at full floor.\n        vm.warp(block.timestamp + 30 days);\n        assertTrue(body.isDead());\n        uint256 full = body.backing();\n        assertGt(full, 0);\n\n        vm.prank(alice);\n        uint256 paid = body.redeem(100 ether);\n        assertEq(paid, 100 ether * full / 1000 ether, \"dead redemption must pay the full floor\");\n    }\n}","reproduction":"State: chainId 4663, PlantOrganism bound to a PLANT token (supply 1000e18) that has a holder-callable burn(uint256); alice parked 100e18 at Lisbon; birth settled (valid challenger LISBON); 10_000e18 IMD deposited; one sunny hour settled so backing() is about 666.67e18 (sip 1000e18; 2/3 to backing, 1/3 reserved for gardeners); alice unparks 100e18. Then mallory (holds 1e18 PLANT, no role) calls PLANT.burn(1). (1) alice.redeem(100e18): expected payout 100e18 * backing / 1000e18 * 9/10, about 60e18 IMD; actual revert SupplyChanged(). (2) relay a valid signed attestation for lastSettledDay+1: expected settles; actual revert SupplyChanged(). (3) warp +30 days: isDead() == true, backing() == IMD held - owed > 0; alice.redeem(100e18): expected 100e18 * backing / 1000e18 (full floor); actual revert SupplyChanged(). No remaining call path moves backing out. Proof test/scratch/SupplyDriftLocksBacking.t.sol: both tests fail with SupplyChanged() on this tree and pass against a copy where L387 rejects only increases (verified locally).","severity":"high","snippet":"        if (plant.totalSupply() != plantSupply) revert SupplyChanged();","title":"Any PLANT totalSupply decrease permanently bricks redeem (and settle), locking all backing IMD with no recovery path"},{"citation":"resolved","description":"lastSuccessfulSettle is set to block.timestamp in the constructor (L127) and is rewritten only by a successful BOUND settle (L322). The unbound settle branch (L265-271) never touches it and bind() (L135-149) neither resets it nor refuses to bind a body already past the deadline. The brief's rule is 'no successful settle for 30 days -> dead', but before bind a successful settle is impossible by construction, so the clock measures the gap between this launch and the separate second launch (PLANT token, pool, hook), which the deployer does not fully control. If bind lands >= 30 days after deployment, isDead() is true the instant bind returns: every bound settle reverts Dead (L272), the plant never births, moves or waters, and all IMD the hook and fee distributor forward becomes 100%-redeemable dead backing. The contract is immutable, so the only remedy is redeploying the organism AND redoing the second launch against the new address. A bind on day 29 leaves under a day for the oracle to issue and a relayer to land the first in-order settle. Related asymmetry from the same root: bind() also leaves lastSettledDay untouched, so binding N days after deployment without first calling the unbound settle forces N historical in-order attestations (L286) inside the same 30-day window. README documents the clock start, but a silently stillborn plant is an operational trap, not a safety property. Merged from audit_flow, audit_economics, audit_math and audit_permissions. Fix preserving design: in bind() set lastSuccessfulSettle = block.timestamp (optionally also lastSettledDay = block.timestamp / DAY), or revert bind() when the body would be dead on arrival.","line":159,"path":"src/PlantOrganism.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PlantOrganism} from \"src/PlantOrganism.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\ncontract ScratchToken {\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        totalSupply += amount;\n        balanceOf[to] += 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\ncontract ScratchHook {\n    address public immutable organism;\n    address public immutable plant;\n\n    constructor(address organism_, address plant_) {\n        organism = organism_;\n        plant = plant_;\n    }\n}\n\n/// @dev The 30-day death clock runs from deployment although no settle can succeed before bind.\n/// Binding 30 days after deployment yields an organism that is dead the moment bind returns.\n/// The test accepts either repair: bind refuses to bind a body that would be dead, or a body that\n/// bind accepts is alive and settles.\ncontract BornDeadTest is Test {\n    uint256 internal constant KEY = 0xA11CE;\n    uint32 internal constant LISBON = 10223579;\n    bytes32 internal constant QUESTION = keccak256(\"frozen test weather question\");\n\n    ScratchToken internal imd;\n    ScratchToken internal token;\n    PlantOrganism internal body;\n    address internal alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        vm.chainId(4663);\n        vm.warp(20000 days + 12 hours);\n        imd = new ScratchToken();\n        token = new ScratchToken();\n        token.mint(alice, 1000 ether);\n        body = new PlantOrganism(address(imd), vm.addr(KEY), LISBON, address(this));\n        imd.mint(address(body), 1000 ether);\n    }\n\n    function attestation(uint256 day) internal view returns (OracleAttestation.Attestation memory a) {\n        bytes32[] memory words = new bytes32[](3);\n        words[0] = bytes32((uint256(1) << 48) | (uint256(LISBON) << 64) | (day << 96));\n        a = OracleAttestation.Attestation({\n            requestId: bytes32(day),\n            chainId: 4663,\n            questionHash: QUESTION,\n            answerType: 5,\n            answer: abi.encode(words),\n            figure: 0,\n            fromBlock: 100,\n            toBlock: 200,\n            blockHash: bytes32(uint256(7)),\n            panelJobId: bytes32(uint256(8)),\n            panelSize: 5,\n            quorum: 4,\n            agreed: 5,\n            issuedAt: uint64(block.timestamp),\n            expiresAt: uint64(block.timestamp + 1 days)\n        });\n    }\n\n    function test_bindThirtyDaysAfterDeploymentMustNotYieldADeadBody() public {\n        // The second launch (PLANT + hook) lands 30 days after the organism was deployed.\n        vm.warp(block.timestamp + 30 days);\n        OracleAttestation.Attestation memory blank;\n        body.settle(blank, \"\"); // keep the day cursor current, as the README advises\n        assertFalse(body.isDead(), \"unbound body is never dead\");\n\n        ScratchHook hook = new ScratchHook(address(body), address(token));\n        (bool ok,) = address(body).call(abi.encodeCall(body.bind, (address(hook), QUESTION)));\n        if (!ok) return; // a bind that refuses a body past its deadline is an acceptable repair\n\n        // bind accepted: a body that has never had a chance to settle must be alive ...\n        assertFalse(body.isDead(), \"organism is dead at the moment it is bound\");\n        assertEq(body.pot(), 1000 ether, \"pot already merged into dead backing\");\n\n        // ... and must accept its first in-order settle on the next day.\n        vm.prank(alice);\n        token.approve(address(body), type(uint256).max);\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        vm.warp(block.timestamp + 1 days);\n        OracleAttestation.Attestation memory a = attestation(body.lastSettledDay() + 1);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(KEY, body.attestationDigest(a));\n        body.settle(a, abi.encodePacked(r, s, v));\n        assertEq(body.location(), LISBON);\n    }\n}","reproduction":"chainId 4663, T0 = 20000 days + 12h. Deploy PlantOrganism(imd, signer, 10223579, deployer); mint 1000e18 IMD to it. warp(T0 + 30 days). Optional: settle(blank, '') unbound advances lastSettledDay to 20030. deployer.bind(hook, Q) with hook.organism()==this and a nonzero-supply PLANT: succeeds. Expected: a freshly bound organism is alive (isDead()==false, pot()==1000e18) and accepts the next day's in-order signed attestation. Actual: isDead()==true in the same second, pot()==0, backing()==1000e18, and settle(valid attestation for lastSettledDay+1, sig) reverts Dead(); no later call can ever change that. Proof test/scratch/BornDead.t.sol fails on this tree with 'organism is dead at the moment it is bound'; it accepts either repair (bind reverting, or bind resetting the clock) and passes against a copy where bind sets lastSuccessfulSettle = block.timestamp (verified locally).","severity":"medium","snippet":"        return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;","title":"Death clock starts at deployment, not at bind: binding 30+ days after deployment yields a permanently dead organism"},{"citation":"resolved","description":"validCell(0) is true (lat 0, lon 0 are in range, L152-156) and park() only checks validCell (L180), so PLANT can be parked at cell 0, which the brief defines as 'nowhere'. While location == 0 (birth) the move test compares parkedTotal[challenger] > parkedTotal[current] with current == 0 (L314), so tokens parked at the sentinel count as support for staying nowhere. The brief's BIRTH rule is unconditional: 'the first settle with a valid challenger >= 5% moves it'. A holder with more PLANT than the leading candidate can pin the plant at nowhere for three settles and force FALLBACK_CELL over the community's choice, at no risk (unpark any time). Tokens at cell 0 can never earn (no hours while 0, challenger != 0 at L313 so 0 is never chosen again), so allowing them has no legitimate use. The constructor itself rejects fallbackCell_ == 0, and test_rejectsChangedSupplyInvalidCellsAndZeroAmounts asserts validCell(0)==true, so the suite enshrines the behaviour. Merged from four specialists. Fix: `if (cell == 0 || !validCell(cell)) revert InvalidCell();` in park(), or treat parkedTotal[current] as 0 when current == 0.","line":314,"path":"src/PlantOrganism.sol","reproduction":"Bound organism, supply 1000e18, location 0. alice.park(PARIS=(195<<16)|9, 90e18) (9% >= 5% threshold of 50e18); bob.park(0, 100e18) succeeds (expected InvalidCell). settle day lastSettledDay+1 with valid=1, challenger=PARIS: expected location == PARIS; actual location == 0 and emptySettles == 1 because 90e18 > 100e18 is false. Two more identical settles: location == 10223579 (Lisbon fallback) instead of PARIS. Reproduced by test/scratch/Leads.t.sol::test_parkAtNowhereVetoesBirth (passes on this tree, documenting the behaviour).","severity":"low","snippet":"                && parkedTotal[challenger] > parkedTotal[current] && parkedTotal[challenger] >= threshold","title":"Cell 0 ('nowhere') is parkable and counts as incumbent support during birth, so a holder can veto a valid >= 5% challenger and force the fallback cell"},{"citation":"resolved","description":"verifyAttestation requires a.issuedAt <= block.timestamp exactly. The protocol's own OracleAttestationConsumer (oracle-consumer REFERENCE.md, ISSUED_AT_TOLERANCE = 5 minutes, L139-142 and L172) deliberately accepts issuedAt up to five minutes ahead because the attester stamps with its wall clock while a block's timestamp is a validator's and may lag by seconds, and the oracle-consumer adapter says the reference wins over a brief restatement on protocol matters. Here an attestation issued seconds before inclusion can revert InvalidWindow when the L2 block timestamp lags wall clock; the relayer must wait and resubmit. Impact is liveness only, but liveness is what the 30-day death clock and the strict in-order day cursor depend on, and docs/ORACLE.md states the exact check as intentional. Recorded so the author makes the choice knowingly. Fix: `if (a.issuedAt > block.timestamp + 5 minutes || block.timestamp > a.expiresAt) revert InvalidWindow();`.","line":353,"path":"src/PlantOrganism.sol","reproduction":"Bound organism, block.timestamp = T on day 20001. Attestation for day 20001 (lastSettledDay+1) with issuedAt = T+1, expiresAt = T+1 day, correctly signed by the signer in this contract's domain. settle(a, sig): expected under the protocol consumer's semantics: accepted; actual: revert InvalidWindow(). Reproduced by test/scratch/Leads.t.sol::test_issuedAtOneSecondAheadRejected and the existing test_expiredAndNotYetValidRejectIncludingOneSecond.","severity":"low","snippet":"        if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();","title":"issuedAt has no clock-skew tolerance: an attestation stamped one second ahead of the block timestamp is rejected"},{"citation":"resolved","description":"settle pins chainId, questionHash, answerType, payload shape, window, signer and day, but never reads a.panelSize, a.quorum or a.agreed. The pinned oracle-consumer reference instructs consumers: 'panelSize, quorum and agreed are signed. Require agreed >= quorum and that the panel and quorum are at least what the contract asked for', and the struct comment (src/OracleAttestation.sol L45-48) says agreed is below quorum exactly when the panel split and a rerun, not the panel, chose the answer. Because the oracle signs such attestations too, a weather/challenger answer the panel did not agree on can spend up to 24 sips of the pot, pay the bounty and move the plant. The signer is trusted, so this is defence in depth rather than a bypass, and docs/ORACLE.md consciously declines a threshold; recorded so the decision is explicit. Merged from audit_flow and audit_permissions. Fix: `if (a.agreed < a.quorum) revert BadAttestation();` (optionally also a minimum quorum constant matching the frozen question).","line":281,"path":"src/PlantOrganism.sol","reproduction":"Bound organism; alice.park(LISBON, 100e18). Attestation for day lastSettledDay+1 with panelSize=20, quorum=15, agreed=0, valid challenger LISBON, otherwise well-formed and signed by the current signer. settle(a, sig): expected per the reference's consumer guidance: rejected; actual: succeeds, lastSettledDay advances and location == LISBON. Reproduced by test/scratch/Leads.t.sol::test_agreedBelowQuorumAccepted.","severity":"low","snippet":"        verifyAttestation(a, sig);","title":"Signed panel metadata is ignored: an attestation with agreed < quorum (even agreed == 0) settles the day"},{"citation":"resolved","description":"rotateSigner accepts any nonzero newSigner != signer backed only by the current signer's signature over RotateSigner(organism,newSigner,nonce) (L361-367). The moment it lands, signer = newSigner (L371) and the previous key retains only attestation authority for SIGNER_GRACE; L367 compares against the CURRENT signer, so the retired key can never rotate back. Two consequences: (a) a typo'd or dead newSigner address (no key) means that after 30 days nobody can produce an accepted attestation, settle can never succeed and the plant dies permanently; (b) the signed message carries no deadline, so an authorization signed for a candidate key and never submitted stays relayable by anyone until some rotation advances the nonce, even years later. This is the checklist's missing two-step handover applied to the only mutable authority in an otherwise immutable contract; the brief's rule (only the current signer can rotate, no governance) is preserved by requiring a second signature from newSigner over the same digest and adding a deadline field to the struct. docs/ORACLE.md documents the absence of an expiry. From audit_permissions.","line":367,"path":"src/PlantOrganism.sol","reproduction":"(a) signer A current. Anyone relays rotateSigner(0x...dead1, sigA(rotationDigest(0x...dead1))): succeeds, signer()==0x...dead1. rotateSigner(A, sigA(rotationDigest(A))) reverts BadSignature (A no longer current). warp +30 days +1: verifyAttestation(a, sigA) reverts BadSignature and isDead()==true since no settle could land; permanent. (b) A signs rotationDigest(B) at T but never submits; at T + 365 days bob relays rotateSigner(B, thatSig): succeeds. Reproduced by test/scratch/Leads.t.sol::test_misrotationIsIrrevocable and test_leakedPreSignedRotationStaysValidUntilNonceAdvances.","severity":"low","snippet":"        if (_recover(rotationDigest(newSigner), sig) != signer) revert BadSignature();","title":"rotateSigner is one-step with no acknowledgement from newSigner and no expiry on the signed authorization: a mis-signed or leaked rotation irrevocably hands over the oracle and kills the plant"}],"hash":"aba71d4113b757cc9196df5a3e186a182e81fbe8dc9d4872e1bc740f5adfa206","nodeId":"5407d303-e786-40f5-a1b6-b109bff6b696","outcome":"completed","summary":"Review complete. No tracked file was changed; `.imd-findings.json` holds 6 findings and a 14-row coverage record.\n\n**Verdict on the specialist reports.** All four areas converged on the same three root causes, which I merged and reproduced. I dropped two leads and kept four lower ones.\n\n- **High, with proof: supply-drift lock.** `_checkSupply` at `src/PlantOrganism.sol:387` reverts on any live PLANT supply difference and runs before redeem's dead branch. If the second-launch PLANT token has any burn path, a 1-wei burn by any holder makes redeem and settle revert forever, so all backing is unredeemable even after death. Redeem never uses the live supply, so rejecting decreases buys nothing. Proof fails on this tree with `SupplyChanged()` and passes on a patched copy that rejects increases only. I rated it high rather than the specialists' mixed medium/high because the loss is total and permanent; it drops to medium only if PLANT is proven unburnable.\n- **Medium, with proof: dead-on-bind.** The 30-day clock starts in the constructor and bind never resets or refuses. Binding 30 days after deployment produces a body that is dead the instant bind returns. My proof accepts either repair, bind reverting or bind resetting the clock.\n- **Low: cell 0 parkable** and counted as birth incumbent, letting a larger holder veto a valid challenger for three settles and force Lisbon.\n- **Low: no issuedAt skew tolerance**, unlike the protocol's own consumer.\n- **Low: agreed < quorum accepted**, contrary to the oracle-consumer reference.\n- **Low: one-step signer rotation** with no newSigner acknowledgement and no expiry on the signed authorization.\n\n**Dropped.** The frozen question-hash concern cannot be reproduced against code and is an integration precondition the docs already flag. The bounty front-run is the brief's requested behaviour, not a defect.\n\n**My own pass** traced the reward accumulator, pending-cohort activation, dust reservation, redemption rounding, dead-state accounting, replay protection and the EIP-712 domain, and found nothing beyond the merged items. The slither and aderyn reentrancy lines are false positives under the shared guard. The existing 67 tests pass, including the invariant suites.","treeHash":null,"usage":{"cachedInputTokens":1356132,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":39841,"runtime":"claude","turns":29,"wallClockMs":732862}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2a9662a76cb5f51d","findings":[],"hash":"b2a6af828db8fabf0209609315db85f92e85ae380322b48ca35b268735bab12a","nodeId":"5407d303-e786-40f5-a1b6-b109bff6b696","outcome":"completed","summary":"All prior findings are settled and the revised contract is clean. The findings file is written with an empty list and a full coverage record.\n\n**What I settled**\n\n- **Supply-drift lock (high):** fixed. The check at `src/PlantOrganism.sol:392` now rejects only live supply above the bind snapshot. The attached proof passes on the revised tree, and the author's new tests cover one-wei burns through park, settle, live redeem and dead redeem.\n- **Death clock from deployment (medium):** fixed. The bind function sets the last successful settle timestamp at `src/PlantOrganism.sol:149`. The attached proof passes, and binding 365 days late with or without an unbound cursor advance settles normally.\n- **Cell 0 vetoing birth (low):** fixed. The move condition bypasses incumbent support while the location is zero. The author's test reproduces my scenario with 400 PLANT at nowhere against a challenger at exactly 5%.\n- **Sub-quorum attestations accepted (low):** fixed. Settle rejects agreed below quorum. The protocol fixture carries agreed 5 against quorum 4, so the conformance vector still verifies.\n- **issuedAt skew tolerance (low):** dropped. The author's dispute holds. The brief states the exact window and the task rules say the brief overrides the reference on this point.\n- **One-step signer rotation (low):** dropped as a defect. The brief fixes the rotateSigner API and the current-signer-only rule. The remaining risk is an operator trust assumption, now documented in ORACLE.md with a key-control verification step.\n\n**Verification run**\n\n- Both attached proofs copied under test/scratch and run against the revised code: 3 passed.\n- Full suite excluding scratch: 81 tests passed across 9 suites, including the two invariant fuzz suites.\n- I re-read every changed line as new code and found nothing the fixes broke or introduced.\n\nScratch copies were removed. The only file left in the tree is the findings file at the repository root.","treeHash":null,"usage":{"cachedInputTokens":933622,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":7153,"runtime":"claude","turns":12,"wallClockMs":171056}},{"artifacts":[],"attempt":2,"bundleHash":"5d5ec1f66196f9c6ad287678d1df0860a8e64962091922f33a5903aa5200b5d9","device":"265d2bb313654afc","findings":[],"hash":"bdc501fbb87ed49f79311d4706ce7fe740e9405851031b8e92636ef865dcb89e","nodeId":"3303df5c-c99b-42dc-ae65-cebae4457b0f","outcome":"completed","summary":"Implemented the contract, launch manifest, vendored dependencies, tests, and documentation.\n\n- `forge build`, `forge test`, and `forge fmt --check` pass.\n- 43 tests pass, including 8,192 invariant calls and the canonical oracle signature fixture.\n- Tested settlement costs 353,839 gas including transaction overhead.\n\nDeployment parameters, assumptions, and operator responsibilities are documented in `README.md`. No transactions were broadcast.","treeHash":"5797132adcda1f1df6501f63bca1128b2bc4d8aa","usage":{"cachedInputTokens":1124608,"inputTokens":107097,"model":"gpt-6-astra","outputTokens":35679,"runtime":"codex","turns":6,"wallClockMs":1040964}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ccb45f5f705bb9fb","findings":[],"hash":"d256f94128c72ca43dbaf97a563eadf3d0a0adc6bc104c7cc641ea1a435e920e","nodeId":"3303df5c-c99b-42dc-ae65-cebae4457b0f","outcome":"failed","summary":"the task produced no changes; the agent's last message was: The workspace blocker persists: terminal commands fail with `bwrap: setting up uid map: Permission denied`, and file writes fail too.\n\nNo files changed. I couldn’t reproduce or repair the findings, write `.imd-responses.json` or `.imd-blocked.json`, or run Foundry.\n\nPlease restore workspace execution and write access, then rerun this revision.","treeHash":null,"usage":{"cachedInputTokens":259200,"inputTokens":47653,"model":"gpt-6-astra","outputTokens":2122,"runtime":"codex","turns":6,"wallClockMs":130968}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d317cdd61f845fb5","findings":[],"hash":"d94e31d0c550081ce5bea85570aed780f090692c17530ab8a9ef1bf928577c62","nodeId":"3303df5c-c99b-42dc-ae65-cebae4457b0f","outcome":"failed","summary":"the task produced no changes; the agent's last message was: The sandbox still fails before commands execute: `bwrap: setting up uid map: Permission denied`. File writes also fail, including `.imd-blocked.json`.\n\nNo files changed. I couldn’t inspect or repair the contracts, write `.imd-responses.json`, or run Foundry.\n\nPlease restore workspace execution and write access, then rerun this revision.","treeHash":null,"usage":{"cachedInputTokens":108416,"inputTokens":56849,"model":"gpt-6-astra","outputTokens":1828,"runtime":"codex","turns":4,"wallClockMs":129490}},{"artifacts":[],"attempt":1,"bundleHash":"6e15bf6a421ea2522f6942efc3032a24a10ac5f19c32b8cf80e22c07f285b203","device":"2a2bbe7421f10a57","findings":[],"hash":"e31708eb7b9a95b528d7b1b1035a867d6ba2c2934ed03e672fd21add87f92f56","nodeId":"f9e2ca39-cc00-4d66-8af7-d2df478f6dac","outcome":"completed","summary":"Updated `launch.json` with notes matching the accepted implementation; verified constructor values and `$owner`.\n\nValidation passed: schema/ABI checks, offline build, formatting, and all 43 tests, including a second seed with 1,024 fuzz runs. Only `launch.json` changed.","treeHash":"4655d5d4903c45bf435fbc1859847565ad60c8cc","usage":{"cachedInputTokens":279296,"inputTokens":47002,"model":"gpt-6-astra","outputTokens":3161,"runtime":"codex","turns":4,"wallClockMs":116398}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa99051b60a858d6","findings":[{"citation":"resolved","description":"_checkSupply() is called at the top of park (L179), redeem (L233) and settle (L273) and reverts on ANY difference between the live PLANT totalSupply and the snapshot taken at bind. Nothing can ever re-sync the snapshot (no admin, bind is once). So a single unit of supply drift — e.g. burn(1) by any holder if the launch-2 PLANT token exposes ERC20Burnable.burn/burnFrom, or any mint by the pool/hook — makes settle revert forever, the plant dies 30 days later (isDead), and redeem — the only exit for the IMD backing — reverts forever too. Only unpark and claim keep working, so all IMD that is backing (and the merged dead pot) is stranded with no code path to release it. The guard is not needed for redeem's safety: redeem divides by the snapshot (plantSupply - burned), so a supply DECREASE can only lower what each PLANT receives (conservative), and the brief's redeem rule ('floor = backing / (PLANT supply − burned)') does not ask for a revert. The README documents 'fixed PLANT supply' as an assumption, but the consequence is an unprivileged, irreversible permanent lock of user funds, which the 'no pause' brief rules out. Minimal fix preserving design: in redeem (and arguably settle) only reject a supply INCREASE (plant.totalSupply() > plantSupply), or drop the check from redeem entirely and keep using the snapshot denominator; a decrease never needs to block exit.","line":387,"path":"src/PlantOrganism.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PlantOrganism} from \"src/PlantOrganism.sol\";\nimport {OracleAttestation} from \"src/OracleAttestation.sol\";\n\n/// @dev Minimal exact-transfer ERC-20 with a public burn, as OpenZeppelin ERC20Burnable gives any holder.\ncontract BurnableToken {\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        totalSupply += amount;\n        balanceOf[to] += amount;\n    }\n\n    function burn(uint256 amount) external {\n        balanceOf[msg.sender] -= 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\ncontract Hook {\n    address public immutable organism;\n    address public immutable plant;\n\n    constructor(address organism_, address plant_) {\n        organism = organism_;\n        plant = plant_;\n    }\n}\n\n/// Finding: a one-wei change in PLANT.totalSupply() by any holder makes settle() AND redeem() revert\n/// SupplyChanged forever. The plant then dies after 30 days, and the IMD backing can never be redeemed.\ncontract SupplyDriftLocksBackingTest is Test {\n    uint256 constant KEY = 0xA11CE;\n    uint32 constant LISBON = 10223579;\n    bytes32 constant QUESTION = keccak256(\"q\");\n    BurnableToken imd;\n    BurnableToken plant;\n    PlantOrganism body;\n    address alice = makeAddr(\"alice\");\n    address mallory = makeAddr(\"mallory\");\n\n    function setUp() public {\n        vm.chainId(4663);\n        vm.warp(20000 days + 12 hours);\n        imd = new BurnableToken();\n        plant = new BurnableToken();\n        plant.mint(alice, 999 ether);\n        plant.mint(mallory, 1 ether);\n        body = new PlantOrganism(address(imd), vm.addr(KEY), LISBON, address(this));\n        body.bind(address(new Hook(address(body), address(plant))), QUESTION);\n        vm.prank(alice);\n        plant.approve(address(body), type(uint256).max);\n        // Give the plant some backing: birth, then one sunny day on 1000 IMD.\n        vm.prank(alice);\n        body.park(LISBON, 100 ether);\n        settleNext(0, true, LISBON);\n        imd.mint(address(body), 1000 ether);\n        settleNext(1, false, 0);\n        assertGt(body.backing(), 0, \"backing exists\");\n    }\n\n    function settleNext(uint24 sun, bool valid, uint32 cell) internal {\n        uint256 day = body.lastSettledDay() + 1;\n        vm.warp(day * 1 days + 12 hours);\n        bytes32[] memory words = new bytes32[](3);\n        words[0] = bytes32(uint256(sun) | (uint256(valid ? 1 : 0) << 48) | (uint256(cell) << 64) | (day << 96));\n        OracleAttestation.Attestation memory a;\n        a.requestId = bytes32(day);\n        a.chainId = 4663;\n        a.questionHash = QUESTION;\n        a.answerType = 5;\n        a.answer = abi.encode(words);\n        a.issuedAt = uint64(block.timestamp);\n        a.expiresAt = uint64(block.timestamp + 1 days);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(KEY, body.attestationDigest(a));\n        body.settle(a, abi.encodePacked(r, s, v));\n    }\n\n    /// Fails on the current code: after an unprivileged holder burns 1 wei of PLANT, alice can never\n    /// redeem her 899 PLANT against the IMD backing. Passes once redeem no longer reverts on a supply\n    /// decrease (the snapshot denominator already makes a decrease safe for backing).\n    function test_oneWeiBurnByAnyHolderLocksAllBackingForever() public {\n        vm.prank(mallory);\n        plant.burn(1);\n\n        // On the current code settle() now reverts SupplyChanged too, so no oracle can keep the plant\n        // alive; it is dead 30 days later. (Not asserted, so a fix that also relaxes settle still passes.)\n        vm.warp(block.timestamp + 31 days);\n        assertTrue(body.isDead());\n        assertGt(body.backing(), 0);\n\n        // Even dead, redemption of the backing must remain possible; today it reverts SupplyChanged\n        // and the IMD is stranded forever (only unpark/claim still work).\n        vm.prank(alice);\n        uint256 paid = body.redeem(100 ether); // Expected: alice's full-floor share. Actual: SupplyChanged.\n        assertGt(paid, 0);\n        assertEq(imd.balanceOf(alice), paid);\n    }\n}","reproduction":"State: bound, alice parked 100 PLANT at Lisbon, birth settled, 1000 IMD deposited, one sunny hour settled so backing > 0. Then mallory (holds 1 PLANT, no role) calls PLANT.burn(1). Next: settle(valid attestation for lastSettledDay+1) -> reverts SupplyChanged (expected: settles). warp +31 days -> isDead()==true, backing()>0. alice.redeem(100e18) -> reverts SupplyChanged (expected per brief: pays 100e18 * backing / remaining in IMD). Same for park. Proof test test/scratch/SupplyDriftLocksBacking.t.sol fails at PlantOrganism.redeem with SupplyChanged(); it passes once redeem no longer reverts on a supply decrease.","severity":"medium","snippet":"    function _checkSupply() private view {\n        if (!bound()) revert Unbound();\n        if (plant.totalSupply() != plantSupply) revert SupplyChanged();\n    }","title":"Any PLANT totalSupply drift permanently disables settle AND redeem: one wei burned by any holder strands the whole IMD backing"},{"citation":"resolved","description":"lastSuccessfulSettle is set to block.timestamp in the constructor (L127) and is only ever rewritten by a successful BOUND settle (L322). The unbound settle branch (L265-271) does not touch it, and bind() does not reset it. The brief defines death as 'no successful settle for 30 days', but no settle can succeed before bind, so the clock measures something the deployer cannot influence: the time it takes the separate second launch (PLANT token + pool + hook) to exist. If bind happens >= 30 days after deployment, isDead() is true the instant bind() returns; the first real settle reverts Dead and nothing can ever revive it (settle is the only writer of lastSuccessfulSettle, and it reverts when dead). Even a bind at day 29 leaves < 1 day for the oracle to issue the first attestation and a relayer to land it, and because the unbound cursor may never have been advanced the first accepted day is deployDay+1 (strict +1 ordering, L286), so the relayer must land one in-order settle before the deadline. The README documents this ('The clock starts at deployment'), but it is a stillborn-plant trap in normal operations, not a security choice. Fix preserving design: start the death clock at bind (set lastSuccessfulSettle = block.timestamp inside bind()), or have the unbound settle branch also refresh it.","line":159,"path":"src/PlantOrganism.sol","reproduction":"Deploy PlantOrganism(imd, signer, 10223579, deployer). vm.warp(+30 days). deployer.bind(hook, Q) succeeds. isDead() == true immediately. settle(valid signed attestation for day lastSettledDay+1) -> reverts Dead(). Expected: a freshly bound organism should be alive and accept its first settle. Reproduced in test/scratch/Leads.t.sol::test_bindThirtyDaysAfterDeployIsPermanentlyDead and test_bindAtDay29LeavesOneDayAndRequiresOrderedCatchup (lastSettledDay still 20000 after 29 days; settle(20029) -> WrongDay).","severity":"medium","snippet":"    function isDead() public view returns (bool) {\n        return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    }","title":"Death clock starts at deployment, not at bind: binding 30+ days after deployment yields a permanently dead organism"},{"citation":"resolved","description":"validCell(0) is true (lat 0, lon 0), so park(0, amount) is accepted, and during birth current == 0 so the move test compares the challenger against parkedTotal[0]. The brief's BIRTH rule is unconditional: 'the first settle with a valid challenger >= 5% moves it'. Here a holder who parks at 0 more than the challenger has parked blocks the move; three such settles send the plant to FALLBACK_CELL instead of the community's chosen cell. The vetoing stake is never at risk (unpark any time, no minimum stay). Tokens parked at 0 can never earn (no hours while location is 0, and location can never return to 0), so there is no legitimate use for them. Fix: treat parkedTotal[current] as 0 when current == 0 in the comparison (or reject park at cell 0).","line":314,"path":"src/PlantOrganism.sol","reproduction":"Bound, supply 1000e18. alice.park(PARIS, 90e18) (9% >= 5% threshold of 50e18). mallory.park(0, 100e18). settle(day, valid=1, challenger=PARIS). Expected per BIRTH rule: location == PARIS. Actual: location stays 0, emptySettles == 1 (reproduced in test/scratch/Leads.t.sol::test_parkingAtNowhereVetoesBirth). Repeat twice more -> location == 10223579 (Lisbon).","severity":"low","snippet":"            valid && challenger != 0 && validCell(challenger) && challenger != current\n                && parkedTotal[challenger] > parkedTotal[current] && parkedTotal[challenger] >= threshold","title":"PLANT parked at cell 0 ('nowhere') counts as incumbent support during birth, letting a holder veto a valid >=5% challenger and force the fallback cell"},{"citation":"resolved","description":"The protocol's own OracleAttestationConsumer (oracle-consumer REFERENCE.md L139-142, L172) allows issuedAt up to 5 minutes ahead of block.timestamp because 'the attester stamps with its own wall clock; a block's timestamp is a validator's'. The oracle-consumer adapter says that where the brief and the protocol reference disagree on the protocol the reference wins. Here any attestation whose issuedAt is even 1 second ahead of the L2 block timestamp reverts InvalidWindow; L2 block timestamps commonly lag wall clock by seconds. Impact is liveness only (the relayer must wait and resubmit), but combined with the 30-day death clock and strict day ordering it adds avoidable failure to the catch-up path. Fix: accept issuedAt <= block.timestamp + 5 minutes, matching the reference consumer.","line":353,"path":"src/PlantOrganism.sol","reproduction":"Bound organism, block.timestamp = T. Attestation for lastSettledDay+1 with issuedAt = T+1, expiresAt = T+1 day, correctly signed by the signer. settle(a, sig) -> reverts InvalidWindow (reproduced in test/scratch/Leads.t.sol::test_issuedAtOneSecondAheadRejected). Expected under the protocol consumer semantics: accepted.","severity":"low","snippet":"        if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();","title":"issuedAt has no clock-drift tolerance: an attestation stamped one second ahead of the block timestamp is rejected"},{"citation":"resolved","description":"settle pins chain, questionHash, answerType, shape and day, and verifyAttestation checks window and signature, but panelSize/quorum/agreed are never read. The oracle reference instructs consumers to 'Require agreed >= quorum and that the panel and quorum are at least what the contract asked for'; the struct docs say agreed may be below quorum when members split and a rerun decided. The oracle signer is trusted, so this is defence in depth rather than a bypass, and docs/ORACLE.md consciously declines to invent a threshold — recorded so the author can make an explicit decision. A one-line check (if (a.agreed < a.quorum) revert BadAttestation()) costs nothing and preserves the design.","line":281,"path":"src/PlantOrganism.sol","reproduction":"Attestation for day lastSettledDay+1 with panelSize=20, quorum=15, agreed=0, otherwise valid and signed by the signer. settle(a, sig) succeeds and advances lastSettledDay (test/scratch/Leads.t.sol::test_agreedBelowQuorumAccepted). Expected under the reference's consumer guidance: rejected.","severity":"low","snippet":"        verifyAttestation(a, sig);","title":"Signed panel metadata is ignored: an attestation with agreed < quorum (even agreed == 0) is accepted"},{"citation":"resolved","description":"The contract pins one questionHash forever at bind, as the brief asks. The oracle-consumer reference states that questionHash 'covers the question, the resolved block window and the definitions, so a contract cannot compute it in advance when the window is relative'. A daily weather request made with a relative window yields a different canonical document (and hash) every day, so every live attestation would fail this check, settle could never succeed, and the plant would die 30 days after deployment. docs/ORACLE.md flags this as a precondition the deployer must verify with the oracle operator before binding. This is a brief/integration gap rather than a coding error, and I cannot verify offline whether the live service supports a frozen recurring question with a constant hash; the evidence needed before bind is one live attestation for this contract whose questionHash equals the hash the deployer intends to bind. Reported so the gap is on record and not silently assumed closed.","line":274,"path":"src/PlantOrganism.sol","reproduction":"Bound with QUESTION_HASH = H. Oracle issues day-N attestation with questionHash = keccak(canonical document for day N) = H_N != H, signed for this contract. settle(a, sig) -> BadAttestation (this is the existing test_wrongChainQuestionAnswerTypeAndLengthRejected path). If H_N differs each day, no settle ever succeeds; isDead() becomes true 30 days after deployment.","severity":"low","snippet":"        if (block.chainid != CHAIN_ID || a.chainId != CHAIN_ID || a.questionHash != QUESTION_HASH) {","title":"Frozen QUESTION_HASH may be unsatisfiable by the live oracle: protocol questionHash covers the resolved window per request"}],"hash":"f16e3853940c6fc6041820d700bca6e85eb8f578e8dc5fb88a2dbe2133d93e4e","nodeId":"20f8c71b-bc97-45cd-b54f-858b50b6824e","outcome":"completed","summary":"The review is complete. `.imd-findings.json` is written at the repository root (excluded from git by the harness's own `.git/info/exclude`, as intended), no tracked file was changed, and the only other writes are under `test/scratch/`.\n\n## Summary\n\n**Area covered:** Execution Trace, Periphery and First Principles over `PlantOrganism.sol` and `OracleAttestation.sol`. I Feynman'd each entry point, traced every writer of `_backing`/`owed`/`parked*`, every IMD/PLANT outflow, and the pending→active cohort machinery (`pendingAt`/`activationAcc` keying, roll ordering in `park`/`unpark`/`settle`), and checked the periphery (strict `bytes32[3]` decode, EIP‑712 hashing against the protocol vector, `ECDSA.tryRecover` zero/high‑s handling, `_tokenCall`/`_pullExact`/`_sendExact` for false/no‑return/fee tokens). The reward accounting is exact and the existing eager‑model invariant suite is genuinely strong; I found no fund‑moving arithmetic or reentrancy defect. The slither/aderyn reentrancy lines are false positives (guards + exact balance checks, state settled before transfers).\n\n**Findings (6), all reproduced in `test/scratch/`:**\n\n| # | Sev | Finding |\n|---|---|---|\n| 1 | medium | `_checkSupply()` (L387) reverts `redeem`/`settle`/`park` on *any* PLANT supply drift, forever. One wei `burn()` by any holder (if PLANT is burnable) → settle bricked → dead in 30 days → `redeem` still bricked → all IMD backing stranded. **Proof attached**; fails on current code at `redeem` with `SupplyChanged`. Fix: only reject supply *increases*, never block exit on a decrease. |\n| 2 | medium | Death clock starts at the constructor (L127/L159), not at `bind`. Binding ≥30 days after deployment produces a permanently dead organism; nothing can revive it. |\n| 3 | low | `parkedTotal[0]` counts as incumbent support during birth (L314), letting a holder veto a valid ≥5 % challenger and force Lisbon, contrary to the BIRTH rule. |\n| 4 | low | No `issuedAt` drift tolerance (L353); protocol consumer allows 5 min. Liveness friction on the catch‑up path. |\n| 5 | low | `agreed`/`quorum`/`panelSize` never read (L281); reference recommends `agreed >= quorum`. |\n| 6 | low | Frozen `QUESTION_HASH` (L274) may be unsatisfiable if the live oracle's hash covers a per‑request window — integration precondition that needs live evidence before `bind`. |\n\n**Coverage:** all 9 ABI entry points have rows (6 `holds`, 3 `finding`), plus rows for the three invariants and periphery I traced, and one honest `unreached` for the launch‑2 PLANT token and live oracle question, which are not in this tree.","treeHash":null,"usage":{"cachedInputTokens":2604836,"inputTokens":47,"model":"claude-fable-5-1","outputTokens":55524,"runtime":"claude","turns":25,"wallClockMs":924241}}],"verification":[{"checks":[{"durationMs":3874,"exitCode":0,"name":"build","output":"Compiling 32 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.73s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/PlantOrganism.sol:135:19\n    │\n135 │     function bind(address hook_, bytes32 questionHash) external nonReentrant {\n    │                   ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:155:21\n    │\n155 │         int16 lat = int16(uint16(cell >> 16));\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:155:27\n    │\n155 │         int16 lat = int16(uint16(cell >> 16));\n    │                           ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:156:21\n    │\n156 │         int16 lon = int16(uint16(cell));\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:156:27\n    │\n156 │         int16 lon = int16(uint16(cell));\n    │                           ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:161:27\n    │\n161 │         return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_guard` is updated\n    ╭▸ src/PlantOrganism.sol:488:42\n    │\n488 │         (bool ok, bytes memory result) = token.call(data);\n    │                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:195:9\n    │\n195 │         emit Parked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:211:9\n    │\n211 │         emit Unparked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:231:9\n    │\n231 │         emit Claimed(holder, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:248:9\n    │\n248 │         emit Redeemed(msg.sender, amount, payout, dead);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:306:23\n    │\n306 │                     ++sips;\n    │                       ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:304:21\n    │\n304 │                     gardeners += sip / 3;\n    │                     ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:269:17\n    │\n269 │             if (today <= lastSettledDay) revert WrongDay();\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:289:42\n    │\n289 │         if (day != lastSettledDay + 1 || day > block.timestamp / DAY) revert WrongDay();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:331:13\n    │\n331 │             emit Moved(current, location, day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:335:9\n    │\n335 │         emit Bounty(msg.sender, bounty);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:336:9\n    │\n336 │         emit Settled(day, words, sips, _backing, water, location);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:288:23\n    │\n288 │         uint256 day = uint32(packed >> 96);\n    │                       ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:312:29\n    │\n312 │         uint32 challenger = uint32(packed >> 64);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:357:13\n    │\n357 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:357:45\n    │\n357 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:359:73\n    │\n359 │         if (recovered != signer && (signerValidUntil[recovered] == 0 || block.timestamp > signerValidUntil[recovered]))\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:376:9\n    │\n376 │         emit SignerRotated(previous, newSigner, rotationNonce++, until);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:452:9\n    │\n452 │         emit Checkpointed(holder, cell, whole);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:474:13\n    │\n474 │         if (token.balanceOf(address(this)) != beforeBalance + amount) revert BadTokenTransfer();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:481:13\n    │\n481 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:481:73\n    │\n481 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━                 ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:397:17\n    │\n397 │         vm.warp(block.timestamp + 4 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:428:17\n    │\n428 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:435:17\n    │\n435 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:449:17\n    │\n449 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:460:17\n    │\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:472:17\n    │\n472 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:499:17\n    │\n499 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:528:17\n    │\n528 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n566 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n566 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n569 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n569 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:581:17\n    │\n581 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:650:17\n    │\n650 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:653:17\n    │\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:722:17\n    │\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantAdversarial.t.sol:218:9\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantAdversarial.t.sol:218:9\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantAdversarial.t.sol:218:9\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantAdversarial.t.sol:218:17\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantAdversarial.t.sol:280:17\n    │\n280 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantLifecycleInvariant.t.sol:127:17\n    │\n127 │         vm.warp(block.timestamp + seconds_);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantLifecycleInvariant.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 17);\n    │             ━━━━━━━━━━━━━━━                 ────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:70:9\n    │\n 70 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:70:9\n    │\n 70 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:70:9\n    │\n 70 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/PlantRevision.t.sol:70:17\n   │\n70 │         vm.warp(block.timestamp + 1 days);\n   │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/PlantRevision.t.sol:84:17\n   │\n84 │         vm.warp(block.timestamp + delay);\n   │         ────────━━━━━━━━━━━━━━━───────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:91:47\n    │\n 91 │         assertEq(body.lastSuccessfulSettle(), block.timestamp);\n    │                                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantOrganism.t.sol:148:45\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:118:27\n    │\n118 │         uint256 boundAt = block.timestamp;\n    │                           ━━━━━━━━━━━━━━━\n119 │         vm.warp(boundAt + 30 days - 1);\n    │         ────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:152:9\n    │\n152 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:152:9\n    │\n152 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:152:9\n    │\n152 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:152:17\n    │\n152 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:182:17\n    │\n182 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":21338,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] test_acceptsFreshSignatureFromProtocolVectorKey() (gas: 30312)\n[PASS] test_acceptsUnchangedProtocolSignature() (gas: 17823)\n[PASS] test_digestMatchesUnchangedProtocolVector() (gas: 13738)\n[PASS] test_settleSelectorUsesCanonicalAttestationTuple() (gas: 3143)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 995.34µs (917.40µs CPU time)\n\nRan 1 test for test/PlantOrganism.t.sol:PlantGasTest\n[PASS] test_settleGasBoundIncludingFreshRewardsPendingActivationAndMove() (gas: 608326)\nLogs:\n  settle execution gas (cold, 24 sips, pending activation, move): 327866\n  settle gas including intrinsic: 354018\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.30ms (1.04ms CPU time)\n\nRan 1 test for test/PlantSettlementGas.t.sol:PlantFractionalSettlementGasTest\n[PASS] test_coldSettlementBudgetWithFractionalDebtActivationAndMove() (gas: 681695)\nLogs:\n  cold settlement gas including calldata and transaction base: 375394\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.78ms (1.48ms CPU time)\n\nRan 1 test for test/PlantSettlementGas.t.sol:PlantManyHoldersSettlementGasTest\n[PASS] test_coldSettlementBudgetWithFractionalDebtActivationAndMove() (gas: 681695)\nLogs:\n  cold settlement gas including calldata and transaction base: 375394\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 39.22ms (1.07ms CPU time)\n\nRan 11 tests for test/PlantRevision.t.sol:PlantRevisionTest\n[PASS] testFuzz_externalBurnKeepsDeadRedemptionAndGardenerClaimsAvailable(uint96) (runs: 1000, μ: 1408935, ~: 1408770)\nLogs:\n  Bound result 511876\n\n[PASS] test_futureIssuedAtRejectsUntilExactTimestamp() (gas: 192304)\n[PASS] test_lateBindHasFullThirtyDaysAndCannotResetByRebinding() (gas: 590261)\n[PASS] test_lateBindWithAdvancedCursorAcceptsNextDay() (gas: 1065181)\n[PASS] test_lateBindWithoutCursorAdvancePreservesInOrderBacklog() (gas: 1022700)\n[PASS] test_nowhereSupportCannotVetoBirthAtFivePercent() (gas: 1306753)\n[PASS] test_oneWeiExternalBurnKeepsParkingSettlementAndLiveRedemptionAvailable() (gas: 1721230)\n[PASS] test_rotationNeedsOnlyCurrentSignerButCannotBeUndoneByRetiredKey() (gas: 239604)\n[PASS] test_subQuorumAttestationCannotSpendMoveOrAdvanceDay() (gas: 1340081)\n[PASS] test_supplyAboveBindingSnapshotStillBlocksParkSettleAndRedeem() (gas: 716279)\n[PASS] test_unusedRotationAuthorizationRemainsValidUntilNonceChanges() (gas: 134693)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 220.95ms (230.04ms CPU time)\n\nRan 37 tests for test/PlantOrganism.t.sol:PlantOrganismTest\n[PASS] testFuzz_maskSimulationAndConservation(uint24,uint24,uint96) (runs: 1000, μ: 987958, ~: 1048196)\n[PASS] testFuzz_redemptionFloorNeverDecreases(uint96,uint96) (runs: 1000, μ: 1006056, ~: 998855)\nLogs:\n  Bound result 262385783611315994237\n\n[PASS] test_bindOnlyExplicitDeployerAndOnlyOnce() (gas: 343272)\n[PASS] test_bindRejectsBadHookQuestionAndToken() (gas: 1106390)\n[PASS] test_birthHasNoHoursAndPaysBounty() (gas: 680536)\n[PASS] test_deadClockTracksSuccessTimestampNotBacklogDay() (gas: 293997)\n[PASS] test_deadPlantMergesPotPaysFullFloorKeepsRewardsAndCannotRevive() (gas: 1376693)\n[PASS] test_expiredAndNotYetValidRejectIncludingOneSecond() (gas: 135469)\n[PASS] test_failedBountyRollsBackEntireSettlement() (gas: 888212)\n[PASS] test_failedClaimKeepsDebtAndFalseTransferCannotLoseTokens() (gas: 1233910)\n[PASS] test_fallbackAfterExactlyThreeEmptySettles() (gas: 834186)\n[PASS] test_falseReturningAndFeeTokensRevertAtomicallyNoReturnWorks() (gas: 1031685)\n[PASS] test_finalRedemptionHasDefinedNondecreasingFloor() (gas: 569308)\n[PASS] test_initialStateAndFeeInflows() (gas: 306026)\n[PASS] test_missedDaysMustBeInOrderAndCannotReplayOrUseFuture() (gas: 718375)\n[PASS] test_moveStrictMajorityRewardsOldCellAndNoMinimumStay() (gas: 2043399)\n[PASS] test_mustUnparkBeforeRedeemingCustodiedTokens() (gas: 617308)\n[PASS] test_noncurrentCellLazyActivationAndRepeatedTopups() (gas: 1879078)\n[PASS] test_pendingCannotEarnInNextSettleAndTopUpDoesNotDilute() (gas: 1509649)\n[PASS] test_pendingOnlyGardenersGoToBacking() (gas: 1220353)\n[PASS] test_rainPrecedenceCapWaterAndDrought() (gas: 1822909)\n[PASS] test_redemptionRetainsTenPercentAndBurnedNeverLeaves() (gas: 1213549)\n[PASS] test_reentrancyBlockedOnPlantPullAndImdPayout() (gas: 1217732)\n[PASS] test_rejectsChangedSupplyInvalidCellsAndZeroAmounts() (gas: 190843)\n[PASS] test_rejectsMalleableHighSSignature() (gas: 77774)\n[PASS] test_rejectsWrongSignerTamperingAndMalformedSignature() (gas: 215314)\n[PASS] test_rotatedSignerSettlesAndOldKeyCannotRotateEvenDuringGrace() (gas: 411048)\n[PASS] test_runtimeBoundedAndHasNoEscapeOpcodes() (gas: 7937351)\n[PASS] test_signerRotationOnlyCurrentWithThirtyDayGraceAndCycleReplayProtection() (gas: 418079)\n[PASS] test_successiveRotationsKeepEachOldKeysGrace() (gas: 240130)\n[PASS] test_syntheticDayAndProRataClaims() (gas: 1563561)\n[PASS] test_temperatureAndWindAreEmittedUnchanged() (gas: 558566)\n[PASS] test_unboundOnlyAdvancesDay() (gas: 259206)\n[PASS] test_unparkDropsActiveImmediatelyAndPendingFirst() (gas: 1478577)\n[PASS] test_windowEndpointsAreInclusive() (gas: 132841)\n[PASS] test_wrongChainQuestionAnswerTypeAndLengthRejected() (gas: 452368)\n[PASS] test_wrongDomainContractChainAndV1Rejected() (gas: 242025)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 226.82ms (482.07ms CPU time)\n\nRan 20 tests for test/PlantAdversarial.t.sol:PlantAdversarialTest\n[PASS] testFuzz_callbacksHitGuardForEveryMutator(uint8,bool) (runs: 1000, μ: 1596393, ~: 1579655)\nLogs:\n  Bound result 2\n\n[PASS] testFuzz_everySignedFieldIsAuthenticated(uint8) (runs: 1000, μ: 40495, ~: 40614)\nLogs:\n  Bound result 0\n\n[PASS] testFuzz_pendingRoundTripsCannotExtractRewards(uint96,uint8) (runs: 1000, μ: 2325394, ~: 2101359)\nLogs:\n  Bound result 3798046\n  Bound result 10\n\n[PASS] test_cellSignedBoundariesAndLisbonPacking() (gas: 46415)\n[PASS] test_claimCannotTakeAnotherHoldersRewardsAndRepeatPaysZero() (gas: 1121757)\n[PASS] test_constructorRejectsMissingAuthorityAndInvalidFallback() (gas: 6701)\n[PASS] test_exactLengthPayloadStillRequiresCanonicalOffsetAndThreeWords() (gas: 142818)\n[PASS] test_failedRedemptionPayoutRollsBackBurnAndPlantTransfer() (gas: 1584888)\n[PASS] test_failedUnparkKeepsPositionAndEarnedRewards() (gas: 1521695)\n[PASS] test_invalidBindAttemptsLeaveAllBindingSlotsUnset() (gas: 663100)\n[PASS] test_noAllowanceParkAndRedeemRevertAtomically() (gas: 1396699)\n[PASS] test_nowhereCanBeParkedAndWithdrawnWithoutRunningWeather() (gas: 749844)\n[PASS] test_oneWeiSipAndZeroSipRoundTowardBacking() (gas: 950637)\n[PASS] test_parkedSupportNeedsValidFlagAndThresholdEvenAfterBurns() (gas: 939450)\n[PASS] test_partialPendingWithdrawalCannotEarnBeforeActivation() (gas: 1687257)\n[PASS] test_rotationIndependentTypedDataAndDomainReplayRejection() (gas: 233091)\n[PASS] test_rotationRejectsZeroSameAndUnsignedDeployerOrHolder() (gas: 160776)\n[PASS] test_supplyMutationAlsoStopsSettleAndRedeemButAllowsExit() (gas: 686674)\n[PASS] test_syncDeathIsPermissionlessIdempotentAndKeepsDebts() (gas: 1286041)\n[PASS] test_zeroAndOversizedWithdrawalsCannotTouchCustody() (gas: 972755)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 234.20ms (702.26ms CPU time)\n\nRan 1 test for test/PlantInvariant.t.sol:PlantInvariantTest\n[PASS] invariant_accountingAndEagerRewardModelAgree() (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------+----------+-------+---------+----------╮\n| Contract     | Selector | Calls | Reverts | Discards |\n+======================================================+\n| PlantHandler | claim    | 3511  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | elapse   | 3441  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | fund     | 3507  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | park     | 3511  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | redeem   | 3520  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | settle   | 3476  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | unpark   | 3610  | 0       | 0        |\n╰--------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.16s (12.16s CPU time)\n\nRan 5 tests for test/PlantLifecycleInvariant.t.sol:PlantLifecycleInvariantTest\n[PASS] invariant_custodyCashFlowFloorAndLifecycle() (runs: 256, calls: 24576, reverts: 0)\n\n╭-----------------------+---------------------+-------+---------+----------╮\n| Contract              | Selector            | Calls | Reverts | Discards |\n+==========================================================================+\n| PlantLifecycleHandler | burnOutsideOrganism | 2258  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | claim               | 2230  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | donatePlant         | 2213  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | elapse              | 2264  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | fund                | 2235  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | park                | 2232  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | redeem              | 2216  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | rotate              | 2217  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | settle              | 2256  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | syncDeath           | 2215  | 0       | 0        |\n|-----------------------+---------------------+-------+---------+----------|\n| PlantLifecycleHandler | unpark              | 2240  | 0       | 0        |\n╰-----------------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 229\n  Bound result 6\n  Bound result 79\n  Bound result 4326646455454963\n  Bound result 11\n  Bound result 141232\n  Bound result 220\n  Bound result 17783\n  Bound result 21245\n  Bound result 10\n  Bound result 28320\n  Bound result 47\n  Bound result 89\n  Bound result 14735\n  Bound result 3\n  Bound result 18\n  Bound result 10\n  Bound result 28\n  Bound result 12\n  Bound result 12\n  Bound result 162514264337593543950313\n  Bound result 60\n  Bound result 24\n  Bound result 1\n  Bound result 105938756\n  Bound result 1\n  Bound result 5\n  Bound result 5\n  Bound result 11\n  Bound result 84\n  Bound result 33\n  Bound result 1\n  Bound result 12402\n  Bound result 37\n  Bound result 140\n  Bound result 1\n  Bound result 44237\n\n[PASS] test_handlerBirthIgnoresNowhereSupportButLaterMovesRequireMoreSupport() (gas: 6474022)\nLogs:\n  Bound result 401\n  Bound result 50\n  Bound result 1\n  Bound result 51\n  Bound result 1\n\n[PASS] test_handlerExercisesBirthRewardsMoveRotationDeathAndFullExit() (gas: 9886158)\nLogs:\n  Bound result 51\n  Bound result 9000\n  Bound result 100\n  Bound result 1\n  Bound result 1\n\n[PASS] test_handlerExternalBurnDoesNotLowerFivePercentBirthThreshold() (gas: 4702498)\nLogs:\n  Bound result 401\n  Bound result 200\n  Bound result 100\n  Bound result 50\n  Bound result 1\n\n[PASS] test_handlerExternalBurnsKeepClaimsSettlementAndBothRedemptionModesAvailable() (gas: 8768808)\nLogs:\n  Bound result 51\n  Bound result 9000\n  Bound result 1\n  Bound result 60\n  Bound result 10\n  Bound result 100\n  Bound result 10\n\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 21.24s (21.29s CPU time)\n\nRan 9 test suites in 21.25s (34.14s CPU time): 81 tests passed, 0 failed, 0 skipped (81 total tests)\n","passed":true},{"durationMs":37,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PlantOrganism.bind(address,bytes32)\",\"PlantOrganism.claim()\",\"PlantOrganism.claim(uint32)\",\"PlantOrganism.park(uint32,uint256)\",\"PlantOrganism.redeem(uint256)\",\"PlantOrganism.rotateSigner(address,bytes)\",\"PlantOrganism.settle((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PlantOrganism.syncDeath()\",\"PlantOrganism.unpark(uint32,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":24,\"docs/ACCOUNTING.md\":15,\"docs/ORACLE.md\":40,\"foundry.toml\":18,\"launch.json\":15,\"src/OracleAttestation.sol\":97,\"src/PlantOrganism.sol\":493,\"test/OracleConformance.t.sol\":85,\"test/PlantAdversarial.t.sol\":434,\"test/PlantInvariant.t.sol\":245,\"test/PlantLifecycleInvariant.t.sol\":464,\"test/PlantOrganism.t.sol\":809,\"test/PlantRevision.t.sol\":213,\"test/PlantSettlementGas.t.sol\":67,\"test/README.md\":31},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"07fb5b7f18cc4b330db1fd859b91c96e821af993aad68efc02dde2a3c6b78440","verifiedTreeHash":"d95d9d23fb34a71b31491e3416f96f12564f9733","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":3335,"exitCode":0,"name":"build","output":"Compiling 31 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.19s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/PlantOrganism.sol:135:19\n    │\n135 │     function bind(address hook_, bytes32 questionHash) external nonReentrant {\n    │                   ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:153:21\n    │\n153 │         int16 lat = int16(uint16(cell >> 16));\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:153:27\n    │\n153 │         int16 lat = int16(uint16(cell >> 16));\n    │                           ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:154:21\n    │\n154 │         int16 lon = int16(uint16(cell));\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:154:27\n    │\n154 │         int16 lon = int16(uint16(cell));\n    │                           ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:159:27\n    │\n159 │         return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_guard` is updated\n    ╭▸ src/PlantOrganism.sol:483:42\n    │\n483 │         (bool ok, bytes memory result) = token.call(data);\n    │                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:193:9\n    │\n193 │         emit Parked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:209:9\n    │\n209 │         emit Unparked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:229:9\n    │\n229 │         emit Claimed(holder, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:246:9\n    │\n246 │         emit Redeemed(msg.sender, amount, payout, dead);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:301:21\n    │\n301 │                     gardeners += sip / 3;\n    │                     ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:303:23\n    │\n303 │                     ++sips;\n    │                       ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:267:17\n    │\n267 │             if (today <= lastSettledDay) revert WrongDay();\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:286:42\n    │\n286 │         if (day != lastSettledDay + 1 || day > block.timestamp / DAY) revert WrongDay();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:327:13\n    │\n327 │             emit Moved(current, location, day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:331:9\n    │\n331 │         emit Bounty(msg.sender, bounty);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:332:9\n    │\n332 │         emit Settled(day, words, sips, _backing, water, location);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:285:23\n    │\n285 │         uint256 day = uint32(packed >> 96);\n    │                       ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:309:29\n    │\n309 │         uint32 challenger = uint32(packed >> 64);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:353:13\n    │\n353 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:353:45\n    │\n353 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:355:73\n    │\n355 │         if (recovered != signer && (signerValidUntil[recovered] == 0 || block.timestamp > signerValidUntil[recovered]))\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:372:9\n    │\n372 │         emit SignerRotated(previous, newSigner, rotationNonce++, until);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:447:9\n    │\n447 │         emit Checkpointed(holder, cell, whole);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:469:13\n    │\n469 │         if (token.balanceOf(address(this)) != beforeBalance + amount) revert BadTokenTransfer();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:476:13\n    │\n476 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:476:73\n    │\n476 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━                 ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:392:17\n    │\n392 │         vm.warp(block.timestamp + 4 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:423:17\n    │\n423 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:430:17\n    │\n430 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:444:17\n    │\n444 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:455:17\n    │\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:467:17\n    │\n467 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:494:17\n    │\n494 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:523:17\n    │\n523 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n561 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n561 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n564 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n564 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:576:17\n    │\n576 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:645:17\n    │\n645 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:648:17\n    │\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:717:17\n    │\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantAdversarial.t.sol:218:9\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantAdversarial.t.sol:218:9\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantAdversarial.t.sol:218:9\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantAdversarial.t.sol:218:17\n    │\n218 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantAdversarial.t.sol:280:17\n    │\n280 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantLifecycleInvariant.t.sol:111:17\n    │\n111 │         vm.warp(block.timestamp + seconds_);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantLifecycleInvariant.t.sol:132:13\n    │\n132 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 17);\n    │             ━━━━━━━━━━━━━━━                 ────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":20836,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] test_acceptsFreshSignatureFromProtocolVectorKey() (gas: 30312)\n[PASS] test_acceptsUnchangedProtocolSignature() (gas: 17823)\n[PASS] test_digestMatchesUnchangedProtocolVector() (gas: 13738)\n[PASS] test_settleSelectorUsesCanonicalAttestationTuple() (gas: 3143)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 838.12µs (828.87µs CPU time)\n\nRan 1 test for test/PlantOrganism.t.sol:PlantGasTest\n[PASS] test_settleGasBoundIncludingFreshRewardsPendingActivationAndMove() (gas: 608213)\nLogs:\n  settle execution gas (cold, 24 sips, pending activation, move): 327687\n  settle gas including intrinsic: 353839\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.72ms (579.28µs CPU time)\n\nRan 1 test for test/PlantSettlementGas.t.sol:PlantFractionalSettlementGasTest\n[PASS] test_coldSettlementBudgetWithFractionalDebtActivationAndMove() (gas: 681582)\nLogs:\n  cold settlement gas including calldata and transaction base: 375215\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.39ms (1.12ms CPU time)\n\nRan 1 test for test/PlantSettlementGas.t.sol:PlantManyHoldersSettlementGasTest\n[PASS] test_coldSettlementBudgetWithFractionalDebtActivationAndMove() (gas: 681582)\nLogs:\n  cold settlement gas including calldata and transaction base: 375215\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 22.21ms (609.87µs CPU time)\n\nRan 37 tests for test/PlantOrganism.t.sol:PlantOrganismTest\n[PASS] testFuzz_maskSimulationAndConservation(uint24,uint24,uint96) (runs: 1000, μ: 989096, ~: 1050207)\n[PASS] testFuzz_redemptionFloorNeverDecreases(uint96,uint96) (runs: 1000, μ: 1006314, ~: 1000878)\nLogs:\n  Bound result 8182865162469791474\n\n[PASS] test_bindOnlyExplicitDeployerAndOnlyOnce() (gas: 341067)\n[PASS] test_bindRejectsBadHookQuestionAndToken() (gas: 1078393)\n[PASS] test_birthHasNoHoursAndPaysBounty() (gas: 682833)\n[PASS] test_deadClockTracksSuccessTimestampNotBacklogDay() (gas: 293479)\n[PASS] test_deadPlantMergesPotPaysFullFloorKeepsRewardsAndCannotRevive() (gas: 1379013)\n[PASS] test_expiredAndNotYetValidRejectIncludingOneSecond() (gas: 134907)\n[PASS] test_failedBountyRollsBackEntireSettlement() (gas: 890162)\n[PASS] test_failedClaimKeepsDebtAndFalseTransferCannotLoseTokens() (gas: 1236080)\n[PASS] test_fallbackAfterExactlyThreeEmptySettles() (gas: 835509)\n[PASS] test_falseReturningAndFeeTokensRevertAtomicallyNoReturnWorks() (gas: 1031940)\n[PASS] test_finalRedemptionHasDefinedNondecreasingFloor() (gas: 569785)\n[PASS] test_initialStateAndFeeInflows() (gas: 306090)\n[PASS] test_missedDaysMustBeInOrderAndCannotReplayOrUseFuture() (gas: 716496)\n[PASS] test_moveStrictMajorityRewardsOldCellAndNoMinimumStay() (gas: 2045106)\n[PASS] test_mustUnparkBeforeRedeemingCustodiedTokens() (gas: 617563)\n[PASS] test_noncurrentCellLazyActivationAndRepeatedTopups() (gas: 1880498)\n[PASS] test_pendingCannotEarnInNextSettleAndTopUpDoesNotDilute() (gas: 1511576)\n[PASS] test_pendingOnlyGardenersGoToBacking() (gas: 1219296)\n[PASS] test_rainPrecedenceCapWaterAndDrought() (gas: 1823134)\n[PASS] test_redemptionRetainsTenPercentAndBurnedNeverLeaves() (gas: 1215804)\n[PASS] test_reentrancyBlockedOnPlantPullAndImdPayout() (gas: 1219745)\n[PASS] test_rejectsChangedSupplyInvalidCellsAndZeroAmounts() (gas: 190811)\n[PASS] test_rejectsMalleableHighSSignature() (gas: 77493)\n[PASS] test_rejectsWrongSignerTamperingAndMalformedSignature() (gas: 214471)\n[PASS] test_rotatedSignerSettlesAndOldKeyCannotRotateEvenDuringGrace() (gas: 410530)\n[PASS] test_runtimeBoundedAndHasNoEscapeOpcodes() (gas: 7887200)\n[PASS] test_signerRotationOnlyCurrentWithThirtyDayGraceAndCycleReplayProtection() (gas: 418079)\n[PASS] test_successiveRotationsKeepEachOldKeysGrace() (gas: 240130)\n[PASS] test_syntheticDayAndProRataClaims() (gas: 1565601)\n[PASS] test_temperatureAndWindAreEmittedUnchanged() (gas: 560407)\n[PASS] test_unboundOnlyAdvancesDay() (gas: 259205)\n[PASS] test_unparkDropsActiveImmediatelyAndPendingFirst() (gas: 1480793)\n[PASS] test_windowEndpointsAreInclusive() (gas: 132582)\n[PASS] test_wrongChainQuestionAnswerTypeAndLengthRejected() (gas: 451235)\n[PASS] test_wrongDomainContractChainAndV1Rejected() (gas: 241182)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 188.54ms (428.74ms CPU time)\n\nRan 20 tests for test/PlantAdversarial.t.sol:PlantAdversarialTest\n[PASS] testFuzz_callbacksHitGuardForEveryMutator(uint8,bool) (runs: 1000, μ: 1598951, ~: 1581759)\nLogs:\n  Bound result 5\n\n[PASS] testFuzz_everySignedFieldIsAuthenticated(uint8) (runs: 1000, μ: 40490, ~: 40614)\nLogs:\n  Bound result 5\n\n[PASS] testFuzz_pendingRoundTripsCannotExtractRewards(uint96,uint8) (runs: 1000, μ: 2342672, ~: 2103199)\nLogs:\n  Bound result 213640432967678199395\n  Bound result 1\n\n[PASS] test_cellSignedBoundariesAndLisbonPacking() (gas: 46415)\n[PASS] test_claimCannotTakeAnotherHoldersRewardsAndRepeatPaysZero() (gas: 1123839)\n[PASS] test_constructorRejectsMissingAuthorityAndInvalidFallback() (gas: 6701)\n[PASS] test_exactLengthPayloadStillRequiresCanonicalOffsetAndThreeWords() (gas: 142256)\n[PASS] test_failedRedemptionPayoutRollsBackBurnAndPlantTransfer() (gas: 1587404)\n[PASS] test_failedUnparkKeepsPositionAndEarnedRewards() (gas: 1524041)\n[PASS] test_invalidBindAttemptsLeaveAllBindingSlotsUnset() (gas: 660895)\n[PASS] test_noAllowanceParkAndRedeemRevertAtomically() (gas: 1398885)\n[PASS] test_nowhereCanBeParkedAndWithdrawnWithoutRunningWeather() (gas: 749889)\n[PASS] test_oneWeiSipAndZeroSipRoundTowardBacking() (gas: 952306)\n[PASS] test_parkedSupportNeedsValidFlagAndThresholdEvenAfterBurns() (gas: 943432)\n[PASS] test_partialPendingWithdrawalCannotEarnBeforeActivation() (gas: 1689451)\n[PASS] test_rotationIndependentTypedDataAndDomainReplayRejection() (gas: 233091)\n[PASS] test_rotationRejectsZeroSameAndUnsignedDeployerOrHolder() (gas: 160776)\n[PASS] test_supplyMutationAlsoStopsSettleAndRedeemButAllowsExit() (gas: 688855)\n[PASS] test_syncDeathIsPermissionlessIdempotentAndKeepsDebts() (gas: 1288232)\n[PASS] test_zeroAndOversizedWithdrawalsCannotTouchCustody() (gas: 975069)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 203.07ms (591.67ms CPU time)\n\nRan 1 test for test/PlantInvariant.t.sol:PlantInvariantTest\n[PASS] invariant_accountingAndEagerRewardModelAgree() (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------+----------+-------+---------+----------╮\n| Contract     | Selector | Calls | Reverts | Discards |\n+======================================================+\n| PlantHandler | claim    | 3483  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | elapse   | 3540  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | fund     | 3471  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | park     | 3586  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | redeem   | 3537  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | settle   | 3501  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | unpark   | 3458  | 0       | 0        |\n╰--------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.85s (11.85s CPU time)\n\nRan 2 tests for test/PlantLifecycleInvariant.t.sol:PlantLifecycleInvariantTest\n[PASS] invariant_custodyCashFlowFloorAndLifecycle() (runs: 256, calls: 24576, reverts: 0)\n\n╭-----------------------+-------------+-------+---------+----------╮\n| Contract              | Selector    | Calls | Reverts | Discards |\n+==================================================================+\n| PlantLifecycleHandler | claim       | 2512  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | donatePlant | 2480  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | elapse      | 2364  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | fund        | 2461  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | park        | 2386  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | redeem      | 2485  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | rotate      | 2441  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | settle      | 2422  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | syncDeath   | 2505  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| PlantLifecycleHandler | unpark      | 2520  | 0       | 0        |\n╰-----------------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 3059\n  Bound result 4114\n  Bound result 2634\n  Bound result 1003\n  Bound result 95\n  Bound result 64\n  Bound result 96\n  Bound result 16\n  Bound result 100\n  Bound result 18134\n  Bound result 8666\n  Bound result 261\n  Bound result 4\n  Bound result 163985\n  Bound result 30\n  Bound result 75\n  Bound result 140\n  Bound result 14182\n  Bound result 3430\n  Bound result 119558\n  Bound result 25\n  Bound result 14481\n  Bound result 1\n  Bound result 81\n  Bound result 3\n  Bound result 9903\n  Bound result 14\n  Bound result 8673\n  Bound result 1\n  Bound result 1\n  Bound result 21915\n  Bound result 15518\n  Bound result 165056\n  Bound result 1\n\n[PASS] test_handlerExercisesBirthRewardsMoveRotationDeathAndFullExit() (gas: 9829613)\nLogs:\n  Bound result 51\n  Bound result 9000\n  Bound result 100\n  Bound result 1\n  Bound result 1\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.75s (20.76s CPU time)\n\nRan 8 test suites in 20.76s (33.02s CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PlantOrganism.bind(address,bytes32)\",\"PlantOrganism.claim()\",\"PlantOrganism.claim(uint32)\",\"PlantOrganism.park(uint32,uint256)\",\"PlantOrganism.redeem(uint256)\",\"PlantOrganism.rotateSigner(address,bytes)\",\"PlantOrganism.settle((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PlantOrganism.syncDeath()\",\"PlantOrganism.unpark(uint32,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":24,\"docs/ACCOUNTING.md\":15,\"docs/ORACLE.md\":40,\"foundry.toml\":18,\"launch.json\":15,\"src/OracleAttestation.sol\":97,\"src/PlantOrganism.sol\":488,\"test/OracleConformance.t.sol\":85,\"test/PlantAdversarial.t.sol\":434,\"test/PlantInvariant.t.sol\":242,\"test/PlantLifecycleInvariant.t.sol\":380,\"test/PlantOrganism.t.sol\":804,\"test/PlantSettlementGas.t.sol\":67,\"test/README.md\":30},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"82b027f296a6719008f15c917dcf7c0a4c2b16a5afcb1bf67b27f1a14463b83d","verifiedTreeHash":"b46643008f0766092afa9e5dfa0491512f559a39","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2297,"exitCode":0,"name":"build","output":"Compiling 29 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.17s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/PlantOrganism.sol:135:19\n    │\n135 │     function bind(address hook_, bytes32 questionHash) external nonReentrant {\n    │                   ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:155:21\n    │\n155 │         int16 lat = int16(uint16(cell >> 16));\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:155:27\n    │\n155 │         int16 lat = int16(uint16(cell >> 16));\n    │                           ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:156:21\n    │\n156 │         int16 lon = int16(uint16(cell));\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:156:27\n    │\n156 │         int16 lon = int16(uint16(cell));\n    │                           ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:161:27\n    │\n161 │         return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_guard` is updated\n    ╭▸ src/PlantOrganism.sol:488:42\n    │\n488 │         (bool ok, bytes memory result) = token.call(data);\n    │                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:195:9\n    │\n195 │         emit Parked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:211:9\n    │\n211 │         emit Unparked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:231:9\n    │\n231 │         emit Claimed(holder, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:248:9\n    │\n248 │         emit Redeemed(msg.sender, amount, payout, dead);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:304:21\n    │\n304 │                     gardeners += sip / 3;\n    │                     ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:306:23\n    │\n306 │                     ++sips;\n    │                       ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:269:17\n    │\n269 │             if (today <= lastSettledDay) revert WrongDay();\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:289:42\n    │\n289 │         if (day != lastSettledDay + 1 || day > block.timestamp / DAY) revert WrongDay();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:331:13\n    │\n331 │             emit Moved(current, location, day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:335:9\n    │\n335 │         emit Bounty(msg.sender, bounty);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:336:9\n    │\n336 │         emit Settled(day, words, sips, _backing, water, location);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:288:23\n    │\n288 │         uint256 day = uint32(packed >> 96);\n    │                       ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:312:29\n    │\n312 │         uint32 challenger = uint32(packed >> 64);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:357:13\n    │\n357 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:357:45\n    │\n357 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:359:73\n    │\n359 │         if (recovered != signer && (signerValidUntil[recovered] == 0 || block.timestamp > signerValidUntil[recovered]))\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:376:9\n    │\n376 │         emit SignerRotated(previous, newSigner, rotationNonce++, until);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:452:9\n    │\n452 │         emit Checkpointed(holder, cell, whole);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:474:13\n    │\n474 │         if (token.balanceOf(address(this)) != beforeBalance + amount) revert BadTokenTransfer();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:481:13\n    │\n481 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:481:73\n    │\n481 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━                 ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:397:17\n    │\n397 │         vm.warp(block.timestamp + 4 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:428:17\n    │\n428 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:435:17\n    │\n435 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:449:17\n    │\n449 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:460:17\n    │\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:472:17\n    │\n472 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:499:17\n    │\n499 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:528:17\n    │\n528 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n566 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n566 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n569 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n569 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:581:17\n    │\n581 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:650:17\n    │\n650 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:653:17\n    │\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:722:17\n    │\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:69:9\n    │\n 69 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:69:9\n    │\n 69 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:69:9\n    │\n 69 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/PlantRevision.t.sol:69:17\n   │\n69 │         vm.warp(block.timestamp + 1 days);\n   │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/PlantRevision.t.sol:83:17\n   │\n83 │         vm.warp(block.timestamp + delay);\n   │         ────────━━━━━━━━━━━━━━━───────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:90:47\n    │\n 90 │         assertEq(body.lastSuccessfulSettle(), block.timestamp);\n    │                                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantOrganism.t.sol:148:45\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:117:27\n    │\n117 │         uint256 boundAt = block.timestamp;\n    │                           ━━━━━━━━━━━━━━━\n118 │         vm.warp(boundAt + 30 days - 1);\n    │         ────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:151:9\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:151:9\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:151:9\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:151:17\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:181:17\n    │\n181 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":4591,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] test_acceptsFreshSignatureFromProtocolVectorKey() (gas: 30312)\n[PASS] test_acceptsUnchangedProtocolSignature() (gas: 17823)\n[PASS] test_digestMatchesUnchangedProtocolVector() (gas: 13738)\n[PASS] test_settleSelectorUsesCanonicalAttestationTuple() (gas: 3143)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 911.98µs (941.06µs CPU time)\n\nRan 1 test for test/PlantOrganism.t.sol:PlantGasTest\n[PASS] test_settleGasBoundIncludingFreshRewardsPendingActivationAndMove() (gas: 608326)\nLogs:\n  settle execution gas (cold, 24 sips, pending activation, move): 327866\n  settle gas including intrinsic: 354018\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.64ms (624.69µs CPU time)\n\nRan 37 tests for test/PlantOrganism.t.sol:PlantOrganismTest\n[PASS] testFuzz_maskSimulationAndConservation(uint24,uint24,uint96) (runs: 256, μ: 984991, ~: 1047700)\n[PASS] testFuzz_redemptionFloorNeverDecreases(uint96,uint96) (runs: 256, μ: 1004247, ~: 998867)\nLogs:\n  Bound result 59163483762335686500\n\n[PASS] test_bindOnlyExplicitDeployerAndOnlyOnce() (gas: 343272)\n[PASS] test_bindRejectsBadHookQuestionAndToken() (gas: 1106390)\n[PASS] test_birthHasNoHoursAndPaysBounty() (gas: 680536)\n[PASS] test_deadClockTracksSuccessTimestampNotBacklogDay() (gas: 293997)\n[PASS] test_deadPlantMergesPotPaysFullFloorKeepsRewardsAndCannotRevive() (gas: 1376693)\n[PASS] test_expiredAndNotYetValidRejectIncludingOneSecond() (gas: 135469)\n[PASS] test_failedBountyRollsBackEntireSettlement() (gas: 888212)\n[PASS] test_failedClaimKeepsDebtAndFalseTransferCannotLoseTokens() (gas: 1233910)\n[PASS] test_fallbackAfterExactlyThreeEmptySettles() (gas: 834186)\n[PASS] test_falseReturningAndFeeTokensRevertAtomicallyNoReturnWorks() (gas: 1031685)\n[PASS] test_finalRedemptionHasDefinedNondecreasingFloor() (gas: 569308)\n[PASS] test_initialStateAndFeeInflows() (gas: 306026)\n[PASS] test_missedDaysMustBeInOrderAndCannotReplayOrUseFuture() (gas: 718375)\n[PASS] test_moveStrictMajorityRewardsOldCellAndNoMinimumStay() (gas: 2043399)\n[PASS] test_mustUnparkBeforeRedeemingCustodiedTokens() (gas: 617308)\n[PASS] test_noncurrentCellLazyActivationAndRepeatedTopups() (gas: 1879078)\n[PASS] test_pendingCannotEarnInNextSettleAndTopUpDoesNotDilute() (gas: 1509649)\n[PASS] test_pendingOnlyGardenersGoToBacking() (gas: 1220353)\n[PASS] test_rainPrecedenceCapWaterAndDrought() (gas: 1822909)\n[PASS] test_redemptionRetainsTenPercentAndBurnedNeverLeaves() (gas: 1213549)\n[PASS] test_reentrancyBlockedOnPlantPullAndImdPayout() (gas: 1217732)\n[PASS] test_rejectsChangedSupplyInvalidCellsAndZeroAmounts() (gas: 190843)\n[PASS] test_rejectsMalleableHighSSignature() (gas: 77774)\n[PASS] test_rejectsWrongSignerTamperingAndMalformedSignature() (gas: 215314)\n[PASS] test_rotatedSignerSettlesAndOldKeyCannotRotateEvenDuringGrace() (gas: 411048)\n[PASS] test_runtimeBoundedAndHasNoEscapeOpcodes() (gas: 7937351)\n[PASS] test_signerRotationOnlyCurrentWithThirtyDayGraceAndCycleReplayProtection() (gas: 418079)\n[PASS] test_successiveRotationsKeepEachOldKeysGrace() (gas: 240130)\n[PASS] test_syntheticDayAndProRataClaims() (gas: 1563561)\n[PASS] test_temperatureAndWindAreEmittedUnchanged() (gas: 558566)\n[PASS] test_unboundOnlyAdvancesDay() (gas: 259206)\n[PASS] test_unparkDropsActiveImmediatelyAndPendingFirst() (gas: 1478577)\n[PASS] test_windowEndpointsAreInclusive() (gas: 132841)\n[PASS] test_wrongChainQuestionAnswerTypeAndLengthRejected() (gas: 452368)\n[PASS] test_wrongDomainContractChainAndV1Rejected() (gas: 242025)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 47.29ms (137.74ms CPU time)\n\nRan 11 tests for test/PlantRevision.t.sol:PlantRevisionTest\n[PASS] testFuzz_externalBurnKeepsDeadRedemptionAndGardenerClaimsAvailable(uint96) (runs: 256, μ: 1408938, ~: 1408782)\nLogs:\n  Bound result 682209532\n\n[PASS] test_futureIssuedAtRejectsUntilExactTimestamp() (gas: 192304)\n[PASS] test_lateBindHasFullThirtyDaysAndCannotResetByRebinding() (gas: 590261)\n[PASS] test_lateBindWithAdvancedCursorAcceptsNextDay() (gas: 1065181)\n[PASS] test_lateBindWithoutCursorAdvancePreservesInOrderBacklog() (gas: 1022700)\n[PASS] test_nowhereSupportCannotVetoBirthAtFivePercent() (gas: 1306753)\n[PASS] test_oneWeiExternalBurnKeepsParkingSettlementAndLiveRedemptionAvailable() (gas: 1721230)\n[PASS] test_rotationNeedsOnlyCurrentSignerButCannotBeUndoneByRetiredKey() (gas: 239604)\n[PASS] test_subQuorumAttestationCannotSpendMoveOrAdvanceDay() (gas: 1340081)\n[PASS] test_supplyAboveBindingSnapshotStillBlocksParkSettleAndRedeem() (gas: 716279)\n[PASS] test_unusedRotationAuthorizationRemainsValidUntilNonceChanges() (gas: 134693)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 50.54ms (60.50ms CPU time)\n\nRan 1 test for test/PlantInvariant.t.sol:PlantInvariantTest\n[PASS] invariant_accountingAndEagerRewardModelAgree() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+----------+-------+---------+----------╮\n| Contract     | Selector | Calls | Reverts | Discards |\n+======================================================+\n| PlantHandler | claim    | 1138  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | elapse   | 1168  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | fund     | 1112  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | park     | 1210  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | redeem   | 1209  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | settle   | 1161  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | unpark   | 1194  | 0       | 0        |\n╰--------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.47s (4.46s CPU time)\n\nRan 5 test suites in 4.47s (4.57s CPU time): 54 tests passed, 0 failed, 0 skipped (54 total tests)\n","passed":true},{"durationMs":33,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PlantOrganism.bind(address,bytes32)\",\"PlantOrganism.claim()\",\"PlantOrganism.claim(uint32)\",\"PlantOrganism.park(uint32,uint256)\",\"PlantOrganism.redeem(uint256)\",\"PlantOrganism.rotateSigner(address,bytes)\",\"PlantOrganism.settle((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PlantOrganism.syncDeath()\",\"PlantOrganism.unpark(uint32,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":24,\"docs/ACCOUNTING.md\":15,\"docs/ORACLE.md\":40,\"foundry.toml\":18,\"launch.json\":15,\"src/OracleAttestation.sol\":97,\"src/PlantOrganism.sol\":493,\"test/OracleConformance.t.sol\":85,\"test/PlantInvariant.t.sol\":239,\"test/PlantOrganism.t.sol\":807,\"test/PlantRevision.t.sol\":212},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1214,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/PlantOrganism.sol:477: Reentrancy in PlantOrganism._sendExact(IPlantToken,address,uint256) (src/PlantOrganism.sol#477-485):\n[high/medium] reentrancy-balance at src/PlantOrganism.sol:471: Reentrancy in PlantOrganism._pullExact(IPlantToken,address,uint256) (src/PlantOrganism.sol#471-475):\n[high/medium] reentrancy-balance at src/PlantOrganism.sol:477: Reentrancy in PlantOrganism._sendExact(IPlantToken,address,uint256) (src/PlantOrganism.sol#477-485):\n[medium/medium] uninitialized-local at src/PlantOrganism.sol:294: PlantOrganism.settle(OracleAttestation.Attestation,bytes).gardeners (src/PlantOrganism.sol#294) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PlantOrganism.sol:293: PlantOrganism.settle(OracleAttestation.Attestation,bytes).sips (src/PlantOrganism.sol#293) is a local variable never initialized\n[medium/medium] unused-return at src/PlantOrganism.sol:383: PlantOrganism._recover(bytes32,bytes) (src/PlantOrganism.sol#383-387) ignores return value by (account,err,None) = ECDSA.tryRecover(digest,sig) (src/PlantOrganism.sol#384)\n[low/medium] timestamp at src/PlantOrganism.sol:424: PlantOrganism._checkpoint(uint32,address) (src/PlantOrganism.sol#424-453) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:455: PlantOrganism.earned(uint32,address) (src/PlantOrganism.sol#455-469) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:395: PlantOrganism._rollCell(uint32) (src/PlantOrganism.sol#395-402) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:266: PlantOrganism.settle(OracleAttestation.Attestation,bytes) (src/PlantOrganism.sol#266-337) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:160: PlantOrganism.isDead() (src/PlantOrganism.sol#160-162) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:356: PlantOrganism.verifyAttestation(OracleAttestation.Attestation,bytes) (src/PlantOrganism.sol#356-363) uses timestamp for comparisons","passed":true},{"durationMs":379,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/PlantOrganism.sol:139: Reentrancy: State change after external call (3 places)\n[low] costly-loop at src/PlantOrganism.sol:297: Costly operations inside loop\n[low] literal-instead-of-constant at src/PlantOrganism.sol:157: Literal Instead of Constant (20 places)\n[low] uninitialized-local-variable at src/PlantOrganism.sol:293: Uninitialized Local Variable (2 places)\n[low] unsafe-erc20-operation at src/PlantOrganism.sol:480: Unsafe ERC20 Operation","passed":true},{"durationMs":1243,"exitCode":0,"name":"proof 76b4d5ba71a5","output":"Compiling 26 files with Solc 0.8.26\nSolc 0.8.26 finished in 773.39ms\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-0bdc7bf2/Proof_76b4d5ba71a5.t.sol:SupplyDriftLocksBackingTest\n[PASS] test_oneWeiBurnByAnyHolderBlocksLiveRedemptionOfBacking() (gas: 201759)\n[PASS] test_oneWeiBurnLocksBackingEvenAfterDeath() (gas: 251016)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.82ms (336.03µs CPU time)\n\nRan 1 test suite in 2.53ms (1.82ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true},{"durationMs":1209,"exitCode":0,"name":"proof 02e430113de3","output":"2026-10-08T05:47:34.714434Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-CeQDip/repo/test/imd-proof-0bdc7bf2/Proof_76b4d5ba71a5.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 700.57ms\nCompiler run successful!\n\nRan 1 test for test/imd-proof-ca600372/Proof_02e430113de3.t.sol:BornDeadTest\n[PASS] test_bindThirtyDaysAfterDeploymentMustNotYieldADeadBody() (gas: 843484)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.23ms (623.73µs CPU time)\n\nRan 1 test suite in 2.73ms (1.23ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"865a0d41b73f45ba399c4dcded463761fc075683b7bed9c94d3d29b191fd9a99","verifiedTreeHash":"8cda6d417755900c47c487a65af39853267725b3","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2096,"exitCode":0,"name":"build","output":"Compiling 29 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.99s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/PlantOrganism.sol:135:19\n    │\n135 │     function bind(address hook_, bytes32 questionHash) external nonReentrant {\n    │                   ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:155:21\n    │\n155 │         int16 lat = int16(uint16(cell >> 16));\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:155:27\n    │\n155 │         int16 lat = int16(uint16(cell >> 16));\n    │                           ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:156:21\n    │\n156 │         int16 lon = int16(uint16(cell));\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:156:27\n    │\n156 │         int16 lon = int16(uint16(cell));\n    │                           ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:161:27\n    │\n161 │         return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_guard` is updated\n    ╭▸ src/PlantOrganism.sol:488:42\n    │\n488 │         (bool ok, bytes memory result) = token.call(data);\n    │                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:195:9\n    │\n195 │         emit Parked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:211:9\n    │\n211 │         emit Unparked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:231:9\n    │\n231 │         emit Claimed(holder, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:248:9\n    │\n248 │         emit Redeemed(msg.sender, amount, payout, dead);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:304:21\n    │\n304 │                     gardeners += sip / 3;\n    │                     ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:306:23\n    │\n306 │                     ++sips;\n    │                       ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:269:17\n    │\n269 │             if (today <= lastSettledDay) revert WrongDay();\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:289:42\n    │\n289 │         if (day != lastSettledDay + 1 || day > block.timestamp / DAY) revert WrongDay();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:331:13\n    │\n331 │             emit Moved(current, location, day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:335:9\n    │\n335 │         emit Bounty(msg.sender, bounty);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:336:9\n    │\n336 │         emit Settled(day, words, sips, _backing, water, location);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:288:23\n    │\n288 │         uint256 day = uint32(packed >> 96);\n    │                       ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:312:29\n    │\n312 │         uint32 challenger = uint32(packed >> 64);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:357:13\n    │\n357 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:357:45\n    │\n357 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:359:73\n    │\n359 │         if (recovered != signer && (signerValidUntil[recovered] == 0 || block.timestamp > signerValidUntil[recovered]))\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:376:9\n    │\n376 │         emit SignerRotated(previous, newSigner, rotationNonce++, until);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:452:9\n    │\n452 │         emit Checkpointed(holder, cell, whole);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:474:13\n    │\n474 │         if (token.balanceOf(address(this)) != beforeBalance + amount) revert BadTokenTransfer();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:481:13\n    │\n481 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:481:73\n    │\n481 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━                 ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:397:17\n    │\n397 │         vm.warp(block.timestamp + 4 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:428:17\n    │\n428 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:435:17\n    │\n435 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:449:17\n    │\n449 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:460:17\n    │\n460 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:472:17\n    │\n472 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:499:17\n    │\n499 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:528:17\n    │\n528 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n566 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n566 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n569 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n569 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:581:17\n    │\n581 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n628 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:650:17\n    │\n650 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:653:17\n    │\n653 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:722:17\n    │\n722 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:69:9\n    │\n 69 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:69:9\n    │\n 69 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:69:9\n    │\n 69 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/PlantRevision.t.sol:69:17\n   │\n69 │         vm.warp(block.timestamp + 1 days);\n   │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/PlantRevision.t.sol:83:17\n   │\n83 │         vm.warp(block.timestamp + delay);\n   │         ────────━━━━━━━━━━━━━━━───────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:90:47\n    │\n 90 │         assertEq(body.lastSuccessfulSettle(), block.timestamp);\n    │                                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantOrganism.t.sol:148:45\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:117:27\n    │\n117 │         uint256 boundAt = block.timestamp;\n    │                           ━━━━━━━━━━━━━━━\n118 │         vm.warp(boundAt + 30 days - 1);\n    │         ────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:132:30\n    │\n132 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:151:9\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:133:31\n    │\n133 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:151:9\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:148:13\n    │\n148 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    │\n    ⸬  test/PlantRevision.t.sol:151:9\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:151:17\n    │\n151 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantRevision.t.sol:181:17\n    │\n181 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":4242,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] test_acceptsFreshSignatureFromProtocolVectorKey() (gas: 30312)\n[PASS] test_acceptsUnchangedProtocolSignature() (gas: 17823)\n[PASS] test_digestMatchesUnchangedProtocolVector() (gas: 13738)\n[PASS] test_settleSelectorUsesCanonicalAttestationTuple() (gas: 3143)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 735.46µs (641.53µs CPU time)\n\nRan 1 test for test/PlantOrganism.t.sol:PlantGasTest\n[PASS] test_settleGasBoundIncludingFreshRewardsPendingActivationAndMove() (gas: 608326)\nLogs:\n  settle execution gas (cold, 24 sips, pending activation, move): 327866\n  settle gas including intrinsic: 354018\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.88ms (1.59ms CPU time)\n\nRan 11 tests for test/PlantRevision.t.sol:PlantRevisionTest\n[PASS] testFuzz_externalBurnKeepsDeadRedemptionAndGardenerClaimsAvailable(uint96) (runs: 256, μ: 1408913, ~: 1408770)\nLogs:\n  Bound result 2\n\n[PASS] test_futureIssuedAtRejectsUntilExactTimestamp() (gas: 192304)\n[PASS] test_lateBindHasFullThirtyDaysAndCannotResetByRebinding() (gas: 590261)\n[PASS] test_lateBindWithAdvancedCursorAcceptsNextDay() (gas: 1065181)\n[PASS] test_lateBindWithoutCursorAdvancePreservesInOrderBacklog() (gas: 1022700)\n[PASS] test_nowhereSupportCannotVetoBirthAtFivePercent() (gas: 1306753)\n[PASS] test_oneWeiExternalBurnKeepsParkingSettlementAndLiveRedemptionAvailable() (gas: 1721230)\n[PASS] test_rotationNeedsOnlyCurrentSignerButCannotBeUndoneByRetiredKey() (gas: 239604)\n[PASS] test_subQuorumAttestationCannotSpendMoveOrAdvanceDay() (gas: 1340081)\n[PASS] test_supplyAboveBindingSnapshotStillBlocksParkSettleAndRedeem() (gas: 716279)\n[PASS] test_unusedRotationAuthorizationRemainsValidUntilNonceChanges() (gas: 134693)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 55.53ms (65.32ms CPU time)\n\nRan 37 tests for test/PlantOrganism.t.sol:PlantOrganismTest\n[PASS] testFuzz_maskSimulationAndConservation(uint24,uint24,uint96) (runs: 256, μ: 992816, ~: 1048989)\n[PASS] testFuzz_redemptionFloorNeverDecreases(uint96,uint96) (runs: 256, μ: 1006195, ~: 998849)\nLogs:\n  Bound result 32620767832950534832\n\n[PASS] test_bindOnlyExplicitDeployerAndOnlyOnce() (gas: 343272)\n[PASS] test_bindRejectsBadHookQuestionAndToken() (gas: 1106390)\n[PASS] test_birthHasNoHoursAndPaysBounty() (gas: 680536)\n[PASS] test_deadClockTracksSuccessTimestampNotBacklogDay() (gas: 293997)\n[PASS] test_deadPlantMergesPotPaysFullFloorKeepsRewardsAndCannotRevive() (gas: 1376693)\n[PASS] test_expiredAndNotYetValidRejectIncludingOneSecond() (gas: 135469)\n[PASS] test_failedBountyRollsBackEntireSettlement() (gas: 888212)\n[PASS] test_failedClaimKeepsDebtAndFalseTransferCannotLoseTokens() (gas: 1233910)\n[PASS] test_fallbackAfterExactlyThreeEmptySettles() (gas: 834186)\n[PASS] test_falseReturningAndFeeTokensRevertAtomicallyNoReturnWorks() (gas: 1031685)\n[PASS] test_finalRedemptionHasDefinedNondecreasingFloor() (gas: 569308)\n[PASS] test_initialStateAndFeeInflows() (gas: 306026)\n[PASS] test_missedDaysMustBeInOrderAndCannotReplayOrUseFuture() (gas: 718375)\n[PASS] test_moveStrictMajorityRewardsOldCellAndNoMinimumStay() (gas: 2043399)\n[PASS] test_mustUnparkBeforeRedeemingCustodiedTokens() (gas: 617308)\n[PASS] test_noncurrentCellLazyActivationAndRepeatedTopups() (gas: 1879078)\n[PASS] test_pendingCannotEarnInNextSettleAndTopUpDoesNotDilute() (gas: 1509649)\n[PASS] test_pendingOnlyGardenersGoToBacking() (gas: 1220353)\n[PASS] test_rainPrecedenceCapWaterAndDrought() (gas: 1822909)\n[PASS] test_redemptionRetainsTenPercentAndBurnedNeverLeaves() (gas: 1213549)\n[PASS] test_reentrancyBlockedOnPlantPullAndImdPayout() (gas: 1217732)\n[PASS] test_rejectsChangedSupplyInvalidCellsAndZeroAmounts() (gas: 190843)\n[PASS] test_rejectsMalleableHighSSignature() (gas: 77774)\n[PASS] test_rejectsWrongSignerTamperingAndMalformedSignature() (gas: 215314)\n[PASS] test_rotatedSignerSettlesAndOldKeyCannotRotateEvenDuringGrace() (gas: 411048)\n[PASS] test_runtimeBoundedAndHasNoEscapeOpcodes() (gas: 7937351)\n[PASS] test_signerRotationOnlyCurrentWithThirtyDayGraceAndCycleReplayProtection() (gas: 418079)\n[PASS] test_successiveRotationsKeepEachOldKeysGrace() (gas: 240130)\n[PASS] test_syntheticDayAndProRataClaims() (gas: 1563561)\n[PASS] test_temperatureAndWindAreEmittedUnchanged() (gas: 558566)\n[PASS] test_unboundOnlyAdvancesDay() (gas: 259206)\n[PASS] test_unparkDropsActiveImmediatelyAndPendingFirst() (gas: 1478577)\n[PASS] test_windowEndpointsAreInclusive() (gas: 132841)\n[PASS] test_wrongChainQuestionAnswerTypeAndLengthRejected() (gas: 452368)\n[PASS] test_wrongDomainContractChainAndV1Rejected() (gas: 242025)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 55.61ms (141.36ms CPU time)\n\nRan 1 test for test/PlantInvariant.t.sol:PlantInvariantTest\n[PASS] invariant_accountingAndEagerRewardModelAgree() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+----------+-------+---------+----------╮\n| Contract     | Selector | Calls | Reverts | Discards |\n+======================================================+\n| PlantHandler | claim    | 1189  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | elapse   | 1162  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | fund     | 1187  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | park     | 1257  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | redeem   | 1132  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | settle   | 1125  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | unpark   | 1140  | 0       | 0        |\n╰--------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.17s (4.17s CPU time)\n\nRan 5 test suites in 4.17s (4.29s CPU time): 54 tests passed, 0 failed, 0 skipped (54 total tests)\n","passed":true},{"durationMs":29,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PlantOrganism.bind(address,bytes32)\",\"PlantOrganism.claim()\",\"PlantOrganism.claim(uint32)\",\"PlantOrganism.park(uint32,uint256)\",\"PlantOrganism.redeem(uint256)\",\"PlantOrganism.rotateSigner(address,bytes)\",\"PlantOrganism.settle((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PlantOrganism.syncDeath()\",\"PlantOrganism.unpark(uint32,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":24,\"docs/ACCOUNTING.md\":15,\"docs/ORACLE.md\":40,\"foundry.toml\":18,\"launch.json\":15,\"src/OracleAttestation.sol\":97,\"src/PlantOrganism.sol\":493,\"test/OracleConformance.t.sol\":85,\"test/PlantInvariant.t.sol\":239,\"test/PlantOrganism.t.sol\":807,\"test/PlantRevision.t.sol\":212},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"956acb94fbeb7799ee21edd3d7be0d4d169840935bf4bf63f7ba40975eeb12af","verifiedTreeHash":"7364973cdda572ad151dc43eff5a57648221fbb7","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":1925,"exitCode":0,"name":"build","output":"Compiling 28 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.81s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/PlantOrganism.sol:135:19\n    │\n135 │     function bind(address hook_, bytes32 questionHash) external nonReentrant {\n    │                   ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:153:21\n    │\n153 │         int16 lat = int16(uint16(cell >> 16));\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:153:27\n    │\n153 │         int16 lat = int16(uint16(cell >> 16));\n    │                           ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:154:21\n    │\n154 │         int16 lon = int16(uint16(cell));\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:154:27\n    │\n154 │         int16 lon = int16(uint16(cell));\n    │                           ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:159:27\n    │\n159 │         return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_guard` is updated\n    ╭▸ src/PlantOrganism.sol:483:42\n    │\n483 │         (bool ok, bytes memory result) = token.call(data);\n    │                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:193:9\n    │\n193 │         emit Parked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:209:9\n    │\n209 │         emit Unparked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:229:9\n    │\n229 │         emit Claimed(holder, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:246:9\n    │\n246 │         emit Redeemed(msg.sender, amount, payout, dead);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:303:23\n    │\n303 │                     ++sips;\n    │                       ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:301:21\n    │\n301 │                     gardeners += sip / 3;\n    │                     ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:267:17\n    │\n267 │             if (today <= lastSettledDay) revert WrongDay();\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:286:42\n    │\n286 │         if (day != lastSettledDay + 1 || day > block.timestamp / DAY) revert WrongDay();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:327:13\n    │\n327 │             emit Moved(current, location, day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:331:9\n    │\n331 │         emit Bounty(msg.sender, bounty);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:332:9\n    │\n332 │         emit Settled(day, words, sips, _backing, water, location);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:285:23\n    │\n285 │         uint256 day = uint32(packed >> 96);\n    │                       ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:309:29\n    │\n309 │         uint32 challenger = uint32(packed >> 64);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:353:13\n    │\n353 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:353:45\n    │\n353 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:355:73\n    │\n355 │         if (recovered != signer && (signerValidUntil[recovered] == 0 || block.timestamp > signerValidUntil[recovered]))\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:372:9\n    │\n372 │         emit SignerRotated(previous, newSigner, rotationNonce++, until);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:447:9\n    │\n447 │         emit Checkpointed(holder, cell, whole);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:469:13\n    │\n469 │         if (token.balanceOf(address(this)) != beforeBalance + amount) revert BadTokenTransfer();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:476:13\n    │\n476 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:476:73\n    │\n476 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━                 ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:392:17\n    │\n392 │         vm.warp(block.timestamp + 4 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:423:17\n    │\n423 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:430:17\n    │\n430 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:444:17\n    │\n444 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:455:17\n    │\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:467:17\n    │\n467 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:494:17\n    │\n494 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:523:17\n    │\n523 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n561 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n561 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n564 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n564 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:576:17\n    │\n576 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:645:17\n    │\n645 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:648:17\n    │\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:717:17\n    │\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":4529,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] test_acceptsFreshSignatureFromProtocolVectorKey() (gas: 30312)\n[PASS] test_acceptsUnchangedProtocolSignature() (gas: 17823)\n[PASS] test_digestMatchesUnchangedProtocolVector() (gas: 13738)\n[PASS] test_settleSelectorUsesCanonicalAttestationTuple() (gas: 3143)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.04ms (928.04µs CPU time)\n\nRan 1 test for test/PlantOrganism.t.sol:PlantGasTest\n[PASS] test_settleGasBoundIncludingFreshRewardsPendingActivationAndMove() (gas: 608213)\nLogs:\n  settle execution gas (cold, 24 sips, pending activation, move): 327687\n  settle gas including intrinsic: 353839\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.00ms (1.58ms CPU time)\n\nRan 37 tests for test/PlantOrganism.t.sol:PlantOrganismTest\n[PASS] testFuzz_maskSimulationAndConservation(uint24,uint24,uint96) (runs: 256, μ: 995527, ~: 1050665)\n[PASS] testFuzz_redemptionFloorNeverDecreases(uint96,uint96) (runs: 256, μ: 1003312, ~: 1000878)\nLogs:\n  Bound result 155169851142001375104\n\n[PASS] test_bindOnlyExplicitDeployerAndOnlyOnce() (gas: 341067)\n[PASS] test_bindRejectsBadHookQuestionAndToken() (gas: 1078393)\n[PASS] test_birthHasNoHoursAndPaysBounty() (gas: 682833)\n[PASS] test_deadClockTracksSuccessTimestampNotBacklogDay() (gas: 293479)\n[PASS] test_deadPlantMergesPotPaysFullFloorKeepsRewardsAndCannotRevive() (gas: 1379013)\n[PASS] test_expiredAndNotYetValidRejectIncludingOneSecond() (gas: 134907)\n[PASS] test_failedBountyRollsBackEntireSettlement() (gas: 890162)\n[PASS] test_failedClaimKeepsDebtAndFalseTransferCannotLoseTokens() (gas: 1236080)\n[PASS] test_fallbackAfterExactlyThreeEmptySettles() (gas: 835509)\n[PASS] test_falseReturningAndFeeTokensRevertAtomicallyNoReturnWorks() (gas: 1031940)\n[PASS] test_finalRedemptionHasDefinedNondecreasingFloor() (gas: 569785)\n[PASS] test_initialStateAndFeeInflows() (gas: 306090)\n[PASS] test_missedDaysMustBeInOrderAndCannotReplayOrUseFuture() (gas: 716496)\n[PASS] test_moveStrictMajorityRewardsOldCellAndNoMinimumStay() (gas: 2045106)\n[PASS] test_mustUnparkBeforeRedeemingCustodiedTokens() (gas: 617563)\n[PASS] test_noncurrentCellLazyActivationAndRepeatedTopups() (gas: 1880498)\n[PASS] test_pendingCannotEarnInNextSettleAndTopUpDoesNotDilute() (gas: 1511576)\n[PASS] test_pendingOnlyGardenersGoToBacking() (gas: 1219296)\n[PASS] test_rainPrecedenceCapWaterAndDrought() (gas: 1823134)\n[PASS] test_redemptionRetainsTenPercentAndBurnedNeverLeaves() (gas: 1215804)\n[PASS] test_reentrancyBlockedOnPlantPullAndImdPayout() (gas: 1219745)\n[PASS] test_rejectsChangedSupplyInvalidCellsAndZeroAmounts() (gas: 190811)\n[PASS] test_rejectsMalleableHighSSignature() (gas: 77493)\n[PASS] test_rejectsWrongSignerTamperingAndMalformedSignature() (gas: 214471)\n[PASS] test_rotatedSignerSettlesAndOldKeyCannotRotateEvenDuringGrace() (gas: 410530)\n[PASS] test_runtimeBoundedAndHasNoEscapeOpcodes() (gas: 7887200)\n[PASS] test_signerRotationOnlyCurrentWithThirtyDayGraceAndCycleReplayProtection() (gas: 418079)\n[PASS] test_successiveRotationsKeepEachOldKeysGrace() (gas: 240130)\n[PASS] test_syntheticDayAndProRataClaims() (gas: 1565601)\n[PASS] test_temperatureAndWindAreEmittedUnchanged() (gas: 560407)\n[PASS] test_unboundOnlyAdvancesDay() (gas: 259205)\n[PASS] test_unparkDropsActiveImmediatelyAndPendingFirst() (gas: 1480793)\n[PASS] test_windowEndpointsAreInclusive() (gas: 132582)\n[PASS] test_wrongChainQuestionAnswerTypeAndLengthRejected() (gas: 451235)\n[PASS] test_wrongDomainContractChainAndV1Rejected() (gas: 241182)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 54.50ms (152.35ms CPU time)\n\nRan 1 test for test/PlantInvariant.t.sol:PlantInvariantTest\n[PASS] invariant_accountingAndEagerRewardModelAgree() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+----------+-------+---------+----------╮\n| Contract     | Selector | Calls | Reverts | Discards |\n+======================================================+\n| PlantHandler | claim    | 1191  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | elapse   | 1135  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | fund     | 1161  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | park     | 1185  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | redeem   | 1136  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | settle   | 1140  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | unpark   | 1244  | 0       | 0        |\n╰--------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.45s (4.45s CPU time)\n\nRan 4 test suites in 4.45s (4.51s CPU time): 43 tests passed, 0 failed, 0 skipped (43 total tests)\n","passed":true},{"durationMs":34,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PlantOrganism.bind(address,bytes32)\",\"PlantOrganism.claim()\",\"PlantOrganism.claim(uint32)\",\"PlantOrganism.park(uint32,uint256)\",\"PlantOrganism.redeem(uint256)\",\"PlantOrganism.rotateSigner(address,bytes)\",\"PlantOrganism.settle((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PlantOrganism.syncDeath()\",\"PlantOrganism.unpark(uint32,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":24,\"docs/ACCOUNTING.md\":15,\"docs/ORACLE.md\":40,\"foundry.toml\":18,\"launch.json\":15,\"src/OracleAttestation.sol\":97,\"src/PlantOrganism.sol\":488,\"test/OracleConformance.t.sol\":85,\"test/PlantInvariant.t.sol\":239,\"test/PlantOrganism.t.sol\":802},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1124,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/PlantOrganism.sol:466: Reentrancy in PlantOrganism._pullExact(IPlantToken,address,uint256) (src/PlantOrganism.sol#466-470):\n[high/medium] reentrancy-balance at src/PlantOrganism.sol:472: Reentrancy in PlantOrganism._sendExact(IPlantToken,address,uint256) (src/PlantOrganism.sol#472-480):\n[high/medium] reentrancy-balance at src/PlantOrganism.sol:472: Reentrancy in PlantOrganism._sendExact(IPlantToken,address,uint256) (src/PlantOrganism.sol#472-480):\n[medium/medium] uninitialized-local at src/PlantOrganism.sol:291: PlantOrganism.settle(OracleAttestation.Attestation,bytes).gardeners (src/PlantOrganism.sol#291) is a local variable never initialized\n[medium/medium] uninitialized-local at src/PlantOrganism.sol:290: PlantOrganism.settle(OracleAttestation.Attestation,bytes).sips (src/PlantOrganism.sol#290) is a local variable never initialized\n[medium/medium] unused-return at src/PlantOrganism.sol:379: PlantOrganism._recover(bytes32,bytes) (src/PlantOrganism.sol#379-383) ignores return value by (account,err,None) = ECDSA.tryRecover(digest,sig) (src/PlantOrganism.sol#380)\n[low/medium] timestamp at src/PlantOrganism.sol:158: PlantOrganism.isDead() (src/PlantOrganism.sol#158-160) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:264: PlantOrganism.settle(OracleAttestation.Attestation,bytes) (src/PlantOrganism.sol#264-333) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:390: PlantOrganism._rollCell(uint32) (src/PlantOrganism.sol#390-397) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:419: PlantOrganism._checkpoint(uint32,address) (src/PlantOrganism.sol#419-448) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:450: PlantOrganism.earned(uint32,address) (src/PlantOrganism.sol#450-464) uses timestamp for comparisons\n[low/medium] timestamp at src/PlantOrganism.sol:352: PlantOrganism.verifyAttestation(OracleAttestation.Attestation,bytes) (src/PlantOrganism.sol#352-359) uses timestamp for comparisons","passed":true},{"durationMs":363,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/PlantOrganism.sol:139: Reentrancy: State change after external call (3 places)\n[low] costly-loop at src/PlantOrganism.sol:294: Costly operations inside loop\n[low] literal-instead-of-constant at src/PlantOrganism.sol:155: Literal Instead of Constant (20 places)\n[low] uninitialized-local-variable at src/PlantOrganism.sol:290: Uninitialized Local Variable (2 places)\n[low] unsafe-erc20-operation at src/PlantOrganism.sol:475: Unsafe ERC20 Operation","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"bdc501fbb87ed49f79311d4706ce7fe740e9405851031b8e92636ef865dcb89e","verifiedTreeHash":"5797132adcda1f1df6501f63bca1128b2bc4d8aa","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2513,"exitCode":0,"name":"build","output":"Compiling 28 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.38s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/PlantOrganism.sol:135:19\n    │\n135 │     function bind(address hook_, bytes32 questionHash) external nonReentrant {\n    │                   ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:153:21\n    │\n153 │         int16 lat = int16(uint16(cell >> 16));\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:153:27\n    │\n153 │         int16 lat = int16(uint16(cell >> 16));\n    │                           ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:154:21\n    │\n154 │         int16 lon = int16(uint16(cell));\n    │                     ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:154:27\n    │\n154 │         int16 lon = int16(uint16(cell));\n    │                           ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint16' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:159:27\n    │\n159 │         return bound() && block.timestamp >= lastSuccessfulSettle + DEATH_DELAY;\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_guard` is updated\n    ╭▸ src/PlantOrganism.sol:483:42\n    │\n483 │         (bool ok, bytes memory result) = token.call(data);\n    │                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:193:9\n    │\n193 │         emit Parked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:209:9\n    │\n209 │         emit Unparked(msg.sender, cell, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:229:9\n    │\n229 │         emit Claimed(holder, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:246:9\n    │\n246 │         emit Redeemed(msg.sender, amount, payout, dead);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:301:21\n    │\n301 │                     gardeners += sip / 3;\n    │                     ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PlantOrganism.sol:303:23\n    │\n303 │                     ++sips;\n    │                       ━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:267:17\n    │\n267 │             if (today <= lastSettledDay) revert WrongDay();\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:286:42\n    │\n286 │         if (day != lastSettledDay + 1 || day > block.timestamp / DAY) revert WrongDay();\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:327:13\n    │\n327 │             emit Moved(current, location, day);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:331:9\n    │\n331 │         emit Bounty(msg.sender, bounty);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:332:9\n    │\n332 │         emit Settled(day, words, sips, _backing, water, location);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:285:23\n    │\n285 │         uint256 day = uint32(packed >> 96);\n    │                       ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PlantOrganism.sol:309:29\n    │\n309 │         uint32 challenger = uint32(packed >> 64);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint32' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:353:13\n    │\n353 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:353:45\n    │\n353 │         if (a.issuedAt > block.timestamp || block.timestamp > a.expiresAt) revert InvalidWindow();\n    │                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PlantOrganism.sol:355:73\n    │\n355 │         if (recovered != signer && (signerValidUntil[recovered] == 0 || block.timestamp > signerValidUntil[recovered]))\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:372:9\n    │\n372 │         emit SignerRotated(previous, newSigner, rotationNonce++, until);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PlantOrganism.sol:447:9\n    │\n447 │         emit Checkpointed(holder, cell, whole);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:469:13\n    │\n469 │         if (token.balanceOf(address(this)) != beforeBalance + amount) revert BadTokenTransfer();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:476:13\n    │\n476 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/PlantOrganism.sol:476:73\n    │\n476 │         if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != recipientBefore + amount)\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━                 ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │                                             ──────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:392:17\n    │\n392 │         vm.warp(block.timestamp + 4 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:423:17\n    │\n423 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:430:17\n    │\n430 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:444:17\n    │\n444 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:455:17\n    │\n455 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:467:17\n    │\n467 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:494:17\n    │\n494 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:523:17\n    │\n523 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n561 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n561 │         vm.warp(deadline);\n    │         ───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n564 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n564 │         vm.warp(deadline + 1);\n    │         ───────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:576:17\n    │\n576 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n623 │         vm.warp(last + 30 days - 1);\n    │         ─────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:645:17\n    │\n645 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:648:17\n    │\n648 │         vm.warp(block.timestamp + 29 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:127:30\n    │\n127 │             issuedAt: uint64(block.timestamp),\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:128:31\n    │\n128 │             expiresAt: uint64(block.timestamp + 1 days)\n    │                               ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:143:13\n    │\n143 │         if (block.timestamp / 1 days < day) vm.warp(day * 1 days + 12 hours);\n    │             ━━━━━━━━━━━━━━━\n    ‡\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/PlantOrganism.t.sol:717:17\n    │\n717 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":5028,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/OracleConformance.t.sol:OracleConformanceTest\n[PASS] test_acceptsFreshSignatureFromProtocolVectorKey() (gas: 30312)\n[PASS] test_acceptsUnchangedProtocolSignature() (gas: 17823)\n[PASS] test_digestMatchesUnchangedProtocolVector() (gas: 13738)\n[PASS] test_settleSelectorUsesCanonicalAttestationTuple() (gas: 3143)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.67ms (1.62ms CPU time)\n\nRan 1 test for test/PlantOrganism.t.sol:PlantGasTest\n[PASS] test_settleGasBoundIncludingFreshRewardsPendingActivationAndMove() (gas: 608213)\nLogs:\n  settle execution gas (cold, 24 sips, pending activation, move): 327687\n  settle gas including intrinsic: 353839\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.27ms (848.55µs CPU time)\n\nRan 37 tests for test/PlantOrganism.t.sol:PlantOrganismTest\n[PASS] testFuzz_maskSimulationAndConservation(uint24,uint24,uint96) (runs: 256, μ: 982955, ~: 1049333)\n[PASS] testFuzz_redemptionFloorNeverDecreases(uint96,uint96) (runs: 256, μ: 1010627, ~: 1000914)\nLogs:\n  Bound result 2341\n\n[PASS] test_bindOnlyExplicitDeployerAndOnlyOnce() (gas: 341067)\n[PASS] test_bindRejectsBadHookQuestionAndToken() (gas: 1078393)\n[PASS] test_birthHasNoHoursAndPaysBounty() (gas: 682833)\n[PASS] test_deadClockTracksSuccessTimestampNotBacklogDay() (gas: 293479)\n[PASS] test_deadPlantMergesPotPaysFullFloorKeepsRewardsAndCannotRevive() (gas: 1379013)\n[PASS] test_expiredAndNotYetValidRejectIncludingOneSecond() (gas: 134907)\n[PASS] test_failedBountyRollsBackEntireSettlement() (gas: 890162)\n[PASS] test_failedClaimKeepsDebtAndFalseTransferCannotLoseTokens() (gas: 1236080)\n[PASS] test_fallbackAfterExactlyThreeEmptySettles() (gas: 835509)\n[PASS] test_falseReturningAndFeeTokensRevertAtomicallyNoReturnWorks() (gas: 1031940)\n[PASS] test_finalRedemptionHasDefinedNondecreasingFloor() (gas: 569785)\n[PASS] test_initialStateAndFeeInflows() (gas: 306090)\n[PASS] test_missedDaysMustBeInOrderAndCannotReplayOrUseFuture() (gas: 716496)\n[PASS] test_moveStrictMajorityRewardsOldCellAndNoMinimumStay() (gas: 2045106)\n[PASS] test_mustUnparkBeforeRedeemingCustodiedTokens() (gas: 617563)\n[PASS] test_noncurrentCellLazyActivationAndRepeatedTopups() (gas: 1880498)\n[PASS] test_pendingCannotEarnInNextSettleAndTopUpDoesNotDilute() (gas: 1511576)\n[PASS] test_pendingOnlyGardenersGoToBacking() (gas: 1219296)\n[PASS] test_rainPrecedenceCapWaterAndDrought() (gas: 1823134)\n[PASS] test_redemptionRetainsTenPercentAndBurnedNeverLeaves() (gas: 1215804)\n[PASS] test_reentrancyBlockedOnPlantPullAndImdPayout() (gas: 1219745)\n[PASS] test_rejectsChangedSupplyInvalidCellsAndZeroAmounts() (gas: 190811)\n[PASS] test_rejectsMalleableHighSSignature() (gas: 77493)\n[PASS] test_rejectsWrongSignerTamperingAndMalformedSignature() (gas: 214471)\n[PASS] test_rotatedSignerSettlesAndOldKeyCannotRotateEvenDuringGrace() (gas: 410530)\n[PASS] test_runtimeBoundedAndHasNoEscapeOpcodes() (gas: 7887200)\n[PASS] test_signerRotationOnlyCurrentWithThirtyDayGraceAndCycleReplayProtection() (gas: 418079)\n[PASS] test_successiveRotationsKeepEachOldKeysGrace() (gas: 240130)\n[PASS] test_syntheticDayAndProRataClaims() (gas: 1565601)\n[PASS] test_temperatureAndWindAreEmittedUnchanged() (gas: 560407)\n[PASS] test_unboundOnlyAdvancesDay() (gas: 259205)\n[PASS] test_unparkDropsActiveImmediatelyAndPendingFirst() (gas: 1480793)\n[PASS] test_windowEndpointsAreInclusive() (gas: 132582)\n[PASS] test_wrongChainQuestionAnswerTypeAndLengthRejected() (gas: 451235)\n[PASS] test_wrongDomainContractChainAndV1Rejected() (gas: 241182)\nSuite result: ok. 37 passed; 0 failed; 0 skipped; finished in 58.11ms (169.64ms CPU time)\n\nRan 1 test for test/PlantInvariant.t.sol:PlantInvariantTest\n[PASS] invariant_accountingAndEagerRewardModelAgree() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+----------+-------+---------+----------╮\n| Contract     | Selector | Calls | Reverts | Discards |\n+======================================================+\n| PlantHandler | claim    | 1186  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | elapse   | 1180  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | fund     | 1147  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | park     | 1136  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | redeem   | 1149  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | settle   | 1175  | 0       | 0        |\n|--------------+----------+-------+---------+----------|\n| PlantHandler | unpark   | 1219  | 0       | 0        |\n╰--------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.94s (4.94s CPU time)\n\nRan 4 test suites in 4.94s (5.00s CPU time): 43 tests passed, 0 failed, 0 skipped (43 total tests)\n","passed":true},{"durationMs":46,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"PlantOrganism.bind(address,bytes32)\",\"PlantOrganism.claim()\",\"PlantOrganism.claim(uint32)\",\"PlantOrganism.park(uint32,uint256)\",\"PlantOrganism.redeem(uint256)\",\"PlantOrganism.rotateSigner(address,bytes)\",\"PlantOrganism.settle((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"PlantOrganism.syncDeath()\",\"PlantOrganism.unpark(uint32,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":24,\"docs/ACCOUNTING.md\":15,\"docs/ORACLE.md\":40,\"foundry.toml\":18,\"launch.json\":15,\"src/OracleAttestation.sol\":97,\"src/PlantOrganism.sol\":488,\"test/OracleConformance.t.sol\":85,\"test/PlantInvariant.t.sol\":239,\"test/PlantOrganism.t.sol\":802},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e31708eb7b9a95b528d7b1b1035a867d6ba2c2934ed03e672fd21add87f92f56","verifiedTreeHash":"4655d5d4903c45bf435fbc1859847565ad60c8cc","verifierVersion":"0.1.0+ad90ce4c"}]}