Skip to content

Commit 0e20a7b

Browse files
committed
Update currency launchpad fiat value calculations to use sell amounts
1 parent 4f63c9a commit 0e20a7b

File tree

2 files changed

+106
-38
lines changed

2 files changed

+106
-38
lines changed

pkg/code/currency/usd_market_value.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,29 @@ func CalculateUsdMarketValue(ctx context.Context, data code_data.Provider, mint
1818
return 0, 0, err
1919
}
2020

21-
pricePerCoreMint := 1.0
22-
if mint.PublicKey().ToBase58() != common.CoreMintAccount.PublicKey().ToBase58() {
23-
reserveRecord, err := data.GetCurrencyReserveAtTime(ctx, mint.PublicKey().ToBase58(), at)
24-
if err != nil {
25-
return 0, 0, err
26-
}
27-
28-
pricePerCoreMint, _ = currencycreator.EstimateCurrentPrice(reserveRecord.SupplyFromBonding).Float64()
21+
coreMintQuarksPerUnit := common.GetMintQuarksPerUnit(common.CoreMintAccount)
22+
23+
if common.IsCoreMint(mint) {
24+
units := float64(quarks) / float64(coreMintQuarksPerUnit)
25+
marketValue := usdExchangeRecord.Rate * units
26+
return marketValue, usdExchangeRecord.Rate, nil
27+
}
28+
29+
reserveRecord, err := data.GetCurrencyReserveAtTime(ctx, mint.PublicKey().ToBase58(), at)
30+
if err != nil {
31+
return 0, 0, err
2932
}
3033

31-
rate := pricePerCoreMint * usdExchangeRecord.Rate
34+
coreMintSellValueInQuarks, _ := currencycreator.EstimateSell(&currencycreator.EstimateSellArgs{
35+
SellAmountInQuarks: quarks,
36+
CurrentValueInQuarks: reserveRecord.CoreMintLocked,
37+
ValueMintDecimals: uint8(common.CoreMintDecimals),
38+
SellFeeBps: 0,
39+
})
3240

33-
quarksPerUnit := common.GetMintQuarksPerUnit(mint)
34-
units := float64(quarks) / float64(quarksPerUnit)
35-
marketValue := rate * units
41+
coreMintSellValueInUnits := float64(coreMintSellValueInQuarks) / float64(coreMintQuarksPerUnit)
42+
otherMintUnits := float64(quarks) / float64(common.GetMintQuarksPerUnit(mint))
43+
marketValue := usdExchangeRecord.Rate * coreMintSellValueInUnits
44+
rate := marketValue / otherMintUnits
3645
return marketValue, rate, nil
3746
}

pkg/code/currency/validation.go

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

Comments
 (0)