@@ -63,41 +63,33 @@ func ValidateClientExchangeData(ctx context.Context, data code_data.Provider, pr
6363 return false , "" , err
6464 }
6565
66+ if common .IsCoreMint (mint ) {
67+ return validateCoreMintClientExchangeData (ctx , data , proto )
68+ }
69+ return validateCurrencyLaunchpadClientExchangeData (ctx , data , proto )
70+ }
71+
72+ func validateCoreMintClientExchangeData (ctx context.Context , data code_data.Provider , proto * transactionpb.ExchangeData ) (bool , string , error ) {
6673 latestExchangeRateTime := GetLatestExchangeRateTime ()
6774
75+ // Find an exchange rate that the client could have fetched from a RPC call
76+ // within a reasonable time in the past
6877 var foundRate float64
6978 var isClientRateValid bool
70- for i := range 3 {
79+ for i := range 2 {
7180 exchangeRateTime := latestExchangeRateTime .Add (time .Duration (- i ) * timePerExchangeRateUpdate )
7281
73- coreMintFiatExchangeRateRecord , err := data .GetExchangeRate (ctx , currency_lib .Code (proto .Currency ), exchangeRateTime )
82+ exchangeRateRecord , err := data .GetExchangeRate (ctx , currency_lib .Code (proto .Currency ), exchangeRateTime )
7483 if err == currency .ErrNotFound {
7584 continue
7685 } else if err != nil {
7786 return false , "" , err
7887 }
7988
80- pricePerCoreMint := 1.0
81- if mint .PublicKey ().ToBase58 () != common .CoreMintAccount .PublicKey ().ToBase58 () {
82- reserveRecord , err := data .GetCurrencyReserveAtTime (ctx , mint .PublicKey ().ToBase58 (), exchangeRateTime )
83- if err == currency .ErrNotFound {
84- continue
85- } else if err != nil {
86- return false , "" , err
87- }
88-
89- pricePerCoreMint , _ = currencycreator .EstimateCurrentPrice (reserveRecord .SupplyFromBonding ).Float64 ()
90- }
91-
92- actualRate := pricePerCoreMint * coreMintFiatExchangeRateRecord .Rate
93-
94- // Avoid issues with floating points by examining the percentage difference
95- //
96- // todo: configurable error tolerance?
97- percentDiff := math .Abs (actualRate - proto .ExchangeRate ) / actualRate
89+ percentDiff := math .Abs (exchangeRateRecord .Rate - proto .ExchangeRate ) / exchangeRateRecord .Rate
9890 if percentDiff < 0.001 {
9991 isClientRateValid = true
100- foundRate = actualRate
92+ foundRate = exchangeRateRecord . Rate
10193 break
10294 }
10395 }
@@ -108,14 +100,81 @@ func ValidateClientExchangeData(ctx context.Context, data code_data.Provider, pr
108100
109101 // Validate that the native amount and exchange rate fall reasonably within
110102 // the amount of quarks to send in the transaction.
111- //
112- // todo: configurable error tolerance?
113- quarksPerUnit := common .GetMintQuarksPerUnit (mint )
114- unitsOfMint := proto .NativeAmount / foundRate
115- expectedQuarks := int64 (unitsOfMint * float64 (quarksPerUnit ))
103+ quarksPerUnit := common .GetMintQuarksPerUnit (common .CoreMintAccount )
104+ unitsOfCoreMint := proto .NativeAmount / foundRate
105+ expectedQuarks := int64 (unitsOfCoreMint * float64 (quarksPerUnit ))
116106 if math .Abs (float64 (expectedQuarks - int64 (proto .Quarks ))) > 1000 {
117107 return false , "payment native amount and quark value mismatch" , nil
118108 }
119109
120110 return true , "" , nil
121111}
112+
113+ func validateCurrencyLaunchpadClientExchangeData (ctx context.Context , data code_data.Provider , proto * transactionpb.ExchangeData ) (bool , string , error ) {
114+ mintAccount , err := common .GetBackwardsCompatMint (proto .Mint )
115+ if err != nil {
116+ return false , "" , err
117+ }
118+
119+ coreMintQuarksPerUnit := common .GetMintQuarksPerUnit (common .CoreMintAccount )
120+ otherMintQuarksPerUnit := common .GetMintQuarksPerUnit (mintAccount )
121+
122+ latestExchangeRateTime := GetLatestExchangeRateTime ()
123+ for i := range 2 {
124+ exchangeRateTime := latestExchangeRateTime .Add (time .Duration (- i ) * timePerExchangeRateUpdate )
125+
126+ reserveRecord , err := data .GetCurrencyReserveAtTime (ctx , mintAccount .PublicKey ().ToBase58 (), exchangeRateTime )
127+ if err == currency .ErrNotFound {
128+ continue
129+ } else if err != nil {
130+ return false , "" , err
131+ }
132+
133+ usdExchangeRateRecord , err := data .GetExchangeRate (ctx , currency_lib .USD , exchangeRateTime )
134+ if err == currency .ErrNotFound {
135+ continue
136+ } else if err != nil {
137+ return false , "" , err
138+ }
139+
140+ var otherExchangeRateRecord * currency.ExchangeRateRecord
141+ if proto .Currency == string (currency_lib .USD ) {
142+ otherExchangeRateRecord = usdExchangeRateRecord
143+ } else {
144+ otherExchangeRateRecord , err = data .GetExchangeRate (ctx , currency_lib .Code (proto .Currency ), exchangeRateTime )
145+ if err == currency .ErrNotFound {
146+ continue
147+ } else if err != nil {
148+ return false , "" , err
149+ }
150+ }
151+
152+ // How much core mint would be received for a sell against the currency creator program?
153+ coreMintSellValueInQuarks , _ := currencycreator .EstimateSell (& currencycreator.EstimateSellArgs {
154+ SellAmountInQuarks : proto .Quarks ,
155+ CurrentValueInQuarks : reserveRecord .CoreMintLocked ,
156+ ValueMintDecimals : uint8 (common .CoreMintDecimals ),
157+ SellFeeBps : 0 ,
158+ })
159+
160+ // Given the sell value, does it align with the native amount in the target currency?
161+ coreMintSellValueInUnits := float64 (coreMintSellValueInQuarks ) / float64 (coreMintQuarksPerUnit )
162+ potentialNativeAmount := otherExchangeRateRecord .Rate * coreMintSellValueInUnits / usdExchangeRateRecord .Rate
163+ percentDiff := math .Abs (proto .NativeAmount - potentialNativeAmount ) / potentialNativeAmount
164+ if percentDiff > 0.001 {
165+ continue
166+ }
167+
168+ // For the valid native amount, is the exchange rate calculated correctly?
169+ otherMintUnits := float64 (proto .Quarks ) / float64 (otherMintQuarksPerUnit )
170+ expectedRate := potentialNativeAmount / otherMintUnits
171+ percentDiff = math .Abs (proto .ExchangeRate - expectedRate ) / expectedRate
172+ if percentDiff > 0.001 {
173+ continue
174+ }
175+
176+ return true , "" , nil
177+ }
178+
179+ return false , "fiat exchange data is stale or invalid" , nil
180+ }
0 commit comments