Skip to content

Commit 77ce31d

Browse files
committed
[ci] Add mainnet calibrated tests
1 parent 418b71a commit 77ce31d

1 file changed

Lines changed: 138 additions & 51 deletions

File tree

apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/rewards/RewardComputationInputsTest.scala

Lines changed: 138 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)