pump usdc support

This commit is contained in:
thloyi
2026-05-08 11:21:30 +08:00
parent 0a4aabc67f
commit 5cd3a97d81
4 changed files with 729 additions and 90 deletions

View File

@@ -1,6 +1,7 @@
package pump_parser
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
@@ -100,3 +101,163 @@ func TestPumpCompleteMatchesTradeEvent(t *testing.T) {
t.Fatal("pumpCompleteMatchesTradeEvent() = true for mismatched user")
}
}
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)
}
}