Skip to content

Commit cd51eb4

Browse files
committed
chore: add estimation test for ensuring buy,sell, and value exchange remain in symmetry
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent a08836e commit cd51eb4

2 files changed

Lines changed: 65 additions & 7 deletions

File tree

libs/currency-math/src/main/kotlin/com/flipcash/libs/currency/math/Estimator.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.flipcash.libs.currency.math
22

33
import com.flipcash.libs.currency.math.internal.DefaultMintDecimals
44
import java.math.BigDecimal
5-
import kotlin.math.min
65

76
object Estimator {
87
/**
@@ -97,11 +96,11 @@ object Estimator {
9796
return runCatching {
9897
val curve = ExponentialCurve.getOrThrow()
9998
val valueScale = BigDecimal.TEN.pow(mintDecimals, mc)
100-
val unscaledValue = BigDecimal("$valueInQuarks")
99+
val unscaledValue = BigDecimal(valueInQuarks)
101100
val scaledValue = unscaledValue.divide(valueScale, mc)
102101

103102
val tokenScale = BigDecimal.TEN.pow(DefaultMintDecimals, mc)
104-
val unscaledCurrentSupply = BigDecimal("$currentSupplyInQuarks")
103+
val unscaledCurrentSupply = BigDecimal(currentSupplyInQuarks)
105104
val scaledCurrentSupply = unscaledCurrentSupply.divide(tokenScale, mc)
106105

107106
curve.tokensForValueExchange(scaledCurrentSupply, scaledValue).getOrThrow()
@@ -134,15 +133,15 @@ object Estimator {
134133
val amountScale = BigDecimal.TEN.pow(mintDecimals, mc)
135134

136135
val unscaledBuyAmount = BigDecimal(amountInQuarks, mc)
137-
val scaledBuyAmount = unscaledBuyAmount.divide(amountScale, mc)
136+
val scaledBuyAmount = unscaledBuyAmount.divideWithHighPrecision(amountScale)
138137

139138
val unscaledCurrentSupply = BigDecimal(currentSupplyInQuarks, mc)
140-
val scaledCurrentSupply = unscaledCurrentSupply.divide(tokenScale, mc)
139+
val scaledCurrentSupply = unscaledCurrentSupply.divideWithHighPrecision(tokenScale)
141140

142141
val scaledTokens = curve.tokensBoughtForValue(scaledCurrentSupply, scaledBuyAmount).getOrThrow()
143142
val unscaledTokens = scaledTokens.multiply(tokenScale, mc)
144143

145-
val feePctValue = BigDecimal(feeBps).divide(BigDecimal("10000"), mc)
144+
val feePctValue = BigDecimal(feeBps).divideWithHighPrecision(BigDecimal("10000"))
146145
val scaledFees = scaledTokens.multiply(feePctValue, mc)
147146
val unscaledFees = scaledFees.multiply(tokenScale, mc)
148147

@@ -197,7 +196,6 @@ object Estimator {
197196
val unscaledCurrentValue = BigDecimal(currentValueInQuarks, mc)
198197
val scaledCurrentValue = unscaledCurrentValue.divide(tokenScale, mc)
199198

200-
201199
val scaledTokens = curve.valueFromSellingTokens(scaledCurrentValue, scaledSellAmount).getOrThrow()
202200
val unscaledTokens = scaledTokens.multiply(tokenScale, mc)
203201

libs/currency-math/src/test/java/com/flipcash/libs/currency/math/EstimationTests.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package com.flipcash.libs.currency.math
33
import com.flipcash.libs.currency.math.internal.DefaultMintMaxQuarkSupply
44
import java.math.BigDecimal
55
import java.math.BigInteger
6+
import java.math.RoundingMode
7+
import kotlin.math.abs
8+
import kotlin.math.roundToLong
69
import kotlin.test.Test
710
import kotlin.test.assertEquals
11+
import kotlin.test.assertTrue
12+
import kotlin.test.fail
813

914
class EstimationTests {
1015

@@ -77,4 +82,59 @@ class EstimationTests {
7782

7883
assertEquals( expectedTotal, (received2 + fees2).toBigInteger())
7984
}
85+
86+
/**
87+
* This test generates a CSV table to verify the symmetry and accuracy of the buy, sell, and
88+
* value exchange estimations across a wide range of values. It simulates a scenario where an
89+
* initial amount (`valueLocked`) is used to buy tokens from a zero supply. Then, for various
90+
* sub-amounts (`paymentValue`), it calculates the corresponding token amount (`paymentQuarks`)
91+
* and immediately sells them back.
92+
*
93+
* The core assertion is that the value received from selling the tokens (`sellValue`) should be
94+
* almost identical to the original `paymentValue`, accounting for potential minor rounding
95+
* discrepancies (hence the tolerance of `1.0`).
96+
*
97+
* The test iterates through logarithmically scaled values for `valueLocked` and `paymentValue`,
98+
* starting from 10,000 and going up to 1 quadrillion quarks, ensuring the formulas hold
99+
* for both small and extremely large numbers.
100+
*
101+
*/
102+
@Test
103+
fun `estimate csv table`() {
104+
val startValue = 10_000L
105+
val endValue = 1_000_000_000_000_000L
106+
107+
println("value locked,payment value,payment quarks,sell value")
108+
var valueLocked = startValue
109+
while (valueLocked <= endValue) {
110+
val (circulatingSupply, _) = Estimator.buy(
111+
amountInQuarks = valueLocked,
112+
currentSupplyInQuarks = 0L,
113+
mintDecimals = 6,
114+
feeBps = 0
115+
).getOrThrow()
116+
117+
var paymentValue = startValue
118+
while (paymentValue <= valueLocked) {
119+
val paymentQuarks = Estimator.valueExchangeAsQuarks(
120+
valueInQuarks = paymentValue,
121+
currentSupplyInQuarks = circulatingSupply.setScale(0, RoundingMode.HALF_UP).toDouble().roundToLong(),
122+
mintDecimals = 6,
123+
).getOrThrow()
124+
125+
val (sellValue, _) = Estimator.sell(
126+
amountInQuarks = paymentQuarks.toLong(),
127+
currentValueInQuarks = valueLocked,
128+
mintDecimals = 6,
129+
feeBps = 0
130+
).getOrThrow()
131+
132+
133+
assertTrue { abs(paymentValue.toDouble() - sellValue.toDouble()) <= 1.0 }
134+
println("$valueLocked,$paymentValue,${paymentQuarks.toBigInteger()},${sellValue.toBigInteger()}")
135+
paymentValue *= 10L
136+
}
137+
valueLocked *= 10L
138+
}
139+
}
80140
}

0 commit comments

Comments
 (0)