SKIP TO CONTENT

Demo build — mock protocol data. No chain, no ABIs yet.

  • SYNCING: ……

Emission.t.sol

Emission maths, including the assertion that the Distributor mints 0 when twap <= nav.

143 lines5.1 KBSolidity
Source of test/Emission.t.sol, 143 lines of Solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.24;
3 
4import {Base} from "./Base.t.sol";
5import {K401Distributor} from "../src/K401Distributor.sol";
6 
7/// @notice The premium-clamped emission formula, over its whole domain.
8contract EmissionTest is Base {
9 uint256 internal constant R_MAX_FRACTION = 45e18 / 10_000; // 4.5e15 == 0.45% of supply
10 
11 function test_parameters() public view {
12 assertEq(distributor.EPOCH_LENGTH(), 8 hours);
13 assertEq(distributor.R_MAX_BPS(), 45);
14 assertEq(distributor.K_PREMIUM(), 1.75e18);
15 assertEq(distributor.CALLER_REBATE_BPS(), 50);
16 }
17 
18 function test_rateCurveEndpoints() public view {
19 assertEq(distributor.rateFor(0), 0);
20 assertEq(distributor.rateFor(0.5e18), 0);
21 assertEq(distributor.rateFor(1e18), 0, "exactly at backing: exactly zero");
22 assertEq(distributor.rateFor(1.75e18), R_MAX_FRACTION, "K_PREMIUM caps at R_MAX");
23 assertEq(distributor.rateFor(10e18), R_MAX_FRACTION, "clamped above K_PREMIUM");
24 // Halfway between 1.0 and 1.75 -> half of R_MAX.
25 assertEq(distributor.rateFor(1.375e18), R_MAX_FRACTION / 2);
26 }
27 
28 function testFuzz_rateIsZeroAtOrBelowBacking(uint256 premiumWad) public view {
29 premiumWad = bound(premiumWad, 0, 1e18);
30 assertEq(distributor.rateFor(premiumWad), 0);
31 }
32 
33 function testFuzz_rateNeverExceedsRMax(uint256 premiumWad) public view {
34 premiumWad = bound(premiumWad, 0, 1e30);
35 assertLe(distributor.rateFor(premiumWad), R_MAX_FRACTION);
36 }
37 
38 function testFuzz_rateIsMonotonic(uint256 a, uint256 b) public view {
39 a = bound(a, 0, 5e18);
40 b = bound(b, 0, 5e18);
41 if (a > b) (a, b) = (b, a);
42 assertLe(distributor.rateFor(a), distributor.rateFor(b));
43 }
44 
45 function testFuzz_rateIsLinearInsideTheBand(uint256 premiumWad) public view {
46 premiumWad = bound(premiumWad, 1e18 + 1, 1.75e18);
47 uint256 expected = (45 * (((premiumWad - 1e18) * 1e18) / 0.75e18)) / 10_000;
48 assertEq(distributor.rateFor(premiumWad), expected);
49 }
50 
51 function testFuzz_premiumIsTwapOverNav(uint128 twap, uint128 navWad) public view {
52 uint256 t = bound(twap, 0, 1e24);
53 uint256 n = bound(navWad, 1, 1e24);
54 assertEq(distributor.premium(t, n), (t * 1e18) / n);
55 assertEq(distributor.premium(t, 0), 0, "nav 0 must not divide by zero");
56 }
57 
58 /*//////////////////////////////////////////////////////////////
59 LIVE REBASE BEHAVIOUR
60 //////////////////////////////////////////////////////////////*/
61 
62 function test_rebaseIsPermissionlessAndPaysTheCallerHalfAPercent() public {
63 _primeOracle();
64 _giveSeats(alice, 10);
65 uint256[] memory ids = k401.seatsOf(alice);
66 vm.prank(alice);
67 staking.clockIn(_ids(ids[0], ids[1]));
68 
69 _skip(8 hours + 1);
70 pair.sync();
71 oracle.checkpoint();
72 
73 uint256 supplyBefore = k401.totalSupply();
74 vm.prank(keeper);
75 (uint256 minted, uint256 rebate) = distributor.rebase();
76 
77 assertGt(minted, 0);
78 assertEq(rebate, (minted * 50) / 10_000, "0.5% gas rebate");
79 assertEq(k401.balanceOf(keeper), rebate);
80 assertEq(k401.balanceOf(address(staking)), minted - rebate);
81 assertEq(k401.totalSupply(), supplyBefore + minted);
82 assertEq(distributor.epoch(), 1);
83 }
84 
85 function test_rebaseRevertsBeforeTheEpochIsOver() public {
86 _primeOracle();
87 vm.expectRevert(K401Distributor.EpochNotOver.selector);
88 distributor.rebase();
89 
90 _skip(8 hours + 1);
91 pair.sync();
92 oracle.checkpoint();
93 distributor.rebase();
94 
95 vm.expectRevert(K401Distributor.EpochNotOver.selector);
96 distributor.rebase();
97 }
98 
99 function test_noEmissionWithNothingStaked() public {
100 _primeOracle();
101 _skip(8 hours + 1);
102 pair.sync();
103 oracle.checkpoint();
104 
105 uint256 supplyBefore = k401.totalSupply();
106 (uint256 minted,) = distributor.rebase();
107 assertEq(minted, 0, "nothing staked, nothing to distribute into");
108 assertEq(k401.totalSupply(), supplyBefore);
109 }
110 
111 function testFuzz_mintedMatchesTheFormula(uint256 seats) public {
112 seats = bound(seats, 1, 40);
113 _primeOracle();
114 _giveSeats(alice, seats);
115 uint256[] memory ids = k401.seatsOf(alice);
116 vm.prank(alice);
117 staking.clockIn(_ids(ids[0]));
118 
119 _skip(8 hours + 1);
120 pair.sync();
121 oracle.checkpoint();
122 
123 uint256 twap = oracle.consult();
124 uint256 navWad = treasury.nav();
125 uint256 rate = distributor.rateFor(distributor.premium(twap, navWad));
126 uint256 expected = (k401.totalSupply() * rate) / 1e18;
127 uint256 headroom = treasury.maxMintable();
128 if (expected > headroom) expected = headroom;
129 
130 (uint256 minted,) = distributor.rebase();
131 assertEq(minted, expected);
132 }
133 
134 function test_apyIsPositiveWhenTheRateIs() public {
135 _primeOracle();
136 _giveSeats(alice, 5);
137 uint256[] memory ids = k401.seatsOf(alice);
138 vm.prank(alice);
139 staking.clockIn(_ids(ids[0]));
140 assertGt(distributor.apy(), 0);
141 }
142}
143 

Click any line number to deep-link to it — the target line highlights on load.