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
41 changes: 41 additions & 0 deletions ocp/data/intent/memory/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,44 @@ func (s *store) GetTransactedAmountForAntiMoneyLaundering(ctx context.Context, o
items = s.filterByWithdrawalFlag(items, false)
return sumQuarkAmount(items), sumUsdMarketValue(items), nil
}

func (s *store) GetUsdCostBasis(ctx context.Context, owner string, mint string) (float64, error) {
s.mu.Lock()
defer s.mu.Unlock()

var costBasis float64

for _, item := range s.records {
if item.MintAccount != mint {
continue
}

if item.State == intent.StateRevoked {
continue
}

switch item.IntentType {
case intent.ExternalDeposit:
// Owner is destination, add USD
if item.InitiatorOwnerAccount == owner {
costBasis += item.ExternalDepositMetadata.UsdMarketValue
}
case intent.ReceivePaymentsPublicly:
// Owner is destination, add USD
if item.InitiatorOwnerAccount == owner {
costBasis += item.ReceivePaymentsPubliclyMetadata.UsdMarketValue
}
case intent.SendPublicPayment:
// Owner as initiator is source, subtract USD
if item.InitiatorOwnerAccount == owner {
costBasis -= item.SendPublicPaymentMetadata.UsdMarketValue
}
// Owner as destination_owner is destination, add USD
if item.SendPublicPaymentMetadata.DestinationOwnerAccount == owner {
costBasis += item.SendPublicPaymentMetadata.UsdMarketValue
}
}
}

return costBasis, nil
}
39 changes: 39 additions & 0 deletions ocp/data/intent/postgres/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,42 @@ func dbGetTransactedAmountForAntiMoneyLaundering(ctx context.Context, db *sqlx.D
}
return uint64(res.TotalQuarkValue.Int64), res.TotalUsdMarketValue.Float64, nil
}

func dbGetUsdCostBasis(ctx context.Context, db *sqlx.DB, owner string, mint string) (float64, error) {
var res sql.NullFloat64

// For backwards compatibility, the mint column is NULL for core mint
var mintFilter string
var params []any
if mint == config.CoreMintPublicKeyString {
mintFilter = "mint IS NULL"
params = []any{owner, intent.StateRevoked, intent.ExternalDeposit, intent.ReceivePaymentsPublicly, intent.SendPublicPayment}
} else {
mintFilter = "mint = $6"
params = []any{owner, intent.StateRevoked, intent.ExternalDeposit, intent.ReceivePaymentsPublicly, intent.SendPublicPayment, mint}
}

// USD received as destination:
// - ExternalDeposit, ReceivePaymentsPublicly where owner is initiator
// - SendPublicPayment where owner is destination_owner
// USD sent as source:
// - SendPublicPayment where owner is initiator
query := fmt.Sprintf(`SELECT
(SELECT COALESCE(SUM(usd_market_value), 0) FROM %s WHERE owner = $1 AND %s AND state != $2 AND intent_type IN ($3, $4)) +
(SELECT COALESCE(SUM(usd_market_value), 0) FROM %s WHERE destination_owner = $1 AND %s AND state != $2 AND intent_type = $5) -
(SELECT COALESCE(SUM(usd_market_value), 0) FROM %s WHERE owner = $1 AND %s AND state != $2 AND intent_type = $5);`,
intentTableName, mintFilter,
intentTableName, mintFilter,
intentTableName, mintFilter,
)

err := db.GetContext(ctx, &res, query, params...)
if err != nil {
return 0, err
}

if !res.Valid {
return 0, nil
}
return res.Float64, nil
}
5 changes: 5 additions & 0 deletions ocp/data/intent/postgres/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,8 @@ func (s *store) GetGiftCardClaimedIntent(ctx context.Context, giftCardVault stri
func (s *store) GetTransactedAmountForAntiMoneyLaundering(ctx context.Context, owner string, since time.Time) (uint64, float64, error) {
return dbGetTransactedAmountForAntiMoneyLaundering(ctx, s.db, owner, since)
}

// GetUsdCostBasis gets the net USD market value for an owner account and mint.
func (s *store) GetUsdCostBasis(ctx context.Context, owner string, mint string) (float64, error) {
return dbGetUsdCostBasis(ctx, s.db, owner, mint)
}
5 changes: 5 additions & 0 deletions ocp/data/intent/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ type Store interface {
// GetTransactedAmountForAntiMoneyLaundering gets the total transacted core mint quarks and the
// corresponding USD market value for an owner since a timestamp.
GetTransactedAmountForAntiMoneyLaundering(ctx context.Context, owner string, since time.Time) (uint64, float64, error)

// GetUsdCostBasis gets the net USD market value for an owner account and mint.
// The USD market value is subtracted if the owner is the source, and it is added
// if the owner is the destination.
GetUsdCostBasis(ctx context.Context, owner string, mint string) (float64, error)
}
53 changes: 53 additions & 0 deletions ocp/data/intent/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func RunTests(t *testing.T, s intent.Store, teardown func()) {
testGetGiftCardClaimedIntent,
testGetTransactedAmountForAntiMoneyLaundering,
testGetByOwner,
testGetUsdCostBasis,
} {
tf(t, s)
teardown()
Expand Down Expand Up @@ -609,3 +610,55 @@ func testGetByOwner(t *testing.T, s intent.Store) {
}
})
}

func testGetUsdCostBasis(t *testing.T, s intent.Store) {
t.Run("testGetUsdCostBasis", func(t *testing.T) {
ctx := context.Background()

// No intents results in zero cost basis
costBasis, err := s.GetUsdCostBasis(ctx, "o1", "mint")
require.NoError(t, err)
assert.EqualValues(t, 0, costBasis)

records := []*intent.Record{
// ExternalDeposit: o1 receives +100 USD
{IntentId: "t1", IntentType: intent.ExternalDeposit, InitiatorOwnerAccount: "o1", ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a1", Quantity: 1000, ExchangeCurrency: currency.USD, ExchangeRate: 0.1, NativeAmount: 100, UsdMarketValue: 100}, MintAccount: "mint", State: intent.StateConfirmed},
// ReceivePaymentsPublicly: o1 receives +50 USD
{IntentId: "t2", IntentType: intent.ReceivePaymentsPublicly, InitiatorOwnerAccount: "o1", ReceivePaymentsPubliclyMetadata: &intent.ReceivePaymentsPubliclyMetadata{Source: "s1", Quantity: 500, OriginalExchangeCurrency: currency.USD, OriginalExchangeRate: 0.1, OriginalNativeAmount: 50, UsdMarketValue: 50}, MintAccount: "mint", State: intent.StateConfirmed},
// SendPublicPayment: o1 sends -30 USD to o2
{IntentId: "t3", IntentType: intent.SendPublicPayment, InitiatorOwnerAccount: "o1", SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{DestinationOwnerAccount: "o2", DestinationTokenAccount: "a2", Quantity: 300, ExchangeCurrency: currency.USD, ExchangeRate: 0.1, NativeAmount: 30, UsdMarketValue: 30}, MintAccount: "mint", State: intent.StateConfirmed},
// SendPublicPayment: o2 sends to o1, o1 receives +20 USD
{IntentId: "t4", IntentType: intent.SendPublicPayment, InitiatorOwnerAccount: "o2", SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{DestinationOwnerAccount: "o1", DestinationTokenAccount: "a1", Quantity: 200, ExchangeCurrency: currency.USD, ExchangeRate: 0.1, NativeAmount: 20, UsdMarketValue: 20}, MintAccount: "mint", State: intent.StateConfirmed},
// Revoked intent should be ignored
{IntentId: "t5", IntentType: intent.ExternalDeposit, InitiatorOwnerAccount: "o1", ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a1", Quantity: 10000, ExchangeCurrency: currency.USD, ExchangeRate: 0.1, NativeAmount: 1000, UsdMarketValue: 1000}, MintAccount: "mint", State: intent.StateRevoked},
// Different mint should not be included
{IntentId: "t6", IntentType: intent.ExternalDeposit, InitiatorOwnerAccount: "o1", ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a1", Quantity: 5000, ExchangeCurrency: currency.USD, ExchangeRate: 0.1, NativeAmount: 500, UsdMarketValue: 500}, MintAccount: "other_mint", State: intent.StateConfirmed},
// SendPublicPayment to self: o1 sends and receives, net 0 for this transaction
{IntentId: "t7", IntentType: intent.SendPublicPayment, InitiatorOwnerAccount: "o1", SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{DestinationOwnerAccount: "o1", DestinationTokenAccount: "a1", Quantity: 100, ExchangeCurrency: currency.USD, ExchangeRate: 0.1, NativeAmount: 10, UsdMarketValue: 10}, MintAccount: "mint", State: intent.StateConfirmed},
}

for _, record := range records {
require.NoError(t, s.Save(ctx, record))
}

// o1 cost basis: +100 (deposit) +50 (receive) -30 (send to o2) +20 (receive from o2) -10 (send to self) +10 (receive from self) = 140
costBasis, err = s.GetUsdCostBasis(ctx, "o1", "mint")
require.NoError(t, err)
assert.EqualValues(t, 140, costBasis)

// o2 cost basis: +30 (receive from o1) -20 (send to o1) = 10
costBasis, err = s.GetUsdCostBasis(ctx, "o2", "mint")
require.NoError(t, err)
assert.EqualValues(t, 10, costBasis)

// o1 cost basis for other_mint: +500
costBasis, err = s.GetUsdCostBasis(ctx, "o1", "other_mint")
require.NoError(t, err)
assert.EqualValues(t, 500, costBasis)

// Unknown owner has zero cost basis
costBasis, err = s.GetUsdCostBasis(ctx, "unknown", "mint")
require.NoError(t, err)
assert.EqualValues(t, 0, costBasis)
})
}
4 changes: 4 additions & 0 deletions ocp/data/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ type DatabaseData interface {
GetOriginalGiftCardIssuedIntent(ctx context.Context, giftCardVault string) (*intent.Record, error)
GetGiftCardClaimedIntent(ctx context.Context, giftCardVault string) (*intent.Record, error)
GetTransactedAmountForAntiMoneyLaundering(ctx context.Context, owner string, since time.Time) (uint64, float64, error)
GetUsdCostBasis(ctx context.Context, owner string, mint string) (float64, error)

// Messaging
// --------------------------------------------------------------------------------
Expand Down Expand Up @@ -639,6 +640,9 @@ func (dp *DatabaseProvider) SaveIntent(ctx context.Context, record *intent.Recor
func (dp *DatabaseProvider) GetTransactedAmountForAntiMoneyLaundering(ctx context.Context, owner string, since time.Time) (uint64, float64, error) {
return dp.intents.GetTransactedAmountForAntiMoneyLaundering(ctx, owner, since)
}
func (dp *DatabaseProvider) GetUsdCostBasis(ctx context.Context, owner string, mint string) (float64, error) {
return dp.intents.GetUsdCostBasis(ctx, owner, mint)
}

// Messaging
// --------------------------------------------------------------------------------
Expand Down
13 changes: 12 additions & 1 deletion ocp/rpc/account/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,20 @@ func (s *server) getProtoAccountInfo(ctx context.Context, records *common.Accoun
}
}

usdCostBasis := 100.00 // todo: Mock test data
var usdCostBasis float64
if common.IsCoreMint(mintAccount) && common.IsCoreMintUsdStableCoin() {
usdCostBasis = float64(prefetchedBalanceMetadata.value) / float64(common.CoreMintQuarksPerUnit)
} else {
switch records.General.AccountType {
case commonpb.AccountType_PRIMARY:
// todo: Assumes the structure that each user has exactly one primary account per mint
usdCostBasis, err = s.data.GetUsdCostBasis(ctx, ownerAccount.PublicKey().ToBase58(), mintAccount.PublicKey().ToBase58())
if err != nil {
return nil, err
}
default:
usdCostBasis = 0 // Account type not supported
}
}

return &accountpb.TokenAccountInfo{
Expand Down