mirror of
https://github.com/luxfi/migration.git
synced 2026-10-30 15:25:07 +00:00
114 lines
6.1 KiB
Solidity
114 lines
6.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.24;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {VestingWallet} from "@openzeppelin/contracts/finance/VestingWallet.sol";
|
|
|
|
/// Fork-proof of the Lux tokenomics on-chain split against LIVE mainnet 96369.
|
|
/// Run: forge test --fork-url http://localhost:19630/v1/chain/C/rpc -vv
|
|
contract ForkProof is Test {
|
|
// --- live mainnet addresses (verified on chain 96369) ---
|
|
address constant DAO_SAFE = 0x51284dC2133e8d3a8e213DCa6a6FA768cfDfcce2; // Lux DAO Safe (1T)
|
|
address constant Z_SAFE = 0x864297c069E924a12a3CFEF294aeBB8500507d31; // Lux Team (zpriv) Safe (994.75B)
|
|
address constant TREASURY = 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714; // genesis treasury / gas EOA
|
|
address constant DEAD = 0x000000000000000000000000000000000000dEaD;
|
|
address constant FOUNDATION= 0x00000000000000000000000000000000f0009dA7; // placeholder Foundation Safe (owner to designate)
|
|
|
|
uint256 constant LUX = 1e18;
|
|
uint256 constant TWO_T = 2_000_000_000_000 * LUX; // 2T cap
|
|
uint256 constant ONE_T = 1_000_000_000_000 * LUX;
|
|
uint256 constant HUNDRED_B= 100_000_000_000 * LUX; // team 100B / burn 100B
|
|
uint64 constant YEAR = 365 days;
|
|
uint64 constant HUNDRED_YR = 100 * YEAR;
|
|
|
|
// measured "distributed to real holders/LPs/sale/swept EOAs" residual
|
|
uint256 constant DISTRIBUTED = 5_249_466_428_180_000_000_000_000_000; // ~5.2495B LUX (2T - DAO - Z - treasury)
|
|
|
|
function test_01_live_state_reconciles_to_2T() public {
|
|
uint256 dao = DAO_SAFE.balance;
|
|
uint256 z = Z_SAFE.balance;
|
|
uint256 tre = TREASURY.balance;
|
|
emit log_named_decimal_uint("DAO Safe ", dao, 18);
|
|
emit log_named_decimal_uint("Z/Team Safe", z, 18);
|
|
emit log_named_decimal_uint("Treasury ", tre, 18);
|
|
// DAO half is EXACTLY 1T
|
|
assertEq(dao, ONE_T, "DAO Safe must hold exactly 1T");
|
|
// Z/Team (Fair-Launch lump) is ~994.75B
|
|
assertApproxEqRel(z, 994_750_532_679 * LUX, 1e15, "Z Safe ~994.75B");
|
|
// full reconciliation: DAO + Z + treasury + distributed == 2T (net supply)
|
|
uint256 sum = dao + z + tre + DISTRIBUTED;
|
|
emit log_named_decimal_uint("SUM (should be 2T)", sum, 18);
|
|
assertApproxEqAbs(sum, TWO_T, 1e21, "C-Chain native supply must reconcile to 2T");
|
|
}
|
|
|
|
function test_02_team_vesting_100yr_schedule_and_native_withdraw() public {
|
|
uint64 start = uint64(block.timestamp);
|
|
// Deploy the CANONICAL vesting primitive (OZ VestingWallet), beneficiary = Lux Team (Z) Safe.
|
|
VestingWallet tv = new VestingWallet(Z_SAFE, start, HUNDRED_YR);
|
|
// Fund with the imported P/X team 100B (atomic-import credit modelled by vm.deal).
|
|
vm.deal(address(tv), HUNDRED_B);
|
|
assertEq(address(tv).balance, HUNDRED_B, "vesting funded 100B");
|
|
assertEq(tv.owner(), Z_SAFE, "Z/Team Safe controls the vesting");
|
|
|
|
// t0: nothing vested
|
|
assertEq(tv.releasable(), 0, "t0 releasable == 0");
|
|
// +1yr ~ 1%
|
|
vm.warp(start + YEAR);
|
|
assertApproxEqRel(tv.releasable(), HUNDRED_B / 100, 1e16, "+1yr ~1%");
|
|
// +50yr == 50%
|
|
vm.warp(start + 50 * uint256(YEAR));
|
|
assertApproxEqRel(tv.releasable(), HUNDRED_B / 2, 1e12, "+50yr ~50%");
|
|
|
|
// native withdrawal-to-pay: release vested to the Z Safe
|
|
uint256 zBefore = Z_SAFE.balance;
|
|
tv.release();
|
|
uint256 got = Z_SAFE.balance - zBefore;
|
|
emit log_named_decimal_uint("released@50yr to Z Safe", got, 18);
|
|
assertApproxEqRel(got, HUNDRED_B / 2, 1e12, "Z Safe received ~50B natively");
|
|
|
|
// +100yr == fully vested (remaining releasable == the other half)
|
|
vm.warp(start + 100 * uint256(YEAR));
|
|
assertApproxEqRel(tv.releasable(), HUNDRED_B / 2, 1e12, "+100yr remaining ~50B");
|
|
tv.release();
|
|
assertEq(address(tv).balance, 0, "fully vested & withdrawn at 100yr");
|
|
}
|
|
|
|
function test_03_foundation_vesting_separate_instance() public {
|
|
uint64 start = uint64(block.timestamp);
|
|
VestingWallet fdn = new VestingWallet(FOUNDATION, start, HUNDRED_YR);
|
|
vm.deal(address(fdn), 50_000_000_000 * LUX); // e.g. 50B (bucket TBD by owner)
|
|
assertEq(fdn.owner(), FOUNDATION, "Foundation controls its own timelock");
|
|
vm.warp(start + 10 * uint256(YEAR));
|
|
assertApproxEqRel(fdn.releasable(), (50_000_000_000 * LUX) / 10, 1e13, "+10yr ~10%");
|
|
}
|
|
|
|
function test_04_import_100B_plus_burn_100B_is_net_zero_2T_preserved() public {
|
|
// model the C-Chain native supply as tracked holders (pre-state)
|
|
uint256 preDao = DAO_SAFE.balance; // 1T
|
|
uint256 preZ = Z_SAFE.balance; // 994.75B
|
|
uint256 preTre = TREASURY.balance; // ~892
|
|
uint256 preTotal = preDao + preZ + preTre + DISTRIBUTED;
|
|
assertApproxEqAbs(preTotal, TWO_T, 1e21, "pre = 2T");
|
|
|
|
// (a) IMPORT: atomic-import the P/X team 100B onto C into the team vesting -> +100B on C
|
|
VestingWallet tv = new VestingWallet(Z_SAFE, uint64(block.timestamp), HUNDRED_YR);
|
|
vm.deal(address(tv), HUNDRED_B);
|
|
uint256 afterImport = preTotal + address(tv).balance; // + 100B
|
|
assertApproxEqAbs(afterImport, TWO_T + HUNDRED_B, 1e21, "after import = 2.1T");
|
|
|
|
// (b) BURN: StateUpgrade-style balance reduction of the burn source by exactly 100B,
|
|
// credited to NO ONE (true removal). Recommended source = DAO Safe 1T -> 900B.
|
|
vm.deal(DAO_SAFE, preDao - HUNDRED_B); // reduce, no credit = true burn
|
|
uint256 postDao = DAO_SAFE.balance;
|
|
assertEq(postDao, ONE_T - HUNDRED_B, "DAO 1T -> 900B after 100B burn");
|
|
|
|
// net supply after import + burn
|
|
uint256 postTotal = postDao + preZ + preTre + DISTRIBUTED + address(tv).balance;
|
|
emit log_named_decimal_uint("post-import+burn total", postTotal, 18);
|
|
assertApproxEqAbs(postTotal, TWO_T, 1e21, "NET = 2T preserved (no more minted on C)");
|
|
// team 100B present under Z-Safe-controlled vesting
|
|
assertEq(address(tv).balance, HUNDRED_B, "team 100B present in vesting");
|
|
assertEq(tv.owner(), Z_SAFE, "team vesting controlled by Z/Team Safe");
|
|
}
|
|
}
|