Reference
Security
The security checklist and the review findings fixed before submission.
The contract was reviewed before submission in several rounds. Every contract fix has a regression test in src/tests.rs that pins the fixed behaviour. The full write-up is DESIGN.md, section 10 (review fixes) and section 6 (checklist). The test suite is 120 unit tests in tests.rs, 12 in settle.rs, 2 near-sandbox tests and 4 for the mock token.
Checklist
| Item | Where it is checked |
|---|---|
| ft_on_transfer only from the configured token | ft_on_transfer_from_other_token_rejected, sandbox |
| msg parse error panics so the token refunds | ft_on_transfer_bad_msg_panics_so_token_refunds, sandbox |
| unregistered beneficiary refunded, no state created | ft_on_transfer_refunds_unregistered_beneficiary, sandbox |
| withdraw debits before the promise, callback re-credits on failure | withdraw_debits_before_promise, sandbox withdraw_failure_recredits_and_guards_hold |
| withdraw requires 1 yocto and >= 30 Tgas | withdraw_without_yocto_rejected, withdraw_with_low_gas_rejected |
| every mutating method needs 1 yocto or its storage deposit, except pay, ft_on_transfer and the private callback | accept_without_yocto_rejected, storage_deposit_too_small_rejected, publish_without_deposit_rejected, challenge_without_deposit_rejected |
| roles checked with predecessor_account_id, never signer_account_id | grep finds no signer_account_id in lib.rs |
| checked u128 arithmetic, overflow-checks = true in release | near/Cargo.toml |
| no unbounded iteration: list_certs limit <= 50, <= 16 claims per window | list_certs_paginates, seventeenth_claim_rejected |
| storage deposits cover actual usage | storage_deposit_covers_usage and the runtime StorageNotCovered check |
| no admin setters; config immutable | no setter exists; get_config |
| pay cannot take a deposit, so a function-call key can call it | near-sdk rejects a deposit on non-payable methods |
First review: fixed in bound-v2
Certificate id aliasing. bound-v1 truncated u64 ids to u32. Cert id 2^32 resolved to cert 0 but opened its own claim window, and closing that alias window unfroze cert 0 while the real window was still open, so collateral could leave before an upheld victim was paid. It also bypassed the 16-claim cap. Now any id above u32::MAX is NoSuchCert / NoSuchChallenge, and frozen means a window exists for the certificate's own stored id.
The same round added the claim-window griefing defences: ConflictedChallenger, half the bond forfeited for an unadjudicated claim, the late-claim extension and the arbiter grace in close_window.
Second review: fixed in bound-v3
C1, major: the operator could end its liability early
With no allowlist, the agent could loop the float through an operator sybil until spent > bound; the sybil filed BoundExceeded, and the hygiene settlement released the reserve and the allocation at once, so later victims got NotChallengeable. Now:
- a settlement halts the certificate for spending but leaves it open to Harm claims until the deadline;
release_reserveon an Invalid certificate waits for the deadline, andclose_windowno longer frees the allocation;- trustless predicates are refused on a settled certificate, so the hygiene bounty cannot be drawn twice.
C2, major: claim-slot squatting at the deadline
Sixteen minimum-bond Harm claims filed just before the deadline filled the only window, rejected claims kept their slots, and the real victim was locked out. Now an arbiter-rejected claim leaves the window at once, a claim after the deadline is still accepted into a window that opened before it and is still open, and a window takes one claim per predicate kind.
Minor findings
- Harm overflow. Proven harm is capped at coverage, and the waterfall uses a 256-bit
mul_div, exact for every u128 input. - Empty second settlement. A window that moves no funds keeps the earlier settlement record.
- Client verdict.
assess()follows the contract'sdealflag, so a Verified certificate with float 0 is not a deal. - Deadline boundary. Release needs
now >= deadline; challenge and pay neednow < deadline(or, for a claim, the open pre-deadline window). - Conflicted victim. The victim may not be the operator, agent or auditor (
ConflictedVictim). - Excess deposit. NEAR attached to
publishorchallengeabove the requirement is refunded. - Halted view. Status reads
Haltedwhile halted, anddealis true only for Verified with float above 0. - Superseded certificate.
payrefuses any certificate other than the oneverifyreports (NotCurrentCert).
Second round of that review
- Post-deadline extension capped.
closes_atnever moves pastdeadline + claim_window_secs, so reject-and-refile cannot freeze collateral forever. With a Harm claim undecided, collateral stays frozen until at mostdeadline + claim_window_secs + claim_window_secs/3. - Emptied certificate. When a settlement drew the whole reserve and the float was 0,
release_reservenow marks the certificate Released and returns 0. - Conservation test.
conservation_across_windowschecks that balances, stake, reserve, float, premium and pending bonds add up at every step.
Third review: documentation and tests only
No blocker or major issue. Its minor findings are behaviours of the deployed code, now written down and pinned by tests; the built wasm still hashes to the recorded bound-v3 value.
- A claim into an expired but not yet closed window joins it and extends it under the same rule and cap.
- From
expires_aton, the view readsExpiredwithdeal=falsewhilepaystill works until the deadline. - Summing 16 capped harms overflows only above
u128::MAX / 16coverage, which a 6-decimal token cannot reach.
What remains open is listed under Known limitations.