Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 14 additions & 6 deletions contracts/flashstack-sbtc-pool-v3.clar
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
;; => each share worth more sBTC over time
;;
;; Collateral oracle:
;; get-share-price returns sats per share (scaled by SHARE-PRECISION)
;; get-share-price returns sats per share, scaled by PRICE-PRECISION (1e16)
;; Lending protocols can call this directly - no external oracle needed
;; Flash loan manipulation-resistant: reserve invariant guarantees
;; pool balance only ever grows by >= fee per loan
Expand Down Expand Up @@ -80,6 +80,13 @@
(define-data-var reentrancy-locked bool false)

(define-constant SHARE-PRECISION u100000000) ;; 1e8 - matches sBTC sat precision
;; ajv.3.4 (F-11 shape): deposit mints 1e8 shares per sat, so one share is
;; worth ~1e-8 sat. Scaling the oracle by SHARE-PRECISION alone made it read
;; 1 at launch and ignore any gain short of +100%. The oracle scales by
;; 1e16 instead, so launch reads 1e8 and it resolves a change of 1 part in
;; 1e8. Read-only output only; share mint/burn math is unchanged.
;; Overflow: (21e6 BTC = 2.1e15 sats) * 1e16 = 2.1e31, far below u128 max.
(define-constant PRICE-PRECISION u10000000000000000) ;; 1e16 = SHARE-PRECISION * 1e8

;; F-1 fix (v2): virtual shares + virtual assets (OpenZeppelin ERC-4626
;; inflation-attack mitigation). A permanent phantom position absorbs
Expand Down Expand Up @@ -272,9 +279,10 @@
;; Read-only - including collateral oracle
;; =============================================

;; Current value of one pool share in sats, scaled by SHARE-PRECISION (1e8).
;; Divide result by 1e8 to get sats per share.
;; At launch: 1e8 (= 1 sat per share)
;; Current value of one pool share in sats, scaled by PRICE-PRECISION (1e16).
;; Divide result by 1e16 to get sats per share (equivalently, by 1e8 to get
;; sats per 1e8 shares, the amount one sat mints at launch).
;; At launch: 1e8 (= 1e-8 sat per share, i.e. 1 sat per 1e8 shares)
;; Over time: increases as flash loan fees accumulate
;;
;; This is the primary function lending protocols should consume.
Expand All @@ -286,7 +294,7 @@
(current-shares (var-get total-shares))
)
;; Virtual-offset price is well-defined at zero shares (no special case).
(ok (/ (* (+ pool-balance VIRTUAL-ASSETS) SHARE-PRECISION) (+ current-shares VIRTUAL-SHARES)))
(ok (/ (* (+ pool-balance VIRTUAL-ASSETS) PRICE-PRECISION) (+ current-shares VIRTUAL-SHARES)))
)
)

Expand Down Expand Up @@ -336,7 +344,7 @@
(current-shares (var-get total-shares))
)
(ok {
share-price: (/ (* (+ pool-balance VIRTUAL-ASSETS) SHARE-PRECISION) (+ current-shares VIRTUAL-SHARES)),
share-price: (/ (* (+ pool-balance VIRTUAL-ASSETS) PRICE-PRECISION) (+ current-shares VIRTUAL-SHARES)),
total-shares: current-shares,
pool-balance: pool-balance,
yield-accrued: (var-get total-fees),
Expand Down
20 changes: 14 additions & 6 deletions contracts/test/flashstack-sbtc-pool-v3.clar
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
;; => each share worth more sBTC over time
;;
;; Collateral oracle:
;; get-share-price returns sats per share (scaled by SHARE-PRECISION)
;; get-share-price returns sats per share, scaled by PRICE-PRECISION (1e16)
;; Lending protocols can call this directly - no external oracle needed
;; Flash loan manipulation-resistant: reserve invariant guarantees
;; pool balance only ever grows by >= fee per loan
Expand Down Expand Up @@ -80,6 +80,13 @@
(define-data-var reentrancy-locked bool false)

(define-constant SHARE-PRECISION u100000000) ;; 1e8 - matches sBTC sat precision
;; ajv.3.4 (F-11 shape): deposit mints 1e8 shares per sat, so one share is
;; worth ~1e-8 sat. Scaling the oracle by SHARE-PRECISION alone made it read
;; 1 at launch and ignore any gain short of +100%. The oracle scales by
;; 1e16 instead, so launch reads 1e8 and it resolves a change of 1 part in
;; 1e8. Read-only output only; share mint/burn math is unchanged.
;; Overflow: (21e6 BTC = 2.1e15 sats) * 1e16 = 2.1e31, far below u128 max.
(define-constant PRICE-PRECISION u10000000000000000) ;; 1e16 = SHARE-PRECISION * 1e8

;; F-1 fix (v2): virtual shares + virtual assets (OpenZeppelin ERC-4626
;; inflation-attack mitigation). A permanent phantom position absorbs
Expand Down Expand Up @@ -272,9 +279,10 @@
;; Read-only - including collateral oracle
;; =============================================

;; Current value of one pool share in sats, scaled by SHARE-PRECISION (1e8).
;; Divide result by 1e8 to get sats per share.
;; At launch: 1e8 (= 1 sat per share)
;; Current value of one pool share in sats, scaled by PRICE-PRECISION (1e16).
;; Divide result by 1e16 to get sats per share (equivalently, by 1e8 to get
;; sats per 1e8 shares, the amount one sat mints at launch).
;; At launch: 1e8 (= 1e-8 sat per share, i.e. 1 sat per 1e8 shares)
;; Over time: increases as flash loan fees accumulate
;;
;; This is the primary function lending protocols should consume.
Expand All @@ -286,7 +294,7 @@
(current-shares (var-get total-shares))
)
;; Virtual-offset price is well-defined at zero shares (no special case).
(ok (/ (* (+ pool-balance VIRTUAL-ASSETS) SHARE-PRECISION) (+ current-shares VIRTUAL-SHARES)))
(ok (/ (* (+ pool-balance VIRTUAL-ASSETS) PRICE-PRECISION) (+ current-shares VIRTUAL-SHARES)))
)
)

Expand Down Expand Up @@ -336,7 +344,7 @@
(current-shares (var-get total-shares))
)
(ok {
share-price: (/ (* (+ pool-balance VIRTUAL-ASSETS) SHARE-PRECISION) (+ current-shares VIRTUAL-SHARES)),
share-price: (/ (* (+ pool-balance VIRTUAL-ASSETS) PRICE-PRECISION) (+ current-shares VIRTUAL-SHARES)),
total-shares: current-shares,
pool-balance: pool-balance,
yield-accrued: (var-get total-fees),
Expand Down
8 changes: 4 additions & 4 deletions docs/TESTNET_STAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -770,10 +770,10 @@ mainnet:
- Never `clarinet deployments apply --mainnet` from this repo
(`deployments/README.md`, D6/ajv.6.6).
- The v2 → v3 LP migration path (`Flashstack-ajv.4.4`) has to be decided first.
- Two findings outside this plan apply before mainnet: sbtc-pool-v3's
`get-share-price` has the F-11 scaling gap (`Flashstack-ajv.3.4`, #123). Rescale it
before mainnet, or accept and document it. If it's rescaled, this plan runs against
the rescaled source.
- sbtc-pool-v3's `get-share-price` F-11 scaling gap (`Flashstack-ajv.3.4`, #123) is
fixed: it now scales by `PRICE-PRECISION` (1e16) and reads `1e8` at launch. This plan
runs against the rescaled source, so a staged instance deployed before the fix is
not the audited version.

---

Expand Down
3 changes: 2 additions & 1 deletion docs/security/CLARITY_AUDIT_V2_POOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ on-chain caller would have to be a published contract that names it. No integrat
The same F-11 structure (`VIRTUAL-SHARES` equal to `SHARE-PRECISION`) appears in
`flashstack-sbtc-pool-v2`'s built-in `get-share-price` (1e8/1e8). Checked 2026-10-15
(`Flashstack-ajv.4.12`): it is affected the same way. It reads `1` live, and the detail is in
the FINDINGS_REGISTER F-11 row.
the FINDINGS_REGISTER F-11 row. The undeployed successor `flashstack-sbtc-pool-v3` is
rescaled to 1e16 (`Flashstack-ajv.3.4`).

## Notes

Expand Down
Loading
Loading