Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1ec2afe
rebase cw-abc + cw-curves + dao-abc-factory onto development
juno-ai-dev May 9, 2026
429a45a
fix(cw-abc): apply audit findings C-1 and C-2
juno-ai-dev May 9, 2026
7a3fcfc
fix(cw-abc): apply audit findings H-1 through H-6
juno-ai-dev May 9, 2026
29ba5e1
fix(cw-abc): apply audit findings M-1 through M-6
juno-ai-dev May 9, 2026
84c9bf1
fix(cw-abc): apply audit findings L-1..L-7 (except L-5) and I-1..I-7
juno-ai-dev May 9, 2026
3ba8862
docs(audits): add cw-abc 2026-05-09 security review with status table
juno-ai-dev May 9, 2026
74931ee
fix(cw-curves): convert Curve trait to Result<_, CurveError> (L-5)
juno-ai-dev May 9, 2026
744e609
fix(cw-abc): full pro-rata Refunding sub-state (M-5 full)
juno-ai-dev May 9, 2026
dc9a678
test(cw-abc): audit-defense test matrix (K1..K11)
juno-ai-dev May 9, 2026
701dd86
test(cw-curves): differential test suite vs f64 reference (Phase N)
juno-ai-dev May 9, 2026
56cbb67
docs(cw-abc, dao-abc-factory): expand README + audit status table ref…
juno-ai-dev May 9, 2026
8c8845b
style: cargo fmt across cw-abc, dao-abc-factory, cw-curves, dao-test-…
juno-ai-dev May 9, 2026
bdc5fd7
feat(cw-curves): add Power curve with rational exponent (Phase U)
juno-ai-dev May 9, 2026
e19dac7
feat(cw-curves): add Sigmoid (logistic) curve (Phase V)
juno-ai-dev May 10, 2026
892ceaf
feat(cw-abc): surface DAO-query failures as response attributes (L-2)
juno-ai-dev May 10, 2026
3c73924
chore: remove PR_DRAFT.md scratchpad
juno-ai-dev May 10, 2026
bf45e5a
style: cargo fmt fixes for Phase U/V/T code
juno-ai-dev May 10, 2026
0453cf9
fix(ci): revert C-2 parity on dao-test-custom-factory; default tokenf…
juno-ai-dev May 10, 2026
fea240c
fix(ci): prune unused audit_tests imports + pin cw-plus/cw-nfts wasm …
juno-ai-dev May 10, 2026
01548aa
fix(test-tube): port existing test-tube tests to evolved upstream APIs
juno-ai-dev May 10, 2026
32a0f26
fix(ci): regenerate cw-abc, dao-abc-factory, cw-tokenfactory-issuer s…
juno-ai-dev May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
480 changes: 431 additions & 49 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ cosm-tome = "0.2"
cosmos-sdk-proto = "0.19"
cosmwasm-schema = { version = "1.5.4" }
cosmwasm-std = { version = "1.5.4", features = ["ibc3", "cosmwasm_1_2"] }
cw-address-like = "1.0.4"
cw-controllers = "1.1"
cw-multi-test = { version = "0.20.1", features = ["cosmwasm_1_2"] }
cw-storage-plus = { version = "1.1" }
Expand All @@ -61,15 +62,19 @@ cw4-group = "1.1"
cw721 = "0.18"
cw721-base = "0.18"
env_logger = "0.10"
getrandom = "0.2"
integer-sqrt = "0.1.5"
integer-cbrt = "0.1.2"
once_cell = "1.18"
omniflix-std = "0.1.8"
osmosis-std = "0.20.1"
osmosis-std-derive = "0.20.1"
osmosis-test-tube = "20.1.1"
osmosis-test-tube = "20.1.2"
proc-macro2 = "1.0"
prost = { version = "=0.12.3", features = ["prost-derive"] }
prost-types = { version = "=0.12.3", default-features = false }
prost-reflect = { version = "=0.12.0", features = ["serde"] }
rust_decimal = "1.14.3"
quote = "1.0"
rand = "0.8"
regex = "1.11.1"
Expand All @@ -83,7 +88,8 @@ sg-multi-test = "3.1.0"
sg-std = "3.1.0"
sg721 = "3.1.0"
sg721-base = "3.1.0"
syn = { version = "1.0", features = ["derive"] }
speculoos = "0.11.0"
syn = {version = "1.0", features = ["derive"]}
test-context = "0.1"
thiserror = { version = "1.0" }
wynd-utils = "0.4"
Expand All @@ -93,7 +99,9 @@ wynd-utils = "0.4"
cw-ownable = "0.5"

btsg-ft-factory = { path = "./contracts/external/btsg-ft-factory", version = "2.8.0-alpha.2" }
cw-abc = { path = "./contracts/external/cw-abc", version = "2.8.0-alpha.2", default-features = false }
cw-admin-factory = { path = "./contracts/external/cw-admin-factory", version = "2.8.0-alpha.2" }
cw-curves = { path = "./packages/cw-curves", version = "2.8.0-alpha.2" }
cw-denom = { path = "./packages/cw-denom", version = "2.8.0-alpha.2" }
cw-fund-distributor = { path = "./contracts/distribution/cw-fund-distributor", version = "2.8.0-alpha.2" }
cw-hooks = { path = "./packages/cw-hooks", version = "2.8.0-alpha.2" }
Expand All @@ -113,6 +121,7 @@ cw20-stake = { path = "./contracts/staking/cw20-stake", version = "2.8.0-alpha.2
cw20-stake-external-rewards = { path = "./contracts/staking/cw20-stake-external-rewards", version = "2.8.0-alpha.2" }
cw20-stake-reward-distributor = { path = "./contracts/staking/cw20-stake-reward-distributor", version = "2.8.0-alpha.2" }
cw721-roles = { path = "./contracts/external/cw721-roles", version = "2.8.0-alpha.2" }
dao-abc-factory = { path = "./contracts/external/dao-abc-factory", version = "2.8.0-alpha.2" }
dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "2.8.0-alpha.2" }
dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.8.0-alpha.2" }
dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.8.0-alpha.2" }
Expand Down
616 changes: 616 additions & 0 deletions audits/2026-05-09-cw-abc-security-review.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/dao-dao-core/schema/dao-dao-core.json
Original file line number Diff line number Diff line change
Expand Up @@ -2658,7 +2658,7 @@
"additionalProperties": false
},
{
"description": "Lists all of the items associted with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.",
"description": "Lists all of the items associated with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.",
"type": "object",
"required": [
"list_items"
Expand Down
6 changes: 6 additions & 0 deletions contracts/external/cw-abc/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
unit-test = "test --lib"
integration-test = "test --test integration"
schema = "run --example schema"
67 changes: 67 additions & 0 deletions contracts/external/cw-abc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[package]
name = "cw-abc"
authors = [
"Ethan Frey <ethanfrey@users.noreply.github.com>",
"Jake Hartnell",
"Adair <adairrr@users.noreply.github.com>",
"Gabe Lopez <ismellike@users.noreply.github.com>",
]
description = "Implements an Augmented Bonding Curve"
# Inherits license from previous work
license = "Apache-2.0"
edition = { workspace = true }
repository = { workspace = true }
version = { workspace = true }

[lib]
crate-type = ["cdylib", "rlib"]

[features]
# Default to the osmosis tokenfactory backend so `cargo wasm` builds
# cleanly without an explicit feature flag. Matches the convention 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`.
default = ["osmosis_tokenfactory"]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []
# use test tube feature to enable test-tube integration tests, for example
# cargo test --features "test-tube"
test-tube = []
# different tokenfactory cosmos sdk module standards. enable corresponding
# standard in types library
osmosis_tokenfactory = ["cw-tokenfactory-issuer/osmosis_tokenfactory"]
cosmwasm_tokenfactory = ["cw-tokenfactory-issuer/cosmwasm_tokenfactory"]
thorchain_tokenfactory = ["cw-tokenfactory-issuer/thorchain_tokenfactory"]

[dependencies]
cw-utils = { workspace = true }
cw2 = { workspace = true }
cw-storage-plus = { workspace = true }
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
cw-address-like = { workspace = true }
cw-ownable = { workspace = true }
cw-paginate-storage = { workspace = true }
cw-tokenfactory-issuer = { workspace = true, default-features = false, features = [
"library",
] }
dao-interface = { workspace = true }
getrandom = { workspace = true, features = ["js"] }
thiserror = { workspace = true }
cw-curves = { workspace = true }

[dev-dependencies]
speculoos = { workspace = true }
anyhow = { workspace = true }
cw-multi-test = { workspace = true }
dao-testing = { workspace = true, features = ["test-tube"] }
osmosis-std = { workspace = true }
osmosis-test-tube = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
cw-tokenfactory-issuer = { workspace = true }
dao-voting-token-staked = { workspace = true }
dao-proposal-single = { workspace = true }
dao-voting = { workspace = true }
15 changes: 15 additions & 0 deletions contracts/external/cw-abc/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CW20-Bonding: Bonding Curve to release CW20 token
Copyright 2020-21 Ethan Frey <ethanfrey@users.noreply.github.com>
Copyright 2021-22 Confio GmbH

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
179 changes: 179 additions & 0 deletions contracts/external/cw-abc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# cw-abc

Implements an [Augmented Bonding Curve](https://medium.com/commonsstack/deep-dive-augmented-bonding-curves-b5ca4fad4436).

Forked from and heavily inspired by the work on [cw20-bonding](https://github.com/cosmwasm/cw-tokens/tree/main/contracts/cw20-bonding). This contract uses native and token factory tokens instead.

NOTE: this contract is NOT AUDITED and experimental. NOT RECOMMENDED FOR PRODUCTION USE. Use at your own risk.

## What are Augmented Bonding Curves?
Before we get to the *Augmented* part, we must first describe bonding curves themselves.

### Token Bonding Curves

"A token bonding curve (TBC) is a mathematical curve that defines a relationship between price and token supply." ~[Aavegotchi Wiki](https://wiki.aavegotchi.com/en/curve)

Each bonding curve has a pricing function, also known as the price curve (or `curve_fn` in our implementation). The `curve_fn` is used to determine the price of the asset.

With bonding curves, we will always know what the price of an asset will be based on supply! More on benefits later.

This contract implements two methods:
- `Buy {}` is called with sending along some reserve currency (such as $USDC, or whatever the bonding curve is backed by). The reserve currency is stored by the bonding curve contract, and new tokens are minted and sent to the user.
- `Sell {}` is called along with sending some supply currency (the token minted by the bonding curve). The supply tokens are burned, and reserve currency is returned.

It is possible to use this contact as a basic bonding curve, without any of the augmented features.

#### Math

Given a price curve `f(x)` = price of the `x`th token, we want to figure out how to buy into and sell from the bonding curve. In fact we can look at the total supply issued. let `F(x)` be the integral of `f(x)`. We have issued `x` tokens for `F(x)` sent to the contract. Or, in reverse, if we send `x` tokens to the contract, it will mint `F^-1(x)` tokens.

From this we can create some formulas. Assume we currently have issued `S` tokens in exchange for `N = F(S)` input tokens. If someone sends us `x` tokens, how much will we issue?

`F^-1(N+x) - F^-1(N)` = `F^-1(N+x) - S`

And if we sell `x` tokens, how much we will get out:

`F(S) - F(S-x)` = `N - F(S-x)`

Just one calculation each side. To be safe, make sure to round down and always check against `F(S)` when using `F^-1(S)` to estimate how much should be issued. This will also safely give us how many tokens to return.

There is built in support for safely [raising i128 to an integer power](https://doc.rust-lang.org/std/primitive.i128.html#method.checked_pow). There is also a crate to [provide nth-root of for all integers](https://docs.rs/num-integer/0.1.43/num_integer/trait.Roots.html). With these two, we can handle most math except for logs/exponents.

Compare this to [writing it all in solidity](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7b7ff729b82ea73ea168e495d9c94cb901ae95ce/contracts/math/Power.sol)

Examples:

Price Constant: `f(x) = k` and `F(x) = kx` and `F^-1(x) = x/k`

Price Linear: `f(x) = kx` and `F(x) = kx^2/2` and `F^-1(x) = (2x/k)^(0.5)`

Price Square Root: `f(x) = x^0.5` and `F(x) = x^1.5/1.5` and `F^-1(x) = (1.5*x)^(2/3)`

[You can read more about bonding curve math here](https://yos.io/2018/11/10/bonding-curves/).

#### Benefits

There are a number of benefits to bonding curves:
- There is enough liquidity to back the entire supply without having to list tokens on DEXs
- Easier to wind down projects (there is no going to zero)
- Transparent pricing: looking at the curve will tell you a lot about what kind of project it is.

### Augmented Bonding Curves

Augmented Bonding Curves are nothing new, some articles that inspired this implementation:
- https://medium.com/commonsstack/deep-dive-augmented-bonding-curves-b5ca4fad4436
- https://tokeneconomy.co/token-bonding-curves-in-practice-3eb904720cb8

At a high level, augmented bonding curves extend bonding curves with new functionality:
- Entry and exit fees
- Different phases representing the life cycles of projects

## Features

Example Instantiation message:

``` json
{
"fees_recipient": "address that receives fees",
"token_issuer_code_id": 0,
"supply": {
"subdenom": "utokenname",
"metadata": {
"name": "tokenname",
"description": "Token description.",
"symbol": "TOKEN",
"display": "Token",
},
"decimals": 6,
"max_supply": "100000000000000"
},
"reserve": {
"denom": "ujuno",
"decimals": 6,
},
"curve_type": {
"linear": {
"slope": "2",
"scale": 1
}
},
"phase_config": {
"hatch": {
"contribution_limits": {
"min": "10000000",
"max": "100000000000"
},
"initial_raise": {
"min": "10000000",
"max": "100000000000"
},
"entry_fee": "0.25"
},
"open": {
"exit_fee": "0.01",
"entry_fee": "0.01"
},
"closed": {}
},
"hatcher_allowlist": [
{
"addr": "dao_address",
"config": {
"config_type": { "dao": { "priority": 1 } },
"contribution_limits_override": {
"min": "100000000",
"max": "99999999999999"
}
}
},
{
"addr": "address",
"config": {
"config_type": { "address": {} }
}
}
],
}
```

- `fees_recipient`: the address that will receive fees (usually a DAO).
- `token_issuer_code_id`: the CosmWasm code ID for a `cw-tokenfactory_issuer` contract.
- `supply`: info about the token that will be minted by the curve. This is the token that is created by the bonding curve.
- `reserve`: this is the token that is used to mint the supply token.
- `curve_type`: information about the pricing curve.
- `phase_config`: configuration for the different phase of the augmented bonding curve.
- `hatcher_allowlist`: the list of address allowed to participate in a hatch.

## Trust assumptions

This contract has a privileged `owner` address (set at instantiate, transferable via `UpdateOwnership`) that can:

- Pause the contract (circuit breaker).
- Withdraw the funding pool (`Withdraw`).
- Update the curve type — but **only in the Closed phase**, and only when the new curve produces a reserve at the existing supply within 1% of the recorded reserve (audit fix C-1, see `audits/`).
- Update phase config (Hatch and Open variants).
- Update the maximum supply (`UpdateMaxSupply`).
- Update the hatcher allowlist (`UpdateHatchAllowlist`).
- Close the curve (`Close`).
- Update the funding-pool forwarding address.

**Recommended deployment**: ownership set to a DAO core contract. EOA / single-multisig ownership is technically supported but not recommended for production deployments; the owner has unilateral access to the funding pool and substantial latitude to adjust phase config.

`AbortHatch {}` is permissionless — any address can call it after `hatch_deadline` has passed if the curve has not reached `initial_raise.min`. This transitions the contract to **Refunding** phase, snapshots the pro-rata refund math (`total_pool = reserve + funding` and `total_contributed`), and lets each hatcher reclaim their share via `ClaimRefund {}`. Each hatcher receives `state.contributed * total_pool / total_contributed` reserve tokens in exchange for surrendering their unburned hatcher tokens. The funding pool is locked from owner withdrawal during Refunding — the funds belong to the hatchers.

`UpdateCurve` is gated on Closed phase plus a continuity check that prevents replacement curves from breaking the (reserve, supply) invariant beyond a small tolerance. This closes the rug-via-curve-swap surface identified in the 2026-05-09 security review.

## Vesting (added 2026-05-09)

Hatcher tokens are subject to a configurable vesting schedule once the curve transitions Hatch → Open. See `CommonsPhaseConfig.vesting`:

- `VestingSchedule::None` — no vesting; hatchers can sell immediately at Open.
- `VestingSchedule::Cliff { duration_seconds }` — 0% available until `duration_seconds` after Open transition, 100% after.
- `VestingSchedule::Linear { duration_seconds }` — linear ramp from 0% at Open transition to 100% at `duration_seconds`.

Tokens minted during the Open phase by addresses that did **not** participate in the hatch are not subject to vesting.

## Future Work

- [ ] Implement an expanded set of pricing curves to choose from

11 changes: 11 additions & 0 deletions contracts/external/cw-abc/examples/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use cosmwasm_schema::write_api;

use cw_abc::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

fn main() {
write_api! {
instantiate: InstantiateMsg,
query: QueryMsg,
execute: ExecuteMsg,
}
}
Loading
Loading