Skip to content

[fix]: match the program's parameter and validation rules (2/4) - #124

Closed
dannxbt wants to merge 16 commits into
v2/01-sync-program-0.2.2from
v2/02-parity-fixes
Closed

dannxbt wants to merge 16 commits into
v2/01-sync-program-0.2.2from
v2/02-parity-fixes

Conversation

@dannxbt

@dannxbt dannxbt commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #123 → #124 (this PR) → #125 → #126. Base: #123.

Ten places where the SDK produced parameters or results the program rejects or computes differently.

  • state: getPoolConfigsByOwner filtered on leftover_receiver (offset 72). It now filters on fee_claimer (offset 40).
  • migration: getCurveBreakdown consumes only the remaining threshold per segment, uses a strict comparison, stops at exact completion and throws when the curve cannot hold the threshold. This is fix: honor cumulative thresholds and exact boundaries in getCurveBreakdown #120 by @snuff12 (fixes getCurveBreakdown exceeds cumulative thresholds and overshoots rounded boundaries #119) ported onto helpers/migration.ts with its fixtures.
  • feeParams: getFeeSchedulerParams checks the 25 to 9900 bps bounds for a constant fee too and rejects a totalDuration shorter than numberOfPeriod. An exponential schedule whose ending fee is unreachable throws instead of emitting reductionFactor 0; the migrated market-cap scheduler uses the same helper. getMigratedPoolFeeParams rejects a custom base fee mode or market-cap scheduler with a fixed MigrationFeeOption, requires a market-cap mode exactly when marketCapFeeSchedulerParams is set, and no longer switches the option to Customizable on its own. Its unused fourth argument is removed.
  • vesting: getLockedVestingParams rejects a period frequency that floors to zero and a cliff-only amount below one token.
  • validation: the fee-claimer rule applies only when feeClaimer is passed, since the program rejects Pubkey::default() only. Curve shape, threshold and start price are checked before any curve math runs. validateMigratedPoolFee no longer swallows the base-fee-mode errors.
  • buildCurve: the supply guard allows a rounding delta equal to the leftover, like the program. A migration base plus vesting plus leftover above the supply, a two-segment curve with no valid mid price, a midPrice with no positive liquidity, and a short liquidityWeights array each throw a named error.
  • price: getBaseTokenForSwap and getMigrationThresholdPrice sum in U256 and cast once, like liquidity_distribution.rs.
  • services: the first-buy builders append the instructions sysvar only when the config has enableFirstSwapWithMinFee. The rate-limiter check at activation point 0 is gone.
  • creator: transferPoolCreator rejects newCreator == creator (program InvalidNewCreator).
  • curve: PoolConfig.curve is stored with MAX_CURVE_POINT entries and the unused ones are zero; the program skips them, the SDK walked them. getBaseTokenForSwap, getMigrationThresholdPrice, getCurveBreakdown and getQuoteReserveFromNextSqrtPrice threw Math operation overflow once the target price reached the last live point, which made client.state.getPoolBaseTokenCurveProgress throw at migration, and at any time for a config whose last curve point is the migration price. liveCurve on ./math is the shared trim.

Tests: getCurveBreakdown, feeParams, validation, vesting and storedCurve unit tests, builder guard tests, a shared curve fixture in tests/utils/curve.ts, and validator tests for the fee-claimer filter, the first-buy sysvar and the creator transfer.

dannxbt and others added 7 commits October 11, 2026 00:00
The DBC IDL is the audited 0.2.2 release (program commit 82054df): PoolConfig gains
version and transferFeeFlag, createConfig2 takes an optional TransferFeeParameters,
and the ExceededInputAmount and InvalidFeeClaimer errors are new. The DAMM v2 IDL and
the cp_amm.so fixture are cp_amm 0.2.5. The dynamic_bonding_curve.so fixture is the
same build the IDL was generated from.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds the transfer fee math (epoch fees, included and excluded amounts, the zero-fee
rule) and reworks the swap quote so exact-in, partial-fill and exact-out follow the
program's order of operations, including the referral split on exact-out and the
ExceededInputAmount check on partial fills. Fee schedulers use the program's Q64 pow.
Amounts are checked against the u64 and u128 bounds the program enforces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lidation

common.ts becomes chain, price, migration, feeParams, vesting, swap and quoteSwap.
Curve builders reserve the program migration base and use constant product for
compounding migrations. Validation mirrors the 0.2.2 transfer-fee rules: constant
supply, no locked vesting, Customizable with Compounding, a Token-2022 base fee of at
most 10%, and a non-default fee claimer for partner authorities. Quotes of a buildCurve
result go through getQuoteFromInputAmount and getQuoteFromOutputAmount.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… export list

DynamicBondingCurveClient creates one Anchor program and hands it to the services.
createConfig2 sends transferFeeParameters, createConfigAndPool always uses it, and
migrateToDammV2, withdrawLeftover and the claim flows follow the transfer-fee rules
of the audited program. swapQuote2 quotes an existing pool with its current point.
The package root is an explicit export list and the program math is published from
the ./math entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds transferFee, getQuoteFrom and buildCurveProgramMath tests and updates the
validator-backed tests to the 2.0 client. The token badge flow runs against the
cp_amm 0.2.5 fixture.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
package.json exposes the root and ./math entries only. tsconfig drops the
strictNullChecks override. The changelog, README and docs describe the 2.0 release
as of this sync; the docs are regenerated later in the stack.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The memcmp offset was 72, which is PoolConfig.leftover_receiver. The fee claimer
sits at 40, after the discriminator and quote_mint. The filter has matched the
leftover receiver since 60e8a69; the existing test never noticed because it used
one key for both. The new test creates a config with a distinct leftover receiver.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@snuff12

snuff12 commented Oct 11, 2026

Copy link
Copy Markdown

Thanks for porting #120 into this stack, @dannxbt — glad the getCurveBreakdown fix and fixtures were useful.

If it's not too much trouble, would you mind adding a co-author trailer to the migration commit (13e1740)?

Co-authored-by: snuff12 <snuff12@snu.ac.kr>

If the stack gets squash-merged, keeping that line in the final commit message would be appreciated. Totally fine if rewriting the commit is a hassle at this point, just thought I'd ask. Once this lands I'll close #120.

dannxbt and others added 9 commits October 11, 2026 17:33
…down

Every segment was compared against the full migration threshold and the
terminal segment was credited with the full threshold, so any curve whose
threshold spans more than one segment over-allocated and ended at the wrong
sqrt price. The breakdown now carries the amount left, stops at exact
cumulative completion, and throws when the curve cannot hold the threshold,
matching the program's liquidity_distribution. The strict comparison replaces
the previous greater-or-equal.

This is PR #120 by snuff12 (fixes #119) ported onto helpers/migration.ts, with
its regression fixtures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: snuff12 <snuff12@snu.ac.kr>
…am rejects

getFeeSchedulerParams returned a constant fee before checking the 25 to 9900 bps
bounds, so (10, 10) and (9950, 9950) produced a cliff fee outside MIN_FEE_NUMERATOR
and MAX_FEE_NUMERATOR, and a totalDuration shorter than numberOfPeriod floored the
period frequency to zero. Both are InvalidFeeScheduler on chain. An exponential
schedule whose ending fee is undershot by even one bps of decay returned a
reduction factor of 0, which the program rejects when numberOfPeriod is set; the
builder throws instead. The same decay helper serves the migrated market-cap
scheduler.

getMigratedPoolFeeParams kept a custom migratedPoolBaseFeeMode for the fixed
MigrationFeeOptions, which must send mode 0, and silently switched to
Customizable whenever marketCapFeeSchedulerParams was set. The market-cap branch
was also gated on an unrelated baseFeeParams argument, so without it the scheduler
params were dropped and a market-cap mode went out with zero params. Fixed options
now throw on a custom mode or scheduler, Customizable requires a market-cap mode
exactly when scheduler params are set, and the unused argument is gone.

The buildCurve tests move to a shared fixture in tests/utils/curve.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
getLockedVestingParams floored totalVestingDuration / numberOfVestingPeriod to
zero when the duration was shorter than the period count, and the program
rejects a zero frequency (InvalidVestingParameters). The cliff-only path, which
moves one token into a single period, needs at least one token to do so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
validateConfigParameters threw "Invalid fee claimer" for every Token-2022 partner
update authority config unless options.feeClaimer was passed; the program only
rejects Pubkey::default(), so the rule now applies when a fee claimer is given.

The curve shape, the threshold and the start price are checked before
getMigrationThresholdPrice runs, so an empty or unordered curve reports
"Invalid curve" instead of failing inside the curve math. This is the program's
order in process_create_config.

validateMigratedPoolFee no longer calls validateMigratedPoolBaseFeeMode inside a
try/catch: that call collapsed every base fee mode error into "Invalid migrated
pool fee parameters" and made the detailed messages, and the market-cap pool fee
check that follows, unreachable. validateConfigParameters already runs the mode
validation once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… config

The supply guard threw when the rounding delta equalled the leftover; the
program allows the delta to be at most the leftover, so the guard is now
strictly greater. A migration base, locked vesting and leftover that exceed the
supply surfaced as a SafeMath subtraction overflow from getFirstCurve;
buildCurveWithTwoSegments returned an empty curve when none of its mid prices
gave positive liquidity, buildCurveWithMidPrice ignored the isOk flag, and
buildCurveWithLiquidityWeights read past the end of a short weights array.
Each case throws with a message that names the input.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… program

getBaseTokenForSwap applied the u64 check to every segment delta and
getMigrationThresholdPrice used the u64-checked quote delta. The program
(liquidity_distribution.rs) sums in U256 and casts once, so a curve whose
single segment exceeds u64 but whose total does not was rejected here and
accepted there. Both now use the 256-bit deltas and check the total.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he config asks for it

Every first-buy call site passed enableFirstSwapWithMinFee as true, so the
instructions sysvar was appended to every bundled first swap, and the builders
also evaluated the rate limiter at activation point 0 although a rate-limiter
config can no longer initialize a pool. The builders now take the config's
enableFirstSwapWithMinFee flag and nothing else decides the sysvar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Program 0.2.2 constrains new_creator to differ from creator (InvalidNewCreator).
The SDK now fails before sending the transaction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PoolConfig.curve is stored with MAX_CURVE_POINT entries and the unused ones
are zero. The program skips them in every swap loop and trims them before
get_base_token_for_swap. getBaseTokenForSwap, getMigrationThresholdPrice,
getCurveBreakdown and getQuoteReserveFromNextSqrtPrice walked the padding, so
the first zero point produced a negative delta and threw once the target
price reached the last live point. getPoolBaseTokenCurveProgress hit this at
migration, and for every pool whose last curve point is the migration price,
since its denominator targets that price. liveCurve in ./math is the shared
trim.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@dannxbt
dannxbt force-pushed the v2/02-parity-fixes branch from 6f6b503 to 8b88ee4 Compare October 11, 2026 09:33
@dannxbt

dannxbt commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for porting #120 into this stack, @dannxbt — glad the getCurveBreakdown fix and fixtures were useful.

If it's not too much trouble, would you mind adding a co-author trailer to the migration commit (13e1740)?

Co-authored-by: snuff12 <snuff12@snu.ac.kr>

If the stack gets squash-merged, keeping that line in the final commit message would be appreciated. Totally fine if rewriting the commit is a hassle at this point, just thought I'd ask. Once this lands I'll close #120.

sure, added u in as co-author

@dannxbt

dannxbt commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Added Co-authored-by: snuff12 <snuff12@snu.ac.kr> to the migration commit: c270f29

The same line is at the end of this PR's description, so a squash merge that uses the PR body keeps it on the squash commit.

@snuff12

snuff12 commented Oct 11, 2026

Copy link
Copy Markdown

Thanks @dannxbt for adding me as a co-author on c270f29 — much appreciated!

@dannxbt
dannxbt force-pushed the v2/01-sync-program-0.2.2 branch from bdfe680 to f960aff Compare October 11, 2026 11:50
@dannxbt

dannxbt commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Folded into #123 as a single PR, per review preference. The commits stay on this branch for reading.

@dannxbt dannxbt closed this Oct 11, 2026
@dannxbt
dannxbt removed this pull request from stack #127 October 12, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getCurveBreakdown exceeds cumulative thresholds and overshoots rounded boundaries

2 participants