Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how →
F-2026-0005·configuration

Committed timing profile is development, releasing buyer funds minutes after funding

Fixedescrowarbitrationdispute-resolution
TL;DR

The committed timing profile is the development one, with minute-scale windows compiled into bytecode. A build from the repository as checked out releases buyer funds to sellers minutes after funding, removing buyer protection on every deal.

Severity
HIGH
Impact
HIGH
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
MEDIUM
02Section · Description

Description

contracts/lib/ProtocolTimings.sol is committed carrying the development profile:

solidity
string internal constant PROFILE = "development";
uint256 internal constant DELIVERY_PERIOD_UNIT = 1 minutes;
uint256 internal constant BUYER_OBJECTION_GRACE = 3 minutes;
uint256 internal constant REVEAL_WINDOW = 3 minutes;
uint256 internal constant PENDING_ABORT_WINDOW = 5 minutes;

These values are read as constant at compile time and baked into deployed bytecode, so anyone who compiles the repository as checked out ships minute-scale windows. The regeneration step runs only from Hardhat's precompile hook, so any Foundry build compiles the committed file unchanged, as does any Hardhat compile that does not regenerate first.

Vulnerable Scenario: The following steps illustrate the issue:

  1. A deployer checks out the audited commit and builds without regenerating timings.
  2. Contracts deploy with DELIVERY_PERIOD_UNIT = 1 minutes and BUYER_OBJECTION_GRACE = 3 minutes.
  3. A buyer funds a deal with timeToDeliverDays = 5, expecting five days plus a 72-hour objection window.
  4. _activateFunding sets deliveryDueAt = fundedAt + 5 minutes and deadline = deliveryDueAt + 3 minutes.
  5. Eight minutes after funding, any caller may invoke autoRelease and pay the seller in full. The buyer's entire window to inspect delivery and dispute has elapsed.
03Section · Impact

Impact

Buyer protection is removed on every deal, with escrowed funds released to sellers automatically minutes after funding. No revert or event signals the misconfiguration. protocolTimingProfile() exposes the active profile, but nothing on-chain reads or enforces it, so a deployment carrying development timings is indistinguishable at runtime from a correct one.

04Section · Recommendation

Recommendation

Commit the production profile as the checked-in default so that the safe configuration is what an unmodified build produces and the testnet profile is the one requiring an explicit regeneration step. Add a test asserting the concrete window constants against config/protocol-timings.json — for example BUYER_OBJECTION_GRACE >= 72 hours under the production profile — so that a build carrying development timings fails rather than deploying silently. Assert the constants themselves rather than the PROFILE label, since a label check passes when the label is right and a value is wrong.

test/helpers.js hardcodes the development windows and states that it must match the checked-in profile, so switching the default will break the suite as written. Have the helpers read the constants from the deployed contract rather than duplicate them, otherwise the same drift simply reappears in the fixtures.

05Section · Resolution

Resolution

Fixed. The committed artifact no longer carries the development timing profile.

06Section · Affected files

Affected files

  • contracts/lib/ProtocolTimings.sol
  • config/protocol-timings.json
  • scripts/generate-protocol-timings.js
  • contracts/Escrow.sol#L129-L133
  • test/helpers.js#L10-L23
Status
Fixed
F-2026-0005