"""Verify the bonding curve decodes at every length it occurs in. `create_v2` allocates exactly 125 bytes: creator_fee_bps (u64), can_edit_creator_fee (bool) and is_holder_reward (bool) are appended to BondingCurve and the 36 reserved padding bytes are gone. `extend_account` can grow an account past that to any length the program allows — 151 and 256 both occur live — so a dataSize allowlist is whack-a-mole: the next length silently drops curves again. Anything watching for curves must not filter on dataSize at all, and must decode correctly whichever length turns up. Offline, moves no funds. """ from __future__ import annotations import json import struct import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT / "src")) CURVE_LEN_CREATED = 125 CURVE_LEN_EXTENDED = 151 CURVE_LEN_RESIZED = 256 # occurs live, see the two graduating- # token scripts' module docstrings for the getAccountInfo + decode evidence _VIRTUAL_TOKEN_RESERVES = 1_073_000_000_000_000 def _synthetic_curve(length: int) -> bytes: """Build a valid BondingCurve account image at the given total length. `length` must be at least `CURVE_LEN_CREATED` — the smallest shape the program ever writes. Anything at or above that is a legal `extend_account` target: this appends zero-byte padding to reach it, matching what live 125/151/256-byte curves actually look like past their documented fields. Args: length: Total account length in bytes, including the discriminator Returns: Account bytes matching the IDL's field order and discriminator Raises: ValueError: If length is shorter than CURVE_LEN_CREATED """ if length < CURVE_LEN_CREATED: raise ValueError(f"unexpected curve length: {length}") # noqa: TRY003 idl = json.loads((PROJECT_ROOT / "idl" / "pump_fun_idl.json").read_text()) disc = next( bytes(a["discriminator"]) for a in idl["accounts"] if a["name"] == "BondingCurve" ) reserves = struct.pack( " bool: """A synthetic 125-, 151- and 256-byte curve all decode identically.""" from platforms.pumpfun.curve_manager import PumpFunCurveManager # noqa: PLC0415 from utils.idl_parser import IDLParser # noqa: PLC0415 parser = IDLParser("idl/pump_fun_idl.json") manager = PumpFunCurveManager(client=None, idl_parser=parser) ok = True for length in (CURVE_LEN_CREATED, CURVE_LEN_EXTENDED, CURVE_LEN_RESIZED): data = _synthetic_curve(length) try: state = manager._decode_curve_state_with_idl(data) # noqa: SLF001 except Exception as exc: # noqa: BLE001 - verifier reports, doesn't raise print(f" FAIL {length}B curve failed to decode: {exc}") ok = False continue if state["virtual_token_reserves"] != _VIRTUAL_TOKEN_RESERVES: print(f" FAIL {length}B curve decoded wrong reserves") ok = False else: print(f" OK {length}B curve decodes") return ok def check_no_datasize_filter() -> bool: """Neither graduating-token example may pre-filter by dataSize/datasize. `extend_account` can grow a curve to any length the program allows, so enumerating lengths is whack-a-mole — the fix is to not filter on length at all, not to enumerate one more. Checked by looking for the actual filter-construction syntax (a quoted `"dataSize":` dict key in the WebSocket script, a `.datasize =` protobuf field assignment in the Geyser one) rather than a bare substring match, since both scripts' docstrings legitimately discuss `dataSize`/`datasize` in prose. """ ok = True needles = { "cookbook/pumpfun/graduation/pumpfun_watch_graduating_programsubscribe.py": ('"dataSize":'), "cookbook/pumpfun/graduation/pumpfun_watch_graduating_geyser.py": (".datasize ="), } for rel, needle in needles.items(): text = Path(rel).read_text() if needle in text: print(f" FAIL {rel} still constructs a dataSize filter") ok = False else: print(f" OK {rel} has no dataSize filter") return ok def main() -> int: """Run every check and report pass/fail. Returns: 0 if every check passed, 1 otherwise """ checks = [ ( "125/151/256-byte curves all decode via the IDL parser", check_all_lengths_decode, ), ("graduating-token scripts don't filter on dataSize", check_no_datasize_filter), ] failed = 0 for label, check in checks: print(f"{label}:") try: ok = check() except Exception as error: # noqa: BLE001 - report and continue print(f" FAIL {type(error).__name__}: {error}") failed += 1 continue failed += 0 if ok else 1 print(f"\n{len(checks) - failed}/{len(checks)} checks passed") return 1 if failed else 0 if __name__ == "__main__": sys.exit(main())