Bound

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

ItemWhere it is checked
ft_on_transfer only from the configured tokenft_on_transfer_from_other_token_rejected, sandbox
msg parse error panics so the token refundsft_on_transfer_bad_msg_panics_so_token_refunds, sandbox
unregistered beneficiary refunded, no state createdft_on_transfer_refunds_unregistered_beneficiary, sandbox
withdraw debits before the promise, callback re-credits on failurewithdraw_debits_before_promise, sandbox withdraw_failure_recredits_and_guards_hold
withdraw requires 1 yocto and >= 30 Tgaswithdraw_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 callbackaccept_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_idgrep finds no signer_account_id in lib.rs
checked u128 arithmetic, overflow-checks = true in releasenear/Cargo.toml
no unbounded iteration: list_certs limit <= 50, <= 16 claims per windowlist_certs_paginates, seventeenth_claim_rejected
storage deposits cover actual usagestorage_deposit_covers_usage and the runtime StorageNotCovered check
no admin setters; config immutableno setter exists; get_config
pay cannot take a deposit, so a function-call key can call itnear-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_reserve on an Invalid certificate waits for the deadline, and close_window no 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's deal flag, so a Verified certificate with float 0 is not a deal.
  • Deadline boundary. Release needs now >= deadline; challenge and pay need now < 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 publish or challenge above the requirement is refunded.
  • Halted view. Status reads Halted while halted, and deal is true only for Verified with float above 0.
  • Superseded certificate. pay refuses any certificate other than the one verify reports (NotCurrentCert).

Second round of that review

  • Post-deadline extension capped. closes_at never moves past deadline + claim_window_secs, so reject-and-refile cannot freeze collateral forever. With a Harm claim undecided, collateral stays frozen until at most deadline + claim_window_secs + claim_window_secs/3.
  • Emptied certificate. When a settlement drew the whole reserve and the float was 0, release_reserve now marks the certificate Released and returns 0.
  • Conservation test. conservation_across_windows checks 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_at on, the view reads Expired with deal=false while pay still works until the deadline.
  • Summing 16 capped harms overflows only above u128::MAX / 16 coverage, which a 6-decimal token cannot reach.

What remains open is listed under Known limitations.