Skip to content

Latest commit

 

History

History
146 lines (119 loc) · 7.72 KB

File metadata and controls

146 lines (119 loc) · 7.72 KB

UNTOLL hook: testing guide

Status: testnet-bound. The suite compiles and passes (130 tests across 18 suites on a cleared cache). A formal security review precedes mainnet. Some invariants are proven only against the implemented surface: their end-to-end halves are gated behind two settlement seams (see docs/ARCHITECTURE.md Section 7), and those tests assert the seam boundary today so they stay green without ever reading as a false pass.

Design reference: the invariant suite (25 invariants), docs/INVARIANTS.md, and docs/ARCHITECTURE.md. The always-current invariant map is the header of test/Invariants.t.sol.

The suite uses the conventional Foundry layout (separate unit/ invariant/ integration/ plus a shared base + handler pattern). All code is authored for this project.

1. Dependencies to install

contracts/remappings.txt declares the remappings; contracts/lib/ is git-ignored, so vendor the libraries with forge install. The commits below are the versions this suite is verified against; pin them (or use submodules) so CI and a fresh clone build the same bytecode:

Library Remapping Verified commit
foundry-rs/forge-std forge-std/ a6d71da
Uniswap/v4-core v4-core/, @uniswap/v4-core/ d153b04
Uniswap/v4-periphery v4-periphery/, @uniswap/v4-periphery/ 7ebd04b
OpenZeppelin/openzeppelin-contracts @openzeppelin/contracts/ fcbae53
OpenZeppelin/openzeppelin-contracts-upgradeable @openzeppelin/contracts-upgradeable/ aa677e9
OpenZeppelin/uniswap-hooks uniswap-hooks/, openzeppelin-uniswap-hooks/ acbd604
transmissions11/solmate solmate/ 4b47a19
Uniswap/permit2 permit2/ cc56ad0

Install (Foundry), pinned:

cd contracts
forge install foundry-rs/forge-std@a6d71da
forge install Uniswap/v4-core@d153b04
forge install Uniswap/v4-periphery@7ebd04b
forge install OpenZeppelin/openzeppelin-contracts@fcbae53
forge install OpenZeppelin/openzeppelin-contracts-upgradeable@aa677e9
forge install OpenZeppelin/uniswap-hooks@acbd604
forge install transmissions11/solmate@4b47a19
forge install Uniswap/permit2@cc56ad0

If the exact submodule paths differ from the remappings, adjust remappings.txt, not the imports. foundry.toml is set for solc 0.8.26, evm_version = cancun (transient storage, required by v4 and by the hook's transient guards), via_ir = true, and optimizer_runs matching v4-core / v4-periphery; fuzz runs = 512, invariant runs = 256 / depth = 64 (CI profile: fuzz 5000, invariant 1000 / depth 128).

2. Run commands

cd contracts

# compile
forge build

# the full suite (130 tests, 18 suites)
forge test -vvv

# just the unit lemmas
forge test --match-path "test/unit/*"

# just the stateful invariants
forge test --match-path "test/invariant/*"

# a single property
forge test --match-test invariant_14_splitConservation -vvvv

# the CI profile (deep fuzz + invariant)
FOUNDRY_PROFILE=ci forge test

3. Directory layout

contracts/test/
  Invariants.t.sol                      # the INDEX: 25 invariants -> file/function -> status (read first)
  unit/
    BondingCurve.t.sol                  # curve integral, crossing/no-straddle, continuity reference
    FeeRouter.t.sol                     # four-bucket split conservation, creator anti-grief, reward pro-rata, wash
    LaunchToken.t.sol                   # sole-minter gate, one-way renounce, supply accounting
    AntiSniper.t.sol                    # decaying-fee monotonicity + per-sender cap
    Pause.t.sol                         # pause-new-launches, guardian authority, bounded/auto-expiry, no fund lever
    TokenFactory.t.sol                  # fail-safe param validation, team cap, bucket sum, clone prediction
    ReferralAndHookToken.t.sol          # stake-multiplier timing + referral bind
  integration/
    HookTestBase.sol                    # shared setup: v4 PoolManager + HookMiner + hook + factory + one launch
    GraduationLifecycle.t.sol           # registration + immutability + lifecycle
  invariant/
    SplitConservation.invariant.sol     # inv 14/18/19/24 accrual conservation
    AccrueOnly.invariant.sol            # inv 15 no-external-call-on-swap + inv 8 sink isolation
    CurveConservation.invariant.sol     # inv 1/2/4/5/6 curve loop
    FlipReentrancy.invariant.sol        # inv 3/16 flip + steady-state guards
    NoRug.invariant.sol                 # inv 6/24 structural + accrual non-custody
    handlers/
      SplitHandler.sol                  # bounded fuzz actor for the FeeRouter accrual
      LaunchHandler.sol                 # bounded fuzz actor for the full swap path
  mocks/
    FeeRouterHarness.sol                # exposes the abstract FeeRouter internals for direct testing
    LaunchHookHarness.sol               # exposes LaunchHook internals + a test registrar
    MaliciousFeeSink.sol                # revert / gas-bomb / reentrancy sink (inv 8/15)
    MaliciousRecipient.sol              # reverting + reentrant creator recipient, malicious router (inv 16/18)
    MockHookToken.sol                   # Model B token read shape (inv 21/22)
    Actors.sol                          # FlashStakeActor (inv 22), WashFarmerActor (inv 23)

4. What is proven end-to-end vs gated on a seam

The authoritative, always-current map is the header of test/Invariants.t.sol. In summary:

  • Proven today (RUNS): four-bucket split conservation (14), accrue-only swap path (15), creator anti-grief precision (18), participation-reward conservation (19), O(1) pull distribution (20), multiplier-times-zero (21), and the RUNS halves of supply accounting (2), curve crossing math (4), the marginal-price reference (5), the accrual side of non-custody (6/24), pause blast radius (7), param immutability (9), decaying-fee monotonicity (10), the CEI + guard shape (16), flash-stake consumption (22), own-pool exclusion (23), and referral-bind integrity (25).
  • Gated on a settlement seam (SEAM): the end-to-end halves of 1, 2, 3, 4, 5, 6, 7, 8, 10, 16 and the full graduation lifecycle. These depend on docs/ARCHITECTURE.md Section 7's two seams (bonding-swap currency settlement, and the graduated-swap fee read). The tests assert the seam boundary today and are switched to the full property once the seam is authored.
  • Model B (UNIMPL): 11/12/13 (the platform token + BuyAndBurnSink) are out of this repo under the Model-A-first scope; their tests point at the Model B deliverable.
  • Sim / review-layer (SIM): the economic halves of 22 (time-weighting gameability) and 23 (thin-epoch stacked net-negativity), plus continuity (5) and delta settlement (17), which depend on v4 flash-accounting composition that is the external review's call.

5. The seams that gate the SEAM-marked tests

Per docs/ARCHITECTURE.md Section 7, these LaunchHook functions are provisional reverting seams. Authoring them unblocks the tests noted:

Seam Unblocks
bonding-swap currency settlement (the _beforeSwap take/settle) every bonding swap through the base; inv 1, 3, 5, 6 (POL side), full lifecycle
_graduatedFee (realized fee on a graduated swap) inv 14/15 on the graduated afterSwap path
_payout (fee-asset transfer) inv 8 sink isolation, reentrancy end-to-end (inv 16), claim transfers

6. Convention: how a SEAM test signals status

A SEAM/UNIMPL test carries _BLOCKED_ON_SEAM or _BLOCKED_ON_UNIMPLEMENTED in its name and either (a) vm.expectRevert() around the seam call so the file compiles and the status is explicit, or (b) a revert(...) with a message pointing at the unimplemented feature. When the seam is authored, remove the expectRevert / revert and assert the property documented in the test's NatSpec. This keeps a blocked property from ever reading as a false pass.