Skip to content

feat: reset bonding curve to canonical 18-decimal integer protocol#1922

Open
umwelt wants to merge 5 commits intodevelopmentfrom
feat/1921-bonding-curve-reset-integer-protocol
Open

feat: reset bonding curve to canonical 18-decimal integer protocol#1922
umwelt wants to merge 5 commits intodevelopmentfrom
feat/1921-bonding-curve-reset-integer-protocol

Conversation

@umwelt
Copy link
Contributor

@umwelt umwelt commented Mar 18, 2026

Summary

  • replace the testnet bonding-curve path with the canonical fixed-layout integer protocol surfaces
  • migrate active bonding-curve math and transaction amounts to u128/1e18 semantics and centralize tokenomics constants in lib-types
  • remove the duplicate legacy blockchain mutation path and update tests/spec docs for the reset model

Testing

  • cargo check -p lib-blockchain
  • cargo test -p lib-types --lib -- --nocapture
  • cargo test -p lib-blockchain --test sov_unit_tests -- --nocapture
  • cargo test -p lib-blockchain --test bonding_curve_tx_integration_tests -- --nocapture
  • cargo test -p lib-blockchain --test oracle_cbe_integration_tests -- --nocapture
  • cargo test -p lib-blockchain --test oracle_executor_tests -- --nocapture
  • cargo test -p lib-blockchain --test unified_pricing_tests -- --nocapture
  • cargo test -p lib-blockchain --test cbe_lifecycle_integration_tests -- --nocapture
  • cargo test -p lib-blockchain --lib contracts::bonding_curve:: -- --nocapture
  • cargo test -p lib-blockchain --test phase4_api_completeness_tests -- --nocapture
  • cargo test -p lib-blockchain --test pow_ignored_bft_tests -- --nocapture
  • cargo test -p lib-blockchain --doc
  • cargo test -p lib-blockchain

Closes #1921

Copilot AI review requested due to automatic review settings March 18, 2026 10:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Resets the bonding-curve path toward a canonical 18-decimal, integer-only protocol by centralizing tokenomics constants in lib-types, introducing fixed-layout bonding-curve transaction/receipt types, and widening bonding-curve/pricing-related amounts to u128 across consensus + tests.

Changes:

  • Added lib-types::tokenomics and re-exported canonical token supply/decimal constants (u128, 18-decimal scale).
  • Introduced canonical fixed-layout bonding-curve tx/receipt structs in lib-types and a canonical band table + helpers in lib-blockchain.
  • Migrated many bonding-curve/pricing structs and transaction fields from u64u128, removed/disabled the legacy bonding-curve mutation path, and updated tests/docs accordingly.

Reviewed changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
lib-types/src/tokenomics.rs Adds canonical 18-decimal tokenomics constants (scale, decimals, max supply).
lib-types/src/lib.rs Exposes new bonding_curve + tokenomics modules and re-exports protocol types/constants.
lib-types/src/economy.rs Wires SOV total supply constant through lib-types::tokenomics and widens to u128.
lib-types/src/bonding_curve.rs Adds fixed-layout bonding-curve tx/receipt structs with fixed bincode-size tests.
lib-client/src/bonding_curve_tx.rs Widens client-side bonding-curve builder amounts to u128.
lib-blockchain/tests/unified_pricing_tests.rs Updates pricing test inputs to u128.
lib-blockchain/tests/sov_unit_tests.rs Switches tests to shared tokenomics constants and 18-decimal expectations.
lib-blockchain/tests/phase4_api_completeness_tests.rs Updates expectations for seeded wallet allocations in fresh genesis.
lib-blockchain/tests/phase3c_fee_distribution_tests.rs Aligns fee tests with executor fee model v2 and adjusted genesis/funding setup.
lib-blockchain/tests/oracle_executor_tests.rs Widens bonding-curve token test fixture fields to u128 and 18 decimals.
lib-blockchain/tests/oracle_cbe_integration_tests.rs Widens bonding-curve token test fixture fields to u128 and 18 decimals.
lib-blockchain/tests/dao_voting_power_tests.rs Updates voting power tests for identity-mode floor + allows deprecated round-trip tests.
lib-blockchain/tests/dao_hybrid_tests.rs Updates default hybrid governance parameters + allows deprecated persistence test.
lib-blockchain/tests/cbe_lifecycle_integration_tests.rs Updates bonding-curve lifecycle tests toward u128 amounts and revised split handling.
lib-blockchain/tests/bonding_curve_tx_integration_tests.rs Reworks tests to assert legacy non-executor path rejects bonding-curve txs.
lib-blockchain/tests/bonding_curve_integration_tests.rs Adjusts integration tests to match widened supply type and minor API changes.
lib-blockchain/src/validation/block_validate.rs Removes unused TransactionType import in tests.
lib-blockchain/src/utils/nondeterminism_tests.rs Serializes nondeterminism tests via a global lock and adds deterministic reset helper usage.
lib-blockchain/src/utils.rs Adds reset_consensus_validation_for_tests() for test-only global state reset.
lib-blockchain/src/types/sov.rs Re-exports SOV_MAX_SUPPLY from lib-types (atomic, u128).
lib-blockchain/src/transaction/core.rs Widens bonding-curve tx data fields (u64u128).
lib-blockchain/src/pricing/mod.rs Widens price-related fields and helpers (u64u128).
lib-blockchain/src/integration/economic_integration.rs Updates memo string expectation from “ZHTP” to “SOV”.
lib-blockchain/src/genesis/mod.rs Widens bonding-curve graduation threshold stored in genesis to u128.
lib-blockchain/src/execution/tx_apply.rs Updates disabled bonding-curve apply stubs to u128 parameters.
lib-blockchain/src/execution/executor.rs Widens executor outcomes and deploy parameter conversions to u128; updates tests for block funding flow.
lib-blockchain/src/contracts/tokens/mod.rs Re-exports canonical protocol constants (SOV + CBE) alongside legacy runtime constants.
lib-blockchain/src/contracts/tokens/constants.rs Splits “protocol” (lib-types) vs legacy runtime (u64) SOV constants.
lib-blockchain/src/contracts/tokens/cbe_token.rs Aligns legacy runtime CBE constants with canonical whole-token supply target.
lib-blockchain/src/contracts/bonding_curve/types.rs Widens curve/threshold/stats fields to u128 and ties token scale to canonical scale.
lib-blockchain/src/contracts/bonding_curve/token.rs Migrates bonding-curve token state to u128 and sets decimals to 18.
lib-blockchain/src/contracts/bonding_curve/mod.rs Adds canonical module and re-exports canonical band/tx/receipt items.
lib-blockchain/src/contracts/bonding_curve/events.rs Widens bonding-curve event amount fields to u128.
lib-blockchain/src/contracts/bonding_curve/event_indexer.rs Removes unused ReserveUpdateReason import in tests.
lib-blockchain/src/contracts/bonding_curve/canonical.rs Introduces canonical constants/band table + deterministic integer math helpers.
lib-blockchain/src/contracts/bonding_curve/amm_pool.rs Widens AMM/POL pool creation result fields to u128 and updates price continuity calculations.
lib-blockchain/src/blockchain.rs Disables legacy bonding-curve mutation path; various governance/treasury/reorg guard tweaks; widens CBE price getter return type.
lib-blockchain/src/block/core.rs Marks PoW fields as legacy and adds serde skipping/defaulting for some header fields.
BONDING_CURVE_TECHNICAL_SPEC.md Adds canonical bonding-curve protocol spec for reset.
BONDING_CURVE_MIGRATION_PLAN.md Adds migration plan and merge gates checklist.
Comments suppressed due to low confidence (1)

lib-blockchain/tests/cbe_lifecycle_integration_tests.rs:273

  • The test output still hard-codes 20%/80% labels and divides by 1e8 for “SOV”/“CBE” display, but the split is now driven by RESERVE_SPLIT_* and the protocol is moving to 18-decimal atomic units. Update the labels and formatting to derive percentages from the constants and to use the correct scale so the test output matches the actual protocol semantics.
    println!("✓ Reserve/treasury split verified on purchase");
    println!("  Buy amount: {} SOV", buy_amount as f64 / 100_000_000.0);
    println!("  To reserve (20%): {} SOV", reserve_increase as f64 / 100_000_000.0);
    println!("  To treasury (80%): {} SOV", treasury_increase as f64 / 100_000_000.0);
    println!("  CBE received: {} CBE", cbe_received as f64 / 100_000_000.0);

You can also share your feedback on Copilot code review. Take the survey.

}
CurveType::PiecewiseLinear(curve) => {
curve.price_at(supply).min(u64::MAX as u128) as u64
curve.price_at(u64::try_from(supply).unwrap_or(u64::MAX))
/// Minimum liquidity required for AMM pool creation.
/// Prevents division by zero attacks and ensures meaningful liquidity.
pub const MINIMUM_AMM_LIQUIDITY: u64 = 1_000_000; // 0.01 SOV or equivalent
pub const MINIMUM_AMM_LIQUIDITY: u128 = 1_000_000; // 0.01 SOV or equivalent
// Calculate max tokens we can sell with remaining reserve
let max_tokens = (remaining_reserve as u128 * 100_000_000 / current_price.max(1) as u128) as u64;
let max_tokens = remaining_reserve
.saturating_mul(100_000_000)
Comment on lines +267 to 272
let to_reserve = stable_amount
.checked_mul(RESERVE_SPLIT_NUMERATOR)
.ok_or(CurveError::Overflow)?
.checked_div(RESERVE_SPLIT_DENOMINATOR as u128)
.ok_or(CurveError::Overflow)?,
)
.map_err(|_| CurveError::Overflow)?;
.checked_div(RESERVE_SPLIT_DENOMINATOR)
.ok_or(CurveError::Overflow)?;
let to_treasury = stable_amount - to_reserve;
Comment on lines 24 to +26
/// Issue #1844: Reserve/treasury split — 40% reserve / 60% treasury.
pub const RESERVE_SPLIT_NUMERATOR: u64 = 2;
pub const RESERVE_SPLIT_DENOMINATOR: u64 = 5;
pub const RESERVE_SPLIT_NUMERATOR: u128 = 2;
pub const RESERVE_SPLIT_DENOMINATOR: u128 = 5;
Comment on lines +347 to +349
/// Retained in-memory for compatibility helpers only. BFT serialization
/// drops this field and restores `Difficulty::default()` on decode.
#[serde(skip, default)]
Comment on lines +354 to +356
/// Retained in-memory for compatibility helpers only. BFT serialization
/// drops this field and restores `0` on decode.
#[serde(skip, default)]
Comment on lines 231 to +235
CurveType::PiecewiseLinear(curve) => {
curve.quote_buy(current_supply, stable_amount)
curve.quote_buy(
u64::try_from(current_supply).unwrap_or(u64::MAX),
u64::try_from(stable_amount).unwrap_or(u64::MAX),
) as u128
Comment on lines +78 to +82
let slope_component = band
.slope_num
.checked_mul(supply)
.and_then(|v| v.checked_div(band.slope_den))
.expect("canonical price slope component overflow");
@sonarqubecloud
Copy link

@umwelt
Copy link
Contributor Author

umwelt commented Mar 19, 2026

Security & Blockchain Correctness Review

CRITICAL — Blockers

1. Buy/Sell permanently disabled (tx_apply.rs:694-725)
Both apply_bonding_curve_buy() and apply_bonding_curve_sell() unconditionally return Err(InvalidType(...)). The entire feature is non-functional. Either implement the debit/credit logic or hold this PR until it is done.

2. Slippage protection never enforced
min_tokens_out exists on the buy struct but is never checked in the apply path. When buy is enabled, any transaction with min_tokens_out = 0 will silently accept any price. Reject buys where tokens_out < min_tokens_out and reject if min_tokens_out == 0.

3. max_price and expected_s_c fields unvalidated
Both fields are defined in lib-types/src/bonding_curve.rs but never read in validation or execution. Setting max_price = u128::MAX bypasses all price protection. Must be enforced in the executor before applying state changes.


HIGH — Must Fix Before Merge

4. 40/60 split truncation accumulates loss (token.rs:243-255)
checked_mul(RESERVE_SPLIT_NUMERATOR).checked_div(RESERVE_SPLIT_DENOMINATOR) floors the result. For non-divisible amounts, up to 4 atomic units per tx are destroyed — not credited to reserve or treasury. Use ceiling division for to_reserve so to_reserve + to_treasury == stable_amount always holds exactly.

5. Buy/sell round-trip symmetry untested
cost_to_mint() and payout_for_burn() in canonical.rs have no test verifying that buy(X) → sell(all_tokens) returns approximately X (within rounding). Without this, a systematic bias in the integration could silently drain the reserve. Add a round-trip test covering multiple supply bands.

6. Oracle staleness can cycle and manipulate graduation (token.rs:372-410)
When oracle price goes stale, graduation_pending_since_block resets to None. When a fresh price arrives it sets a new pending block. An oracle operator can cycle stale→fresh to restart the confirmation window indefinitely, or rush graduation by injecting a fresh price right at the threshold. Introduce a first_threshold_met_block that is set once and never reset; require current_block >= first_threshold_met_block + confirmation_blocks regardless of price staleness cycles.

7. Graduation threshold not re-checked at apply time (tx_apply.rs:736-762)
apply_bonding_curve_graduate() calls token.graduate() without verifying that the reserve threshold is still met at the moment of execution. In a block where sells run before graduation, the reserve could drop below threshold between proposal and application. Add an explicit can_graduate() check inside the apply function and reject if it fails.


MEDIUM

8. Bincode field order fragile (token.rs:40-62)
The struct relies on append-only fields for bincode compatibility, with a comment as the only guardrail. Add a version byte or document this invariant in ARCHITECTURE.md and enforce with a serde round-trip test on the exact serialized byte length.

9. Confirmation bypass via pending_blocks = 0 path (types.rs:472-514)
is_met_with_oracle() guard if pending_blocks > 0 && pending_blocks < confirmation_blocks returns false. When pending_blocks == 0 it falls through to true if threshold is met. Ensure pending_blocks >= confirmation_blocks is always required — not just when > 0.

10. USD price scale cross-module assumption (types.rs:510)
USD_PRICE_SCALE is imported from the oracle module but not asserted. If the oracle constant changes, graduation math silently breaks. Add const _: () = assert!(ORACLE_PRICE_SCALE == 100_000_000); where it is consumed.


What's Good

  • Band boundary validation and contiguity tests in canonical.rs are solid.
  • mul_div_floor_u128() correctly delegates to U256 to avoid overflow — good pattern throughout.
  • Nonce48 big-endian encoding and round-trip test are correct; the 88-byte wire size test is a good regression anchor.
  • Multiply-before-divide in the USD threshold check (types.rs:505-510) is correct.
  • Checked arithmetic throughout canonical.rs — no silent overflow paths found.

The mathematical foundation is well-designed. The blockers are all in the execution/validation layer, not the curve math itself. All CRITICAL and HIGH items must be addressed before merge.

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.

Reset testnet and replace bonding curve with canonical 18-decimal fixed-layout integer protocol

2 participants