Files
pumpfun-bonkfun-bot_github/docs/regression-tests.md
T
Anton SauchykandGitHub 503785feb5 fix(trading): sell the exact raw token count the buy received (#245)
* fix(trading): sell the exact raw token count the buy received

The buy read the raw token count off its transaction, kept it as a float, and
the sell rebuilt it with int(amount * 10**6), which for some counts comes out
one raw unit short and leaves dust in the token account. The raw count now
travels on TradeResult and Position to the seller, and an exact-in buy converts
its configured spend through its decimal text.

* fix(trading): keep the exact token count when the spend cannot be read

* fix(trading): price an exact-in buy from its spend when the spend can't be read
2026-11-01 19:10:08 +01:00

8.3 KiB

Regression verifiers

The verifiers

Every fix ships with an offline verifier under tests/regression/. Each script's docstring carries the bug it guards and the checks it runs — read that before touching the code it covers, and run the ones your change reaches. None move funds. uv run tests/regression/run_all.py runs the whole set, or name individual scripts for a subset.

Script Checks
verify_v2_account_layout.py buy_v2/sell_v2 account layouts, PDA/ATA derivations, encoding — against idl/pump_fun_idl.json
verify_curve_account_sizes.py 125/151/256-byte curves all decode, and nothing filters on account length
verify_create_v2_optional_args.py omitted trailing option-typed create_v2 args decode as unset; mandatory args still fail
verify_create_v2_launch_args.py the launch path derives a holder-reward coin's curve creator, reaches all four trailing-arg wire forms, and sizes the opening buy from Global
verify_atomic_launch_v1.py the launch is one v1 transaction holding create and buy, fits 4096 bytes where v0 could not hold it, sends no ComputeBudget instructions, and states the priority fee as a lamport total
verify_quote_asset_selection.py both quote registries are consulted, each admitted mint prices from its own opening reserve, an unlisted mint is refused, and a paused mint is rejected up front
verify_transaction_v1.py every reader asks maxSupportedTransactionVersion: 1; a v1 create_v2 is detected from logs alone with the envelope unreadable, and from the envelope alone with the logs stripped; the same two routes over geyser, plus the inline v1 budget
verify_shreds_listener.py pre-execution creates decode from the instruction alone: user at create_v2 account 5, a holder-reward creator derived as PDA(["holder-rewards", mint]), truncated trailing args decoding as not-holder-reward, lookup-table accounts resolved, and nothing reading a meta the stream has no field for
verify_block_null_guard.py a blockSubscribe frame with value.block: null is skipped, not logged as an error
verify_logs_listener_skips_failed_tx.py a logsNotification carrying an err yields no coin, so a create whose transaction reverted is not bought; a successful one is still detected, and geyser still excludes failed transactions server-side
verify_creator_migration_from_logs.py a fee-sharing coin's creator comes from MigrateBondingCurveCreatorEvent in the same logs, not from CreateEvent.creator, so creator_vault matches the curve and the buy does not revert ConstraintSeeds; ordinary creates unchanged, state_from_event kept, and neither event is honoured unless pump.fun's own invocation emitted it
verify_ping_loop_close_is_quiet.py a WebSocket closing normally ends the ping loop quietly in every listener, while an unexpected failure is still logged
verify_listener_cancellation.py a cancelled WebSocket listener stops, even when websockets reports cancellation as AssertionError
verify_create_log_gate.py the create marker is matched as a whole log line, so a foreign Create* instruction is not a coin and a real create sharing its transaction with one is still detected
verify_extreme_fast_zero_rpc.py zero RPC calls between detection and submission for CreateEvent-sourced tokens
verify_curve_refresh_buy_path.py a TokenInfo without state_from_event refreshes first: an unreadable curve skips the buy, and curve + mint are read in one slot-consistent batch that corrects the token program
verify_cleanup_survives_shutdown.py shutdown cleanup runs to completion through the cancellation that triggered it, bounded, and a cleanup that hangs or raises still lets the process exit
verify_token_queue_shutdown.py cancelling the token queue processor ends it without raising, and task_done() stays balanced on the paths that took an item
verify_pumpfun_buy_token_amount.py a pump.fun buy_v2 asks for the whole sized token count with slippage on the cost ceiling, in extreme_fast_mode and on the regular path
verify_exact_token_amounts.py a position sells the raw token count its buy read, not a float rebuilt from it, including when the buy falls back to the wallet balance; an exact-in buy spends the configured amount to the raw unit
verify_buy_result_not_lost.py a landed buy is never reported failed, and a reverted one never reported landed
verify_tx_status_checks.py every path reads meta.err; --live replays known reverted signatures
verify_tp_sl_exit_price.py the tp/sl exit prices off the trigger price, and a reverted sell is retried, bounded
verify_time_based_exit_retry.py the default time_based exit retries a reverted sell instead of stranding the position
verify_time_exit_without_price.py max_hold_time still fires when every price read fails
verify_exit_sell_confirmation.py an exit sell is retried only when retrying is provably safe
verify_rpc_deadline.py post_rpc bounds wall time, not just attempts (virtual clock)
verify_quote_decimals_resolved.py no trade path prices a coin before resolving its quote mint's decimals; nothing in cookbook/ or src/ falls back to a literal decimal count; both unit helpers raise for an unresolved mint; the two pre-seeded quote tables carry the same mints; the buy clears its quote gate before it sizes
verify_migration_event_discriminator.py the migration decoder rejects any payload that is not the wrapper program's CreatePoolEvent, including a foreign one long enough for the schema
verify_cookbook_arguments.py every cookbook script takes its input as a command-line argument
verify_documentation_links.py no known-dead URL is back; --live fetches every one and fails on 4xx/5xx
verify_no_rpc_credentials_logged.py credentials masked in every log record, including a URL passed as a non-str argument, and every site that installs a root handler installs the redaction first
verify_prices_without_hardcoded_sol_unit.py nothing in core/, trading/ or platforms/ scales an amount by LAMPORTS_PER_SOL
verify_sell_after_graduation.py a graduated pump.fun curve and a migrated LaunchLab pool report graduated and refuse a price; the PumpSwap and CPMM pools derive from the mint; each market's sell matches a real sell on chain; prices come from the pools; the seller and the trader's price read switch to the market, and a pool not created yet sends nothing
verify_pumpswap_account_layout.py pump-amm's pool-v2 account is gated on coin_creator, the buyback pair stays last, and base-token decimals are resolved rather than assumed; --live re-reads the authorized recipients from GlobalConfig
verify_launchlab_trades.py against real StonkFun trades, the LaunchLab curve math reproduces each buy and sell to the unit — fee rate read from the configs, transfer fee from the mint; price comes from virtual plus real reserves; the swap names the on-chain 18 accounts with fee vaults keyed by the pool's quote mint; an exact-in buy gets the configured amount unpadded; buy and sell floors are net of curve fee and transfer fee; single-token mode waits past a coin its filters refuse instead of exiting on it
verify_launchlab_launch_parsing.py legacy, v0 and v1 StonkFun launches parse from every account slot over blockSubscribe and geyser alike, lookup-table accounts included; the creator slot wins over the payer; a router-issued launch is found; letsbonk and StonkFun each keep to their own platform configs; LaunchLab listeners subscribe on platform configs, pump.fun on its program
verify_cleanup_harvests_withheld_fees.py cleanup harvests withheld Token-2022 transfer fees into the mint before closing a token account, and sends a bare close when nothing is withheld

Two mainnet simulations, also no funds moved:

uv run tools/simulate_v2_trades.py <MINT>   # buy_v2/sell_v2 for one coin, reports CU
uv run tools/simulate_bot_buy_path.py       # the bot's whole buy path, fresh coin

After any pump.fun program upgrade run verify_v2_account_layout, verify_curve_account_sizes and both simulations, then retune get_buy_compute_unit_limit / get_sell_compute_unit_limit in platforms/pumpfun/instruction_builder.py from the reported unitsConsumed.