Skip to content

Commit 57a2040

Browse files
smypmsaclaude
andcommitted
fix: address CodeRabbit review — writable flag, account layout tests, parametrize surfpool
- Set MAYHEM_PROGRAM_ID to is_writable=False in create_v2 (read-only per IDL) - Add account count + position assertions to create_v2 instruction tests - Parametrize 8 surfpool launch tests into single matrix test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ad25194 commit 57a2040

File tree

3 files changed

+60
-194
lines changed

3 files changed

+60
-194
lines changed

src/pumpfun_cli/protocol/instructions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def build_create_instructions(
323323
AccountMeta(pubkey=ASSOCIATED_TOKEN_PROGRAM, is_signer=False, is_writable=False),
324324
# Mayhem accounts are always required by create_v2 per IDL,
325325
# regardless of is_mayhem_mode flag value.
326-
AccountMeta(pubkey=MAYHEM_PROGRAM_ID, is_signer=False, is_writable=True),
326+
AccountMeta(pubkey=MAYHEM_PROGRAM_ID, is_signer=False, is_writable=False),
327327
AccountMeta(pubkey=MAYHEM_GLOBAL_PARAMS, is_signer=False, is_writable=False),
328328
AccountMeta(pubkey=MAYHEM_SOL_VAULT, is_signer=False, is_writable=True),
329329
AccountMeta(pubkey=mayhem_state, is_signer=False, is_writable=True),

tests/test_protocol/test_instructions.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,23 @@
33
from solders.pubkey import Pubkey
44

55
from pumpfun_cli.protocol.address import derive_associated_bonding_curve, derive_bonding_curve
6-
from pumpfun_cli.protocol.contracts import BUY_EXACT_SOL_IN_DISCRIMINATOR
6+
from pumpfun_cli.protocol.contracts import (
7+
BUY_EXACT_SOL_IN_DISCRIMINATOR,
8+
MAYHEM_GLOBAL_PARAMS,
9+
MAYHEM_PROGRAM_ID,
10+
MAYHEM_SOL_VAULT,
11+
)
712
from pumpfun_cli.protocol.idl_parser import IDLParser
813
from pumpfun_cli.protocol.instructions import (
914
build_buy_exact_sol_in_instructions,
1015
build_buy_instructions,
16+
build_create_instructions,
1117
build_sell_instructions,
1218
)
1319

20+
# create_v2 must always have exactly 16 accounts (including mayhem accounts).
21+
_EXPECTED_CREATE_V2_ACCOUNTS = 16
22+
1423
IDL_PATH = Path(__file__).parent.parent.parent / "idl" / "pump_fun_idl.json"
1524
_MINT = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
1625
_USER = Pubkey.from_string("11111111111111111111111111111112")
@@ -85,8 +94,6 @@ def test_buy_exact_sol_in_discriminator():
8594

8695
def test_create_instructions_cashback_false():
8796
"""create_v2 with is_cashback=False encodes OptionBool as 0x00."""
88-
from pumpfun_cli.protocol.instructions import build_create_instructions
89-
9097
idl = IDLParser(str(IDL_PATH))
9198
ixs = build_create_instructions(
9299
idl=idl,
@@ -100,6 +107,11 @@ def test_create_instructions_cashback_false():
100107
)
101108
assert len(ixs) == 1
102109
create_ix = ixs[0]
110+
# Lock account layout to prevent AccountNotEnoughKeys regressions
111+
assert len(create_ix.accounts) == _EXPECTED_CREATE_V2_ACCOUNTS
112+
assert create_ix.accounts[9].pubkey == MAYHEM_PROGRAM_ID
113+
assert create_ix.accounts[10].pubkey == MAYHEM_GLOBAL_PARAMS
114+
assert create_ix.accounts[11].pubkey == MAYHEM_SOL_VAULT
103115
# Last byte should be 0x00 (is_cashback_enabled = false)
104116
assert create_ix.data[-1:] == b"\x00"
105117
# Second-to-last byte is is_mayhem_mode = false
@@ -108,8 +120,6 @@ def test_create_instructions_cashback_false():
108120

109121
def test_create_instructions_cashback_true():
110122
"""create_v2 with is_cashback=True encodes OptionBool as 0x01."""
111-
from pumpfun_cli.protocol.instructions import build_create_instructions
112-
113123
idl = IDLParser(str(IDL_PATH))
114124
ixs = build_create_instructions(
115125
idl=idl,
@@ -123,6 +133,10 @@ def test_create_instructions_cashback_true():
123133
)
124134
assert len(ixs) == 1
125135
create_ix = ixs[0]
136+
assert len(create_ix.accounts) == _EXPECTED_CREATE_V2_ACCOUNTS
137+
assert create_ix.accounts[9].pubkey == MAYHEM_PROGRAM_ID
138+
assert create_ix.accounts[10].pubkey == MAYHEM_GLOBAL_PARAMS
139+
assert create_ix.accounts[11].pubkey == MAYHEM_SOL_VAULT
126140
# Last byte should be 0x01 (is_cashback_enabled = true)
127141
assert create_ix.data[-1:] == b"\x01"
128142
# Second-to-last byte is is_mayhem_mode = false
@@ -131,8 +145,6 @@ def test_create_instructions_cashback_true():
131145

132146
def test_create_instructions_mayhem_and_cashback():
133147
"""create_v2 with both is_mayhem=True and is_cashback=True."""
134-
from pumpfun_cli.protocol.instructions import build_create_instructions
135-
136148
idl = IDLParser(str(IDL_PATH))
137149
ixs = build_create_instructions(
138150
idl=idl,
@@ -146,6 +158,10 @@ def test_create_instructions_mayhem_and_cashback():
146158
)
147159
assert len(ixs) == 1
148160
create_ix = ixs[0]
161+
assert len(create_ix.accounts) == _EXPECTED_CREATE_V2_ACCOUNTS
162+
assert create_ix.accounts[9].pubkey == MAYHEM_PROGRAM_ID
163+
assert create_ix.accounts[10].pubkey == MAYHEM_GLOBAL_PARAMS
164+
assert create_ix.accounts[11].pubkey == MAYHEM_SOL_VAULT
149165
# Last byte = cashback true, second-to-last = mayhem true
150166
assert create_ix.data[-1:] == b"\x01"
151167
assert create_ix.data[-2:-1] == b"\x01"

tests/test_surfpool/test_launch.py

Lines changed: 36 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -24,208 +24,58 @@
2424
return_value=FAKE_URI,
2525
)
2626

27-
28-
# ── mayhem=False, cashback=False (default) ──────────────────────────────
29-
30-
31-
@pytest.mark.asyncio
32-
@_UPLOAD_PATCH
33-
async def test_launch_default(
34-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
35-
):
36-
"""Launch with defaults: no mayhem, no cashback."""
37-
result = await launch_token(
38-
rpc_url=surfpool_rpc,
39-
keystore_path=str(test_keystore),
40-
password=test_password,
41-
name="Default",
42-
ticker="DFLT",
43-
description="default launch",
44-
)
45-
46-
assert "error" not in result, f"Launch failed: {result}"
47-
assert result["action"] == "launch"
48-
assert result["is_cashback"] is False
49-
assert result["signature"]
50-
51-
info = await get_token_info(surfpool_rpc, result["mint"])
52-
assert "error" not in info, f"Token not found: {info}"
53-
assert info["graduated"] is False
54-
55-
56-
# ── mayhem=False, cashback=True ─────────────────────────────────────────
27+
_LAUNCH_MATRIX = [
28+
pytest.param(False, False, None, id="default"),
29+
pytest.param(False, True, None, id="cashback"),
30+
pytest.param(True, False, None, id="mayhem"),
31+
pytest.param(True, True, None, id="mayhem+cashback"),
32+
pytest.param(False, False, 0.001, id="default+buy"),
33+
pytest.param(False, True, 0.001, id="cashback+buy"),
34+
pytest.param(True, False, 0.001, id="mayhem+buy"),
35+
pytest.param(True, True, 0.001, id="mayhem+cashback+buy"),
36+
]
5737

5838

5939
@pytest.mark.asyncio
40+
@pytest.mark.parametrize("is_mayhem,is_cashback,initial_buy_sol", _LAUNCH_MATRIX)
6041
@_UPLOAD_PATCH
61-
async def test_launch_cashback(
62-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
42+
async def test_launch(
43+
_mock_upload,
44+
surfpool_rpc,
45+
funded_keypair,
46+
test_keystore,
47+
test_password,
48+
is_mayhem,
49+
is_cashback,
50+
initial_buy_sol,
6351
):
64-
"""Launch with cashback enabled, no mayhem."""
65-
result = await launch_token(
66-
rpc_url=surfpool_rpc,
67-
keystore_path=str(test_keystore),
68-
password=test_password,
69-
name="Cashback",
70-
ticker="CSHB",
71-
description="cashback launch",
72-
is_cashback=True,
73-
)
74-
75-
assert "error" not in result, f"Launch failed: {result}"
76-
assert result["is_cashback"] is True
77-
assert result["signature"]
78-
79-
info = await get_token_info(surfpool_rpc, result["mint"])
80-
assert "error" not in info, f"Token not found: {info}"
81-
assert info["graduated"] is False
82-
83-
84-
# ── mayhem=True, cashback=False ─────────────────────────────────────────
52+
"""Launch token with given mayhem/cashback/buy combination."""
53+
kwargs = {}
54+
if is_mayhem:
55+
kwargs["is_mayhem"] = True
56+
if is_cashback:
57+
kwargs["is_cashback"] = True
58+
if initial_buy_sol is not None:
59+
kwargs["initial_buy_sol"] = initial_buy_sol
8560

86-
87-
@pytest.mark.asyncio
88-
@_UPLOAD_PATCH
89-
async def test_launch_mayhem(
90-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
91-
):
92-
"""Launch with mayhem enabled, no cashback."""
9361
result = await launch_token(
9462
rpc_url=surfpool_rpc,
9563
keystore_path=str(test_keystore),
9664
password=test_password,
97-
name="Mayhem",
98-
ticker="MYHM",
99-
description="mayhem launch",
100-
is_mayhem=True,
65+
name="Test",
66+
ticker="TST",
67+
description="parametrized launch",
68+
**kwargs,
10169
)
10270

10371
assert "error" not in result, f"Launch failed: {result}"
104-
assert result["is_cashback"] is False
72+
assert result["action"] == "launch"
73+
assert result["is_cashback"] is is_cashback
10574
assert result["signature"]
10675

107-
info = await get_token_info(surfpool_rpc, result["mint"])
108-
assert "error" not in info, f"Token not found: {info}"
109-
assert info["graduated"] is False
110-
111-
112-
# ── mayhem=True, cashback=True ──────────────────────────────────────────
113-
114-
115-
@pytest.mark.asyncio
116-
@_UPLOAD_PATCH
117-
async def test_launch_mayhem_cashback(
118-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
119-
):
120-
"""Launch with both mayhem and cashback enabled."""
121-
result = await launch_token(
122-
rpc_url=surfpool_rpc,
123-
keystore_path=str(test_keystore),
124-
password=test_password,
125-
name="MayhemCB",
126-
ticker="MHCB",
127-
description="mayhem + cashback launch",
128-
is_mayhem=True,
129-
is_cashback=True,
130-
)
131-
132-
assert "error" not in result, f"Launch failed: {result}"
133-
assert result["is_cashback"] is True
134-
assert result["signature"]
76+
if initial_buy_sol is not None:
77+
assert result["initial_buy_sol"] == initial_buy_sol
13578

13679
info = await get_token_info(surfpool_rpc, result["mint"])
13780
assert "error" not in info, f"Token not found: {info}"
13881
assert info["graduated"] is False
139-
140-
141-
# ── with initial buy ────────────────────────────────────────────────────
142-
143-
144-
@pytest.mark.asyncio
145-
@_UPLOAD_PATCH
146-
async def test_launch_default_with_buy(
147-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
148-
):
149-
"""Launch with initial buy, no mayhem, no cashback."""
150-
result = await launch_token(
151-
rpc_url=surfpool_rpc,
152-
keystore_path=str(test_keystore),
153-
password=test_password,
154-
name="DefBuy",
155-
ticker="DBUY",
156-
description="default + buy",
157-
initial_buy_sol=0.001,
158-
)
159-
160-
assert "error" not in result, f"Launch failed: {result}"
161-
assert result["initial_buy_sol"] == 0.001
162-
assert result["signature"]
163-
164-
165-
@pytest.mark.asyncio
166-
@_UPLOAD_PATCH
167-
async def test_launch_cashback_with_buy(
168-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
169-
):
170-
"""Launch cashback token with initial buy."""
171-
result = await launch_token(
172-
rpc_url=surfpool_rpc,
173-
keystore_path=str(test_keystore),
174-
password=test_password,
175-
name="CashBuy",
176-
ticker="CBUY",
177-
description="cashback + buy",
178-
is_cashback=True,
179-
initial_buy_sol=0.001,
180-
)
181-
182-
assert "error" not in result, f"Launch failed: {result}"
183-
assert result["is_cashback"] is True
184-
assert result["initial_buy_sol"] == 0.001
185-
assert result["signature"]
186-
187-
188-
@pytest.mark.asyncio
189-
@_UPLOAD_PATCH
190-
async def test_launch_mayhem_with_buy(
191-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
192-
):
193-
"""Launch mayhem token with initial buy."""
194-
result = await launch_token(
195-
rpc_url=surfpool_rpc,
196-
keystore_path=str(test_keystore),
197-
password=test_password,
198-
name="MyhBuy",
199-
ticker="MBUY",
200-
description="mayhem + buy",
201-
is_mayhem=True,
202-
initial_buy_sol=0.001,
203-
)
204-
205-
assert "error" not in result, f"Launch failed: {result}"
206-
assert result["initial_buy_sol"] == 0.001
207-
assert result["signature"]
208-
209-
210-
@pytest.mark.asyncio
211-
@_UPLOAD_PATCH
212-
async def test_launch_mayhem_cashback_with_buy(
213-
_mock_upload, surfpool_rpc, funded_keypair, test_keystore, test_password
214-
):
215-
"""Launch mayhem + cashback token with initial buy."""
216-
result = await launch_token(
217-
rpc_url=surfpool_rpc,
218-
keystore_path=str(test_keystore),
219-
password=test_password,
220-
name="MhCbBuy",
221-
ticker="MCBB",
222-
description="mayhem + cashback + buy",
223-
is_mayhem=True,
224-
is_cashback=True,
225-
initial_buy_sol=0.001,
226-
)
227-
228-
assert "error" not in result, f"Launch failed: {result}"
229-
assert result["is_cashback"] is True
230-
assert result["initial_buy_sol"] == 0.001
231-
assert result["signature"]

0 commit comments

Comments
 (0)