Skip to main content

EscrowManager Fuzz Coverage

Vertical entrypoint: test/fuzzing/FuzzEscrowManagerIntegrity.sol Invariants: test/fuzzing/properties/Properties_ESCROW.sol Descriptions: test/fuzzing/properties/PropertiesDescriptions.sol

Scope

Direct exercise of EscrowManager deposits, withdrawals, claims, module authorization, direct surplus funding, and surplus recovery — bypassing the Marketplace offer flow. The harness registers a dedicated test module (FUZZ_ESCROW_TEST_MODULE at 0xC0000) under its own module type and routes authorized actions through it, so this vertical is isolated from the Marketplace / OrderbookMarketplace wirings.

Handlers

EntrypointCallerTargetPurpose
fuzz_createEscrowFUZZ_ESCROW_TEST_MODULEescrowManager.createEscrowAuthorized deposit path
fuzz_createEscrowUnauthorizedFUZZ_ESCROW_UNAUTH_CALLER (0xD0000)escrowManager.createEscrowAuthorization boundary
fuzz_withdrawFUZZ_ESCROW_TEST_MODULEescrowManager.withdrawReturn tokens to depositor
fuzz_claimFUZZ_ESCROW_TEST_MODULEescrowManager.claimTransfer escrow to beneficiary
fuzz_directTransferToEscrowtracked user (USER1..3)token.transferExercise the protected-receiver guard for direct bond-token pushes into EscrowManager
fuzz_multiStandardEscrowSurfaceFUZZ_ESCROW_TEST_MODULEcreateEscrow, escrows, supportsInterface, withdrawExercise ERC20, ERC721, and ERC1155 escrow transfer branches with fuzz-only mocks
fuzz_sweepharness admin (address(this))escrowManager.sweepRecover untracked surplus
fuzz_registerModuleharness adminescrowManager.registerModuleRegister a candidate module address
fuzz_deactivateModuleharness adminescrowManager.deactivateModuleDeactivate a registered module

FUZZ_ESCROW_TEST_MODULE and FUZZ_ESCROW_UNAUTH_CALLER are intentionally kept out of the engine sender sets (0x10000, 0x20000, 0x30000) so the fuzzer cannot impersonate them — authorized calls only reach the EscrowManager through the handler-controlled prank.

Invariants

createEscrow — group 20

Checked in createEscrowPostconditions for fuzz_createEscrow.

IDConditionChecked
ESCR-20createEscrow stores depositor, tokenAddress, tokenId, amount, asset type (ERC6909) and module type correctlyon success
ESCR-21nextEscrowId increments by exactly 1on success
ESCR-22EscrowManager bond balance grows by amounton success
ESCR-23Depositor bond balance shrinks by amounton success
ESCR-24escrowReserved grows by amounton success
ESCR-25No unexpected reverts (allowedErrors is empty)on revert

Multi-standard and residual coverage — group 70

Checked by the multi-standard escrow surface and the Orderbook residual coverage handler.

IDCondition
ESCR-70ERC20/ERC721/ERC1155 approval, create, getter/interface, and withdraw coverage calls do not unexpectedly revert
ESCR-71ERC721 balanceHeld coverage reports one for the minted escrow-held token
ESCR-72Invalid asset-type residual coverage reverts with the expected selector

createEscrow — authorization boundary

Checked in createEscrowUnauthorizedPostconditions for fuzz_createEscrowUnauthorized.

IDCondition
ESCR-26The call must revert with EscrowManager__ModuleNotRegistered(address) or EscrowManager__ModuleNotAuthorized(address,bytes32) — any other outcome is a bug

withdraw — group 30

Checked in withdrawEscrowPostconditions for fuzz_withdraw.

IDConditionChecked
ESCR-30escrow.amount shrinks by the withdrawn amounton success
ESCR-31Depositor bond balance grows by the withdrawn amounton success
ESCR-32EscrowManager bond balance shrinks by the withdrawn amounton success
ESCR-33No unexpected revertson revert

claim — group 40

Checked in claimEscrowPostconditions for fuzz_claim.

IDConditionChecked
ESCR-40escrow.amount shrinks by the claimed amounton success
ESCR-41Beneficiary bond balance grows by the claimed amounton success
ESCR-42EscrowManager bond balance shrinks by the claimed amounton success
ESCR-43No unexpected revertson revert

sweep — group 50

Checked in sweepPostconditions for fuzz_sweep.

IDConditionChecked
ESCR-50trackedEscrowAmountSum is preserved — sweep must not touch reserved escrowson success
ESCR-51escrowBalance >= escrowReserved is preservedon success
ESCR-52No unexpected revertson revert

directTransferToEscrow — group 53

Checked in directTransferToEscrowPostconditions for fuzz_directTransferToEscrow.

This action exercises the direct-push path into protected EscrowManager. If a deployment or test setup leaves the receiver unprotected, a successful transfer creates sweepable surplus; in the protected default setup the expected revert is Token__ProtectedReceiverTransferNotAllowed.

IDConditionChecked
ESCR-53Sender bond balance shrinks by the transferred amounton success
ESCR-54EscrowManager bond balance grows by the transferred amounton success
ESCR-55trackedEscrowAmountSum is preservedon success
ESCR-56escrowReserved is preservedon success
ESCR-57escrowSweepable grows by the transferred amounton success
ESCR-58Only the protected-receiver push-transfer guard is an expected reverton revert

Multi-standard escrow surface

fuzz_multiStandardEscrowSurface deploys and configures fuzz-only ERC20, ERC721, and ERC1155 tokens in setup, then creates and fully withdraws one synthetic escrow through the authorized test module. These escrows are not added to the tracked ERC6909 escrow buckets, because their purpose is branch coverage for transfer-standard handling, escrows(id), and supportsInterface rather than bond-token accounting invariants.

Global invariants — group 60

These run after any successful action via onSuccessInvariantsGeneral and are also checked from module-lifecycle postconditions.

IDConditionChecked
ESCR-01EscrowManager bond balance equals trackedEscrowAmountSum + escrowSweepableafter any success
ESCR-60escrowBalance >= escrowReserved must hold at all timesafter any success
ESCR-61registerModule and deactivateModule do not mutate trackedEscrowAmountSum, escrowBalance, or escrowReservedon success of lifecycle ops
ESCR-62After deactivateModule, the module address is no longer authorized and its moduleTypeOf entry is clearedon success of fuzz_deactivateModule
ESCR-63registerModule does not unexpectedly reverton revert
ESCR-64deactivateModule only reverts with ModuleHasActiveEscrows after handler preconditionson revert

Preconditions

Summary of the clamping and state-selection logic applied before each handler issues a protocol call. Full implementation in helper/preconditions/PreconditionsEscrowManager.sol.

HandlerClamp rules
createEscrow / createEscrowUnauthorizedToken must not be paused. Depositor picked from tracked users with balance > frozenBalance and isOperator(user, escrowManager) == true; amount clamped to [1, freeBalance] where freeBalance = balance - frozenBalance. Unauthorized variant reuses the same param shape so the only distinguishing factor is the prank target.
withdrawToken must not be paused. Escrow picked from EscrowBucket.FuzzTestFunded (filtered by moduleType == FUZZ_ESCROW_TEST_MODULE_TYPE and amount > 0). Amount clamped to [1, escrow.amount].
claimToken must not be paused. Same escrow-bucket rule as withdraw. Amount clamped to [1, escrow.amount]. Beneficiary picked uniformly from tracked users.
directTransferToEscrowToken must not be paused. Sender picked from tracked users with balance > frozenBalance; amount clamped to [1, freeBalance]. Unlike createEscrow, this is a direct push transfer; protected EscrowManager is expected to reject it.
multiStandardEscrowSurfacePicks ERC20, ERC721, or ERC1155 from the fuzz-only mock set. Mints enough test inventory to the selected depositor, approves EscrowManager, creates one authorized escrow, checks getter/interface surfaces, then withdraws the full synthetic amount.
sweepToken must not be paused. Requires non-zero getSweepableAmount(ERC6909, token, bondTokenId). Amount clamped to [1, surplus]. Beneficiary picked uniformly from tracked users.
registerModuleCandidate picked from tracked users; requires moduleTypeOf(candidate) == bytes32(0) so an already-registered address cannot be re-registered.
deactivateModuleScans tracked users in rotation from the seed and picks the first one where moduleTypeOf(user) == FUZZ_ESCROW_TEST_MODULE_TYPE. Core modules (MARKETPLACE_MODULE, ORDERBOOK_MARKETPLACE_MODULE) and FUZZ_ESCROW_TEST_MODULE itself are never reachable from this handler.

All clamp failures raise ClampFail(string), which the integrity layer accepts as a legitimate "skip this tick" outcome rather than a bug.