Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unified trades and offers processor #5545

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
115 changes: 115 additions & 0 deletions ingest/trades/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package trades

import "github.com/stellar/go/xdr"

type TradeEventType int

const (
TradeEventTypeUnknown TradeEventType = iota // Default value
TradeEventTypeOfferCreated // Offer created event
TradeEventTypeOfferUpdated // Offer updated event
TradeEventTypeOfferClosed // Offer closed event
TradeEventTypeLiquidityPoolUpdated // Liquidity pool update event
)

type TradeEvent interface {
GetTradeEventType() TradeEventType // Method to retrieve the type of the trade event
}

type OfferBase struct {
Copy link
Contributor Author

@karthikiyer56 karthikiyer56 Dec 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no mention of sellerId or offerId
just amounts, price and flags.

The reason why I am not using xdr.OfferEntry struct for this is:
It is not a given that an offer will ever create an entry (if it is fully filled)
this distinction helps in the OfferUpdatedEvent where I am capturing prev state and updated state
And also the fact that there is no offerId or sellerId

Selling xdr.Asset // Asset being sold
Buying xdr.Asset // Asset being bought
Amount xdr.Int64 // Total amount of the selling asset
Price xdr.Price // Price of the offer
Flags uint32 // Flags for the offer (e.g., passive, sell offers)
}

type OfferCreatedEvent struct {
SellerId xdr.AccountId // Account ID of the seller
OfferId xdr.Int64 // ID of the created offer
OfferState OfferBase // Initial state of the offer
CreatedLedgerSeq uint32 // Ledger sequence where the offer was created
Fills []FillInfo // List of fills that occurred during the creation
}

func (e OfferCreatedEvent) GetTradeEventType() TradeEventType {
return TradeEventTypeOfferCreated
}

type OfferUpdatedEvent struct {
SellerId xdr.AccountId // Account ID of the seller
OfferId xdr.Int64 // ID of the updated offer
PrevUpdatedLedgerSeq uint32 // Ledger sequence of the previous update
PreviousOfferState OfferBase // Previous state of the offer
UpdatedOfferState OfferBase // Updated state of the offer
UpdatedLedgerSeq uint32 // Ledger sequence where the offer was updated
Fills []FillInfo // List of fills that occurred during the update
}

func (e OfferUpdatedEvent) GetTradeEventType() TradeEventType {
return TradeEventTypeOfferUpdated
}

type OfferClosedEvent struct {
SellerId xdr.AccountId // Account ID of the seller
OfferId xdr.Int64 // ID of the closed offer
LastOfferState OfferBase // Last state of the offer before closing
ClosedLedgerSeq uint32 // Ledger sequence where the offer was closed
}

func (e OfferClosedEvent) GetTradeEventType() TradeEventType {
return TradeEventTypeOfferClosed
}

type LiquidityPoolUpdateEvent struct {
Fills []FillInfo // List of fills for this liquidity pool update
}

func (e LiquidityPoolUpdateEvent) GetTradeEventType() TradeEventType {
return TradeEventTypeLiquidityPoolUpdated
}

type FillSourceOperationType uint32

const (
FillSourceOperationTypeUnknown FillSourceOperationType = iota
FillSourceOperationTypeManageBuy
FillSourceOperationTypeManageSell
FillSourceOperationTypePathPaymentStrictSend
FillSourceOperationTypePathPaymentStrictReceive
)

type FillSource struct {
// Type of the operation that caused this fill (ManageBuyOffer, ManageSellOffer, PathPaymentStrictSend, PathPaymentStrictReceive)
SourceOperation FillSourceOperationType

// The taker's information. Who caused this fill???
ManageOfferInfo *ManageOfferInfo // Details of a ManageBuy/ManageSell operation (optional)
PathPaymentInfo *PathPaymentInfo // Details of a PathPayment operation (optional)
}

// ManageBuy/ManageSell operation details
type ManageOfferInfo struct {
// Account that initiated the operation. Source of operation or source of transaction
SourceAccount xdr.AccountId

// Did the taking operation create an offerId/offerEntry that rested after being partially filled OR Was it fully filled
OfferFullyFilled bool

OfferId *xdr.Int64 // Offer ID, if an offer entry was created (nil if fully filled)
}

type PathPaymentInfo struct {
SourceAccount xdr.AccountId // Source account of the PathPayment
DestinationAccount xdr.AccountId // Destination account of the PathPayment
}

type FillInfo struct {
AssetSold xdr.Asset // Asset sold in this fill
AmountSold xdr.Int64 // Amount of the asset sold in this fill
AssetBought xdr.Asset // Asset bought in this fill
AmountBought xdr.Int64 // Amount of the asset bought in this fill
LedgerSeq uint32 // Ledger sequence in which the fill occurred

FillSourceInfo FillSource // Details about what operation (and details) caused this fill
}
77 changes: 77 additions & 0 deletions services/horizon/internal/integration/change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"context"
"encoding/json"
"github.com/stellar/go/ingest"
"github.com/stellar/go/ingest/ledgerbackend"
"github.com/stellar/go/keypair"
Expand Down Expand Up @@ -123,6 +124,82 @@ func TestOneTxOneOperationChanges(t *testing.T) {
tt.True(accountFromEntry(destAccChange.Pre).Balance < accountFromEntry(destAccChange.Post).Balance)
}

func TestSomething(t *testing.T) {
//tt := assert.New(t)
itest := integration.NewTest(t, integration.Config{})
master := itest.Master()
keys, accounts := itest.CreateAccounts(3, "1000")
keyA, keyB := keys[0], keys[1]
accountA, accountB := accounts[0], accounts[1]

// Some random asset
xyzAsset := txnbuild.CreditAsset{Code: "XYZ", Issuer: itest.Master().Address()}
itest.MustEstablishTrustline(keyA, accountA, xyzAsset)

itest.MustEstablishTrustline(keyB, accountB, xyzAsset)

t.Logf("*****")
paymentOperation := txnbuild.Payment{
Destination: keyA.Address(),
Asset: xyzAsset,
Amount: "2000",
}
txResp := itest.MustSubmitOperations(itest.MasterAccount(), itest.Master(), &paymentOperation)
data, _ := json.MarshalIndent(txResp, "", " ")

t.Logf("Acc A: %v, Acc B: %v", keyA.Address(), keyB.Address())

sellOfferOperationFromA := txnbuild.ManageSellOffer{
Selling: xyzAsset,
Buying: txnbuild.NativeAsset{},
Amount: "50",
Price: xdr.Price{N: 1, D: 1},
SourceAccount: keyA.Address(),
}
txResp = itest.MustSubmitMultiSigOperations(itest.MasterAccount(), []*keypair.Full{master, keyA}, &sellOfferOperationFromA)
data, _ = json.MarshalIndent(txResp, "", " ")
t.Logf("Transaction Sell Offer: %v", string(data))
t.Logf("Tx response meta xdr Sell Offer: %v", txResp.ResultMetaXdr)
t.Logf("*****")

sellOfferLedgerSeq := uint32(txResp.Ledger)

buyOfferOperationFromB := txnbuild.ManageBuyOffer{
Buying: xyzAsset,
Selling: txnbuild.NativeAsset{},
Amount: "66",
Price: xdr.Price{N: 1, D: 1},
SourceAccount: keyB.Address(),
}

txResp = itest.MustSubmitMultiSigOperations(itest.MasterAccount(), []*keypair.Full{master, keyB}, &buyOfferOperationFromB)
data, _ = json.MarshalIndent(txResp, "", " ")
t.Logf("Transaction Buy Offer: %v", string(data))
t.Logf("Tx response meta xdr Buy Offer: %v", txResp.ResultMetaXdr)
t.Logf("*****")

buyOfferLedgerSeq := uint32(txResp.Ledger)

t.Logf("Sell Offer Ledger:%v, Buy Offer Ledger: %v", sellOfferLedgerSeq, buyOfferLedgerSeq)

waitForLedgerInArchive(t, 15*time.Second, buyOfferLedgerSeq)

ledgerMap := getLedgers(itest, sellOfferLedgerSeq, buyOfferLedgerSeq)
for ledgerSeq, ledger := range ledgerMap {
t.Logf("LedgerSeq:::::::::::::::::::::: %v", ledgerSeq)
changes := getChangesFromLedger(itest, ledger)
for _, change := range changes {
if change.Reason != ingest.LedgerEntryChangeReasonOperation {
continue
}
typ := change.Type.String()
pre, _ := change.Pre.MarshalBinaryBase64()
post, _ := change.Post.MarshalBinaryBase64()
t.Logf("ledger: %v, Change - type - %v, pre: %v, post: %v", ledgerSeq, typ, pre, post)
}
}
}

func getChangesFromLedger(itest *integration.Test, ledger xdr.LedgerCloseMeta) []ingest.Change {
t := itest.CurrentTest()
changeReader, err := ingest.NewLedgerChangeReaderFromLedgerCloseMeta(itest.GetPassPhrase(), ledger)
Expand Down
19 changes: 18 additions & 1 deletion xdr/ledger_entry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package xdr

import "fmt"
import (
"encoding/base64"
"fmt"
)

// LedgerKey implements the `Keyer` interface
func (entry *LedgerEntry) LedgerKey() (LedgerKey, error) {
Expand Down Expand Up @@ -183,3 +186,17 @@ func (data *LedgerEntryData) LedgerKey() (LedgerKey, error) {

return key, nil
}

// MarshalBinaryBase64 marshals XDR into a binary form and then encodes it
// using base64.
func (e *LedgerEntry) MarshalBinaryBase64() (string, error) {
if e == nil {
return "nil", nil
}
b, err := e.MarshalBinary()
if err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(b), nil
}
14 changes: 14 additions & 0 deletions xdr/transaction_envelope.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package xdr

import "encoding/base64"

// IsFeeBump returns true if the transaction envelope is a fee bump transaction
func (e TransactionEnvelope) IsFeeBump() bool {
return e.Type == EnvelopeTypeEnvelopeTypeTxFeeBump
Expand Down Expand Up @@ -242,3 +244,15 @@ func (e TransactionEnvelope) Memo() Memo {
panic("unsupported transaction type: " + e.Type.String())
}
}

func (e *TransactionEnvelope) MarshalBinaryBase64() (string, error) {
if e == nil {
return "nil", nil
}
b, err := e.MarshalBinary()
if err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(b), nil
}
Loading