289 lines
11 KiB
Go
289 lines
11 KiB
Go
package pump_parser
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"testing"
|
|
|
|
agbinary "github.com/gagliardetto/binary"
|
|
"github.com/gagliardetto/solana-go"
|
|
"github.com/mr-tron/base58"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
func TestTradeEvent(t *testing.T) {
|
|
hexData := "e445a52e51cb9a1dbddb7fd34ee661ee051d1834b36cc6f04cc5bd998d53ab2a566a0ca2415bcfad5f9ed6941a851d3f84ecb200000000006c267d17170000000190d2c525ef0ea205f4b4abfdb6eaaf37fcb5a1b1dec2e2689448eecab6ba93b6c922246900000000c314f11a0d000000be71bf9e22080200c368cd1e06000000bed9ac52910901004ac2f8d0dd5cbc97e3289c197cb5062a54f3d956b9ce6e5115f96567aa5cb3e65f000000000000002c6a010000000000c9e17c171227a50a5b62e3a4a3f8ff4fafe0bca9c332bdf7f32eedbc4229604d1e000000000000005f72000000000000010000000000000000000000000000000000000000000000000000000000000000100000006275795f65786163745f736f6c5f696e"
|
|
d, err := hex.DecodeString(hexData)
|
|
if err != nil {
|
|
t.Errorf("Failed to decode base64 data: %v", err)
|
|
}
|
|
|
|
var tradeEvent legacyPumpTradeEvent
|
|
|
|
err = agbinary.NewBorshDecoder(d[16:]).Decode(&tradeEvent)
|
|
if err != nil {
|
|
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")
|
|
}
|
|
t.Logf("Trade Event: %+v", tradeEvent)
|
|
|
|
xx, err := base58.Decode("3Bxs48EzTZB4tzRd")
|
|
fmt.Println(len(xx), err)
|
|
|
|
}
|
|
func TestCal(t *testing.T) {
|
|
//e445a52e51cb9a1db94afc7d1bd7bc6f5e99e54b
|
|
// . b94afc7d1bd7bc6f
|
|
s := calculateDiscriminator("global:initialize_with_permission")
|
|
fmt.Println(hex.EncodeToString(s[:]))
|
|
|
|
s2, _ := base58.Decode("6ApXSNCamGdm")
|
|
s3 := binary.LittleEndian.Uint64(s2[1:])
|
|
fmt.Println(s2, s3)
|
|
|
|
fmt.Println(solana.MustPublicKeyFromBase58("BM9CcyErJcu2mjrFvUsRRrD3snGeHDDVirJLvL6EjvMN").IsOnCurve())
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
func TestPumpExactQuoteInKeepsFeeArgBeforeMatchedTrade(t *testing.T) {
|
|
EnableAllParsers()
|
|
|
|
tx := mustParseRPCFixtureTx(t, "3jugr2KthX3cUHzPrMpaFKM7RtxXM6Gcxi8eFjDL7aZGLXpc6f1RaVdnAoB4ye5bRVYsP2fFs3aLaP19Utz91ewv")
|
|
if len(tx.Swaps) != 4 {
|
|
t.Fatalf("swaps len = %d, want 4", len(tx.Swaps))
|
|
}
|
|
|
|
for i := 0; i < 3; i++ {
|
|
swap := tx.Swaps[i]
|
|
if swap.Program != SolProgramPump || swap.Event != "buy" {
|
|
t.Fatalf("swap[%d] = %s/%s, want Pump/buy", i, swap.Program, swap.Event)
|
|
}
|
|
assertDecimalString(t, fmt.Sprintf("swap[%d].quote_amount", i), swap.QuoteAmount, "329217")
|
|
assertDecimalString(t, fmt.Sprintf("swap[%d].fixed_amount", i), swap.FixedAmount, "333333")
|
|
}
|
|
|
|
sell := tx.Swaps[3]
|
|
if sell.Program != SolProgramPump || sell.Event != "sell" {
|
|
t.Fatalf("swap[3] = %s/%s, want Pump/sell", sell.Program, sell.Event)
|
|
}
|
|
assertDecimalString(t, "swap[3].base_amount", sell.BaseAmount, "12282189230")
|
|
assertDecimalString(t, "swap[3].quote_amount", sell.QuoteAmount, "987647")
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|