Skip to content

Commit d60356e

Browse files
committed
Update client exchange data validation to use bigs
1 parent 20c7a1e commit d60356e

File tree

1 file changed

+45
-23
lines changed

1 file changed

+45
-23
lines changed

pkg/code/currency/validation.go

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package currency
22

33
import (
44
"context"
5-
"math"
5+
"math/big"
66
"time"
77

88
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
@@ -31,9 +31,15 @@ func ValidateClientExchangeData(ctx context.Context, data code_data.Provider, pr
3131
func validateCoreMintClientExchangeData(ctx context.Context, data code_data.Provider, proto *transactionpb.ExchangeData) (bool, string, error) {
3232
latestExchangeRateTime := GetLatestExchangeRateTime()
3333

34+
clientRate := big.NewFloat(proto.ExchangeRate).SetPrec(18)
35+
clientNativeAmount := big.NewFloat(proto.NativeAmount).SetPrec(18)
36+
clientQuarks := big.NewFloat(float64(proto.Quarks)).SetPrec(18)
37+
38+
rateErrorThreshold := big.NewFloat(0.01).SetPrec(18)
39+
quarkErrorThreshold := big.NewFloat(1000).SetPrec(18)
40+
3441
// Find an exchange rate that the client could have fetched from a RPC call
3542
// within a reasonable time in the past
36-
var foundRate float64
3743
var isClientRateValid bool
3844
for i := range 2 {
3945
exchangeRateTime := latestExchangeRateTime.Add(time.Duration(-i) * timePerExchangeRateUpdate)
@@ -44,11 +50,11 @@ func validateCoreMintClientExchangeData(ctx context.Context, data code_data.Prov
4450
} else if err != nil {
4551
return false, "", err
4652
}
53+
actualRate := big.NewFloat(exchangeRateRecord.Rate)
4754

48-
percentDiff := math.Abs(exchangeRateRecord.Rate-proto.ExchangeRate) / exchangeRateRecord.Rate
49-
if percentDiff < 0.001 {
55+
percentDiff := new(big.Float).Quo(new(big.Float).Abs(new(big.Float).Sub(clientRate, actualRate)), actualRate)
56+
if percentDiff.Cmp(rateErrorThreshold) < 0 {
5057
isClientRateValid = true
51-
foundRate = exchangeRateRecord.Rate
5258
break
5359
}
5460
}
@@ -59,10 +65,11 @@ func validateCoreMintClientExchangeData(ctx context.Context, data code_data.Prov
5965

6066
// Validate that the native amount and exchange rate fall reasonably within
6167
// the amount of quarks to send in the transaction.
62-
quarksPerUnit := common.GetMintQuarksPerUnit(common.CoreMintAccount)
63-
unitsOfCoreMint := proto.NativeAmount / foundRate
64-
expectedQuarks := int64(unitsOfCoreMint * float64(quarksPerUnit))
65-
if math.Abs(float64(expectedQuarks-int64(proto.Quarks))) > 1000 {
68+
quarksPerUnit := big.NewFloat(float64(common.GetMintQuarksPerUnit(common.CoreMintAccount)))
69+
unitsOfCoreMint := new(big.Float).Quo(clientNativeAmount, clientRate)
70+
expectedQuarks := new(big.Float).Mul(unitsOfCoreMint, quarksPerUnit)
71+
diff := new(big.Float).Abs(new(big.Float).Sub(expectedQuarks, clientQuarks))
72+
if diff.Cmp(quarkErrorThreshold) > 1000 {
6673
return false, "payment native amount and quark value mismatch", nil
6774
}
6875

@@ -75,10 +82,17 @@ func validateCurrencyLaunchpadClientExchangeData(ctx context.Context, data code_
7582
return false, "", err
7683
}
7784

85+
clientQuarks := big.NewFloat(float64(proto.Quarks)).SetPrec(18)
86+
clientRate := big.NewFloat(proto.ExchangeRate).SetPrec(18)
87+
clientNativeAmount := big.NewFloat(proto.NativeAmount).SetPrec(18)
88+
89+
rateErrorThreshold := big.NewFloat(0.01).SetPrec(18)
90+
nativeAmountErrorThreshold := big.NewFloat(0.005).SetPrec(18)
91+
7892
log := logrus.StandardLogger().WithFields(logrus.Fields{
7993
"currency": proto.Currency,
80-
"native_amount": proto.NativeAmount,
81-
"exhange_rate": proto.ExchangeRate,
94+
"native_amount": clientNativeAmount,
95+
"exhange_rate": clientRate,
8296
"quarks": proto.Quarks,
8397
"mint": mintAccount.PublicKey().ToBase58(),
8498
})
@@ -103,6 +117,7 @@ func validateCurrencyLaunchpadClientExchangeData(ctx context.Context, data code_
103117
} else if err != nil {
104118
return false, "", err
105119
}
120+
usdRate := big.NewFloat(usdExchangeRateRecord.Rate).SetPrec(18)
106121

107122
var otherExchangeRateRecord *currency.ExchangeRateRecord
108123
if proto.Currency == string(currency_lib.USD) {
@@ -115,6 +130,7 @@ func validateCurrencyLaunchpadClientExchangeData(ctx context.Context, data code_
115130
return false, "", err
116131
}
117132
}
133+
otherRate := big.NewFloat(otherExchangeRateRecord.Rate).SetPrec(18)
118134

119135
// How much core mint would be received for a sell against the currency creator program?
120136
coreMintSellValueInQuarks, _ := currencycreator.EstimateSell(&currencycreator.EstimateSellArgs{
@@ -126,16 +142,19 @@ func validateCurrencyLaunchpadClientExchangeData(ctx context.Context, data code_
126142

127143
// Given the sell value, does it align with the native amount in the target currency
128144
// within half a penny?
129-
errorThreshold := 0.005
130-
nativeAmountLowerBound := proto.NativeAmount - errorThreshold
131-
if nativeAmountLowerBound < errorThreshold {
132-
nativeAmountLowerBound = errorThreshold
145+
nativeAmountLowerBound := new(big.Float).Sub(clientNativeAmount, nativeAmountErrorThreshold)
146+
if nativeAmountLowerBound.Cmp(nativeAmountErrorThreshold) < 0 {
147+
nativeAmountLowerBound = nativeAmountErrorThreshold
133148
}
134-
nativeAmountUpperBound := proto.NativeAmount + errorThreshold
135-
coreMintSellValueInUnits := float64(coreMintSellValueInQuarks) / float64(coreMintQuarksPerUnit)
136-
potentialNativeAmount := otherExchangeRateRecord.Rate * coreMintSellValueInUnits / usdExchangeRateRecord.Rate
137-
if potentialNativeAmount < nativeAmountLowerBound || potentialNativeAmount > nativeAmountUpperBound {
149+
nativeAmountUpperBound := new(big.Float).Add(clientNativeAmount, nativeAmountErrorThreshold)
150+
coreMintSellValueInUnits := new(big.Float).Quo(
151+
big.NewFloat(float64(coreMintSellValueInQuarks)).SetPrec(18),
152+
big.NewFloat(float64(coreMintQuarksPerUnit)).SetPrec(18),
153+
)
154+
potentialNativeAmount := new(big.Float).Mul(new(big.Float).Quo(otherRate, usdRate), coreMintSellValueInUnits)
155+
if potentialNativeAmount.Cmp(nativeAmountLowerBound) < 0 || potentialNativeAmount.Cmp(nativeAmountUpperBound) > 0 {
138156
log.WithFields(logrus.Fields{
157+
"core_mint_sell_value": coreMintSellValueInUnits,
139158
"native_amount_lower_bound": nativeAmountLowerBound,
140159
"native_amount_upper_bound": nativeAmountUpperBound,
141160
"potential_native_amount": potentialNativeAmount,
@@ -144,10 +163,13 @@ func validateCurrencyLaunchpadClientExchangeData(ctx context.Context, data code_
144163
}
145164

146165
// For the valid native amount, is the exchange rate calculated correctly?
147-
otherMintUnits := float64(proto.Quarks) / float64(otherMintQuarksPerUnit)
148-
expectedRate := potentialNativeAmount / otherMintUnits
149-
percentDiff := math.Abs(proto.ExchangeRate-expectedRate) / expectedRate
150-
if percentDiff > 0.001 {
166+
otherMintUnits := new(big.Float).Quo(
167+
clientQuarks,
168+
big.NewFloat(float64(otherMintQuarksPerUnit)).SetPrec(18),
169+
)
170+
expectedRate := new(big.Float).Quo(potentialNativeAmount, otherMintUnits)
171+
percentDiff := new(big.Float).Quo(new(big.Float).Abs(new(big.Float).Sub(clientRate, expectedRate)), expectedRate)
172+
if percentDiff.Cmp(rateErrorThreshold) > 0 {
151173
log.WithField("potential_exchange_rate", expectedRate).Info("exchange rate is outside error threshold")
152174
continue
153175
}

0 commit comments

Comments
 (0)