Files
pump-parser/pump_test.go

264 lines
9.7 KiB
Go
Raw Normal View History

2025-11-24 17:47:56 +08:00
package pump_parser
import (
2026-05-08 11:21:30 +08:00
"bytes"
2026-02-09 14:46:19 +08:00
"encoding/binary"
2025-11-24 17:47:56 +08:00
"encoding/hex"
2025-11-27 16:39:01 +08:00
"fmt"
2025-11-24 17:47:56 +08:00
"testing"
agbinary "github.com/gagliardetto/binary"
2026-02-09 14:46:19 +08:00
"github.com/gagliardetto/solana-go"
2025-11-27 16:39:01 +08:00
"github.com/mr-tron/base58"
2025-11-24 17:47:56 +08:00
)
2026-04-16 14:24:14 +08:00
type legacyPumpTradeEvent struct {
Mint solana.PublicKey
SolAmount uint64
TokenAmount uint64
IsBuy bool
User solana.PublicKey
Timestamp int64
VirtualSolReserves uint64
VirtualTokenReserves uint64
RealSolReserves uint64
RealTokenReserves uint64
FeeRecipient solana.PublicKey
FeeBasisPoints uint64
Fee uint64
Creator solana.PublicKey
CreatorFeeBasisPoints uint64
CreatorFee uint64
TrackVolume bool
TotalUnclaimedTokens uint64
TotalClaimedTokens uint64
CurrentSolVolume uint64
LastUpdateTimestamp int64
IxName string
}
2025-11-24 17:47:56 +08:00
func TestTradeEvent(t *testing.T) {
hexData := "e445a52e51cb9a1dbddb7fd34ee661ee051d1834b36cc6f04cc5bd998d53ab2a566a0ca2415bcfad5f9ed6941a851d3f84ecb200000000006c267d17170000000190d2c525ef0ea205f4b4abfdb6eaaf37fcb5a1b1dec2e2689448eecab6ba93b6c922246900000000c314f11a0d000000be71bf9e22080200c368cd1e06000000bed9ac52910901004ac2f8d0dd5cbc97e3289c197cb5062a54f3d956b9ce6e5115f96567aa5cb3e65f000000000000002c6a010000000000c9e17c171227a50a5b62e3a4a3f8ff4fafe0bca9c332bdf7f32eedbc4229604d1e000000000000005f72000000000000010000000000000000000000000000000000000000000000000000000000000000100000006275795f65786163745f736f6c5f696e"
d, err := hex.DecodeString(hexData)
if err != nil {
t.Errorf("Failed to decode base64 data: %v", err)
}
2026-04-16 14:24:14 +08:00
var tradeEvent legacyPumpTradeEvent
2025-11-24 17:47:56 +08:00
err = agbinary.NewBorshDecoder(d[16:]).Decode(&tradeEvent)
if err != nil {
2026-04-16 14:24:14 +08:00
t.Fatalf("Failed to deserialize trade event: %v", err)
}
if tradeEvent.IxName != "buy_exact_sol_in" {
t.Fatalf("IxName = %q, want buy_exact_sol_in", tradeEvent.IxName)
}
if tradeEvent.SolAmount != 11725956 {
t.Fatalf("SolAmount = %d, want 11725956", tradeEvent.SolAmount)
}
if !tradeEvent.IsBuy {
t.Fatalf("IsBuy = false, want true")
2025-11-24 17:47:56 +08:00
}
t.Logf("Trade Event: %+v", tradeEvent)
2025-11-27 16:39:01 +08:00
xx, err := base58.Decode("3Bxs48EzTZB4tzRd")
fmt.Println(len(xx), err)
2025-11-24 17:47:56 +08:00
}
2026-02-02 17:59:47 +08:00
func TestCal(t *testing.T) {
//e445a52e51cb9a1db94afc7d1bd7bc6f5e99e54b
// . b94afc7d1bd7bc6f
2026-02-09 14:46:19 +08:00
s := calculateDiscriminator("global:initialize_with_permission")
2026-02-02 17:59:47 +08:00
fmt.Println(hex.EncodeToString(s[:]))
2026-02-09 14:46:19 +08:00
s2, _ := base58.Decode("6ApXSNCamGdm")
s3 := binary.LittleEndian.Uint64(s2[1:])
fmt.Println(s2, s3)
fmt.Println(solana.MustPublicKeyFromBase58("BM9CcyErJcu2mjrFvUsRRrD3snGeHDDVirJLvL6EjvMN").IsOnCurve())
2026-02-02 17:59:47 +08:00
}
2026-04-20 16:26:55 +08:00
func TestPumpCompleteMatchesTradeEvent(t *testing.T) {
mint := solana.MustPublicKeyFromBase58("8GNGkNnfBuoTP3QRnmdNzSYuuE15M8tvcNvxNsV4pump")
user := solana.MustPublicKeyFromBase58("DS95KxqUCCjwQaXhD7fhKatXbivwWDNrJdNV5ZcubGdz")
bondingCurve := solana.MustPublicKeyFromBase58("Gz5EX3X7kUDS48baijJKubQDKy3BBKpnMJQ3f3W1e9jA")
tradeEvent := PumpTradeEvent{
Mint: mint,
User: user,
}
completeEvent := CompleteEvent{
Mint: mint,
User: user,
BondingCurve: bondingCurve,
}
if !pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, bondingCurve) {
t.Fatal("pumpCompleteMatchesTradeEvent() = false, want true")
}
completeEvent.User = solana.MustPublicKeyFromBase58("3g89wLRwJ5P22fkCdPJBAP7iiYAo6yY96geQvMYj6tYm")
if pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, bondingCurve) {
t.Fatal("pumpCompleteMatchesTradeEvent() = true for mismatched user")
}
}
2026-05-08 11:21:30 +08:00
func TestPumpV2Discriminators(t *testing.T) {
tests := []struct {
name string
got [8]byte
want [8]byte
}{
{name: "buy_exact_sol_in", got: pumpBuyExactSolInDiscriminator, want: [8]byte{56, 252, 116, 8, 158, 223, 205, 95}},
{name: "buy_v2", got: pumpBuyV2Discriminator, want: [8]byte{184, 23, 238, 97, 103, 197, 211, 61}},
{name: "buy_exact_quote_in_v2", got: pumpBuyExactQuoteInV2Discriminator, want: [8]byte{194, 171, 28, 70, 104, 77, 91, 47}},
{name: "sell_v2", got: pumpSellV2Discriminator, want: [8]byte{93, 246, 130, 60, 231, 233, 64, 178}},
{name: "create_v2", got: pumpCreateV2Discriminator, want: [8]byte{214, 144, 76, 236, 95, 139, 49, 180}},
{name: "migrate_v2", got: pumpMigrateV2Discriminator, want: [8]byte{187, 203, 18, 31, 206, 237, 254, 41}},
}
for _, tt := range tests {
if tt.got != tt.want {
t.Fatalf("%s discriminator = %v, want %v", tt.name, tt.got, tt.want)
}
}
}
func TestPumpMigrateLayoutV2(t *testing.T) {
accounts := make([]int, 27)
for i := range accounts {
accounts[i] = i
}
layout, ok := pumpMigrateLayout(Instruction{
Data: pumpMigrateV2Discriminator[:],
Accounts: accounts,
})
if !ok {
t.Fatal("migrate_v2 layout not recognized")
}
if !layout.IsV2 ||
layout.BaseMint != 2 ||
layout.QuoteMint != 3 ||
layout.Pool != 4 ||
layout.BasePoolToken != 5 ||
layout.QuotePoolToken != 6 ||
layout.User != 7 ||
layout.BaseTokenProgram != 19 ||
layout.QuoteTokenProgram != 20 {
t.Fatalf("migrate_v2 layout = %+v", layout)
}
}
func TestPumpTradeAmountInfoV2(t *testing.T) {
tests := []struct {
name string
disc [8]byte
wantMode SwapMode
}{
{name: "legacy exact quote in", disc: pumpBuyExactSolInDiscriminator, wantMode: SwapModeExactIn},
{name: "v2 exact quote in", disc: pumpBuyExactQuoteInV2Discriminator, wantMode: SwapModeExactIn},
{name: "v2 buy exact out", disc: pumpBuyV2Discriminator, wantMode: SwapModeExactOut},
{name: "v2 sell exact in", disc: pumpSellV2Discriminator, wantMode: SwapModeExactIn},
}
for _, tt := range tests {
mode, fixed, limit, ok := pumpTradeAmountInfoFromArgs(PumpTradeArgs{
Discriminator: tt.disc,
Amount1: 11,
Amount2: 22,
})
if !ok {
t.Fatalf("%s not recognized", tt.name)
}
if mode != tt.wantMode {
t.Fatalf("%s mode = %s, want %s", tt.name, mode.String(), tt.wantMode.String())
}
if fixed.String() != "11" || limit.String() != "22" {
t.Fatalf("%s fixed/limit = %s/%s, want 11/22", tt.name, fixed, limit)
}
}
}
func TestPumpCreateQuoteAccountsOptional(t *testing.T) {
createAccounts := make([]int, 17)
for i := range createAccounts {
createAccounts[i] = i
}
createV2Accounts := make([]int, 19)
for i := range createV2Accounts {
createV2Accounts[i] = i
}
createV2Accounts[16] = 14
createV2Accounts[18] = 16
accountList := make([]solana.PublicKey, 19)
accountList[14] = usdcMint
accountList[16] = solana.TokenProgramID
accountList[18] = solana.TokenProgramID
result := &RawTx{accountList: accountList}
quoteMint, quoteTokenProgram, quoteDecimals := pumpCreateQuoteAccounts(result, Instruction{
Data: pumpCreateDiscriminator[:],
Accounts: createAccounts,
}, PumpCreateEvent{})
if !quoteMint.IsZero() || !quoteTokenProgram.IsZero() || quoteDecimals != 9 {
t.Fatalf("create quote accounts = %s/%s/%d, want zero/zero/9", quoteMint, quoteTokenProgram, quoteDecimals)
}
quoteMint, quoteTokenProgram, quoteDecimals = pumpCreateQuoteAccounts(result, Instruction{
Data: pumpCreateV2Discriminator[:],
Accounts: createV2Accounts,
}, PumpCreateEvent{})
if !quoteMint.Equals(usdcMint) || !quoteTokenProgram.Equals(solana.TokenProgramID) || quoteDecimals != 6 {
t.Fatalf("create_v2 quote accounts = %s/%s/%d, want USDC/token/6", quoteMint, quoteTokenProgram, quoteDecimals)
}
}
func TestDecodePumpTradeEventV2QuoteFields(t *testing.T) {
user := solana.MustPublicKeyFromBase58("DS95KxqUCCjwQaXhD7fhKatXbivwWDNrJdNV5ZcubGdz")
mint := solana.MustPublicKeyFromBase58("8GNGkNnfBuoTP3QRnmdNzSYuuE15M8tvcNvxNsV4pump")
want := PumpTradeEvent{
Mint: mint,
SolAmount: 1,
TokenAmount: 2,
IsBuy: true,
User: user,
VirtualTokenReserves: 3,
RealTokenReserves: 4,
IxName: "buy_v2",
Shareholders: []PumpShareholder{{Address: user, ShareBps: 250}},
QuoteMint: usdcMint,
QuoteAmount: 5,
VirtualQuoteReserves: 6,
RealQuoteReserves: 7,
}
var buf bytes.Buffer
if err := agbinary.NewBorshEncoder(&buf).Encode(want); err != nil {
t.Fatalf("encode v2 trade event: %v", err)
}
got, err := decodePumpTradeEvent(buf.Bytes())
if err != nil {
t.Fatalf("decodePumpTradeEvent() error = %v", err)
}
if !got.QuoteMint.Equals(usdcMint) || got.QuoteAmount != 5 || got.VirtualQuoteReserves != 6 || got.RealQuoteReserves != 7 {
t.Fatalf("decoded quote fields = %s/%d/%d/%d", got.QuoteMint, got.QuoteAmount, got.VirtualQuoteReserves, got.RealQuoteReserves)
}
if len(got.Shareholders) != 1 || got.Shareholders[0].ShareBps != 250 {
t.Fatalf("decoded shareholders = %+v", got.Shareholders)
}
}
func TestDecodePumpTradeEventLegacyFallback(t *testing.T) {
hexData := "e445a52e51cb9a1dbddb7fd34ee661ee051d1834b36cc6f04cc5bd998d53ab2a566a0ca2415bcfad5f9ed6941a851d3f84ecb200000000006c267d17170000000190d2c525ef0ea205f4b4abfdb6eaaf37fcb5a1b1dec2e2689448eecab6ba93b6c922246900000000c314f11a0d000000be71bf9e22080200c368cd1e06000000bed9ac52910901004ac2f8d0dd5cbc97e3289c197cb5062a54f3d956b9ce6e5115f96567aa5cb3e65f000000000000002c6a010000000000c9e17c171227a50a5b62e3a4a3f8ff4fafe0bca9c332bdf7f32eedbc4229604d1e000000000000005f72000000000000010000000000000000000000000000000000000000000000000000000000000000100000006275795f65786163745f736f6c5f696e"
data, err := hex.DecodeString(hexData)
if err != nil {
t.Fatalf("decode hex: %v", err)
}
got, err := decodePumpTradeEvent(data[16:])
if err != nil {
t.Fatalf("decodePumpTradeEvent() legacy error = %v", err)
}
if got.IxName != "buy_exact_sol_in" || got.SolAmount != 11725956 || !got.IsBuy {
t.Fatalf("legacy event = %+v", got)
}
if !got.QuoteMint.IsZero() || got.QuoteAmount != 0 || got.RealQuoteReserves != 0 {
t.Fatalf("legacy quote fields = %s/%d/%d, want zero", got.QuoteMint, got.QuoteAmount, got.RealQuoteReserves)
}
}