@@ -2,7 +2,7 @@ package currency
22
33import (
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
3131func 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