fix: fix: Bootstrap VWAP with seed trade during deployment (#567) (#567)

Deploy scripts (DeployLocal.sol and DeployBase.sol) now execute a
seed buy + double-recenter sequence before handing control to users:

1. Temporarily grant deployer recenterAccess (via self as feeDestination)
2. Fund LM with a small amount and call recenter() -> places thin positions
3. SeedSwapper executes a small buy, generating a non-zero WETH fee
4. Second recenter() hits the cumulativeVolume==0 bootstrap path with
   ethFee>0 -> _recordVolumeAndPrice fires -> cumulativeVolume>0
5. Revoke recenterAccess and restore the real feeDestination

After deployment, cumulativeVolume>0, so the bootstrap path is
unreachable by external users and cannot be front-run by an attacker
inflating the initial VWAP anchor with a whale buy.

Also adds:
- tools/deploy-optimizer.sh: verification step checks cumulativeVolume>0
  after a fresh local deployment
- test_vwapBootstrappedBySeedTrade() in VWAPFloorProtection.t.sol:
  confirms the deploy sequence (recenter + buy + recenter) leaves
  cumulativeVolume>0 and getVWAP()>0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-12 21:15:35 +00:00
parent b456bc75fd
commit c05b20d640
4 changed files with 278 additions and 20 deletions

View file

@ -212,6 +212,42 @@ contract VWAPFloorProtectionTest is UniSwapHelper {
}
}
// =========================================================================
// Deployment bootstrap: seed trade seeds VWAP before protocol goes live
// =========================================================================
/**
* @notice Verifies the deployment bootstrap sequence from DeployLocal.sol / DeployBase.sol.
*
* The deploy scripts execute:
* 1. First recenter places bootstrap positions; no fees, cumulativeVolume stays 0.
* 2. Seed buy small swap generates a non-zero WETH fee in the anchor position
* and moves the tick >400 (amplitude gate for the second recenter).
* 3. Second recenter cumulativeVolume==0 path fires (shouldRecordVWAP=true) and
* ethFee>0, so _recordVolumeAndPrice is called.
*
* After step 3, cumulativeVolume>0 and the bootstrap path is permanently closed to
* external users. This test mirrors that sequence and asserts the invariant holds.
*/
function test_vwapBootstrappedBySeedTrade() public {
// Step 1: Initial recenter places positions, no fees yet.
vm.prank(RECENTER_CALLER);
lm.recenter();
assertEq(lm.cumulativeVolume(), 0, "no fees before seed trade: cumulativeVolume must be 0");
// Step 2: Seed buy enough to move the tick >400 (amplitude gate) and generate fee.
// 25 ether against a 100 ETH LM pool reliably satisfies the amplitude check
// (same amount used across other bootstrap tests in this file).
buyRaw(25 ether);
// Step 3: Second recenter bootstrap path records VWAP.
vm.prank(RECENTER_CALLER);
lm.recenter();
assertGt(lm.cumulativeVolume(), 0, "seed trade must bootstrap cumulativeVolume to non-zero");
assertGt(lm.getVWAP(), 0, "seed trade must anchor VWAP to the real launch price");
}
// =========================================================================
// getLiquidityManager override for UniSwapHelper boundary helpers
// =========================================================================