787 lines
27 KiB
Go
787 lines
27 KiB
Go
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 TestTxBinaryAcceptsKnownEventEnums(t *testing.T) {
|
|
events := []string{
|
|
TxEventAddLP,
|
|
TxEventRemoveLP,
|
|
TxEventBuy,
|
|
TxEventSell,
|
|
TxEventBuyFailed,
|
|
TxEventSellFailed,
|
|
TxEventBurn,
|
|
TxEventCreate,
|
|
TxEventComplete,
|
|
TxEventMigrate,
|
|
TxEventDeposit,
|
|
TxEventWithdraw,
|
|
TxEventOpen,
|
|
TxEventClose,
|
|
TxEventClaimFee,
|
|
TxEventAddLiquidity,
|
|
TxEventAddLiquidityOneSide,
|
|
TxEventRemoveLiquidity,
|
|
TxEventRemoveLiquidityOneSide,
|
|
}
|
|
|
|
for _, event := range events {
|
|
t.Run(event, func(t *testing.T) {
|
|
txBinary := &TxBinary{
|
|
SchemaVersion: txBinarySchemaVersionCurrent,
|
|
EnumVersion: txBinaryEnumVersionV1,
|
|
AddressTable: []solana.PublicKey{
|
|
mustPubKey("11111111111111111111111111111111"),
|
|
mustPubKey("So11111111111111111111111111111111111111112"),
|
|
solana.TokenProgramID,
|
|
mustPubKey("BPFLoader1111111111111111111111111111111111"),
|
|
mustPubKey("SysvarRent111111111111111111111111111111111"),
|
|
},
|
|
Swaps: []SwapBinary{
|
|
{
|
|
Program: SolProgramPump,
|
|
Event: event,
|
|
Pool: 0,
|
|
BaseMint: 1,
|
|
QuoteMint: 1,
|
|
BaseTokenProgram: 2,
|
|
QuoteTokenProgram: 2,
|
|
Creator: 3,
|
|
User: 4,
|
|
FixedMint: 1,
|
|
LimitMint: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
encoded, err := txBinary.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("MarshalBinary() error = %v", err)
|
|
}
|
|
|
|
var decoded TxBinary
|
|
if err := decoded.UnmarshalBinary(encoded); err != nil {
|
|
t.Fatalf("UnmarshalBinary() error = %v", err)
|
|
}
|
|
if got := decoded.Swaps[0].Event; got != event {
|
|
t.Fatalf("decoded event = %q, want %q", got, event)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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 TestMergeTxsBinarySourcesToWriterWithBatchHeaderFuncSkip(t *testing.T) {
|
|
tx1 := Tx{
|
|
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
|
|
Block: 31,
|
|
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 = 32
|
|
tx2.BlockIndex = 2
|
|
tx2.Signer = mustPubKey("SysvarRent111111111111111111111111111111111")
|
|
tx3 := tx1
|
|
tx3.Block = 33
|
|
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)
|
|
}
|
|
|
|
source := &testTxsBinarySource{
|
|
data: append(
|
|
append(
|
|
append([]byte{}, testBatchHeader(false)...),
|
|
batch1...,
|
|
),
|
|
append(
|
|
append(testBatchHeader(true), batch2...),
|
|
append(testBatchHeader(false), batch3...)...,
|
|
)...,
|
|
),
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
err = MergeTxsBinarySourcesToWriterWithOptions(
|
|
[]TxsBinaryReaderSource{source},
|
|
&out,
|
|
TxsBinaryMergeOptions{
|
|
BatchHeaderFunc: func(ctx *TxsBinaryBatchHeaderContext) (bool, error) {
|
|
header := make([]byte, 5)
|
|
if _, err := io.ReadFull(ctx.Reader, header); err != nil {
|
|
return false, err
|
|
}
|
|
if !bytes.Equal(header[:4], []byte("BHDR")) {
|
|
return false, io.ErrUnexpectedEOF
|
|
}
|
|
return header[4] == 1, nil
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("MergeTxsBinarySourcesToWriterWithOptions() error = %v", err)
|
|
}
|
|
|
|
decoded, err := DecodeTxsBinary(out.Bytes())
|
|
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 != tx3.Block {
|
|
t.Fatalf("decoded block order mismatch after skip")
|
|
}
|
|
if source.opens != 2 {
|
|
t.Fatalf("source.opens = %d, want 2", source.opens)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func testBatchHeader(skip bool) []byte {
|
|
header := []byte("BHDR\x00")
|
|
if skip {
|
|
header[4] = 1
|
|
}
|
|
return header
|
|
}
|