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.
8bf98e1897c946b88f860083edf3b743dbd234bdVERDICT: all checks passed
VERDICT: all checks passed
No substantiated defects found. Reviewed contracts, tests, ABI exports, and manifest against the approved requirements, including authorization, replay protection, expiry boundaries, accounting, and reentrancy. Wrote `{"findings":[]}` to `.imd-findings.json`. Project files remain unchanged. Tests were inspected, not rerun.
Created [launch.json](/home/worker/.identitymd/work/b56fcd51-cd2d-4cb8-9a7d-9ad319b700c8/d1945465-17c0-4727-9854-d5f9196334d3/launch.json) for: - Token: `ConduitToken` (`Conduit`, `CNDT`, 18 decimals) - Application: `PaymentChannel($token)` - Native ETH pool with canonical fee, spacing, and initial price Verification passed: - Valid JSON and manifest-shape checks - `forge build` - `forge test`: 44 passed, 0 failed - Only `launch.json` is changed
Review complete. Source tree untouched (`.imd-findings.json` is in `.git/info/exclude`); all PoCs ran in a throwaway copy at `/tmp/rev`. **What I verified first:** `forge build` + `forge test` (44 pass), `forge fmt --check` clean, `lib/forge-std` vendored as plain files with no submodule, and `docs/abi/*.json` byte-identical to `forge inspect … abi` for both contracts. I also reconstructed the real launch shape and ran the protected floor against it (factory-as-CREATE2-deployer, token at salt 1 then `PaymentChannel($token)`, chain 11155111): all 8 protected tests pass, including supply preservation across the application constructor and the no-DELEGATECALL/SELFDESTRUCT runtime scan. **What held up under attack:** a 30,000-call stateful invariant run (open/close/reclaim/warp, 4 actors, random signers) kept `balanceOf(pc) == sum of open deposits` and total supply conserved, and never once closed with a foreign signature or reclaimed before expiry / by a non-payer. Cross-function reentrancy (re-entering `open` from `close`'s transfer) is blocked by the shared guard, not just the same-function cases their tests cover. Signature handling is strict and correct: 65-byte only, low-s per EIP-2, v ∈ {27,28}, zero-recovery rejected, digest recomputed per call so chain-id and verifyingContract replay both fail. The close-after-expiry race is per the brief ("payee closes at any time" / "after the timeout the payer reclaims"), not a defect. **5 findings, ranked:** | Sev | Finding | |---|---| | medium | `open` silently accepts a smart-account payer (Safe/Coinbase Smart Wallet — reachable via the mandated RainbowKit stack); `close`'s ecrecover-only check then rejects every voucher forever, so the payee is never paid and the payer reclaims 100% at expiry. PoC: ERC-1271 wallet opens a 1000e18 channel, its own `isValidSignature` accepts the voucher, `close` reverts `InvalidSignature()`. ERC-1271 support is out of design (no external calls beyond the token), so I stated the tradeoff instead of prescribing a redesign — a code-length guard in `open` would also reject EIP-7702 EOAs, which can sign. | | low | A voucher signed for a not-yet-existing channel id is live against the payer's next channel with that id, for whatever payee it names. PoC: sign `voucherDigest(1, 1000e18)` at `channelCount == 0`, then `open(...)` → id 1, payee closes for the whole deposit. README line 53 implies this cannot happen. | | info | CNDT sent directly to `PaymentChannel` is unrecoverable (consequence of the required no-owner design; does not affect solvency). | | info | `ConduitToken.transferFrom(address(0), x, 0)` succeeds from any caller and emits a mint-shaped `Transfer(0x0, x, 0)`; `_transfer` checks `to` but not `from`. No economic impact, misleading log stream. | | info | Scope note: no `launch.json` in the tree at 54ea1fd, so the manifest half of "contracts and manifest" is still owed a pass. I pre-checked the README's stated manifest plan against the LaunchManifest schema and it is consistent; the risk to watch is a hard-coded token address replacing `$token`. | Findings written to `.imd-findings.json`; no repository file was created, modified or deleted.
I built the Conduit token and the PaymentChannel contract, with tests, ABI files and a README. `forge build`, `forge test` (44 tests, all passing) and `forge fmt --check` all pass offline. I also ran the two provided protected test suites locally against the actual creation code. For the project check I simulated a factory deployment at chain id 11155111. All 8 checks passed. **What's there** - **`src/ConduitToken.sol`**: the CNDT token, with 18 decimals and no constructor arguments. It mints exactly 10^27 base units (1 billion CNDT) to the deployer. There is no mint function, owner, pause, proxy or self-destruct. - **`src/PaymentChannel.sol`**: its only constructor argument is the token address (`$token`). - **`open(payee, amount, expiresAt)`**: the payer deposits CNDT for a payee. It rejects a zero or self payee, a zero amount, and an expiry that isn't in the future. It also checks the contract received exactly the deposit. - **`close(channelId, amount, signature)`**: only the payee can call it, at any time, including after expiry. It needs the payer's EIP-712 voucher for a cumulative amount no larger than the deposit. The payee gets that amount and the payer gets the rest back. - **`reclaim(channelId)`**: only the payer can call it, once the current block time is at or past `expiresAt`. It returns the whole deposit. - There is no owner, admin, fee or upgrade path. State is cleared before any token transfer, and a reentrancy guard covers all three functions. Every state change emits an event. - **`test/`**: tests for every path: wrong amounts, unauthorized callers, expiry exactly at and one second before the timeout, and double close or reclaim. They also cover replaying a voucher on another channel, contract or chain; malformed, reordered or high-`s` signatures; tokens that return false or take a transfer fee; reentrancy through a malicious token on all three functions; and fuzz tests showing no funds are created or lost. - **`docs/abi/ConduitToken.json`** and **`docs/abi/PaymentChannel.json`**. - **`README.md`**: covers the rules, the voucher format for the website, assumptions, deployment parameters and who is responsible for each later step. - **Build setup**: `foundry.toml` pins solc 0.8.26 with `bytecode_hash = "none"` and no ffi or file access. `forge-std` is copied into `lib/` as plain files, not a submodule. **Limitations (documented in the README)** - Only vouchers signed by ordinary wallets are accepted. Contract wallets would need calls to contracts other than the token, which the spec forbids. - After expiry, whichever of `close` and `reclaim` runs first settles the channel. A payee who waits past the timeout can lose amounts the payer already signed for. - Channels can't be extended or topped up; the payer has to open a new one. - Timeouts depend on block time, which can drift by a few seconds. **Still to do (outside this assignment)** - The manifest node writes `launch.json`: token `ConduitToken`, then `PaymentChannel` with `["$token"]`. No argument grants a privileged role. - The independent review before deployment has not happened yet. Passing tests is not an audit. I committed everything to `main` as `7e0e8dd`. Git had no author identity set, so I used your name and email for that commit only, without changing any git config.
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.