shape:chain
WORKFLOW CONTRACT STAGE CONTEXT: the stage produces implemented and tested contracts, ABI documentation and an independently reviewed launch.json. Each assignment contributes only within its own role and write scope. Source-producing assignments own implementation, tests and ABI exports at docs/abi/<Contract>.json where their scope permits. The generated manifest assignment writes only launch.json. Review assignments inspect accepted source and manifest and return findings without editing files; they do not implement contracts or generate ABI files. Use the supplied canonical manifest guidance: policy and signed artifact linkage belong to services, while concrete source, constructor, policy or authorization conflicts remain review findings. Services publish source, attest, admit and deploy after this stage, then start the frontend. Read .imd/reads/workflow.md for the complete approved requirements and apply them to your assigned contribution; later service outcomes are not prerequisites of this assignment.
339db0c6f8060e9fa0b10dc3c8d1ad0672c85c5cVERDICT: all checks passed
VERDICT: all checks passed
No substantiated defects found. Wrote `{"findings":[]}` to `.imd-findings.json`. Reviewed contracts, tests, protected checks, and manifest; verified exported ABIs against compiler output. Source, tests, and configuration remain unchanged.
`launch.json` is written; it is the only file added or changed. **What it declares** - `kind`: `"evm_project"`. - `token`: `CliffhangerToken` / "Cliffhanger" / "CLIF" / 18 — matches `src/CliffhangerToken.sol:9-11` exactly. Zero-argument constructor, so no args field, and no supply/allocation fields. - `contracts`: one entry, `VestingCliff`, with `constructorArgs: ["$token"]`. `src/VestingCliff.sol:71` takes exactly one parameter, `address token_`, so the single reference sits in an address-typed slot and resolves to the launch token, which is deployed first. No `$contract:` reference is needed and ordering is trivially forward-consistent. - No `$owner` anywhere: the accepted source has no owner/admin/fee/revoke parameter or role to assign, so naming one would misdescribe the implementation. Ownership stays a policy concern. - `pool`: native ETH (zero address), fee 3000, tickSpacing 60, `initialPrice` "79228162514264337593543950336" — Sepolia policy v3 defaults, no hook. - `notes` (3820 chars): what each contract is, why the argument list is what it is, the vesting/claim behavior a reviewer should cross-check against source, and an explicit statement of which deployment inputs are service-owned and deliberately absent. **Checks run** - `forge build` clean, `forge test` 43/43 passing across all 3 suites (unchanged — I touched no Solidity). - Validated the manifest in `test/scratch/validate.py` against the canonical schema plus its refinements: field sets, identifier pattern and ASCII/32-char limits, uniqueness, `MerkleDistributor` reservation, backward-only references, `initialPrice` < 2^256, arg count/length, notes ≤ 4000. Result: valid. - `git status` shows only `launch.json` untracked; nothing else modified, no submodules, no config touched.
Review complete. No repository files were changed — the findings are in `.imd-findings.json` (git-excluded by design; `git status` is clean). ## Settling the previous finding **[high] Vesting accrues before the cliff (`src/VestingCliff.sol:178`) — CONFIRMED FIXED.** `_vested` now reads `(s.amount * (timestamp - s.cliff)) / (s.end - s.cliff)` (`src/VestingCliff.sol:179`). I ran the original reproduction verbatim on the current code (amount 1000e18, start 1700000000, cliff 1700000250, end 1700001000): | time | vested | before | |---|---|---| | cliff (1700000250) | **0** | 250e18 | | cliff+375 (half of cliff→end) | 500e18 | — | | end−1 | 998.666…e18 | — | | end | 1000e18 | — | `claimableAmount` is 0 at the cliff and `claim` reverts `NothingToClaim(0)` with custody and `totalLocked` untouched. The `cliff == end` case still releases the full amount at `end` without dividing by zero (line 175 short-circuits before line 179). I independently verified beyond the regression test: a per-second sweep over a 300-second schedule is monotonic, exactly zero at and before the cliff, and reaches the full amount at `end`; 27 incremental claims across an odd 997-second duration settle to exactly the granted amount with `token.balanceOf(vesting) == totalLocked()` after every step; a whole-supply schedule (999,500,000e18) ending at `type(uint64).max` neither overflows nor under-pays. `forge test` passes 43/43, `forge fmt --check` passes, and both `docs/abi/*.json` files are byte-for-byte equivalent to `forge inspect … abi` for the current sources. I also replicated the protected floor's shape (factory CREATE2 deploys the token first, then `VestingCliff` with the token address): supply intact at 1e27 held by the factory, runtimes 1,338 and 3,670 bytes, no `DELEGATECALL`/`CALLCODE`/`SELFDESTRUCT`, and the application rejects ETH. ## New finding One, **low**, non-blocking: `createSchedule` lets anyone push unbounded entries into `_byBeneficiary[victim]` with 1 minor unit of CLIF, and `schedulesOfBeneficiary` (`src/VestingCliff.sol:158`) returns the whole array with no pagination or length getter. Measured: 215,399 gas per spam schedule for the attacker; the view costs 1,180,189 gas at 500 entries, 3,573,139 at 1,500, 7,315,129 at 3,000 (~2,440/entry), so ~12,000 entries push it past a 30M-gas `eth_call` ceiling for that address permanently. Funds are never at risk — `claim(id)`, `vestedAmount(id)` and `claimableAmount(id)` are O(1) and unaffected, and `ScheduleCreated` events still carry the ids — so this is an availability issue for the website's "see schedules" view, not a loss or authorization defect. I did not prescribe a fix; pagination, a minimum amount, or off-chain event indexing in `web/` are alternatives that belong to whoever owns that scope. Nothing else reproduced. I probed cross-schedule claim confusion (blocked by `NotBeneficiary`), direct CLIF donations (stuck, documented, accounting unaffected), sub-duration dust amounts (floor rounding, full amount still paid at `end`), allowance front-running (`transferFrom` pulls only from `msg.sender`), `totalLocked` under/overflow, and the reentrancy guard on both entry points. No `launch.json` exists in the tree yet, which is expected — the manifest assignment has not run, and per the stage guidance an early review does not require it.
Reproduced and fixed the finding. Vesting now accrues from cliff to end, with zero vested at the cliff when `cliff < end`. Updated regression tests, documentation, and `.imd-responses.json`. ABIs remain unchanged. Offline checks passed: `forge build`, all 43 tests, and `forge fmt --check`.
Created [.imd-findings.json](/home/worker/.identitymd/work/c976c67e-0fa9-4ba8-b264-f11d8c0cbae6/b3fe0a06-3492-46ca-9192-681bc66fceb8/.imd-findings.json) with one high-severity, reproducible vesting-semantics finding. No other files were modified.
I've built the Cliffhanger token (CLIF), the VestingCliff contract, their tests, the ABI files and a README, and committed them as `6f07dcb`. Run offline, `forge build` compiles cleanly, `forge test` passes all 42 tests, and `forge fmt --check` passes. **What's delivered** - **`src/CliffhangerToken.sol`:** Cliffhanger (CLIF), 18 decimals, no constructor arguments. The constructor mints exactly 1,000,000,000 CLIF to whoever deploys it. There is no mint function, owner, admin, pause or upgrade path. - **`src/VestingCliff.sol`:** its only constructor argument is the token address (`$token`). There is no owner, fee, admin or upgrade path, and no way to revoke a schedule. - `createSchedule` pulls the tokens in and checks exactly that amount arrived. - `claim` can only be called by the beneficiary. - Read functions cover what the website needs: schedule details, vested and claimable amounts, and schedule lists per beneficiary and per funder. - Every state change emits an event. The contract updates its own records before calling the token, and also blocks re-entry. - **Tests:** they cover invalid inputs, missing allowance or balance, the timing boundaries at the cliff and the end, and partial, full and repeated claims. They also check that strangers and the funder can't claim, and that there is no revoke or admin function. Adversarial tokens cover re-entry during both create and claim, a token that takes a transfer fee, one that returns false, and one that returns nothing. A fuzz test checks that vesting only goes up and that no tokens are lost. - **ABIs** are in `docs/abi/CliffhangerToken.json` and `docs/abi/VestingCliff.json`. - **`README.md`** documents the rules, assumptions, deployment parameters (Sepolia 11155111; manifest: token `CliffhangerToken`, then `VestingCliff` with `["$token"]`; pool terms) and who is responsible for what. **Protected checks:** I also ran both supplied protected suites locally against the real compiled code, with a stand-in factory and the predicted addresses. The token suite passed 6 of 6 and the project suite passed 2 of 2. **Decision for you:** the brief doesn't say where the straight-line vesting starts. I made it start when the schedule is created, with nothing claimable before the cliff. That means the share earned between creation and the cliff becomes claimable all at once at the cliff, which is the usual meaning of a cliff. If you meant vesting to start from zero at the cliff and rise to the full amount at the end, the formula needs changing before launch. The README says this. **Other notes** - forge-std v1.9.7 is included in `lib/forge-std` as ordinary files, not a submodule, so the build needs no network. - Git had no author identity set up, so I committed using your email via command-line flags, without changing any git config. - Tokens sent straight to the VestingCliff contract, outside `createSchedule`, can never be recovered. A beneficiary that can't call `claim` leaves its tokens locked for good. Both are on purpose because schedules can't be revoked, and both are documented. - Passing tests are not an audit. The independent review before deployment is still needed.
Proof Of IMD pays $POI to the current owner of the seat whose submission passed verification. The owner is the ERC-721 holder reported by GET /seats/:tokenId.
No result files for this job.