From 060a81900e567cc4a272318e590269a2627e1248 Mon Sep 17 00:00:00 2001 From: c Date: Tue, 1 Sep 2026 12:33:14 +0000 Subject: [PATCH 1/3] =?UTF-8?q?genesis:=201,000,000,000=20VSP=20exactly=20?= =?UTF-8?q?=E2=80=94=20defaults=201B=20/=20100M=20liquid=20/=20900M=20lock?= =?UTF-8?q?ed=20(patch=5Fgenesis=5F1b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Founder decision 2026-09-01. The former 1,000,002,000 stacked a 2,000 VSP LP-seed allowance on the liquid tranche; the LP seed is now drawn from the 100M liquid tranche instead. Script defaults + GenesisOneShot.t.sol constants updated; the in-script liquid+locked == INCEPTION_SUPPLY assertion is unchanged. With a flat cap genesis is irreversible, so the CODE default must be the decided number — env overrides alone would leave a mainnet footgun. --- script/Deploy.s.sol | 7 +++++-- test/GenesisOneShot.t.sol | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index f3489d6..7dcc870 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -119,7 +119,10 @@ contract Deploy is Script { // wrongly-granted future minter cannot mint. StakeEngine (exempt) is // the only address that can move supply, per protocol staking mechanics. vm.envOr("VSP_INCEPTION_TIMESTAMP", uint256(1778544000)), - vm.envOr("VSP_INCEPTION_SUPPLY", uint256(1_000_002_000 * 1e18)), + // 2026-09-01 (patch_genesis_1b): genesis is 1,000,000,000 VSP EXACTLY + // (founder decision). The former +2,000 was an LP-seed allowance stacked + // on the liquid tranche; the LP seed is now drawn FROM liquid instead. + vm.envOr("VSP_INCEPTION_SUPPLY", uint256(1_000_000_000 * 1e18)), vm.envOr("VSP_GROWTH_BASE_PER_YEAR", uint256(1e18)), predictedStakeProxy // patch_bundle10_5_part2a_stakeengine_exempt ); // patch_bundle10_5_part2a_timecap: 4-arg constructor @@ -215,7 +218,7 @@ contract Deploy is Script { // VSP_INCEPTION_SUPPLY so the flat cap is filled exactly: any later // capped mint of even 1 wei reverts MintExceedsTimeWindowCap. address genesisTreasury = vm.envOr("VSP_GENESIS_TREASURY", deployer); - uint256 genesisLiquid = vm.envOr("VSP_GENESIS_LIQUID_SUPPLY", uint256(100_002_000 * 1e18)); + uint256 genesisLiquid = vm.envOr("VSP_GENESIS_LIQUID_SUPPLY", uint256(100_000_000 * 1e18)); // patch_genesis_1b uint256 genesisLocked = vm.envOr("VSP_GENESIS_LOCKED_SUPPLY", uint256(900_000_000 * 1e18)); require( genesisLiquid + genesisLocked == token.INCEPTION_SUPPLY(), diff --git a/test/GenesisOneShot.t.sol b/test/GenesisOneShot.t.sol index 4b55a84..b0569a5 100644 --- a/test/GenesisOneShot.t.sol +++ b/test/GenesisOneShot.t.sol @@ -19,8 +19,8 @@ import {MockCPAMM} from "../src/mock/MockCPAMM.sol"; /// everything at end; release() pays the beneficiary /// G6 after revoking the deployer, the minter set is exactly {stakeEngine} contract GenesisOneShotTest is Test { - uint256 constant GENESIS = 1_000_002_000 * 1e18; - uint256 constant LIQUID = 100_002_000 * 1e18; + uint256 constant GENESIS = 1_000_000_000 * 1e18; // patch_genesis_1b: 1B exactly + uint256 constant LIQUID = 100_000_000 * 1e18; uint256 constant LOCKED = 900_000_000 * 1e18; uint256 constant INCEPTION_TS = 1_778_544_000; uint64 constant VEST_DURATION = uint64(4 * 365 days); From 36abebdc0e7d378696843ebe890e85733f8894fb Mon Sep 17 00:00:00 2001 From: c Date: Tue, 1 Sep 2026 15:06:33 +0000 Subject: [PATCH 2/3] =?UTF-8?q?genesis:=20no=20lock=20at=20genesis=20?= =?UTF-8?q?=E2=80=94=20whole=20supply=20to=20treasury;=20VestingWallet=20o?= =?UTF-8?q?ptional=20via=20env=20(patch=5Fgenesis=5Fnolock)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Founder ruling 2026-09-01: no treasury lock at genesis. A lock is one-way (add later by transferring into a VestingWallet; never removable), so it is deferred until a concrete trigger (aggregator listing, counsel, cap increase). Deploy.s.sol: VSP_GENESIS_LOCKED_SUPPLY defaults to 0; liquid defaults to INCEPTION_SUPPLY - locked; the VestingWallet is constructed only when a locked tranche is configured. The liquid+locked == INCEPTION_SUPPLY and totalSupply == INCEPTION_SUPPLY assertions are unchanged. GenesisOneShot.t.sol: setUp mints the whole supply to the treasury; G5 now exercises the optional lock path (treasury -> VestingWallet transfer) and a G5b asserts the no-lock genesis. --- script/Deploy.s.sol | 35 ++++++++++++++++++++++------------- test/GenesisOneShot.t.sol | 27 ++++++++++++++++++++------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 7dcc870..8a39ab4 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -209,31 +209,40 @@ contract Deploy is Script { ) ); - // ── patch_oneshot_genesis: ONE-SHOT GENESIS MINT + LOCK ── - // The full supply is minted here, once, split liquid/locked: - // liquid -> GENESIS_TREASURY (LP seed + board-authorized ops/grants) - // locked -> OZ VestingWallet (linear release to GENESIS_TREASURY) + // ── patch_oneshot_genesis: ONE-SHOT GENESIS MINT ── + // The full supply is minted here, once. Default (patch_genesis_nolock, + // founder ruling 2026-09-01): ALL of it to GENESIS_TREASURY, no lock at + // genesis. A lock is one-way (can be added later by transferring into a + // VestingWallet, never removed), so it is deferred until a concrete + // trigger exists. Setting VSP_GENESIS_LOCKED_SUPPLY > 0 re-enables the + // locked tranche -> OZ VestingWallet (linear release to the treasury); + // when enabled, set VSP_VESTING_START_TS explicitly (default is + // INCEPTION_TIMESTAMP, which may be in the past at deploy time). // The deployer can mint ONLY inside this script (Authority constructor // auto-grant, revoked at the end). Liquid+locked must equal // VSP_INCEPTION_SUPPLY so the flat cap is filled exactly: any later // capped mint of even 1 wei reverts MintExceedsTimeWindowCap. address genesisTreasury = vm.envOr("VSP_GENESIS_TREASURY", deployer); - uint256 genesisLiquid = vm.envOr("VSP_GENESIS_LIQUID_SUPPLY", uint256(100_000_000 * 1e18)); // patch_genesis_1b - uint256 genesisLocked = vm.envOr("VSP_GENESIS_LOCKED_SUPPLY", uint256(900_000_000 * 1e18)); + uint256 genesisLocked = vm.envOr("VSP_GENESIS_LOCKED_SUPPLY", uint256(0)); // patch_genesis_nolock + uint256 genesisLiquid = vm.envOr("VSP_GENESIS_LIQUID_SUPPLY", token.INCEPTION_SUPPLY() - genesisLocked); require( genesisLiquid + genesisLocked == token.INCEPTION_SUPPLY(), "Deploy: genesis liquid+locked must equal VSP_INCEPTION_SUPPLY (fill the flat cap exactly)" ); - uint64 vestStart = uint64(vm.envOr("VSP_VESTING_START_TS", uint256(token.INCEPTION_TIMESTAMP()))); - uint64 vestDuration = uint64(vm.envOr("VSP_VESTING_DURATION_SECONDS", uint256(4 * 365 days))); - VestingWallet vestingWallet = new VestingWallet(genesisTreasury, vestStart, vestDuration); token.mint(genesisTreasury, genesisLiquid); - token.mint(address(vestingWallet), genesisLocked); + console.log("GENESIS: liquid -> treasury:", genesisTreasury); + if (genesisLocked > 0) { + uint64 vestStart = uint64(vm.envOr("VSP_VESTING_START_TS", uint256(token.INCEPTION_TIMESTAMP()))); + uint64 vestDuration = uint64(vm.envOr("VSP_VESTING_DURATION_SECONDS", uint256(4 * 365 days))); + VestingWallet vestingWallet = new VestingWallet(genesisTreasury, vestStart, vestDuration); + token.mint(address(vestingWallet), genesisLocked); + console.log("GENESIS: locked -> VestingWallet:", address(vestingWallet)); + console.log("GENESIS: vesting start / duration (s):", vestStart, vestDuration); + } else { + console.log("GENESIS: no locked tranche (VSP_GENESIS_LOCKED_SUPPLY=0)"); + } require(token.totalSupply() == token.INCEPTION_SUPPLY(), "Deploy: genesis mint != inception supply"); console.log("GENESIS: total supply (wei):", token.totalSupply()); - console.log("GENESIS: liquid -> treasury:", genesisTreasury); - console.log("GENESIS: locked -> VestingWallet:", address(vestingWallet)); - console.log("GENESIS: vesting start / duration (s):", vestStart, vestDuration); vm.label(address(registry), "PostRegistry"); vm.label(address(graph), "LinkGraph"); diff --git a/test/GenesisOneShot.t.sol b/test/GenesisOneShot.t.sol index b0569a5..e7ff666 100644 --- a/test/GenesisOneShot.t.sol +++ b/test/GenesisOneShot.t.sol @@ -20,7 +20,9 @@ import {MockCPAMM} from "../src/mock/MockCPAMM.sol"; /// G6 after revoking the deployer, the minter set is exactly {stakeEngine} contract GenesisOneShotTest is Test { uint256 constant GENESIS = 1_000_000_000 * 1e18; // patch_genesis_1b: 1B exactly - uint256 constant LIQUID = 100_000_000 * 1e18; + // patch_genesis_nolock: default genesis mints ALL of GENESIS to the treasury. + // LOCKED below is only used by G5, which exercises the OPTIONAL lock path + // (a lock added later by transferring treasury supply into a VestingWallet). uint256 constant LOCKED = 900_000_000 * 1e18; uint256 constant INCEPTION_TS = 1_778_544_000; uint64 constant VEST_DURATION = uint64(4 * 365 days); @@ -46,10 +48,8 @@ contract GenesisOneShotTest is Test { ERC1967Proxy proxy = new ERC1967Proxy(address(impl), abi.encodeCall(VSPToken.initialize, (address(authority)))); token = VSPToken(address(proxy)); - // genesis: mint liquid to treasury, locked to the vesting wallet - vest = new VestingWallet(treasury, uint64(INCEPTION_TS), VEST_DURATION); - token.mint(treasury, LIQUID); - token.mint(address(vest), LOCKED); + // genesis (no lock, patch_genesis_nolock): the whole supply to the treasury + token.mint(treasury, GENESIS); // mirror the deploy script's end state: StakeEngine granted, deployer revoked authority.setMinter(stakeEngine, true); @@ -90,9 +90,16 @@ contract GenesisOneShotTest is Test { assertEq(token.maxAllowedSupply(), GENESIS); } - // G5 + // G5 — OPTIONAL lock path: the lock is not part of genesis; it can be added + // later by moving treasury supply into a VestingWallet. Verifies the schedule + // math and that the deferred path works unchanged. function test_vesting_schedule() public { + vest = new VestingWallet(treasury, uint64(INCEPTION_TS), VEST_DURATION); + vm.prank(treasury); + token.transfer(address(vest), LOCKED); + uint256 LIQUID = GENESIS - LOCKED; assertEq(token.balanceOf(address(vest)), LOCKED); + assertEq(token.balanceOf(treasury), LIQUID); // nothing before start (warp back to just before inception) vm.warp(INCEPTION_TS - 1); @@ -109,10 +116,16 @@ contract GenesisOneShotTest is Test { // everything at end vm.warp(INCEPTION_TS + VEST_DURATION + 1); vest.release(address(token)); - assertEq(token.balanceOf(treasury), LIQUID + LOCKED); + assertEq(token.balanceOf(treasury), GENESIS); assertEq(token.balanceOf(address(vest)), 0); } + // G5b — the no-lock genesis itself: whole supply sits with the treasury + function test_genesis_no_lock_all_to_treasury() public view { + assertEq(token.balanceOf(treasury), GENESIS); + assertEq(token.totalSupply(), GENESIS); + } + // G6 function test_minter_set_is_exactly_stake_engine() public view { assertTrue(authority.isMinter(stakeEngine)); From 8ff154c496918e610dd2c863a2cd4a2859c6558a Mon Sep 17 00:00:00 2001 From: c Date: Tue, 1 Sep 2026 15:36:24 +0000 Subject: [PATCH 3/3] fixup: hoist vestingWalletAddr for the addresses.json export (address(0) when no lock) (patch_genesis_nolock) --- script/Deploy.s.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 8a39ab4..d520cd5 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -231,12 +231,14 @@ contract Deploy is Script { ); token.mint(genesisTreasury, genesisLiquid); console.log("GENESIS: liquid -> treasury:", genesisTreasury); + address vestingWalletAddr = address(0); // stays zero when no lock (addresses.json sentinel) if (genesisLocked > 0) { uint64 vestStart = uint64(vm.envOr("VSP_VESTING_START_TS", uint256(token.INCEPTION_TIMESTAMP()))); uint64 vestDuration = uint64(vm.envOr("VSP_VESTING_DURATION_SECONDS", uint256(4 * 365 days))); VestingWallet vestingWallet = new VestingWallet(genesisTreasury, vestStart, vestDuration); - token.mint(address(vestingWallet), genesisLocked); - console.log("GENESIS: locked -> VestingWallet:", address(vestingWallet)); + vestingWalletAddr = address(vestingWallet); + token.mint(vestingWalletAddr, genesisLocked); + console.log("GENESIS: locked -> VestingWallet:", vestingWalletAddr); console.log("GENESIS: vesting start / duration (s):", vestStart, vestDuration); } else { console.log("GENESIS: no locked tranche (VSP_GENESIS_LOCKED_SUPPLY=0)"); @@ -299,7 +301,7 @@ contract Deploy is Script { '","ProtocolPolicy":"', vm.toString(address(protocolPolicy)), '","VestingWallet":"', - vm.toString(address(vestingWallet)), + vm.toString(vestingWalletAddr), // address(0) when no lock at genesis '"}' );