Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions ocp/currency/exchange_rate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import (
)

// CalculateExchangeRate calculates the exchange rate for a crypto value exchange.
func CalculateExchangeRate(mintAccount *common.Account, nativeAmount float64, quarks uint64) float64 {
func CalculateExchangeRate(mintAccount *common.Account, quarks uint64, nativeAmount float64) float64 {
quarksPerUnit := common.GetMintQuarksPerUnit(mintAccount)
tokenUnits := new(big.Float).Quo(
tokenUnitsBig := new(big.Float).Quo(
big.NewFloat(float64(quarks)).SetPrec(defaultPrecision),
big.NewFloat(float64(quarksPerUnit)).SetPrec(defaultPrecision),
)
exchangeRate := new(big.Float).Quo(
exchangeRateBig := new(big.Float).Quo(
big.NewFloat(float64(nativeAmount)).SetPrec(defaultPrecision),
tokenUnits,
tokenUnitsBig,
)
res, _ := exchangeRate.Float64()
return res
exchangeRate, _ := exchangeRateBig.Float64()
return exchangeRate
}
2 changes: 2 additions & 0 deletions ocp/currency/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const (
// GetLatestExchangeRateTime gets the latest time for fetching an exchange rate.
// By synchronizing on a time, we can eliminate the amount of perceived volatility
// over short time spans.
//
// Deprecated: Use real-time data and favour volatility
func GetLatestExchangeRateTime() time.Time {
// Standardize to concrete 15 minute intervals to reduce perceived volatility.
// Notably, don't fall exactly on the 15 minute interval, so we remove 1 second.
Expand Down
64 changes: 45 additions & 19 deletions ocp/currency/usd_market_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,67 @@ package currency

import (
"context"
"errors"
"math/big"
"time"

currency_lib "github.com/code-payments/ocp-server/currency"
"github.com/code-payments/ocp-server/ocp/common"
ocp_data "github.com/code-payments/ocp-server/ocp/data"
"github.com/code-payments/ocp-server/ocp/data/currency"
"github.com/code-payments/ocp-server/solana/currencycreator"
)

// CalculateUsdMarketValue calculates the current USD market value of a crypto
// amount in quarks.
func CalculateUsdMarketValue(ctx context.Context, data ocp_data.Provider, mint *common.Account, quarks uint64, at time.Time) (float64, float64, error) {
// CalculateUsdMarketValueFromFiatAmount calculates the USD market value for a given
// fiat value and exchange rate.
func CalculateUsdMarketValueFromFiatAmount(fiatAmount, fiatToUsdRate float64) (float64, error) {
if !common.IsCoreMintUsdStableCoin() {
return 0, errors.New("non-usd stable coin not implemented")
}

fiatAmountBig := big.NewFloat(fiatAmount).SetPrec(defaultPrecision)
fiatToUsdRateBig := big.NewFloat(fiatToUsdRate).SetPrec(defaultPrecision)
usdMarketValue, _ := new(big.Float).Quo(fiatAmountBig, fiatToUsdRateBig).Float64()
return usdMarketValue, nil
}

// CalculateUsdMarketValueFromTokenAmount calculates the current USD market value
// of a crypto amount in quarks.
func CalculateUsdMarketValueFromTokenAmount(ctx context.Context, data ocp_data.Provider, mint *common.Account, quarks uint64, at time.Time) (float64, error) {
isSupportedMint, err := common.IsSupportedMint(ctx, data, mint)
if err != nil {
return 0, 0, err
return 0, err
} else if !isSupportedMint {
return 0, 0, common.ErrUnsupportedMint
return 0, common.ErrUnsupportedMint
}

usdExchangeRecord, err := data.GetExchangeRate(ctx, currency_lib.USD, at)
if err != nil {
return 0, 0, err
}
coreMintQuarksPerUnitBig := big.NewFloat(float64(common.GetMintQuarksPerUnit(common.CoreMintAccount))).SetPrec(defaultPrecision)

coreMintQuarksPerUnit := common.GetMintQuarksPerUnit(common.CoreMintAccount)
var exchangeRateRecord *currency.ExchangeRateRecord
if common.IsCoreMintUsdStableCoin() {
exchangeRateRecord = &currency.ExchangeRateRecord{
Symbol: string(currency_lib.USD),
Rate: 1.0,
Time: at,
}
} else {
exchangeRateRecord, err = data.GetExchangeRate(ctx, currency_lib.USD, at)
if err != nil {
return 0, err
}
}
exchangeRateBig := big.NewFloat(exchangeRateRecord.Rate).SetPrec(defaultPrecision)

if common.IsCoreMint(mint) {
units := float64(quarks) / float64(coreMintQuarksPerUnit)
marketValue := usdExchangeRecord.Rate * units
return marketValue, usdExchangeRecord.Rate, nil
quarksBig := big.NewFloat(float64(quarks)).SetPrec(defaultPrecision)
unitsBig := new(big.Float).Quo(quarksBig, coreMintQuarksPerUnitBig)
marketValue, _ := new(big.Float).Mul(exchangeRateBig, unitsBig).Float64()
return marketValue, nil
}

reserveRecord, err := data.GetCurrencyReserveAtTime(ctx, mint.PublicKey().ToBase58(), at)
if err != nil {
return 0, 0, err
return 0, err
}

coreMintSellValueInQuarks, _ := currencycreator.EstimateSell(&currencycreator.EstimateSellArgs{
Expand All @@ -44,10 +71,9 @@ func CalculateUsdMarketValue(ctx context.Context, data ocp_data.Provider, mint *
ValueMintDecimals: uint8(common.CoreMintDecimals),
SellFeeBps: 0,
})
coreMintSellValueInQuarksBig := big.NewFloat(float64(coreMintSellValueInQuarks)).SetPrec(defaultPrecision)

coreMintSellValueInUnits := float64(coreMintSellValueInQuarks) / float64(coreMintQuarksPerUnit)
otherMintUnits := float64(quarks) / float64(common.GetMintQuarksPerUnit(mint))
marketValue := usdExchangeRecord.Rate * coreMintSellValueInUnits
rate := marketValue / otherMintUnits
return marketValue, rate, nil
coreMintSellValueInUnitsBig := new(big.Float).Quo(coreMintSellValueInQuarksBig, coreMintQuarksPerUnitBig)
marketValue, _ := new(big.Float).Mul(exchangeRateBig, coreMintSellValueInUnitsBig).Float64()
return marketValue, nil
}
2 changes: 1 addition & 1 deletion ocp/rpc/transaction/airdrop.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func (s *transactionServer) airdrop(ctx context.Context, intentId string, owner
coreMintAmount := nativeAmount / exchangeRateRecord.Rate
quarkAmount := uint64(coreMintAmount*float64(common.CoreMintQuarksPerUnit)) + additionalQuarks

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, s.data, common.CoreMintAccount, quarkAmount, currency_util.GetLatestExchangeRateTime())
usdMarketValue, err := currency_util.CalculateUsdMarketValueFromTokenAmount(ctx, s.data, common.CoreMintAccount, quarkAmount, time.Now())
if err != nil {
log.With(zap.Error(err)).Warn("failure calculating usd market value")
return nil, err
Expand Down
18 changes: 4 additions & 14 deletions ocp/rpc/transaction/intent_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func (h *SendPublicPaymentIntentHandler) PopulateMetadata(ctx context.Context, i
if common.IsCoreMint(mint) {
exchangeRate = typed.ClientExchangeData.CoreMintFiatExchangeRate.ExchangeRate.ExchangeRate
} else {
exchangeRate = currency_util.CalculateExchangeRate(mint, typed.ClientExchangeData.NativeAmount, typed.ClientExchangeData.Quarks)
exchangeRate = currency_util.CalculateExchangeRate(mint, typed.ClientExchangeData.Quarks, typed.ClientExchangeData.NativeAmount)
}
quarks = typed.ClientExchangeData.Quarks
case *transactionpb.SendPublicPaymentMetadata_ServerExchangeData: // todo: deprecate this flow
Expand All @@ -419,7 +419,7 @@ func (h *SendPublicPaymentIntentHandler) PopulateMetadata(ctx context.Context, i
return NewIntentDeniedError("client exchange data not provided")
}

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, h.data, mint, quarks, currency_util.GetLatestExchangeRateTime())
usdMarketValue, err := currency_util.CalculateUsdMarketValueFromFiatAmount(nativeAmount, exchangeRate)
if err != nil {
return err
}
Expand Down Expand Up @@ -999,11 +999,6 @@ func (h *ReceivePaymentsPubliclyIntentHandler) PopulateMetadata(ctx context.Cont
}
h.cachedGiftCardIssuedIntentRecord = giftCardIssuedIntentRecord

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, h.data, mint, typedProtoMetadata.Quarks, currency_util.GetLatestExchangeRateTime())
if err != nil {
return err
}

intentRecord.IntentType = intent.ReceivePaymentsPublicly
intentRecord.MintAccount = mint.PublicKey().ToBase58()
intentRecord.ReceivePaymentsPubliclyMetadata = &intent.ReceivePaymentsPubliclyMetadata{
Expand All @@ -1018,7 +1013,7 @@ func (h *ReceivePaymentsPubliclyIntentHandler) PopulateMetadata(ctx context.Cont
OriginalExchangeRate: giftCardIssuedIntentRecord.SendPublicPaymentMetadata.ExchangeRate,
OriginalNativeAmount: giftCardIssuedIntentRecord.SendPublicPaymentMetadata.NativeAmount,

UsdMarketValue: usdMarketValue,
UsdMarketValue: giftCardIssuedIntentRecord.SendPublicPaymentMetadata.UsdMarketValue,
}

return nil
Expand Down Expand Up @@ -1332,17 +1327,12 @@ func (h *PublicDistributionIntentHandler) PopulateMetadata(ctx context.Context,
totalQuarks += distribution.Quarks
}

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, h.data, mint, totalQuarks, currency_util.GetLatestExchangeRateTime())
if err != nil {
return err
}

intentRecord.IntentType = intent.PublicDistribution
intentRecord.MintAccount = mint.PublicKey().ToBase58()
intentRecord.PublicDistributionMetadata = &intent.PublicDistributionMetadata{
Source: source.PublicKey().ToBase58(),
Quantity: totalQuarks,
UsdMarketValue: usdMarketValue,
UsdMarketValue: 0, // todo: Sum USD market values of each funding into pool
}

destinationTokenAddresses := make([]string, len(typedProtoMetadata.Distributions))
Expand Down
8 changes: 1 addition & 7 deletions ocp/worker/account/gift_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/code-payments/ocp-server/metrics"
"github.com/code-payments/ocp-server/ocp/balance"
"github.com/code-payments/ocp-server/ocp/common"
currency_util "github.com/code-payments/ocp-server/ocp/currency"
ocp_data "github.com/code-payments/ocp-server/ocp/data"
"github.com/code-payments/ocp-server/ocp/data/account"
"github.com/code-payments/ocp-server/ocp/data/action"
Expand Down Expand Up @@ -309,11 +308,6 @@ func insertAutoReturnIntentRecord(ctx context.Context, data ocp_data.Provider, g
return err
}

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, data, mintAccount, giftCardIssuedIntent.SendPublicPaymentMetadata.Quantity, time.Now())
if err != nil {
return err
}

// We need to insert a faked completed public receive intent so it can appear
// as a return in the user's payment history. Think of it as a server-initiated
// intent on behalf of the user based on pre-approved conditional actions.
Expand All @@ -337,7 +331,7 @@ func insertAutoReturnIntentRecord(ctx context.Context, data ocp_data.Provider, g
OriginalExchangeRate: giftCardIssuedIntent.SendPublicPaymentMetadata.ExchangeRate,
OriginalNativeAmount: giftCardIssuedIntent.SendPublicPaymentMetadata.NativeAmount,

UsdMarketValue: usdMarketValue,
UsdMarketValue: giftCardIssuedIntent.SendPublicPaymentMetadata.UsdMarketValue,
},

State: intent.StateConfirmed,
Expand Down
2 changes: 1 addition & 1 deletion ocp/worker/account/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (e *testEnv) assertGiftCardAutoReturned(t *testing.T, giftCard *testGiftCar
assert.Equal(t, giftCard.issuedIntentRecord.SendPublicPaymentMetadata.ExchangeCurrency, historyRecord.ReceivePaymentsPubliclyMetadata.OriginalExchangeCurrency)
assert.Equal(t, giftCard.issuedIntentRecord.SendPublicPaymentMetadata.ExchangeRate, historyRecord.ReceivePaymentsPubliclyMetadata.OriginalExchangeRate)
assert.Equal(t, giftCard.issuedIntentRecord.SendPublicPaymentMetadata.NativeAmount, historyRecord.ReceivePaymentsPubliclyMetadata.OriginalNativeAmount)
assert.Equal(t, 1234.5, historyRecord.ReceivePaymentsPubliclyMetadata.UsdMarketValue)
assert.Equal(t, giftCard.issuedIntentRecord.SendPublicPaymentMetadata.UsdMarketValue, historyRecord.ReceivePaymentsPubliclyMetadata.UsdMarketValue)
assert.Equal(t, intent.StateConfirmed, historyRecord.State)
}

Expand Down
2 changes: 1 addition & 1 deletion ocp/worker/geyser/external_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func processPotentialExternalDepositIntoVm(ctx context.Context, data ocp_data.Pr
return errors.Wrap(err, "invalid owner account")
}

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, data, mint, uint64(deltaQuarksIntoOmnibus), time.Now())
usdMarketValue, err := currency_util.CalculateUsdMarketValueFromTokenAmount(ctx, data, mint, uint64(deltaQuarksIntoOmnibus), time.Now())
if err != nil {
return errors.Wrap(err, "error calculating usd market value")
}
Expand Down
48 changes: 39 additions & 9 deletions ocp/worker/swap/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,18 @@ func (p *runtime) submitTransaction(ctx context.Context, record *swap.Record) er
return nil
}

func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, record *swap.Record) (uint64, error) {
owner, err := common.NewAccountFromPublicKeyString(record.Owner)
func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, swapRecord *swap.Record) (uint64, error) {
owner, err := common.NewAccountFromPublicKeyString(swapRecord.Owner)
if err != nil {
return 0, err
}

toMint, err := common.NewAccountFromPublicKeyString(record.ToMint)
fromMint, err := common.NewAccountFromPublicKeyString(swapRecord.FromMint)
if err != nil {
return 0, err
}

toMint, err := common.NewAccountFromPublicKeyString(swapRecord.ToMint)
if err != nil {
return 0, err
}
Expand All @@ -152,7 +157,7 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, record *sw
return 0, err
}

tokenBalances, err := p.data.GetBlockchainTransactionTokenBalances(ctx, record.TransactionSignature)
tokenBalances, err := p.data.GetBlockchainTransactionTokenBalances(ctx, swapRecord.TransactionSignature)
if err != nil {
return 0, err
}
Expand All @@ -165,15 +170,40 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, record *sw
return 0, errors.New("delta quarks into destination vm omnibus is not positive")
}

usdMarketValue, _, err := currency_util.CalculateUsdMarketValue(ctx, p.data, toMint, uint64(deltaQuarksIntoOmnibus), time.Now())
if err != nil {
return 0, err
var usdMarketValue float64
switch swapRecord.FundingSource {
case swap.FundingSourceSubmitIntent:
fundingIntentRecord, err := p.data.GetIntent(ctx, swapRecord.FundingId)
if err != nil {
return 0, err
}

if fundingIntentRecord.IntentType != intent.SendPublicPayment {
return 0, errors.New("unexpected intent type")
}

usdMarketValue = fundingIntentRecord.SendPublicPaymentMetadata.UsdMarketValue
case swap.FundingSourceExternalWallet:
if !common.IsCoreMint(fromMint) {
return 0, errors.New("unexpected source mint")
}

if !common.IsCoreMintUsdStableCoin() {
return 0, errors.New("core mint is not a usd stable coin")
}

usdMarketValue, err = currency_util.CalculateUsdMarketValueFromTokenAmount(ctx, p.data, common.CoreMintAccount, swapRecord.Amount, time.Now())
if err != nil {
return 0, err
}
default:
return 0, errors.New("unsupported funding source")
}

err = p.data.ExecuteInTx(ctx, sql.LevelDefault, func(ctx context.Context) error {
// For transaction history
intentRecord := &intent.Record{
IntentId: getSwapDepositIntentID(record.TransactionSignature, ownerDestinationTimelockVault),
IntentId: getSwapDepositIntentID(swapRecord.TransactionSignature, ownerDestinationTimelockVault),
IntentType: intent.ExternalDeposit,

MintAccount: toMint.PublicKey().ToBase58(),
Expand All @@ -196,7 +226,7 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, record *sw

// For tracking in cached balances
externalDepositRecord := &deposit.Record{
Signature: record.TransactionSignature,
Signature: swapRecord.TransactionSignature,
Destination: ownerDestinationTimelockVault.PublicKey().ToBase58(),
Amount: uint64(deltaQuarksIntoOmnibus),
UsdMarketValue: usdMarketValue,
Expand Down