@@ -60,6 +60,28 @@ object RewardComputationInputsTest {
6060 private val microsPerYear : Long = 365L * 24 * 3600 * 1000000L
6161 private val roundsPerYear : BigDecimal = BigDecimal (microsPerYear) / BigDecimal (tickDurationMicros)
6262
63+ /** MainNet-calibrated inputs
64+ *
65+ * Uses the 10+ year issuance curve segment:
66+ * amuletToIssuePerYear = 2.5e9, appRewardPercentage = 0.75
67+ * featuredAppRewardCap = 100, developmentFundPercentage = 0
68+ * trafficPrice = 60 $/MB, amuletPrice = 0.15 $/CC
69+ *
70+ * Derived: amuletsToIssueInRound ≈ 47564.69, rewardsToIssue ≈ 35673.52
71+ * trafficPriceInCCperMB = 400, threshold_CC ≈ 3.33
72+ */
73+ val mainNet = RewardComputationInputs (
74+ amuletToIssuePerYear = n(BigDecimal (" 2500000000" )),
75+ appRewardPercentage = n(BigDecimal (" 0.75" )),
76+ featuredAppRewardCap = n(BigDecimal (" 100" )),
77+ unfeaturedAppRewardCap = n(BigDecimal (" 0.6" )),
78+ developmentFundPercentage = n(BigDecimal (" 0" )),
79+ tickDurationMicros = tickDurationMicros,
80+ amuletPrice = n(BigDecimal (" 0.15" )),
81+ trafficPrice = n(BigDecimal (" 60" )),
82+ appRewardCouponThreshold = n(BigDecimal (" 0.5" )),
83+ )
84+
6385 /** Baseline inputs: simple round numbers for easy manual verification.
6486 *
6587 * amuletToIssuePerYear = 52560 → amuletsToIssueInRound = 1.0
@@ -69,7 +91,7 @@ object RewardComputationInputsTest {
6991 * featuredAppRewardCap = 100 (high, so cap doesn't bind)
7092 * appRewardCouponThreshold = 0.5 → threshold_CC = 0.5
7193 */
72- private val baseline = RewardComputationInputs (
94+ private val simpleArithmetic = RewardComputationInputs (
7395 amuletToIssuePerYear = n(roundsPerYear), // yields exactly 1.0 per round
7496 appRewardPercentage = n(BigDecimal (" 0.5" )),
7597 featuredAppRewardCap = n(BigDecimal (" 100" )),
@@ -88,20 +110,31 @@ object RewardComputationInputsTest {
88110 expected : RewardIssuanceParams ,
89111 )
90112
91- // With baseline: rewardsToIssue = 0.45, trafficPriceInCCperMB = 1.0
92- // totalCoupons_CC = weight / 1e6 * 1.0
93- // issuancePerCoupon = min(0.45 / totalCoupons_CC, 100)
94- // issuancePerFeaturedAppTraffic_CCperMB = 1.0 * issuancePerCoupon
95- // unclaimed = 0.45 - issuancePerCoupon * totalCoupons_CC
113+ // Test cases are grouped in pairs: MainNet-calibrated first (realistic values
114+ // matching cantonscan.com), then a simple-arithmetic equivalent (round numbers
115+ // for easy manual verification of the same concept).
96116
97117 val cases : Seq [TestCase ] = Seq (
98- // 1 MB of traffic → totalCoupons_CC = 1.0
99- // issuancePerCoupon = min(0.45 / 1.0, 100) = 0.45
100- // rate = 1.0 * 0.45 = 0.45
101- // unclaimed = 0.45 - 0.45 * 1.0 = 0.0
118+ // --- 1. Normal traffic, cap does not bind ---
119+
120+ // MainNet: BME = 0.8 (~95 MB traffic), cap does not bind (0.8 > 1/1.5 ≈ 0.66)
121+ // ≈ 375 CC/MB ≈ $56/MB in rewards — within Simon's expected $60-90/MB range
102122 TestCase (
103- label = " basic: 1 MB traffic, no cap binding" ,
104- inputs = baseline,
123+ label = " MainNet: BME=0.8, cap does not bind" ,
124+ inputs = mainNet,
125+ totalRoundAppActivityWeight = 95129376L ,
126+ expected = RewardIssuanceParams (
127+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 374.9999998" ),
128+ threshold_CC = BigDecimal (" 3.3333333333" ),
129+ totalIssuanceForFeaturedAppRewards = BigDecimal (" 35673.5159817352" ),
130+ unclaimedAppRewardAmount = BigDecimal (" 0.0000007611" ),
131+ ),
132+ ),
133+ // Simple: 1 MB traffic, no cap binding
134+ // issuancePerCoupon = 0.45/1.0 = 0.45, unclaimed = 0
135+ TestCase (
136+ label = " simple: 1 MB traffic, no cap binding" ,
137+ inputs = simpleArithmetic,
105138 totalRoundAppActivityWeight = 1000000L ,
106139 expected = RewardIssuanceParams (
107140 issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 0.45" ),
@@ -110,13 +143,26 @@ object RewardComputationInputsTest {
110143 unclaimedAppRewardAmount = BigDecimal (" 0" ),
111144 ),
112145 ),
113- // 10 MB of traffic → totalCoupons_CC = 10.0
114- // issuancePerCoupon = min(0.45 / 10.0, 100) = 0.045
115- // rate = 1.0 * 0.045 = 0.045
116- // unclaimed = 0.45 - 0.045 * 10.0 = 0.0
146+
147+ // --- 2. More traffic dilutes rate ---
148+
149+ // MainNet: doubled traffic price $120/MB → more coupons → lower rate
117150 TestCase (
118- label = " more traffic dilutes issuance rate" ,
119- inputs = baseline,
151+ label = " MainNet: doubled traffic price $120/MB dilutes rate" ,
152+ inputs = mainNet.copy(trafficPrice = n(BigDecimal (" 120" ))),
153+ totalRoundAppActivityWeight = 95129376L ,
154+ expected = RewardIssuanceParams (
155+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 374.99999984" ),
156+ threshold_CC = BigDecimal (" 3.3333333333" ),
157+ totalIssuanceForFeaturedAppRewards = BigDecimal (" 35673.5159817352" ),
158+ unclaimedAppRewardAmount = BigDecimal (" 0E-10" ),
159+ ),
160+ ),
161+ // Simple: 10 MB traffic dilutes issuance rate
162+ // issuancePerCoupon = 0.45/10.0 = 0.045
163+ TestCase (
164+ label = " simple: more traffic dilutes issuance rate" ,
165+ inputs = simpleArithmetic,
120166 totalRoundAppActivityWeight = 10000000L ,
121167 expected = RewardIssuanceParams (
122168 issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 0.045" ),
@@ -125,13 +171,32 @@ object RewardComputationInputsTest {
125171 unclaimedAppRewardAmount = BigDecimal (" 0" ),
126172 ),
127173 ),
128- // Cap = 0.1 (binds), 1 MB traffic → totalCoupons_CC = 1.0
129- // issuancePerCoupon = min(0.45 / 1.0, 0.1) = 0.1
130- // rate = 1.0 * 0.1 = 0.1
131- // unclaimed = 0.45 - 0.1 * 1.0 = 0.35
174+
175+ // --- 3. Cap binds, producing unclaimed rewards ---
176+
177+ // MainNet: very low traffic so cap binds.
178+ // Cap binds when rewardsToIssue / totalCoupons_CC > featuredAppRewardCap (100).
179+ // That requires totalCoupons_CC < 35673.52 / 100 = 356.74 CC,
180+ // i.e., traffic < 356.74 / 400 ≈ 0.89 MB.
181+ // Using 0.5 MB = 500000 bytes:
182+ // totalCoupons_CC = 0.5 * 400 = 200
183+ // issuancePerCoupon = min(35673.52/200, 100) = 100 (cap binds)
184+ // unclaimed = 35673.52 - 100 * 200 = 15673.52
132185 TestCase (
133- label = " cap binds, producing unclaimed rewards" ,
134- inputs = baseline.copy(featuredAppRewardCap = n(BigDecimal (" 0.1" ))),
186+ label = " MainNet: very low traffic, cap binds, unclaimed rewards" ,
187+ inputs = mainNet,
188+ totalRoundAppActivityWeight = 500000L ,
189+ expected = RewardIssuanceParams (
190+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 40000" ),
191+ threshold_CC = BigDecimal (" 3.3333333333" ),
192+ totalIssuanceForFeaturedAppRewards = BigDecimal (" 35673.5159817352" ),
193+ unclaimedAppRewardAmount = BigDecimal (" 15673.5159817352" ),
194+ ),
195+ ),
196+ // Simple: cap = 0.1, 1 MB → cap binds, unclaimed = 0.35
197+ TestCase (
198+ label = " simple: cap binds, producing unclaimed rewards" ,
199+ inputs = simpleArithmetic.copy(featuredAppRewardCap = n(BigDecimal (" 0.1" ))),
135200 totalRoundAppActivityWeight = 1000000L ,
136201 expected = RewardIssuanceParams (
137202 issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 0.1" ),
@@ -140,29 +205,41 @@ object RewardComputationInputsTest {
140205 unclaimedAppRewardAmount = BigDecimal (" 0.35" ),
141206 ),
142207 ),
143- // Zero traffic → totalCoupons_CC = 0
144- // issuancePerCoupon = capPerCoupon (Daml returns cap when no coupons)
145- // rate = trafficPriceInCCperMB * capPerCoupon = 1.0 * 100 = 100
146- // unclaimed = 0.45 - 100 * 0 = 0.45
208+
209+ // --- 4. Cap binds with multiple providers ---
210+
211+ // Simple: cap = 0.2, 2 MB → cap binds, unclaimed = 0.05
147212 TestCase (
148- label = " zero traffic: all rewards unclaimed " ,
149- inputs = baseline ,
150- totalRoundAppActivityWeight = 0L ,
213+ label = " simple: cap binds with multiple MB of traffic " ,
214+ inputs = simpleArithmetic.copy(featuredAppRewardCap = n( BigDecimal ( " 0.2 " ))) ,
215+ totalRoundAppActivityWeight = 2000000L ,
151216 expected = RewardIssuanceParams (
152- issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 100 " ),
217+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 0.2 " ),
153218 threshold_CC = BigDecimal (" 0.5" ),
154219 totalIssuanceForFeaturedAppRewards = BigDecimal (" 0.45" ),
155- unclaimedAppRewardAmount = BigDecimal (" 0.45" ),
220+ unclaimedAppRewardAmount = BigDecimal (" 0.05" ),
221+ ),
222+ ),
223+
224+ // --- 5. Coin price affects threshold and conversion ---
225+
226+ // MainNet: higher coin price $1.00/CC
227+ // trafficPriceInCCperMB = 60/1.0 = 60, threshold_CC = 0.5/1.0 = 0.5
228+ TestCase (
229+ label = " MainNet: higher coin price $1.00/CC" ,
230+ inputs = mainNet.copy(amuletPrice = n(BigDecimal (" 1.00" ))),
231+ totalRoundAppActivityWeight = 95129376L ,
232+ expected = RewardIssuanceParams (
233+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 374.999999808" ),
234+ threshold_CC = BigDecimal (" 0.5" ),
235+ totalIssuanceForFeaturedAppRewards = BigDecimal (" 35673.5159817352" ),
236+ unclaimedAppRewardAmount = BigDecimal (" 0E-10" ),
156237 ),
157238 ),
158- // amuletPrice = 2.0 → trafficPriceInCCperMB = 0.5, threshold_CC = 0.25
159- // 1 MB → totalCoupons_CC = 0.5
160- // issuancePerCoupon = min(0.45 / 0.5, 100) = 0.9
161- // rate = 0.5 * 0.9 = 0.45
162- // unclaimed = 0.45 - 0.9 * 0.5 = 0.0
239+ // Simple: amuletPrice = 2.0 → trafficPriceInCCperMB = 0.5, threshold_CC = 0.25
163240 TestCase (
164- label = " amuletPrice affects threshold and traffic conversion" ,
165- inputs = baseline .copy(amuletPrice = n(BigDecimal (" 2.0" ))),
241+ label = " simple: amuletPrice affects threshold and traffic conversion" ,
242+ inputs = simpleArithmetic .copy(amuletPrice = n(BigDecimal (" 2.0" ))),
166243 totalRoundAppActivityWeight = 1000000L ,
167244 expected = RewardIssuanceParams (
168245 issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 0.45" ),
@@ -171,21 +248,31 @@ object RewardComputationInputsTest {
171248 unclaimedAppRewardAmount = BigDecimal (" 0" ),
172249 ),
173250 ),
174- // Cap = 0.2, 2 MB → totalCoupons_CC = 2.0
175- // scaledTotalCoupons = 0.2 * 2.0 = 0.4
176- // cappedRewardsToIssue = min(0.45, 0.4) = 0.4
177- // issuancePerCoupon = (0.4 * 0.2) / 0.4 = 0.2
178- // rate = 1.0 * 0.2 = 0.2
179- // unclaimed = 0.45 - 0.2 * 2.0 = 0.05
251+
252+ // --- 6. Zero traffic: all rewards unclaimed ---
253+
254+ // MainNet: rate = trafficPriceInCCperMB * cap = 400 * 100 = 40000
180255 TestCase (
181- label = " cap binds with multiple MB of traffic " ,
182- inputs = baseline.copy(featuredAppRewardCap = n( BigDecimal ( " 0.2 " ))) ,
183- totalRoundAppActivityWeight = 2000000L ,
256+ label = " MainNet: zero traffic, all unclaimed " ,
257+ inputs = mainNet ,
258+ totalRoundAppActivityWeight = 0L ,
184259 expected = RewardIssuanceParams (
185- issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 0.2" ),
260+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 40000" ),
261+ threshold_CC = BigDecimal (" 3.3333333333" ),
262+ totalIssuanceForFeaturedAppRewards = BigDecimal (" 35673.5159817352" ),
263+ unclaimedAppRewardAmount = BigDecimal (" 35673.5159817352" ),
264+ ),
265+ ),
266+ // Simple: rate = 1.0 * 100 = 100, all 0.45 unclaimed
267+ TestCase (
268+ label = " simple: zero traffic, all rewards unclaimed" ,
269+ inputs = simpleArithmetic,
270+ totalRoundAppActivityWeight = 0L ,
271+ expected = RewardIssuanceParams (
272+ issuancePerFeaturedAppTraffic_CCperMB = BigDecimal (" 100" ),
186273 threshold_CC = BigDecimal (" 0.5" ),
187274 totalIssuanceForFeaturedAppRewards = BigDecimal (" 0.45" ),
188- unclaimedAppRewardAmount = BigDecimal (" 0.05 " ),
275+ unclaimedAppRewardAmount = BigDecimal (" 0.45 " ),
189276 ),
190277 ),
191278 )
0 commit comments