batch encode
This commit is contained in:
134
cmd/measure_tx_binary_block/main.go
Normal file
134
cmd/measure_tx_binary_block/main.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
pump_parser "github.com/thloyi/pump-parser"
|
||||
)
|
||||
|
||||
type blockResponse struct {
|
||||
Result blockResult `json:"result"`
|
||||
}
|
||||
|
||||
type blockResult struct {
|
||||
BlockTime *int64 `json:"blockTime"`
|
||||
Transactions []pump_parser.RawTx `json:"transactions"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
filePath = flag.String("file", "", "path to getBlock payload json")
|
||||
slot = flag.Uint64("slot", 0, "block slot")
|
||||
swapsOnly = flag.Bool("swaps-only", false, "only include transactions with swaps > 0")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *filePath == "" || *slot == 0 {
|
||||
fmt.Fprintln(os.Stderr, "usage: measure_tx_binary_block -file /path/block.json -slot 413539056")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
raw, err := os.ReadFile(*filePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "read file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var response blockResponse
|
||||
if err := json.Unmarshal(raw, &response); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unmarshal block payload: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var blockTime *uint64
|
||||
if response.Result.BlockTime != nil {
|
||||
bt := uint64(*response.Result.BlockTime)
|
||||
blockTime = &bt
|
||||
}
|
||||
|
||||
total := len(response.Result.Transactions)
|
||||
converted := 0
|
||||
parsed := 0
|
||||
convertFailures := 0
|
||||
parseFailures := 0
|
||||
encodeFailures := 0
|
||||
filteredOutNoSwaps := 0
|
||||
var totalRawTxBytes int
|
||||
var totalSingleEncoded int
|
||||
minSingleEncoded := -1
|
||||
maxSingleEncoded := 0
|
||||
|
||||
parsedTxs := make([]pump_parser.Tx, 0, total)
|
||||
for i, rawTx := range response.Result.Transactions {
|
||||
transactionJSON, err := json.Marshal(rawTx.Transaction)
|
||||
if err == nil {
|
||||
totalRawTxBytes += len(transactionJSON)
|
||||
}
|
||||
rawTx.BlockTime = 0
|
||||
if blockTime != nil {
|
||||
rawTx.BlockTime = int64(*blockTime)
|
||||
}
|
||||
rawTx.Slot = *slot
|
||||
rawTx.IndexWithinBlock = int64(i)
|
||||
converted++
|
||||
|
||||
tx, err := pump_parser.ParseRawTx(&rawTx)
|
||||
if err != nil {
|
||||
parseFailures++
|
||||
continue
|
||||
}
|
||||
if *swapsOnly && len(tx.Swaps) == 0 {
|
||||
filteredOutNoSwaps++
|
||||
continue
|
||||
}
|
||||
parsed++
|
||||
|
||||
encoded, err := pump_parser.EncodeTxBinary(tx)
|
||||
if err != nil {
|
||||
encodeFailures++
|
||||
continue
|
||||
}
|
||||
size := len(encoded)
|
||||
totalSingleEncoded += size
|
||||
if minSingleEncoded == -1 || size < minSingleEncoded {
|
||||
minSingleEncoded = size
|
||||
}
|
||||
if size > maxSingleEncoded {
|
||||
maxSingleEncoded = size
|
||||
}
|
||||
parsedTxs = append(parsedTxs, *tx)
|
||||
}
|
||||
|
||||
batchEncoded, err := pump_parser.EncodeTxsBinary(parsedTxs)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "encode txs binary: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
avgSingleEncoded := 0
|
||||
if parsed > 0 {
|
||||
avgSingleEncoded = totalSingleEncoded / parsed
|
||||
}
|
||||
|
||||
fmt.Printf("block_slot=%d\n", *slot)
|
||||
fmt.Printf("payload_json_bytes=%d\n", len(raw))
|
||||
fmt.Printf("transactions_total=%d\n", total)
|
||||
fmt.Printf("transactions_converted=%d\n", converted)
|
||||
fmt.Printf("transactions_parsed=%d\n", parsed)
|
||||
fmt.Printf("transactions_filtered_no_swaps=%d\n", filteredOutNoSwaps)
|
||||
fmt.Printf("convert_failures=%d\n", convertFailures)
|
||||
fmt.Printf("parse_failures=%d\n", parseFailures)
|
||||
fmt.Printf("encode_failures=%d\n", encodeFailures)
|
||||
fmt.Printf("raw_tx_total_bytes=%d\n", totalRawTxBytes)
|
||||
fmt.Printf("single_txbinary_total_bytes=%d\n", totalSingleEncoded)
|
||||
fmt.Printf("single_txbinary_avg_bytes=%d\n", avgSingleEncoded)
|
||||
fmt.Printf("single_txbinary_min_bytes=%d\n", minSingleEncoded)
|
||||
fmt.Printf("single_txbinary_max_bytes=%d\n", maxSingleEncoded)
|
||||
fmt.Printf("batch_shared_table_bytes=%d\n", len(batchEncoded))
|
||||
if totalSingleEncoded > 0 {
|
||||
fmt.Printf("batch_vs_single_saved_bytes=%d\n", totalSingleEncoded-len(batchEncoded))
|
||||
}
|
||||
}
|
||||
2
enum.go
2
enum.go
@@ -123,5 +123,7 @@ const (
|
||||
TxEventRemoveLP = "remove"
|
||||
TxEventBuy = "buy"
|
||||
TxEventSell = "sell"
|
||||
TxEventBuyFailed = "buy_failed"
|
||||
TxEventSellFailed = "sell_failed"
|
||||
TxEventBurn = "burn"
|
||||
)
|
||||
|
||||
2100
tx_binary.go
Normal file
2100
tx_binary.go
Normal file
File diff suppressed because it is too large
Load Diff
146
tx_binary_realdata_test.go
Normal file
146
tx_binary_realdata_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package pump_parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
)
|
||||
|
||||
func TestTxBinaryRealFixtureSizes(t *testing.T) {
|
||||
fixtures, err := filepath.Glob(filepath.Join("testdata", "rpc", "*.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("glob fixtures: %v", err)
|
||||
}
|
||||
if len(fixtures) == 0 {
|
||||
t.Fatal("no rpc fixtures found")
|
||||
}
|
||||
sort.Strings(fixtures)
|
||||
|
||||
type sizeResult struct {
|
||||
name string
|
||||
swaps int
|
||||
platforms int
|
||||
mevAgents int
|
||||
addresses int
|
||||
encodedBytes int
|
||||
fixtureBytes int
|
||||
txBinaryBytes int
|
||||
}
|
||||
|
||||
results := make([]sizeResult, 0, len(fixtures))
|
||||
totalEncoded := 0
|
||||
|
||||
for _, fixture := range fixtures {
|
||||
tx, rawTxBytesLen, fixtureBytesLen := mustParseRPCFixtureTxForBinarySize(t, fixture)
|
||||
binaryTx, err := NewTxBinary(tx)
|
||||
if err != nil {
|
||||
t.Fatalf("build tx binary fixture %s: %v", fixture, err)
|
||||
}
|
||||
encoded, err := binaryTx.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("encode fixture %s: %v", fixture, err)
|
||||
}
|
||||
|
||||
result := sizeResult{
|
||||
name: strings.TrimSuffix(filepath.Base(fixture), filepath.Ext(fixture)),
|
||||
swaps: len(tx.Swaps),
|
||||
platforms: len(tx.Platform),
|
||||
mevAgents: len(tx.MevAgent),
|
||||
addresses: len(binaryTx.AddressTable),
|
||||
encodedBytes: len(encoded),
|
||||
fixtureBytes: fixtureBytesLen,
|
||||
txBinaryBytes: rawTxBytesLen,
|
||||
}
|
||||
results = append(results, result)
|
||||
totalEncoded += result.encodedBytes
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
t.Logf(
|
||||
"%s encoded=%dB swaps=%d platforms=%d mev=%d addresses=%d fixture_json=%dB raw_tx=%dB",
|
||||
result.name,
|
||||
result.encodedBytes,
|
||||
result.swaps,
|
||||
result.platforms,
|
||||
result.mevAgents,
|
||||
result.addresses,
|
||||
result.fixtureBytes,
|
||||
result.txBinaryBytes,
|
||||
)
|
||||
}
|
||||
|
||||
minResult := results[0]
|
||||
maxResult := results[0]
|
||||
for _, result := range results[1:] {
|
||||
if result.encodedBytes < minResult.encodedBytes {
|
||||
minResult = result
|
||||
}
|
||||
if result.encodedBytes > maxResult.encodedBytes {
|
||||
maxResult = result
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf(
|
||||
"summary fixtures=%d avg=%dB min=%dB(%s) max=%dB(%s)",
|
||||
len(results),
|
||||
totalEncoded/len(results),
|
||||
minResult.encodedBytes,
|
||||
minResult.name,
|
||||
maxResult.encodedBytes,
|
||||
maxResult.name,
|
||||
)
|
||||
}
|
||||
|
||||
func mustParseRPCFixtureTxForBinarySize(t *testing.T, fixturePath string) (*Tx, int, int) {
|
||||
t.Helper()
|
||||
|
||||
raw, err := os.ReadFile(fixturePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read fixture %s: %v", fixturePath, err)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Result *rpc.GetTransactionResult `json:"result"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &response); err != nil {
|
||||
t.Fatalf("unmarshal fixture %s: %v", fixturePath, err)
|
||||
}
|
||||
if response.Result == nil || response.Result.Transaction == nil || response.Result.Meta == nil {
|
||||
t.Fatalf("fixture %s is missing transaction data", fixturePath)
|
||||
}
|
||||
|
||||
rawBinary := response.Result.Transaction.GetBinary()
|
||||
if len(rawBinary) == 0 {
|
||||
t.Fatalf("fixture %s has empty transaction bytes", fixturePath)
|
||||
}
|
||||
|
||||
txWithMeta := rpc.TransactionWithMeta{
|
||||
Slot: response.Result.Slot,
|
||||
BlockTime: response.Result.BlockTime,
|
||||
Transaction: rpc.DataBytesOrJSONFromBytes(rawBinary),
|
||||
Meta: response.Result.Meta,
|
||||
Version: response.Result.Version,
|
||||
}
|
||||
|
||||
var blockTime *uint64
|
||||
if response.Result.BlockTime != nil {
|
||||
bt := uint64(*response.Result.BlockTime)
|
||||
blockTime = &bt
|
||||
}
|
||||
|
||||
rawTx, err := FromRpcTransactionWithMeta(txWithMeta, blockTime, response.Result.Slot, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("convert fixture %s: %v", fixturePath, err)
|
||||
}
|
||||
|
||||
tx, err := ParseRawTx(rawTx)
|
||||
if err != nil {
|
||||
t.Fatalf("parse fixture %s: %v", fixturePath, err)
|
||||
}
|
||||
return tx, len(rawBinary), len(raw)
|
||||
}
|
||||
627
tx_binary_test.go
Normal file
627
tx_binary_test.go
Normal file
@@ -0,0 +1,627 @@
|
||||
package pump_parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func TestTxBinaryRoundTrip(t *testing.T) {
|
||||
txHash := [64]byte{}
|
||||
for i := range txHash {
|
||||
txHash[i] = byte(i + 1)
|
||||
}
|
||||
|
||||
original := &Tx{
|
||||
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
Block: 123456789,
|
||||
BlockIndex: 42,
|
||||
TxHash: &txHash,
|
||||
CuFee: decimal.NewFromInt(5000),
|
||||
CUPrice: decimal.RequireFromString("0.123456"),
|
||||
BeforeSolBalance: decimal.RequireFromString("1.500000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("1.234567890"),
|
||||
ComputeUnitsConsumed: 345678,
|
||||
CuLimit: 400000,
|
||||
Platform: map[string]platformInfo{
|
||||
PlatformGMGN: {
|
||||
Platform: PlatformGMGN,
|
||||
PlatformFee: decimal.RequireFromString("0.010000000"),
|
||||
},
|
||||
PlatformPhoton: {
|
||||
Platform: PlatformPhoton,
|
||||
PlatformFee: decimal.RequireFromString("0.020000000"),
|
||||
},
|
||||
},
|
||||
MevAgent: map[string]mevInfo{
|
||||
MevAgentJito: {
|
||||
MevAgent: MevAgentJito,
|
||||
MevAgentFee: decimal.RequireFromString("0.030000000"),
|
||||
},
|
||||
},
|
||||
Swaps: []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: TxEventBuy,
|
||||
TxIndex: 7,
|
||||
InstrIdx: 2,
|
||||
InnerIdx: 1,
|
||||
Pool: mustPubKey("11111111111111111111111111111111"),
|
||||
BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
QuoteMint: solana.WrappedSol,
|
||||
BaseTokenProgram: solana.TokenProgramID,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"),
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: mustPubKey("SysvarRent111111111111111111111111111111111"),
|
||||
BaseAmount: decimal.NewFromInt(1200),
|
||||
QuoteAmount: decimal.NewFromInt(3400),
|
||||
SwapMode: SwapModeExactIn,
|
||||
FixedAmount: decimal.NewFromInt(3400),
|
||||
FixedAmountSide: SwapAmountSideQuote,
|
||||
FixedMint: solana.WrappedSol,
|
||||
LimitAmountType: SwapLimitTypeMinOut,
|
||||
LimitAmount: decimal.NewFromInt(1000),
|
||||
LimitAmountSide: SwapAmountSideBase,
|
||||
LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
ActualLimitAmount: decimal.NewFromInt(1200),
|
||||
ActualLimitAmountSide: SwapAmountSideBase,
|
||||
SlippageBps: decimal.RequireFromString("833.3333"),
|
||||
BaseReserve: decimal.NewFromInt(5555),
|
||||
QuoteReserve: decimal.NewFromInt(9999),
|
||||
Mayhem: true,
|
||||
Cashback: false,
|
||||
UserBaseBalance: decimal.NewFromInt(777),
|
||||
UserQuoteBalance: decimal.NewFromInt(888),
|
||||
EntryContract: mustPubKey("ComputeBudget111111111111111111111111111111"),
|
||||
MigrateToPool: mustPubKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
|
||||
MigrateTopProgram: mustPubKey("AddressLookupTab1e1111111111111111111111111"),
|
||||
LpMint: mustPubKey("4Nd1mJf8JQhRVTfJxW2YxXLNQKhPYo1JzN1u2KAPY1Hn"),
|
||||
AfterSOLBalance: decimal.RequireFromString("0.321000000"),
|
||||
ActiveBinId: 11,
|
||||
FeeAmount: decimal.NewFromInt(99),
|
||||
FeeBps: "123",
|
||||
FeeSide: "base",
|
||||
ConsumeUnit: 9999,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded, err := EncodeTxBinary(original)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxBinary() error = %v", err)
|
||||
}
|
||||
|
||||
decoded, err := DecodeTxBinary(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeTxBinary() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Signer != original.Signer {
|
||||
t.Fatalf("Signer = %s, want %s", decoded.Signer, original.Signer)
|
||||
}
|
||||
if decoded.Block != original.Block {
|
||||
t.Fatalf("Block = %d, want %d", decoded.Block, original.Block)
|
||||
}
|
||||
if decoded.BlockIndex != original.BlockIndex {
|
||||
t.Fatalf("BlockIndex = %d, want %d", decoded.BlockIndex, original.BlockIndex)
|
||||
}
|
||||
if decoded.TxHash == nil {
|
||||
t.Fatal("TxHash = nil, want non-nil")
|
||||
}
|
||||
if *decoded.TxHash != *original.TxHash {
|
||||
t.Fatalf("TxHash mismatch")
|
||||
}
|
||||
if !decoded.CuFee.Equal(original.CuFee) {
|
||||
t.Fatalf("CuFee = %s, want %s", decoded.CuFee, original.CuFee)
|
||||
}
|
||||
if !decoded.CUPrice.Equal(original.CUPrice) {
|
||||
t.Fatalf("CUPrice = %s, want %s", decoded.CUPrice, original.CUPrice)
|
||||
}
|
||||
if decoded.BeforeSolBalance.StringFixed(9) != original.BeforeSolBalance.StringFixed(9) {
|
||||
t.Fatalf("BeforeSolBalance = %s, want %s", decoded.BeforeSolBalance, original.BeforeSolBalance)
|
||||
}
|
||||
if decoded.AfterSOLBalance.StringFixed(9) != original.AfterSOLBalance.StringFixed(9) {
|
||||
t.Fatalf("AfterSOLBalance = %s, want %s", decoded.AfterSOLBalance, original.AfterSOLBalance)
|
||||
}
|
||||
if decoded.CuLimit != original.CuLimit {
|
||||
t.Fatalf("CuLimit = %d, want %d", decoded.CuLimit, original.CuLimit)
|
||||
}
|
||||
if decoded.ComputeUnitsConsumed != original.ComputeUnitsConsumed {
|
||||
t.Fatalf("ComputeUnitsConsumed = %d, want %d", decoded.ComputeUnitsConsumed, original.ComputeUnitsConsumed)
|
||||
}
|
||||
if len(decoded.Platform) != len(original.Platform) {
|
||||
t.Fatalf("Platform len = %d, want %d", len(decoded.Platform), len(original.Platform))
|
||||
}
|
||||
if !decoded.Platform[PlatformGMGN].PlatformFee.Equal(original.Platform[PlatformGMGN].PlatformFee) {
|
||||
t.Fatalf("Platform fee mismatch")
|
||||
}
|
||||
if len(decoded.MevAgent) != len(original.MevAgent) {
|
||||
t.Fatalf("MevAgent len = %d, want %d", len(decoded.MevAgent), len(original.MevAgent))
|
||||
}
|
||||
if !decoded.MevAgent[MevAgentJito].MevAgentFee.Equal(original.MevAgent[MevAgentJito].MevAgentFee) {
|
||||
t.Fatalf("MevAgent fee mismatch")
|
||||
}
|
||||
if len(decoded.Swaps) != 1 {
|
||||
t.Fatalf("Swaps len = %d, want 1", len(decoded.Swaps))
|
||||
}
|
||||
|
||||
swap := decoded.Swaps[0]
|
||||
if swap.Program != original.Swaps[0].Program {
|
||||
t.Fatalf("swap.Program = %s, want %s", swap.Program, original.Swaps[0].Program)
|
||||
}
|
||||
if swap.Event != original.Swaps[0].Event {
|
||||
t.Fatalf("swap.Event = %s, want %s", swap.Event, original.Swaps[0].Event)
|
||||
}
|
||||
if swap.TxIndex != original.Swaps[0].TxIndex {
|
||||
t.Fatalf("swap.TxIndex = %d, want %d", swap.TxIndex, original.Swaps[0].TxIndex)
|
||||
}
|
||||
if !swap.BaseAmount.Equal(original.Swaps[0].BaseAmount) {
|
||||
t.Fatalf("swap.BaseAmount = %s, want %s", swap.BaseAmount, original.Swaps[0].BaseAmount)
|
||||
}
|
||||
if !swap.QuoteAmount.Equal(original.Swaps[0].QuoteAmount) {
|
||||
t.Fatalf("swap.QuoteAmount = %s, want %s", swap.QuoteAmount, original.Swaps[0].QuoteAmount)
|
||||
}
|
||||
if !swap.FixedAmount.Equal(original.Swaps[0].FixedAmount) {
|
||||
t.Fatalf("swap.FixedAmount = %s, want %s", swap.FixedAmount, original.Swaps[0].FixedAmount)
|
||||
}
|
||||
if !swap.LimitAmount.Equal(original.Swaps[0].LimitAmount) {
|
||||
t.Fatalf("swap.LimitAmount = %s, want %s", swap.LimitAmount, original.Swaps[0].LimitAmount)
|
||||
}
|
||||
if !swap.ActualLimitAmount.Equal(original.Swaps[0].ActualLimitAmount) {
|
||||
t.Fatalf("swap.ActualLimitAmount = %s, want %s", swap.ActualLimitAmount, original.Swaps[0].ActualLimitAmount)
|
||||
}
|
||||
if swap.SlippageBps.String() != "833" {
|
||||
t.Fatalf("swap.SlippageBps = %s, want 833", swap.SlippageBps)
|
||||
}
|
||||
if !swap.BaseReserve.Equal(original.Swaps[0].BaseReserve) {
|
||||
t.Fatalf("swap.BaseReserve = %s, want %s", swap.BaseReserve, original.Swaps[0].BaseReserve)
|
||||
}
|
||||
if !swap.QuoteReserve.Equal(original.Swaps[0].QuoteReserve) {
|
||||
t.Fatalf("swap.QuoteReserve = %s, want %s", swap.QuoteReserve, original.Swaps[0].QuoteReserve)
|
||||
}
|
||||
if !swap.UserBaseBalance.Equal(original.Swaps[0].UserBaseBalance) {
|
||||
t.Fatalf("swap.UserBaseBalance = %s, want %s", swap.UserBaseBalance, original.Swaps[0].UserBaseBalance)
|
||||
}
|
||||
if !swap.UserQuoteBalance.Equal(original.Swaps[0].UserQuoteBalance) {
|
||||
t.Fatalf("swap.UserQuoteBalance = %s, want %s", swap.UserQuoteBalance, original.Swaps[0].UserQuoteBalance)
|
||||
}
|
||||
if swap.AfterSOLBalance.StringFixed(9) != original.Swaps[0].AfterSOLBalance.StringFixed(9) {
|
||||
t.Fatalf("swap.AfterSOLBalance = %s, want %s", swap.AfterSOLBalance, original.Swaps[0].AfterSOLBalance)
|
||||
}
|
||||
|
||||
if swap.ActiveBinId != 0 {
|
||||
t.Fatalf("swap.ActiveBinId = %d, want 0", swap.ActiveBinId)
|
||||
}
|
||||
if !swap.FeeAmount.IsZero() {
|
||||
t.Fatalf("swap.FeeAmount = %s, want 0", swap.FeeAmount)
|
||||
}
|
||||
if swap.FeeBps != "" {
|
||||
t.Fatalf("swap.FeeBps = %q, want empty", swap.FeeBps)
|
||||
}
|
||||
if swap.FeeSide != "" {
|
||||
t.Fatalf("swap.FeeSide = %q, want empty", swap.FeeSide)
|
||||
}
|
||||
if swap.ConsumeUnit != 0 {
|
||||
t.Fatalf("swap.ConsumeUnit = %d, want 0", swap.ConsumeUnit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxBinaryRejectsUnknownProgramEnum(t *testing.T) {
|
||||
txBinary := &TxBinary{
|
||||
SchemaVersion: txBinarySchemaVersionCurrent,
|
||||
EnumVersion: txBinaryEnumVersionV1,
|
||||
Swaps: []SwapBinary{
|
||||
{Program: "unknown_program"},
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := txBinary.MarshalBinary(); err == nil {
|
||||
t.Fatal("MarshalBinary() error = nil, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxsBinaryRoundTripWithSharedAddressTable(t *testing.T) {
|
||||
tx1 := Tx{
|
||||
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
Block: 1,
|
||||
BlockIndex: 1,
|
||||
CuFee: decimal.NewFromInt(1000),
|
||||
CUPrice: decimal.RequireFromString("0.123456"),
|
||||
BeforeSolBalance: decimal.RequireFromString("1.000000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("0.900000000"),
|
||||
ComputeUnitsConsumed: 100,
|
||||
CuLimit: 200000,
|
||||
Swaps: []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: TxEventBuy,
|
||||
TxIndex: 1,
|
||||
InstrIdx: 0,
|
||||
InnerIdx: 0,
|
||||
Pool: mustPubKey("11111111111111111111111111111111"),
|
||||
BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
QuoteMint: solana.WrappedSol,
|
||||
BaseTokenProgram: solana.TokenProgramID,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"),
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: mustPubKey("SysvarRent111111111111111111111111111111111"),
|
||||
BaseAmount: decimal.NewFromInt(10),
|
||||
QuoteAmount: decimal.NewFromInt(20),
|
||||
SwapMode: SwapModeExactIn,
|
||||
FixedAmount: decimal.NewFromInt(20),
|
||||
FixedAmountSide: SwapAmountSideQuote,
|
||||
FixedMint: solana.WrappedSol,
|
||||
LimitAmountType: SwapLimitTypeMinOut,
|
||||
LimitAmount: decimal.NewFromInt(9),
|
||||
LimitAmountSide: SwapAmountSideBase,
|
||||
LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
ActualLimitAmount: decimal.NewFromInt(10),
|
||||
ActualLimitAmountSide: SwapAmountSideBase,
|
||||
SlippageBps: decimal.RequireFromString("100.2"),
|
||||
BaseReserve: decimal.NewFromInt(100),
|
||||
QuoteReserve: decimal.NewFromInt(200),
|
||||
UserBaseBalance: decimal.NewFromInt(1),
|
||||
UserQuoteBalance: decimal.NewFromInt(2),
|
||||
EntryContract: solana.PublicKey{},
|
||||
MigrateToPool: solana.PublicKey{},
|
||||
MigrateTopProgram: solana.PublicKey{},
|
||||
LpMint: solana.PublicKey{},
|
||||
AfterSOLBalance: decimal.RequireFromString("0.800000000"),
|
||||
},
|
||||
},
|
||||
}
|
||||
tx2 := tx1
|
||||
tx2.Block = 2
|
||||
tx2.BlockIndex = 2
|
||||
tx2.CuFee = decimal.NewFromInt(2000)
|
||||
tx2.AfterSOLBalance = decimal.RequireFromString("0.700000000")
|
||||
tx2.Swaps = []Swap{tx1.Swaps[0]}
|
||||
tx2.Swaps[0].TxIndex = 2
|
||||
tx2.Swaps[0].BaseAmount = decimal.NewFromInt(30)
|
||||
tx2.Swaps[0].QuoteAmount = decimal.NewFromInt(40)
|
||||
|
||||
batchEncoded, err := EncodeTxsBinary([]Tx{tx1, tx2})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary() error = %v", err)
|
||||
}
|
||||
decoded, err := DecodeTxsBinary(batchEncoded)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeTxsBinary() error = %v", err)
|
||||
}
|
||||
if len(decoded) != 2 {
|
||||
t.Fatalf("decoded len = %d, want 2", len(decoded))
|
||||
}
|
||||
if decoded[0].Signer != tx1.Signer || decoded[1].Signer != tx2.Signer {
|
||||
t.Fatalf("decoded signer mismatch")
|
||||
}
|
||||
if decoded[0].Swaps[0].Pool != tx1.Swaps[0].Pool || decoded[1].Swaps[0].Pool != tx2.Swaps[0].Pool {
|
||||
t.Fatalf("decoded shared address mismatch")
|
||||
}
|
||||
|
||||
single1, err := EncodeTxBinary(&tx1)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxBinary(tx1) error = %v", err)
|
||||
}
|
||||
single2, err := EncodeTxBinary(&tx2)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxBinary(tx2) error = %v", err)
|
||||
}
|
||||
if len(batchEncoded) >= len(single1)+len(single2) {
|
||||
t.Fatalf("batch encoded = %d, want smaller than singles sum %d", len(batchEncoded), len(single1)+len(single2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeTxsBinaryReader(t *testing.T) {
|
||||
tx1 := Tx{
|
||||
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
Block: 100,
|
||||
BlockIndex: 7,
|
||||
CuFee: decimal.NewFromInt(111),
|
||||
CUPrice: decimal.RequireFromString("0.123456"),
|
||||
BeforeSolBalance: decimal.RequireFromString("1.000000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("0.500000000"),
|
||||
ComputeUnitsConsumed: 1234,
|
||||
CuLimit: 250000,
|
||||
Swaps: []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: TxEventBuy,
|
||||
TxIndex: 3,
|
||||
InstrIdx: 1,
|
||||
InnerIdx: 2,
|
||||
Pool: mustPubKey("11111111111111111111111111111111"),
|
||||
BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
QuoteMint: solana.WrappedSol,
|
||||
BaseTokenProgram: solana.TokenProgramID,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"),
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: mustPubKey("SysvarRent111111111111111111111111111111111"),
|
||||
BaseAmount: decimal.NewFromInt(100),
|
||||
QuoteAmount: decimal.NewFromInt(200),
|
||||
SwapMode: SwapModeExactIn,
|
||||
FixedAmount: decimal.NewFromInt(200),
|
||||
FixedAmountSide: SwapAmountSideQuote,
|
||||
FixedMint: solana.WrappedSol,
|
||||
LimitAmountType: SwapLimitTypeMinOut,
|
||||
LimitAmount: decimal.NewFromInt(90),
|
||||
LimitAmountSide: SwapAmountSideBase,
|
||||
LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
ActualLimitAmount: decimal.NewFromInt(100),
|
||||
ActualLimitAmountSide: SwapAmountSideBase,
|
||||
SlippageBps: decimal.RequireFromString("99.6"),
|
||||
BaseReserve: decimal.NewFromInt(1000),
|
||||
QuoteReserve: decimal.NewFromInt(2000),
|
||||
UserBaseBalance: decimal.NewFromInt(10),
|
||||
UserQuoteBalance: decimal.NewFromInt(20),
|
||||
AfterSOLBalance: decimal.RequireFromString("0.400000000"),
|
||||
},
|
||||
},
|
||||
}
|
||||
tx2 := tx1
|
||||
tx2.Block = 101
|
||||
tx2.BlockIndex = 8
|
||||
tx2.CuFee = decimal.NewFromInt(222)
|
||||
tx2.AfterSOLBalance = decimal.RequireFromString("0.300000000")
|
||||
tx2.Swaps = []Swap{tx1.Swaps[0]}
|
||||
tx2.Swaps[0].TxIndex = 4
|
||||
tx2.Swaps[0].BaseAmount = decimal.NewFromInt(300)
|
||||
|
||||
encoded, err := EncodeTxsBinary([]Tx{tx1, tx2})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded []*Tx
|
||||
for tx, err := range DecodeTxsBinaryReader(bytes.NewReader(encoded)) {
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeTxsBinaryReader() error = %v", err)
|
||||
}
|
||||
decoded = append(decoded, tx)
|
||||
}
|
||||
|
||||
if len(decoded) != 2 {
|
||||
t.Fatalf("decoded len = %d, want 2", len(decoded))
|
||||
}
|
||||
if decoded[0].Signer != tx1.Signer || decoded[1].Signer != tx2.Signer {
|
||||
t.Fatalf("decoded signer mismatch")
|
||||
}
|
||||
if decoded[0].Block != tx1.Block || decoded[1].Block != tx2.Block {
|
||||
t.Fatalf("decoded block mismatch")
|
||||
}
|
||||
if decoded[0].Swaps[0].BaseAmount.Cmp(tx1.Swaps[0].BaseAmount) != 0 {
|
||||
t.Fatalf("decoded tx1 swap base amount = %s, want %s", decoded[0].Swaps[0].BaseAmount, tx1.Swaps[0].BaseAmount)
|
||||
}
|
||||
if decoded[1].Swaps[0].BaseAmount.Cmp(tx2.Swaps[0].BaseAmount) != 0 {
|
||||
t.Fatalf("decoded tx2 swap base amount = %s, want %s", decoded[1].Swaps[0].BaseAmount, tx2.Swaps[0].BaseAmount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeTxsBinaryReaderEarlyStop(t *testing.T) {
|
||||
tx := Tx{
|
||||
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
Block: 1,
|
||||
BlockIndex: 1,
|
||||
CuFee: decimal.NewFromInt(1),
|
||||
CUPrice: decimal.RequireFromString("0.000001"),
|
||||
BeforeSolBalance: decimal.RequireFromString("1.000000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("0.999999999"),
|
||||
ComputeUnitsConsumed: 1,
|
||||
CuLimit: 1,
|
||||
}
|
||||
encoded, err := EncodeTxsBinary([]Tx{tx, tx, tx})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary() error = %v", err)
|
||||
}
|
||||
|
||||
count := 0
|
||||
for decodedTx, err := range DecodeTxsBinaryReader(bytes.NewReader(encoded)) {
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeTxsBinaryReader() error = %v", err)
|
||||
}
|
||||
if decodedTx == nil {
|
||||
t.Fatal("decoded tx is nil")
|
||||
}
|
||||
count++
|
||||
break
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
t.Fatalf("count = %d, want 1", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeTxsBinaryBytes(t *testing.T) {
|
||||
tx1 := Tx{
|
||||
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
Block: 11,
|
||||
BlockIndex: 1,
|
||||
CuFee: decimal.NewFromInt(10),
|
||||
CUPrice: decimal.RequireFromString("0.000123"),
|
||||
BeforeSolBalance: decimal.RequireFromString("1.100000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("1.000000000"),
|
||||
ComputeUnitsConsumed: 10,
|
||||
CuLimit: 100,
|
||||
Swaps: []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: TxEventBuy,
|
||||
TxIndex: 1,
|
||||
Pool: mustPubKey("11111111111111111111111111111111"),
|
||||
BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
QuoteMint: solana.WrappedSol,
|
||||
BaseTokenProgram: solana.TokenProgramID,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"),
|
||||
User: mustPubKey("SysvarRent111111111111111111111111111111111"),
|
||||
FixedMint: solana.WrappedSol,
|
||||
LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
|
||||
EntryContract: solana.PublicKey{},
|
||||
MigrateToPool: solana.PublicKey{},
|
||||
MigrateTopProgram: solana.PublicKey{},
|
||||
LpMint: solana.PublicKey{},
|
||||
},
|
||||
},
|
||||
}
|
||||
tx2 := Tx{
|
||||
Signer: mustPubKey("SysvarRent111111111111111111111111111111111"),
|
||||
Block: 12,
|
||||
BlockIndex: 2,
|
||||
CuFee: decimal.NewFromInt(20),
|
||||
CUPrice: decimal.RequireFromString("0.000456"),
|
||||
BeforeSolBalance: decimal.RequireFromString("2.200000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("2.000000000"),
|
||||
ComputeUnitsConsumed: 20,
|
||||
CuLimit: 200,
|
||||
Swaps: []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: TxEventSell,
|
||||
TxIndex: 2,
|
||||
Pool: mustPubKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
|
||||
BaseMint: mustPubKey("4Nd1mJf8JQhRVTfJxW2YxXLNQKhPYo1JzN1u2KAPY1Hn"),
|
||||
QuoteMint: solana.WrappedSol,
|
||||
BaseTokenProgram: solana.TokenProgramID,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
Creator: mustPubKey("ComputeBudget111111111111111111111111111111"),
|
||||
User: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
FixedMint: solana.WrappedSol,
|
||||
LimitMint: mustPubKey("4Nd1mJf8JQhRVTfJxW2YxXLNQKhPYo1JzN1u2KAPY1Hn"),
|
||||
EntryContract: solana.PublicKey{},
|
||||
MigrateToPool: solana.PublicKey{},
|
||||
MigrateTopProgram: solana.PublicKey{},
|
||||
LpMint: solana.PublicKey{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
batch1, err := EncodeTxsBinary([]Tx{tx1})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary(batch1) error = %v", err)
|
||||
}
|
||||
batch2, err := EncodeTxsBinary([]Tx{tx2})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary(batch2) error = %v", err)
|
||||
}
|
||||
|
||||
merged, err := MergeTxsBinaryBytes([][]byte{batch1, batch2})
|
||||
if err != nil {
|
||||
t.Fatalf("MergeTxsBinaryBytes() error = %v", err)
|
||||
}
|
||||
|
||||
var mergedBinary TxsBinary
|
||||
if err := mergedBinary.UnmarshalBinary(merged); err != nil {
|
||||
t.Fatalf("UnmarshalBinary(merged) error = %v", err)
|
||||
}
|
||||
if len(mergedBinary.Txs) != 2 {
|
||||
t.Fatalf("merged tx count = %d, want 2", len(mergedBinary.Txs))
|
||||
}
|
||||
if len(mergedBinary.AddressTable) >= len(mustTxBinary(t, batch1).AddressTable)+len(mustTxBinary(t, batch2).AddressTable) {
|
||||
t.Fatalf("merged address table was not deduplicated")
|
||||
}
|
||||
|
||||
decoded, err := DecodeTxsBinary(merged)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeTxsBinary(merged) error = %v", err)
|
||||
}
|
||||
if len(decoded) != 2 {
|
||||
t.Fatalf("decoded len = %d, want 2", len(decoded))
|
||||
}
|
||||
if decoded[0].Block != tx1.Block || decoded[1].Block != tx2.Block {
|
||||
t.Fatalf("decoded block mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeTxsBinarySourcesToWriterWithConcatenatedBatches(t *testing.T) {
|
||||
tx1 := Tx{
|
||||
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
||||
Block: 21,
|
||||
BlockIndex: 1,
|
||||
CuFee: decimal.NewFromInt(1),
|
||||
CUPrice: decimal.RequireFromString("0.000001"),
|
||||
BeforeSolBalance: decimal.RequireFromString("1.000000000"),
|
||||
AfterSOLBalance: decimal.RequireFromString("0.900000000"),
|
||||
ComputeUnitsConsumed: 11,
|
||||
CuLimit: 111,
|
||||
}
|
||||
tx2 := tx1
|
||||
tx2.Block = 22
|
||||
tx2.BlockIndex = 2
|
||||
tx2.Signer = mustPubKey("SysvarRent111111111111111111111111111111111")
|
||||
tx3 := tx1
|
||||
tx3.Block = 23
|
||||
tx3.BlockIndex = 3
|
||||
tx3.Signer = mustPubKey("ComputeBudget111111111111111111111111111111")
|
||||
|
||||
batch1, err := EncodeTxsBinary([]Tx{tx1})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary(batch1) error = %v", err)
|
||||
}
|
||||
batch2, err := EncodeTxsBinary([]Tx{tx2})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary(batch2) error = %v", err)
|
||||
}
|
||||
batch3, err := EncodeTxsBinary([]Tx{tx3})
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeTxsBinary(batch3) error = %v", err)
|
||||
}
|
||||
|
||||
source1 := &testTxsBinarySource{
|
||||
data: append(append([]byte{}, batch1...), batch2...),
|
||||
}
|
||||
source2 := &testTxsBinarySource{
|
||||
data: batch3,
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
if err := MergeTxsBinarySourcesToWriter([]TxsBinaryReaderSource{source1, source2}, &out); err != nil {
|
||||
t.Fatalf("MergeTxsBinarySourcesToWriter() error = %v", err)
|
||||
}
|
||||
|
||||
if source1.opens != 2 || source2.opens != 2 {
|
||||
t.Fatalf("source opens = (%d, %d), want (2, 2)", source1.opens, source2.opens)
|
||||
}
|
||||
|
||||
decoded, err := DecodeTxsBinary(out.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeTxsBinary(merged) error = %v", err)
|
||||
}
|
||||
if len(decoded) != 3 {
|
||||
t.Fatalf("decoded len = %d, want 3", len(decoded))
|
||||
}
|
||||
if decoded[0].Block != tx1.Block || decoded[1].Block != tx2.Block || decoded[2].Block != tx3.Block {
|
||||
t.Fatalf("decoded block order mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func mustPubKey(value string) solana.PublicKey {
|
||||
return solana.MustPublicKeyFromBase58(value)
|
||||
}
|
||||
|
||||
func mustTxBinary(t *testing.T, data []byte) *TxsBinary {
|
||||
t.Helper()
|
||||
|
||||
var txsBinary TxsBinary
|
||||
if err := txsBinary.UnmarshalBinary(data); err != nil {
|
||||
t.Fatalf("UnmarshalBinary() error = %v", err)
|
||||
}
|
||||
return &txsBinary
|
||||
}
|
||||
|
||||
type testTxsBinarySource struct {
|
||||
data []byte
|
||||
opens int
|
||||
}
|
||||
|
||||
func (s *testTxsBinarySource) OpenTxsBinaryReader() (io.ReadCloser, error) {
|
||||
s.opens++
|
||||
return io.NopCloser(bytes.NewReader(s.data)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user