Augmented Bonding Curves - #926
Open
JakeHartnell wants to merge 21 commits into
Open
Conversation
Brings the cw-abc branch (~2y stale, last touched 2024-05-20) forward to
development as a single squash commit, ready for audit fixes.
Changes:
- New crates: packages/cw-curves, contracts/external/cw-abc,
contracts/external/dao-abc-factory
- Workspace: declared the three new members at version 2.8.0-alpha.2,
added rust_decimal/cw-address-like to workspace dependencies
- Cargo.lock: regenerated against pinned nightly-2024-01-08;
rust_decimal pinned to 1.34.3 and base64ct/arrayvec pinned to
pre-edition2024 versions to maintain toolchain compatibility
- cw-abc + dao-abc-factory: adopted the workspace tokenfactory feature
pattern (osmosis_tokenfactory / cosmwasm_tokenfactory /
thorchain_tokenfactory) to match dao-voting-token-staked
- Conflict resolution: kept development's cw-tokenfactory-issuer (2y of
upstream evolution); kept the cw-abc branch's contract source as-is
(no source-level conflicts on the new crates themselves)
- Spelling fixes ported forward from cw-abc branch in
packages/{dao-voting,dao-interface,cw-paginate-storage}
Verification (with pinned nightly + cosmwasm_tokenfactory feature):
- cargo check -p cw-curves -p cw-abc -p dao-abc-factory: clean
- cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory
-- -D warnings: clean
- cargo test -p cw-curves: 3/3 passing
- Wasm release builds: cw_abc 16KB, dao_abc_factory 304KB
Known environment gaps (not code issues):
- cw-abc/dao-abc-factory unit tests blocked by libclang-less container
(osmosis-test-tube build script needs libclang for bindgen). Tests
will run on CI machines that have libclang.
- dao-voting-token-staked has a pre-existing tokenfactory-feature
unification quirk when checked together with multiple feature flavors;
not introduced by this rebase.
Next: apply 21 audit findings from
audits/2026-05-09-cw-abc-security-review.md to bring the branch to
audit-ready state for upstream review against #697.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
C-1 — gate update_curve against rug-via-curve-replacement
============================================================
The previous update_curve allowed an owner to swap the bonding curve
under the existing (reserve, supply) pair with no invariant check. A
malicious or compromised owner could combine this with UpdateMaxSupply
to mint a flood at the next buy and drain the reserve. Audit details:
audits/2026-05-09-cw-abc-security-review.md (C-1).
Layered fix in commands.rs::update_curve:
- Hard gate on phase: only allowed in Closed phase.
- Continuity check: new_curve.reserve(curve_state.supply) must be
within MAX_CURVE_DRIFT_BPS (100 bps = 1%) of curve_state.reserve.
- New typed error CurveDriftExceeded carrying the comparison values.
- Fixed the copy-paste log attribute ("close" -> "update_curve")
(also closes I-3).
C-2 — authenticate factory callers via reverse handshake
============================================================
dao-abc-factory and dao-test-custom-factory both accepted any caller
claiming to be a voting module via VotingModuleQueryMsg::Dao, then
transferred ownership of a freshly minted contract to the
attacker-chosen "DAO" address. Audit details: C-2.
Fix in dao-abc-factory and dao-test-custom-factory:
- Reverse-handshake: query info.sender for its DAO, then query that
DAO for its VotingModule, assert it equals info.sender.
- Wires the previously-unused Unauthorized variants (also closes I-1
partially).
- dao-test-custom-factory adopts a small private helper
assert_caller_is_voting_module that's reused by both NFT and token
factory handlers.
L-7 — clear factory temp state in reply
============================================================
CURRENT_DAO and VOTING_MODULE in dao-abc-factory now Cleared at the
end of the reply handler so they behave as TempState rather than
long-lived stale records.
Verification (pinned nightly-2024-01-08):
- cargo check + cargo clippy --lib -- -D warnings:
green for cw-abc + dao-abc-factory under cosmwasm_tokenfactory and
for dao-test-custom-factory under osmosis_tokenfactory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
H-1 — inline hatcher token vesting
==========================================================
Hatchers' tokens are now locked according to a configurable schedule
(None / Cliff / Linear) that begins at the Hatch->Open transition. This
closes the original arbitrage hole where hatchers could buy at the
hatch curve and immediately sell at the (unchanged) curve in Open
phase. Audit details: H-1.
Implementation:
- New VestingSchedule enum on CommonsPhaseConfig (abc.rs).
- HATCHERS map type changed from `Map<&Addr, Uint128>` to
`Map<&Addr, HatcherState>` carrying contributed / minted /
already_burned / vesting_started_at.
- `buy()` in Hatch tracks both gross contribution (used for
contribution_limits.max check) and minted token count.
- Hatch->Open transition iterates HATCHERS to stamp
vesting_started_at = env.block.time on every entry.
- New `helpers::vested_amount` computes the unlocked portion at any
given timestamp.
- `sell()` checks per-hatcher vested - already_burned >= burn_amount
and rejects with new typed `HatcherTokensNotVested` error otherwise.
- Non-hatchers (no entry in HATCHERS) sell freely. Open-phase buyers
who didn't hatch are not subject to vesting.
H-2 — remove reachable todo!() panic
==========================================================
`UpdatePhaseConfigMsg::Closed {}` was a no-field variant that fell
through to `todo!()` in update_phase_config, panicking on any owner
input. Variant removed entirely (closed-phase config has no
configurable fields). Audit details: H-2.
H-3 — strict < 100% on entry_fee (Hatch + Open)
==========================================================
HatchConfig::validate and OpenConfig::validate now reject
entry_fee == 100% (which would brick the curve by diverting all
payment to the funding pool). Audit details: H-3.
H-4 — strict < 100% on exit_fee (Open)
==========================================================
OpenConfig::validate now rejects exit_fee == 100% (which would silently
rug every seller). Wires the previously-unused InvalidExitFee variant.
Audit details: H-4.
H-5 — bound supply/reserve token decimals
==========================================================
instantiate now rejects decimals >= 38 with new typed
InvalidDecimals { decimals, max } error. cw-curves uses
10u128.pow(decimals) which overflows at 39+. Audit details: H-5.
H-6 — validate contribution_limits ordering
==========================================================
HatchConfig::validate now ensures contribution_limits.min <=
contribution_limits.max. Equality is allowed (fixed-amount hatches are
a legitimate pattern). Audit details: H-6.
I-3 — fixed copy-paste log attribute on update_curve
==========================================================
"action" attribute now reads "update_curve" rather than "close".
Verification (pinned nightly):
- cargo check + clippy --lib -- -D warnings: green for cw-abc,
cw-curves, dao-abc-factory under cosmwasm_tokenfactory.
- cargo test -p cw-curves: 3/3 passing.
- Wasm release builds: produce expected artifacts.
Schema-breaking changes (acknowledged, branch is pre-mainnet):
- HATCHERS now stores HatcherState rather than Uint128.
- HatchersResponse and Hatcher query response types updated
accordingly.
- CommonsPhaseConfig gains a `vesting` field.
- UpdatePhaseConfigMsg loses the `Closed {}` variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M-1 — rewrite hatcher priority queue insertion
==========================================================
The previous binary_search_by + .then(Ordering::Less) approach was a
no-op (comparator never returned Equal, so binary_search always took
the Err branch and the priority field was effectively ignored — every
DAO entry was appended to the queue regardless of priority). Replaced
with a partition_point-based insert that maintains the queue sorted by
priority ascending, with `None`-priority entries trailing. Audit: M-1.
M-2 — cw2 version check on migrate
==========================================================
migrate now verifies the stored cw2 contract name matches CONTRACT_NAME
and returns a typed InvalidMigration error otherwise, preventing
silent overwrite when migrating from a foreign contract. Audit: M-2.
M-3 — HATCHERS semantics clarified
==========================================================
Net of the H-1 vesting refactor, HATCHERS now stores HatcherState with
explicit `contributed` (gross hatch intake — used for
contribution_limits.max checks) and `minted - already_burned` (live
token balance — used for vesting checks). Documented in state.rs and
the contract README. Audit: M-3.
M-4 — remove dead HATCHER_ALLOWLIST Map
==========================================================
The bare `Map<&Addr, HatcherAllowlistConfig>` constant that
namespace-collided with the IndexedMap (`hatcher_allowlist()`) is now
removed. Both shared the prefix "hatcher_allowlist" so a future
contributor writing through the bare Map would have silently bypassed
the secondary index. Audit: M-4.
M-5 — hatch deadline + permissionless abort path
==========================================================
HatchConfig gains an optional `hatch_deadline: Option<Timestamp>` and
ExecuteMsg gains `AbortHatch {}`. Anyone can call AbortHatch after the
deadline if `initial_raise.min` has not been reached; the contract
transitions to Closed phase so hatchers can recover their reserve via
existing sells. UpdatePhaseConfigMsg::Hatch can update the deadline.
This is the v1 implementation: hatchers recover their reserve-side
contribution but the funding-pool portion remains under owner control.
The full pro-rata Refunding sub-state is deferred to a follow-up PR
per the audit-readiness plan. Audit: M-5.
M-6 — trust-model documentation
==========================================================
cw-abc README now has a "Trust assumptions" section detailing owner
privileges and recommending DAO-core ownership for production. Also
added a "Vesting" section describing the H-1 schedule semantics, and
trimmed the now-obsolete "Optionally vest tokens" Future Work bullet.
Verification:
- cargo clippy --lib -p cw-abc -p dao-abc-factory --features
cosmwasm_tokenfactory -- -D warnings: clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L-1 — bound query_hatcher_allowlist ======================================================== Replaced unbounded `iter.collect()` for `limit: None` with DEFAULT_LIMIT=30 / MAX_LIMIT=100 caps. Audit: L-1. L-2 — surface DAO query errors in allowlist check ======================================================== `assert_allowlisted_through_daos` now distinguishes "DAO returned 0" from "DAO query errored." Inline doc comment guides operators to monitor the queue for stale entries that no longer respond to `VotingPowerAtHeight`. Audit: L-2. L-3 — eliminate self-call auth bypass ======================================================== Instantiate now calls `commands::update_hatch_allowlist` inline rather than via a deferred CosmosMsg::Wasm self-call, removing the `if env.contract.address != info.sender` auth-bypass branch from update_hatch_allowlist. Future code paths that introduce additional self-calls can no longer accidentally hit the bypass. Audit: L-3. L-4 — guard u128 -> i128 cast in cw-curves::decimal ======================================================== Added an assertion in packages/cw-curves/src/utils.rs::decimal that catches values >= 2^127 before the cast that would otherwise produce a silently-negative i128. Audit: L-4. L-6 — raise cube_root precision (EXTRA_DIGITS 9 -> 15) ======================================================== SquareRoot::supply on small reserves was rounding to 3 supply decimals, leaking up to 170 micro-units per call on test cases. Boost to 15-digit cube_root precision. Two existing test expectations updated to the more accurate values (matching the test comments' stated true values). Audit: L-6. L-7 — was addressed in the C-2 commit. L-5 (Curve trait -> Result<>) deferred — invasive multi-file change; queued as a follow-up to keep this audit-readiness PR focused. I-1 — remove unused error variants ======================================================== - cw-abc: dropped `MismatchedSellAmount` and `Unauthorized` (cw-abc uses cw_ownable::OwnershipError for unauthorized owner-only paths). - dao-abc-factory: dropped `UnsupportedFactoryMsg`. `Unauthorized` retained — now used by C-2's reverse-handshake check. Audit: I-1. I-2 — zero exit_fee on close ======================================================== `commands::close` now resets `phase_config.open.exit_fee` to zero so the stored config matches runtime semantics (`calculate_sell_quote` returns Decimal::zero() in Closed phase). Audit: I-2. I-4 — standardize CONTRACT_NAME format ======================================================== `dao-abc-factory` now uses `"crates.io:dao-abc-factory"` to match cw-abc's convention. Audit: I-4. I-5 — doc comment on assert_allowlisted DAO-typed-individual rejection ======================================================== Audit: I-5. I-6 — doc comment on strict `>` in MAX_SUPPLY check ======================================================== Audit: I-6. I-7 — code comment on TEMP_SUPPLY load/remove ordering ======================================================== Audit: I-7. Verification: - cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory --features cosmwasm_tokenfactory -- -D warnings: clean. - cargo test -p cw-curves: 3/3 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviews packages/cw-curves, contracts/external/cw-abc, and contracts/external/dao-abc-factory. 21 findings (2 Critical, 6 High, 6 Medium, 7 Low, 7 Informational). Status table appended showing per-finding fix commit: - 19 fully fixed - 2 partial (M-5 Refunding sub-state, L-2 attribute surface) - 1 deferred (L-5 — Curve trait Result conversion is invasive, queued as separate PR) All Criticals and Highs fully addressed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the previous `unwrap()`-on-overflow surface in the Curve trait
and its three impls with a typed `CurveError`. Closes audit finding L-5
(was deferred from the prior round of fixes).
cw-curves changes
==========================================================
- New `CurveError` enum with `Overflow { scale, value }` and
`DivisionByZero` variants. Added thiserror as a workspace dep.
- `Curve` trait sigs change: `spot_price`, `reserve`, `supply` all
return `Result<_, CurveError>`.
- `DecimalPlaces::to_reserve` and `to_supply` return Result; `to_u128`
failures surface as `CurveError::Overflow`.
- `utils::square_root` and `cube_root` return Result; `decimal_to_std`
also returns Result (rare-path, mostly theoretical).
- All three curve impls (Constant, Linear, SquareRoot) propagate via
`?` and convert zero-slope/zero-value into `DivisionByZero`.
- 3 new unit tests for division-by-zero (one per curve type).
cw-abc consumer changes
==========================================================
- `helpers::calculate_buy_quote` and `calculate_sell_quote`: propagate
`?` through `curve.supply()` / `curve.reserve()` calls.
- `commands::update_curve`: propagate `?` through the continuity check.
- `queries::query_curve_info`: convert `CurveError` to `StdError::generic_err`
for the spot_price call.
- `error::ContractError`: new `CurveError(#[from] cw_curves::CurveError)`
variant.
Verification:
- cargo +nightly-2024-01-08 test -p cw-curves: 6/6 passing (3 happy-path
+ 3 division-by-zero).
- cargo +nightly-2024-01-08 clippy --lib -p cw-curves -p cw-abc
-p dao-abc-factory --features cosmwasm_tokenfactory -- -D warnings:
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous round shipped AbortHatch as a transition to Closed —
hatchers recovered their reserve-side contribution but the funding-pool
portion remained owner-withdrawable. This commit completes M-5:
hatchers now reclaim their pro-rata share of (reserve + funding).
Design
==========================================================
- New `CommonsPhase::Refunding` variant + `expect_refunding` helper.
- New `RefundSnapshot { total_pool, total_contributed }` Item, written
at AbortHatch time so pro-rata math is deterministic and late
claimants don't dilute earlier ones.
- New `HatcherState.claimed_refund: bool` to prevent double-claim.
- New `ExecuteMsg::ClaimRefund {}` handler — caller must be a hatcher,
must surrender their unburned hatcher tokens (mirrors `sell` semantics
of must_pay supply_denom), and receives
`state.contributed * snapshot.total_pool / snapshot.total_contributed`
reserve tokens.
- New typed errors: `RefundAlreadyClaimed`, `RefundBurnMismatch`.
Refunding-phase rejections (M-5 invariant: funding pool reserved for hatchers):
- buy(): rejected (CommonsClosed).
- sell(): rejected via calculate_sell_quote (use ClaimRefund instead).
- withdraw(): rejected (owner can't drain the funding pool).
- close(): rejected (Refunding is terminal).
- update_curve(): already gated on Closed phase, won't fire.
- AbortHatch: already gated on Hatch phase, won't fire.
Other operational handlers (toggle_pause, update_funding_pool_forwarding,
update_max_supply, update_hatch_allowlist) remain enabled — they don't
extract value from the refund pool.
abort_hatch flow:
- Existing deadline + initial_raise.min checks unchanged.
- Iterates HATCHERS to compute total_contributed.
- Snapshots `(total_pool, total_contributed)`.
- Transitions to Refunding (was Closed).
- Emits attributes for indexers.
claim_refund flow:
- expect_refunding() phase guard.
- Loads HatcherState; rejects with SenderNotAllowlisted if non-hatcher.
- Rejects RefundAlreadyClaimed if already claimed.
- must_pay(supply_denom) for unburned hatcher tokens; rejects with
RefundBurnMismatch if amount != minted - already_burned.
- multiply_ratio computes pro-rata refund.
- State updated FIRST (claimed_refund = true) before issuing burn/refund
messages — re-entrancy via reply can't double-claim.
- BankMsg::Send for the refund + Issuer Burn for the surrendered tokens.
README + audit report
==========================================================
- README "Trust assumptions" updated to describe Refunding-phase
funding-pool lock.
- Future Work bullet about Refunding (now implemented) removed.
Verification (pinned nightly):
- cargo clippy --lib -p cw-abc -p dao-abc-factory
--features cosmwasm_tokenfactory -- -D warnings: clean.
- cargo test -p cw-curves: 6/6 passing.
K-phase rejection tests for the Refunding lifecycle land in a
follow-up commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `src/audit_tests.rs` with rejection tests for each Critical, High,
and load-bearing Medium audit finding. One or more tests per finding
asserting the defended-against behavior is now rejected.
Coverage
==========================================================
C-1: update_curve rejects in Hatch / Open phase, rejects on >1% drift,
rejects non-owner. (4 tests)
H-1: vested_amount math — None / Cliff pre / Cliff post / Linear partial /
Linear full / no-clock-defensive. (6 tests)
H-2: UpdatePhaseConfigMsg::Closed no longer deserializes; sanity check
that Open variant still does. (1 test)
H-3+H-4: instantiate rejects 100% entry_fee in Hatch and Open;
instantiate rejects 100% exit_fee; update_phase_config rejects
the same updates. (4 tests)
H-5: instantiate rejects supply_decimals=38, reserve_decimals=39, accepts
decimals=18. (3 tests)
H-6: instantiate rejects min > max contribution_limits, accepts equal.
(2 tests)
M-1: priority queue insert order — mixed [3, None, 1, 2, None] resolves
to [1, 2, 3, None_a, None_b]. (1 test)
M-2: migrate rejects foreign cw2 contract name; accepts matching.
(2 tests)
M-5: buy / withdraw / close all rejected in Refunding phase;
claim_refund rejects non-hatcher; claim_refund rejects double-claim;
abort_hatch rejects pre-deadline; abort_hatch rejects when no
deadline configured. (7 tests)
L-3: update_hatch_allowlist no longer admits self-call (sender ==
contract.address now fails ownership check). (1 test)
Total: 30 audit-defense tests. Plus the existing 6 cw-curves tests
(3 happy-path + 3 division-by-zero) and the existing donate / withdraw /
toggle_pause tests in commands.rs.
Module structure
==========================================================
- `src/audit_tests.rs` declared in `lib.rs` under `#[cfg(test)]`.
- `commands::insert_into_priority_queue` made `pub(crate)` so the M-1
test can exercise it directly without going through the full
update_hatch_allowlist flow.
Verification
==========================================================
The test build pulls in `osmosis-test-tube` via `dao-testing` (dev-dep
graph), which requires libclang for bindgen. This container does not
have libclang installed and the pinned nightly does not support the
optional-dev-dep pattern that would let us gate it. CI machines have
libclang and run the full suite. The test code is verified by
inspection; logic mirrors existing test patterns in
`commands.rs::tests::donate`.
Tests deferred to a follow-up cw-multi-test integration setup
==========================================================
- C-2 factory reverse-handshake roundtrip (needs mock voting module +
cw-multi-test app)
- H-1 full vesting matrix through buy/sell flow (needs token-factory
simulation)
- M-5 full ClaimRefund roundtrip with actual mint/burn (same)
- K12 curve overflow propagation through ContractError::CurveError
The vested_amount math test (H-1) covers the scheduler in isolation;
the integration matrix is queued.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds packages/cw-curves/src/diff_tests.rs with pure-Rust f64 reference implementations for each curve type, then random-walks 1000 iterations per curve per (supply_decimals, reserve_decimals) matrix asserting the production curve matches the reference within tolerance. Coverage ========================================================== - diff_constant_random_walk: 4 (sd, rd) combinations × 1000 iterations, abs_tol=10, rel_tol=1e-3. - diff_linear_random_walk: 3 combinations × 1000 iterations, abs_tol=100, rel_tol=1e-3. - diff_sqrt_random_walk: 2 combinations × 1000 iterations, abs_tol=100, rel_tol=5e-3 (extra precision overhead from sqrt). - diff_*_round_trip: 500 iterations of supply(reserve(s)) ≈ s, bounded drift checks. SquareRoot variant skips reserves < 100 base units where quantization dominates. - diff_boundary_supply_zero / one: edge-case sanity for all 3 curves. Determinism ========================================================== SplitMix64 PRNG seeded per test (no rand dep). Hardcoded seeds so failures reproduce; assertion message prints seed and inputs. Verification ========================================================== cargo +nightly-2024-01-08 test -p cw-curves: 14/14 passing - 3 happy-path (constant_curve, linear_curve, sqrt_curve) - 3 division-by-zero (typed CurveError) - 6 differential random walks - 2 round-trip identity - 2 boundary-case Closes the audit-suggested differential test work item that was queued as a follow-on. The suite is the reference for future curve additions (S-curve, Taylor series, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…resh dao-abc-factory README ========================================================== Renamed `README` -> `README.md` for consistency with sibling contracts. Expanded from a 4-line stub to a full doc covering: - The DAO instantiation flow (dao-dao-core -> voting-module -> this factory -> cw-abc -> ownership handoff via TokenFactoryCallback / ModuleInstantiateCallback). - The C-2 reverse-handshake authentication pattern with the actual code snippet and rationale. - State variables (DAOS map, CURRENT_DAO + VOTING_MODULE TempState). - Query surface. - Audit cross-reference. Audit report status table ========================================================== - M-5 promoted from Partial -> Fixed; references both 29ba5e1 (deadline + abort) and 744e609 (full Refunding sub-state with ClaimRefund). - L-5 promoted from Deferred -> Fixed; references 74931ee (Curve trait Result conversion). - Summary updated: 20 fully fixed, 1 partial (L-2 attribute surface). All Criticals, Highs, and Mediums fully addressed. - Verification section updated: cw-curves now reports 14/14 tests (was 3/3); test-tube + schema regen explicitly noted as deferred to libclang-equipped CI. Schema regen (Phase O) deferred ========================================================== `bash scripts/schema.sh` for cw-abc + dao-abc-factory triggers the osmosis-test-tube build script via the dao-testing dev-dep graph, which needs libclang. Container can't sudo apt-install. CI machines have libclang and will regen as part of the standard build pipeline; the existing schemas in source remain stale until then. Documented in the audit report Verification section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…custom-factory `cargo +nightly-2024-01-08 fmt --all` applied to bring formatting in line with the pinned nightly's rustfmt. No semantic changes. Final verification (Phase Q gate): - cargo fmt --check: clean - cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory --features cosmwasm_tokenfactory -- -D warnings: clean - cargo test -p cw-curves: 14/14 passing - cargo build --release --target wasm32-unknown-unknown for cw-abc and dao-abc-factory: green - cw_abc.wasm: 16KB - dao_abc_factory.wasm: 311KB Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generalizes Constant, Linear, and SquareRoot under a single curve type with a rational exponent `num/den`. Closes the "additional curve types" follow-up from PR #926 partially; Sigmoid lands in a follow-on commit. cw-curves additions ========================================================== - New `nth_root(value, n) -> Result<Decimal, CurveError>` helper in `utils.rs`. Newton-Raphson on Decimal with a bit-count-guided initial guess (`x_0 = 2^(bits/n)` where `bits ≈ log2(value)`) so `x^(n-1)` doesn't saturate rust_decimal on the first iteration. Falls through to `square_root` (n=2) and `cube_root` (n=3) fast paths. Bounded at 64 iterations with overflow-safe `checked_mul`. - New `pow_rational(base, num, den)` helper that dispatches to integer power for `den=1`, otherwise computes `base^num` then `nth_root(_, den)`. GCD-reduces num/den to maximize fast-path hits. - `Power` curve impl in `curves/power.rs`: f(s) = slope * s^(num/den) F(s) = slope * den / (num + den) * s^((num + den) / den) F^-1(r) = ((num + den) * r / (slope * den))^(den / (num + den)) - 5 new unit tests: - power_curve_matches_linear_at_n1 / matches_square_root_at_n_half / matches_constant_at_n0: parity with existing curves on shared exponents. - power_curve_round_trip: 4 supplies through reserve()/supply() with exotic exponent 3/4. - power_division_by_zero_on_zero_slope. - 2 new differential tests in `diff_tests.rs`: - diff_power_random_walk: 6 (num, den) pairs × 2 (sd, rd) matrices × 500 iterations against an f64 reference. - diff_power_round_trip: 200 iterations of supply(reserve(s)) ≈ s for n=3/4, skipping pathologically-small reserves. cw-abc wiring ========================================================== - New `CurveType::Power { slope, scale, exponent_num, exponent_den }` variant in `abc.rs`. Existing Constant/Linear/SquareRoot retained for back-compat; new deployments can use Power for any rational exponent including the existing 0/1, 1/1, 1/2. Verification ========================================================== - cargo +nightly-2024-01-08 test -p cw-curves: **21 passed; 0 failed** (was 14 → 21; 5 Power unit tests + 2 Power diff tests). - cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory --features cosmwasm_tokenfactory -- -D warnings: clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the "additional curve types" follow-up alongside the Power curve.
Sigmoid is the standard logistic shape used in production by Token
Engineering Commons for smoothed price discovery.
cw-curves additions
==========================================================
- New `taylor_exp(x: Decimal) -> Result<Decimal, CurveError>` helper:
range-reduces x = k + r where k is the integer part and |r| < 1, then
computes e^k by repeated multiplication and e^r via 20-term Taylor.
Negative inputs handled via 1/e^|x| to avoid alternating-sign series
precision loss. Bounded |x| ≤ 30 to keep results in Decimal range.
- `decimal_to_std` now rounds to 18 decimal places before string
conversion (StdDecimal precision cap was rejecting longer mantissas).
- New `Sigmoid` curve impl in `curves/sigmoid.rs`:
f(s) = amplitude / (1 + e^(-steepness * (s - midpoint)))
Numerical strategy: spot_price closed-form via taylor_exp; reserve via
Simpson's rule (32 panels); supply via Newton-Raphson on the integral
(≤ 32 iters). Closed-form integral via softplus would have required
taylor_ln; deferred for impl simplicity. Documented as research-quality
precision (~1e-3 relative error) with higher gas cost than the
closed-form curves.
- 11 new unit tests:
- 5 taylor_exp tests (zero, e, 1/e, 2.5, large-rejection)
- 6 sigmoid tests (midpoint half-amplitude, saturation, reserve zero,
reserve monotonic, round-trip, division-by-zero)
- 1 new differential test: 100 sigmoid spot-price samples vs f64 reference.
cw-abc wiring
==========================================================
- New `CurveType::Sigmoid` variant carrying `(amplitude, amplitude_scale,
steepness_num, steepness_den, midpoint, midpoint_scale)`. Steepness as
rational so it round-trips cleanly through JSON.
Verification
==========================================================
- cargo +nightly-2024-01-08 test -p cw-curves: **33 passed; 0 failed**
(was 21 → 33).
- cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory
--features cosmwasm_tokenfactory -- -D warnings: clean.
Future work (out of scope this PR)
==========================================================
- Closed-form integral via taylor_ln helper (would replace Simpson's
rule with O(1) instead of O(panels) cost).
- Tighter bounds on Newton convergence near the asymptote regime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the L-2 follow-up that was previously partial. The `assert_allowlisted_through_daos` helper now returns a `Vec<Attribute>` alongside the override-limits result; each entry whose `VotingPowerAtHeight` query errored produces a `try_dao_query_failed` attribute carrying the DAO address. `commands::buy()` attaches these attributes to the buy response so operators can detect stale or migrated DAO entries from chain logs without inferring from gas profile. Code changes ========================================================== - `commands::assert_allowlisted` returns `Result<(HatchConfig, Vec<Attribute>), ContractError>`. - `commands::assert_allowlisted_through_daos` returns `Result<(Option<MinMax>, Vec<Attribute>), ContractError>`. The Err branch of each per-DAO query pushes a `try_dao_query_failed` attribute and continues to the next entry; iteration semantics unchanged (a single broken DAO doesn't lock out users with voting power in other allowlisted DAOs). - `commands::buy()` Hatch branch captures the attributes from `assert_allowlisted` and threads them through `add_attributes(...)` on the response. Audit report ========================================================== - L-2 row promoted from Partial → Fixed. - Summary updated: **21/21 findings fully fixed**. Verification ========================================================== - cargo clippy --lib -p cw-abc --features cosmwasm_tokenfactory -- -D warnings: clean. - Unit test for the attribute path queued to land in Phase S (cw-multi-test integration test) where we can wire a mock voting module that errors on VotingPowerAtHeight; the audit_tests.rs mock_dependencies harness doesn't model wasm_smart queries cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Was inadvertently swept into the Phase U commit. Moving back to working-tree-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's `Lints` job caught formatting deviations in the Phase U (Power),
Phase V (Sigmoid), and Phase T (L-2) code that I didn't re-run fmt on
after the edits. `cargo +nightly-2024-01-08 fmt --all` applied across
cw-abc/abc.rs, cw-abc/commands.rs, cw-curves/{utils,tests,diff_tests,
curves/power}.rs.
No semantic changes.
Verification (post-fmt):
- cargo fmt --all -- --check: clean
- cargo test -p cw-curves: 33 passing
- cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory
--features cosmwasm_tokenfactory -- -D warnings: clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…actory feature on cw-abc + dao-abc-factory
Two CI failures from the previous push:
1. `Test Suite` (Basic workflow) — `dao-voting-cw721-staked::test_factory`
and `test_factory_with_funds_pass_through` were failing because the
C-2 parity reverse-handshake on `dao-test-custom-factory` queries
`info.sender` for `VotingModuleQueryMsg::Dao` then queries that DAO
for `VotingModule {}`. Those sibling tests instantiate cw721-staked
directly from an EOA (`creator`) — there's no DAO at all in the
test setup, so the second handshake step queries an EOA address
(returned as the "DAO") and fails with "ContractData not found".
Fix: revert the C-2 fix on dao-test-custom-factory. It's a `contracts/test/...`
contract that's exercised by sibling-crate tests in patterns the
production C-2 hardening doesn't accommodate. The production fix
on `dao-abc-factory` (commit 429a45a) stays as-is.
Audit report updated: C-2 row notes the test-factory parity revert
with reasoning. Production C-2 fix unchanged.
2. `workspace-optimize` (Test Tube workflow setup) — wasm build of
cw-abc / dao-abc-factory was failing with cw-tokenfactory-types'
compile_error because no tokenfactory backend was selected.
Fix: add `default = ["osmosis_tokenfactory"]` to both crates'
Cargo.toml [features] sections. Matches the pattern used by
cw-tokenfactory-issuer, dao-voting-token-staked, and
dao-test-custom-factory. Juno deployments which use
cosmwasm_tokenfactory override at build time:
`cargo wasm --no-default-features --features cosmwasm_tokenfactory`.
Code changes
==========================================================
- `contracts/test/dao-test-custom-factory/src/contract.rs`: removed
`assert_caller_is_voting_module` helper and its call sites; restored
the original `info.sender → VotingModuleQueryMsg::Dao` query (no
reverse-handshake). Cleaned up unused `ensure`, `QuerierWrapper`,
`DaoQueryMsg` imports.
- `contracts/external/cw-abc/Cargo.toml`: `default = ["osmosis_tokenfactory"]`.
- `contracts/external/dao-abc-factory/Cargo.toml`: same.
- `audits/2026-05-09-cw-abc-security-review.md`: C-2 row reflects the
test-factory parity revert.
Verification (post-fix)
==========================================================
- cargo fmt --check: clean
- cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory
-p dao-test-custom-factory -- -D warnings: clean (without explicit
feature flags — defaults pick a backend cleanly)
- cargo test -p cw-curves: 33 passed
- cargo wasm --release for cw-abc + dao-abc-factory: builds clean with
no explicit feature flag
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## development #926 +/- ##
===============================================
- Coverage 96.56% 89.41% -7.16%
===============================================
Files 199 174 -25
Lines 67407 30663 -36744
===============================================
- Hits 65094 27417 -37677
- Misses 2313 3246 +933 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…versions Two CI failures from the previous push: 1. `Basic` workflow Lints job — `cargo clippy --all-targets -- -D warnings` caught unused imports in `audit_tests.rs`: ClosedConfig, CommonsPhaseConfig, HatchConfig, OpenConfig, ReserveToken, SupplyToken. Locally I only ran `clippy --lib` which doesn't include test targets, so the warning was missed. Pruned to only the imports actually used (CommonsPhase, CurveType, MinMax, VestingSchedule). 2. `Integration Tests` workflow — all 8 tests failing with "Unknown opcode 192: create wasm contract failed" against the pinned juno v15.0.0 chain (wasmd v0.31, cosmwasm-vm 1.2). The just `download-deps` recipe pulled `cw-plus/releases/latest` and `cw-nfts/releases/latest`; recent cw-plus releases ship wasm built against newer cosmwasm-vm features (Wasm sign-extension proposal — opcode 0xC0 = 192) that the test chain can't deserialize. Fix: pin to v1.1.2 (cw-plus) and v0.18.0 (cw-nfts) which match the workspace's `cw20 = "1.1"` and `cw721 = "0.18"` deps and are known-compatible with the test chain. HEAD-checked the assets are downloadable at those tags. This is a pre-existing latent issue affecting any PR opened against `development` after a recent cw-plus release; not specific to the cw-abc branch but unblocks #926 CI here. Verification (post-fix): - cargo fmt --all -- --check: clean - cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory -p dao-test-custom-factory -- -D warnings: clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test Tube workflow was failing with 13 compilation errors in
contracts/external/cw-abc/src/test_tube/{test_env,integration_tests}.rs
and contracts/external/dao-abc-factory/src/test_tube/test_env.rs. The
test scaffolding from the rebase referenced struct shapes that have
evolved on `development` since the cw-abc branch was last touched
(2y ago). All errors fall into known categories:
- `HatchConfig` gained `hatch_deadline: Option<Timestamp>` (M-5 fix)
- `CommonsPhaseConfig` gained `vesting: VestingSchedule` (H-1 fix)
- `NewTokenInfo` gained `token_issuer_salt` (upstream)
- `ModuleInstantiateInfo` gained `salt` (upstream)
- `ModuleInstantiateInfo.funds` changed from `Vec<Coin>` to
`Option<Vec<Coin>>` (upstream)
- `dao_proposal_single::msg::InstantiateMsg` gained `delegation_module`
(upstream)
- `dao_interface::msg::InstantiateMsg` gained `initial_actions`
(upstream)
Fix: updated each call site to provide the new fields. Defaulted
post-audit fields:
- `hatch_deadline: None` (no deadline in existing test scenarios)
- `vesting: VestingSchedule::None` (no vesting; tests pre-date H-1)
- `delegation_module: None`, `initial_actions: None`,
`token_issuer_salt: None`, `salt: None`, `funds: None`
Files updated:
- `contracts/external/cw-abc/src/test_tube/test_env.rs`
- `contracts/external/cw-abc/src/test_tube/integration_tests.rs`
- `contracts/external/dao-abc-factory/src/test_tube/test_env.rs`
These are mechanical "field-add" updates to keep existing test
coverage running. Net new test scenarios (H-1 vesting matrix, C-2
factory reverse-handshake roundtrip, M-5 ClaimRefund) for the
audit-defense surface remain queued as Phase S.
Verification (post-fix):
- cargo fmt --all -- --check: clean
- cargo clippy --lib -p cw-curves -p cw-abc -p dao-abc-factory
-- -D warnings: clean (no explicit feature flags; defaults pick
osmosis_tokenfactory which now flows through cleanly)
Note: the previously-flagged `unused imports` warning in audit_tests.rs
was already fixed in commit fea240c (CI ran on a pre-fix commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chemas The Lints CI step diffs committed schema JSON against fresh `cargo run --example schema` output and rejects any drift. Three schemas had diverged from current contract code: - cw-abc: Phase U/V (Power, Sigmoid curves), Refunding sub-state, ClaimRefund + AbortHatch handlers, VestingSchedule, HatcherState, cube_root precision bump, typed CurveError variant - dao-abc-factory: dao-abc-factory contract_version bump to 2.8.0-alpha.2; vesting field on phase config; Open phase description refresh; Power/Sigmoid curve variants - cw-tokenfactory-issuer: doc-comment typo fixes (trasfer/recieve/intedended/whis/wiil/allownances) PR #926 description already flagged this as pending CI work.
JakeHartnell
marked this pull request as ready for review
May 12, 2026 22:53
Member
Author
|
Need to fix integration tests (a problem for many PRs), but otherwise this is ready. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Revive
cw-abc+ shipcw-curvesanddao-abc-factorywith full audit passSummary
This PR revives the
cw-abc(Augmented Bonding Curve) branch — last touched in May 2024, ~2 years stale — by rebasing it ontodevelopmentand shipping a complete fix for all 21 findings from a fresh internal security review.It introduces three new workspace members:
packages/cw-curves— bonding-curve math primitives (Constant, Linear, SquareRoot)contracts/external/cw-abc— the Augmented Bonding Curve contractcontracts/external/dao-abc-factory— DAO-side factory wiring cw-abc into a DAO viadao-voting-token-stakedIncludes an internal security review at
audits/2026-05-09-cw-abc-security-review.mdthat documents the 21 findings, their fixes, and the remaining work for an external audit.Closes (or builds on) the original draft: #697.
Why this exists
ABCs are a Commons-Stack-lineage primitive for community-funded project tokens — a bonding curve where buyers deposit a reserve asset and receive curve-priced tokens, augmented with a hatcher phase, a funding pool, and (now) inline vesting to combat early-arb. Used by Token Engineering Commons, Commons Stack, Giveth, Praise. They're a substrate-level addition for any DAO that wants tokenized community capitalization, and they're a precondition for meme-DAO-style launches (the bonding-curve + graduation pattern that Pump.fun popularized).
The original cw-abc work sat in a 2-year-old branch with substantial drift; reviving it means getting the rebase to a clean baseline and treating the audit findings seriously rather than carrying the partial-shape forward.
Approach
A few load-bearing design decisions, in case reviewers want to push back on them early:
cw-vestinginstantiate costs ~150–300k gas; a popular hatch with hundreds of hatchers would exceed any per-block gas limit at the Hatch→Open transition. Instead, we track per-hatcher state inHatcherState(contributed / minted / already_burned / vesting_started_at / claimed_refund) and compute vested amounts inline against a sharedVestingSchedule.dao-test-custom-factory.update_curvewas a clean rug: pause → swap to a curve wherenew_curve.supply(reserve) >> current_supply→ buy 1 unit → mint flood → drain. Closing it required both restricting WHEN curves can change and a tolerance check on the (reserve, supply) invariant.(reserve + funding), not just reserve. The first round of fixes shippedAbortHatchas a transition to Closed (refunds reserve only, owner keeps funding pool). This PR completes M-5 with aRefundingsub-state, snapshot-locked pro-rata math, and a permissionlessClaimRefundhandler. Funding pool is locked from owner withdrawal during Refunding.What's in this PR (by commit)
1ec2afe3eosmosis_tokenfactory/cosmwasm_tokenfactory/thorchain_tokenfactoryfeature pattern adopted to matchdao-voting-token-staked.429a45af2update_curvegated on Closed phase + 1% continuity check. Factory reverse-handshake auth (dao-abc-factory + dao-test-custom-factory parity). Factory temp state cleared in reply (L-7).7a3fcfcedVestingSchedule::{None, Cliff, Linear}+HatcherState). Removed reachabletodo!()panic. Strict<100% on entry/exit fees.decimals < 38.contribution_limits.min ≤ max.29ba5e12dpartition_pointinstead of brokenbinary_search_by). cw2 migrate guard. Removed deadHATCHER_ALLOWLISTmap.hatch_deadline+AbortHatch. Trust-model README.84c9bf12decimal()cast guard,cube_rootprecision raised 9→15. Variant cleanup, exit-fee zeroed on close, naming, doc comments.3ba8862f2audits/2026-05-09-cw-abc-security-review.md.74931ee30Result<_, CurveError>. Allunwrap()-on-overflow paths replaced with typedCurveError::{Overflow, DivisionByZero}. NewContractError::CurveErrorvariant.744e609feCommonsPhase::Refunding+RefundSnapshot+ClaimRefundhandler. Pro-rata(reserve + funding)refunds, snapshot-locked at AbortHatch time. Buys / sells / withdraw / close blocked in Refunding.dc9a678cbcw-abc/src/audit_tests.rscovering C-1, H-1 vesting math, H-2..H-6, M-1, M-2, M-5, L-3.701dd86b1cw-curves/src/diff_tests.rs: SplitMix64-seeded random walks vs f64 reference impls (1k iters per curve per (sd, rd) matrix), round-trip identity, boundary cases. 14/14 cw-curves tests passing.56cbb672edao-abc-factory/README.md(DAO instantiation flow, reverse-handshake auth doc). Audit report status table refreshed: M-5 → Fixed, L-5 → Fixed (20 of 21 fully fixed; L-2 attribute surface partial).8c8845bbbcargo fmt --all.Audit closure summary
20 of 21 findings fully fixed; 1 partial (L-2 attribute surface — explicit Err arm + operator-monitoring note shipped, per-skipped-DAO response attribute deferred since the helper is private and doesn't return attributes today). All Criticals, Highs, and Mediums fully addressed.
Per-finding fix-commit refs in
audits/2026-05-09-cw-abc-security-review.mdunder "Status as of 2026-05-09".Schema-breaking changes (heads up for reviewers)
These would be breaking for any deployed instance, but the branch is pre-mainnet:
HATCHERSmap type changed fromMap<&Addr, Uint128>toMap<&Addr, HatcherState>.HatchersResponseandHatcherquery response types updated accordingly.CommonsPhaseConfiggains avesting: VestingSchedulefield.HatchConfiggains an optionalhatch_deadline: Option<Timestamp>field.UpdatePhaseConfigMsgloses theClosed {}variant (was atodo!()panic).UpdatePhaseConfigMsg::Hatchgainshatch_deadline: Option<Option<Timestamp>>.CommonsPhasegains aRefundingvariant.ExecuteMsggainsAbortHatch {}andClaimRefund {}.CurveDriftExceeded,InvalidDecimals,HatcherTokensNotVested,InvalidMigration,RefundAlreadyClaimed,RefundBurnMismatch,CurveError(#[from] cw_curves::CurveError).MismatchedSellAmount,Unauthorized(cw-abc),UnsupportedFactoryMsg(dao-abc-factory).Verification
Run with the workspace's pinned
nightly-2024-01-08toolchain. Per-feature gates:Locally green (all four):
cargo fmt --check: cleancargo clippy --lib -- -D warnings: cleancargo test -p cw-curves: 14 passed; 0 failed (3 happy-path + 3 division-by-zero + 6 differential random walks + 2 round-trip identity + 2 boundary)cw_abc.wasm16KB,dao_abc_factory.wasm311KBPending CI (libclang gating; the dev container we built this in didn't have libclang and the pinned nightly doesn't support optional dev-dependencies, so we couldn't gate
osmosis-test-tubelocally):cargo +nightly-2024-01-08 test -p cw-abc -p dao-abc-factory --features cosmwasm_tokenfactory— 30 unit tests incw-abc/src/audit_tests.rsexercising the full audit-defense matrix.bash scripts/schema.shregen forcw-abc.jsonanddao-abc-factory.json(the committed JSON is stale).cargo test --features test-tubefor chain-binary integration tests.