Job

02f34926shapedagBlocked

Blocked: node docs: path_violation

Implement ExpiringMerkleDistributor in src/ExpiringMerkleDistributor.sol, test it, review it and document it. Do not deploy. constructor(address token, bytes32 merkleRoot, uint256 expiry, address sweepTo), all immutable; reverts unless expiry > block.timestamp and sweepTo and token are nonzero.

Leaves are keccak256(bytes.concat(keccak256(abi.encode(uint256 index, address account, uint256 amount)))) (OpenZeppelin StandardMerkleTree double hash, so an inner node cannot pass as a leaf); proofs …

Work

  1. posted4 minto the first attempt
  2. built
    #592Impl1 file changed
    writes to
    src/ExpiringMerkleDistributor.sol

    ExpiringMerkleDistributor is implemented, built, tested, and committed as b52b6e6. Only src/ExpiringMerkleDistributor.sol was added. No dependencies or config files were touched, and the repo had no foundry.toml, so Foundry's defaults were used.

    What the contract does

    • Constructor stores token, merkleRoot, expiry and sweepTo as immutables. It reverts with ZeroAddress for a zero token or sweepTo, and with ExpiryInPast unless expiry is strictly after the current timestamp.
    • claim is open to anyone and always pays the account in the leaf. It reverts with ClaimWindowClosed after expiry, AlreadyClaimed for a used index, and InvalidProof otherwise. The bitmap bit is set and Claimed emitted before the token transfer, so a callback token cannot re-enter and double claim.
    • Leaf and proof format match OpenZeppelin StandardMerkleTree: the leaf is the double keccak of the ABI-encoded triple, and proof siblings are hashed as sorted pairs. The double hash rules out passing an inner node as a leaf.
    • isClaimed reads a bitmap with 256 indices per storage word.
    • sweep is open to anyone, reverts with NotYetExpired while the timestamp is at or before expiry, and otherwise transfers the whole balance to sweepTo and emits Swept. It can be repeated for tokens that arrive later, including a zero-balance sweep.
    • Safe transfer helper uses a low-level call. It accepts no return data only if the token has code, accepts a 32-byte word equal to one, and reverts with TransferFailed on a revert, a returned false, malformed data, or a codeless token.

    Verification

    • forge build succeeds. The only compiler output is the linter's standard warning about comparing against block.timestamp, which is inherent to a deadline contract.
    • forge test passes 31 tests in test/scratch, written against an inlined cheatcode interface so no forge-std was needed. They cover all constructor reverts, the exact expiry boundary for both claim and sweep, double claims, bitmap word boundaries at indices 255 and 256, wrong index, account, amount, proof and proof length, a single-hashed leaf tree being rejected, a single-leaf tree with an empty proof, third-party relaying, a no-return token, a false-returning token, malformed return data, a codeless token, an underfunded distributor, and two fuzz tests, one of which builds random trees of up to 20 leaves and checks every leaf claims exactly once with full balance conservation.

    Assumptions and notes for the integrator

    • The contract has no admin and no pause. The only privileged address is the passive sweepTo recipient.
    • Swept is emitted even when the balance is zero. That is a design choice, not an error.
    • Fee-on-transfer or rebasing tokens are not specially handled. Claimants may receive less than the leaf amount and the contract does not check funding at deploy time.
    • The claim event parameters are unindexed, matching the signature given in the task. If off-chain indexers want filtering by account, that would be a signature change and should be agreed first.
    • A zero merkleRoot is accepted by the constructor. No leaf can be proven against it, so such a deployment is effectively sweep-only after expiry.
    ran onclaude · claude-fable-5-1 · 10 turns · 3m 31s · 290 in · 16.1K out · 389.8K cached
    submission2b4dfc402f4a2a534fa2f1e11f82a05b318e10c4dd224b5668dcb0abe50bf6c2
    device29ea8835baa3dfe123fd5a001f4e9b467fad3076df4065b528a6bc1ad94dca33
    started from0243d7da4a4337ae8b16bcdf15bb4ead736fd68f
    bundleaf1c0e210992f1bffba1226a70320e3f1fb6aa4b1bae44e0f43c46f0cd9cc02f · 3.1 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 1 file
    src/ExpiringMerkleDistributor.sol
  3. reviewed
    #1846Review1 finding · 1 low
    afterImpl

    Wrote .imd-findings.json with one low-severity finding: the required Foundry tests are missing.

    No substantiated contract exploit was found in the requested areas. Review used source inspection and read-only checks; no EVM tests were executed. Implementation files remain unchanged.

    ran oncodex · gpt-6-astra · 4 turns · 2m 46s · 35.1K in · 5K out · 193.4K cached
    submission1c528c714c415565d41c6225c0ac563210dc2fd1b37ec4cd5dafe3143b1fce11
    device8df16409e9706163f892e77e58b850c47e95fd72b1c7ccf056024111340c4fad
    started fromd35dde28837a0c410fdb7cbfa81880406515ffb4
    bundlenone
    applied onaf1c0e210992f1bffba1226a70320e3f1fb6aa4b1bae44e0f43c46f0cd9cc02f
    changed · 0 filesnothing
    • lowRequired Foundry tests are missingsrc/ExpiringMerkleDistributor.sol:33

      The implementation includes no tests, despite the explicit requirement to test the distributor. The only tracked project file is this contract, and there are no test contracts or test functions. Consequently, none of the required adversarial cases has committed regression coverage: double-hashed leaves versus inner nodes, bitmap boundaries, the inclusive claim deadline, early and repeated sweeps, or no-bool transfers and failed-transfer rollback.

      This is a missing deliverable and verification gap, not a demonstrated contract exploit. Add a self-contained Foundry test suite covering these cases.

      In the supplied checkout, run git ls-files and git ls-files -- '*.t.sol', then inspect the listed Solidity source for test functions.

      The first command returns only src/ExpiringMerkleDistributor.sol; the second returns nothing, and the source contains no test functions.

      Expected: committed executable tests exercising the required behavior.

      Actual: zero committed tests.

      Concrete missing cases include claims at indices 255 and 256 without interference; claim success and sweep rejection at timestamp == expiry; claim rejection and sweep success at expiry + 1; and a successful claim against an ERC-20 whose transfer returns zero bytes.

      These cases have no executable assertions in the submission.

  4. tested
    #592Tests1 file changed
    afterImpl
    writes to
    test/ExpiringMerkleDistributor.t.sol

    The build artifacts under cache/ and out/ are git-ignored, so the only change in the tree is the test file. The work is complete.

    Result: test/ExpiringMerkleDistributor.t.sol adds 52 tests, all passing under forge test with 256 fuzz runs each. No dependencies were added and no configuration files were touched.

    What the suite covers

    • Fixture: a 6-leaf tree built in Solidity with StandardMerkleTree double-hashed leaves and sorted-pair inner nodes. Index 0 is present, Alice appears at indexes 0 and 2, and indexes 255, 256 and 1000 span three bitmap words. A local verifier confirms every fixture proof before the tests rely on it.
    • Constructor: zero token, zero sweepTo, expiry equal to now, expiry in the past, expiry one second ahead, and the revert priority when both checks fail.
    • Claims: happy path with event, relayer submission paying the account rather than the caller, same account across two indexes, arbitrary claim order, and conservation of funded tokens under fuzzed claim subsets.
    • Double claims: same caller, different caller, garbage proof after a claim, and both sides of the 255/256 word boundary.
    • Bad inputs: wrong index, swapped indexes between the two Alice leaves, wrong account, wrong amount, another leaf's proof, empty, truncated, extended, bit-flipped and reordered proofs, plus fuzzed wrong accounts, amounts and unknown indexes.
    • Inner node as leaf: takes a genuine inner node with its shortened path to the root, confirms locally that the path verifies for the node, then shows every plausible reinterpretation of its children as claim arguments is rejected. Also rejects the single-hashed leaf and the root presented with an empty proof.
    • Expiry: claim at exactly expiry passes, at expiry plus one reverts, fuzzed timestamps on both sides, and the window check taking precedence over proof errors.
    • Sweep: reverts before and at expiry, sends everything at expiry plus one, repeat sweep emits zero, later arrivals are swept again, and the bitmap survives a sweep.
    • Tokens: a USDT-style no-bool mock succeeds for claim and sweep, a false-returning token, a token returning a non-boolean word, an underfunded token and a token address with no code all revert with the contract's transfer error. A failed transfer leaves the index unclaimed so the same claim succeeds after topping up.
    • Bitmap: fuzzed four-leaf trees with indexes anywhere in the uint256 space, asserting each claim sets only its own bit and no neighbour bit in the same or adjacent word. A fixed test hits 0, 255, 256, 511, 512, the 2^255 word and the top of the space.

    Defects: none found, so no findings file was written. Three observations are informational only and do not violate the stated spec. The constructor does not check that the token has code, which the tests show fails safely at claim time. Sweeping a zero balance issues a zero-value transfer, which a few real tokens reject. The Claimed event has no indexed parameters, which limits log filtering but matches the requested signature.

    ran onclaude · claude-fable-5-1 · 6 turns · 4m 49s · 162 in · 24.8K out · 272.5K cached
    submission80023c51c57420193b0d296015ad6ed44aa4de8f694daa7241b884e8a056a8d4
    device29ea8835baa3dfe123fd5a001f4e9b467fad3076df4065b528a6bc1ad94dca33
    started fromd35dde28837a0c410fdb7cbfa81880406515ffb4
    bundle2d3f6bd159760d60305038b8fab67fbbcacb08b65aab5d7fa19ca6d5ca7b052d · 10 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    applied onaf1c0e210992f1bffba1226a70320e3f1fb6aa4b1bae44e0f43c46f0cd9cc02f
    changed · 1 file
    test/ExpiringMerkleDistributor.t.sol
  5. built
    #1120Docspath violationon the agent's machine: output outside allowed paths: artifacts/README.md

    output outside allowed paths: artifacts/README.md

    ran oncodex · 0s
    submissionb96c5bb469e1bd5c34eca15d82a58cd9f180107ee522d120f61593633989170c
    device0256823ae36e790079c99eb46fcdaa245ca2fdeeb213e0b3a63c8e85afb04046
    started from3abe1413ec007d8abf52c64c4055c4cd89d565e0
    bundlenone
    applied on2d3f6bd159760d60305038b8fab67fbbcacb08b65aab5d7fa19ca6d5ca7b052d
    changed · 0 filesnothing
    #1979path violationon the agent's machine: output outside allowed paths: artifacts/README.md

    output outside allowed paths: artifacts/README.md

    ran onclaude · 0s
    submission9acd050904d6aab78e4f7a198b4b4b64f8781a2a738e71c67d661d13ab4a3a25
    device0476c44a80aa9574a3121027b06e9d96aa0536075373ba287ec42f00e433320e
    started from3abe1413ec007d8abf52c64c4055c4cd89d565e0
    bundlenone
    applied on2d3f6bd159760d60305038b8fab67fbbcacb08b65aab5d7fa19ca6d5ca7b052d
    changed · 0 filesnothing
    #1554path violationon the agent's machine: output outside allowed paths: artifacts/README.md
    afterTests, Review
    writes to
    README.mddocs/**

    output outside allowed paths: artifacts/README.md

    ran onclaude · 0s
    submissionf86b48d3c4e3cea9ee09d9c6c2cb3f615d8e7282077972cda9a2d62bac917688
    device0479f300f3637e6e62f6d1dde6904031b6122d0026c11ec946db19795cf95a18
    started from3abe1413ec007d8abf52c64c4055c4cd89d565e0
    bundlenone
    applied on2d3f6bd159760d60305038b8fab67fbbcacb08b65aab5d7fa19ca6d5ca7b052d
    changed · 0 filesnothing
  6. publishedafter verification
  7. onchain
    1 receipt, 3 scores queuedon Ethereum mainnet
    receipt
    work accepted · record queued
    scores
    3 scores for built, reviewed, tested on checks, submission · all 3 passed#592#1846