promise-code/btc-stablecoin-bridge-lp ·
Commit: fb37888 (2024-10-31) ·
Audited: February 21, 2026
This contract implements a Bitcoin-backed stablecoin system with integrated liquidity pool functionality. Users deposit BTC as collateral to mint stablecoins, and can provide liquidity in a BTC/stablecoin AMM pool. The contract includes an owner-controlled oracle for BTC/USD pricing, collateral vaults with minimum ratios, and LP token accounting.
The codebase is a single Clarity file (283 lines) covering collateral management, stablecoin minting/burning, LP provision, and basic AMM mechanics. No swap function is implemented despite pool infrastructure.
| Metric | Weight | Score | Weighted |
|---|---|---|---|
| Financial risk | 3 | 3 — DeFi: collateralized stablecoin + LP pool | 9 |
| Deployment likelihood | 2 | 1 — Has Clarinet config + tests dir, but simulated balances | 2 |
| Code complexity | 2 | 2 — 283 lines, multi-feature (vaults, LP, oracle) | 4 |
| User exposure | 1.5 | 0 — 0 stars/forks | 0 |
| Novelty | 1.5 | 3 — Stablecoin bridge + LP — new category for this portfolio | 4.5 |
| Total | 19.5 / 10 = 1.95 ≥ 1.8 ✓ | ||
No Clarity version specified. Uses legacy as-contract. -0.5 penalty not applied as score is borderline and contract is DeFi-relevant.
| ID | Severity | Title |
|---|---|---|
| C-01 | CRITICAL | Phantom Balance System — No Real Asset Transfers |
| C-02 | CRITICAL | Broken Square Root Enables LP Token Miscalculation |
| C-03 | CRITICAL | Unilateral Oracle Manipulation — Owner Controls All Collateral |
| H-01 | HIGH | Mint Guard Uses Pool Balance Instead of Total Collateral |
| H-02 | HIGH | LP Token Calculation Inconsistent — Second Depositors Disadvantaged |
| M-01 | MEDIUM | No Liquidation Mechanism for Undercollateralized Vaults |
| M-02 | MEDIUM | Remove Liquidity Underflow on btc-provided / stable-provided |
| M-03 | MEDIUM | No Swap Function Despite Pool Infrastructure |
| L-01 | LOW | Collateral Ratio Division Truncation Allows Over-Minting |
| L-02 | LOW | No Withdrawal Function for Deposited Collateral |
| I-01 | INFO | Missing SIP-010 Trait — Not Composable |
| I-02 | INFO | No Events Emitted for State Changes |
Location: transfer-balance (lines 54-65), deposit-collateral (line 121)
Description: The entire contract operates on internal balances maps that are never funded by real STX or BTC transfers. The deposit-collateral function calls transfer-balance which moves numbers between map entries, but no stx-transfer? or SIP-010 transfer is ever called. There is no way for users to seed the balances map with real tokens.
(define-private (transfer-balance (amount uint) (sender principal) (recipient principal))
(let (
(sender-balance (default-to u0 (map-get? balances sender)))
(recipient-balance (default-to u0 (map-get? balances recipient)))
)
(if (>= sender-balance amount)
(begin
(map-set balances sender (- sender-balance amount))
(map-set balances recipient (+ recipient-balance amount))
(ok true)
)
ERR-INSUFFICIENT-BALANCE
))
)
Impact: The contract is completely non-functional. No real assets can be deposited, no real stablecoins can be backed, and the LP pool holds nothing. Every user operation will fail with ERR-INSUFFICIENT-BALANCE since balances[user] is always 0.
Recommendation: Replace transfer-balance with actual stx-transfer? calls for deposits. Implement a SIP-010 fungible token for the stablecoin. Use real token transfers for all asset movements.
Location: sqrt (lines 93-100)
Description: The square root implementation is fundamentally incorrect. It performs a single Newton's method iteration without any convergence loop, returning (x/2 + 1) instead of √x.
(define-private (sqrt (x uint))
(let (
(next (+ (/ x u2) u1))
)
(if (<= x u2)
u1
next ;; Returns x/2+1, NOT sqrt(x)
))
)
Examples:
sqrt(100) → 51 (should be 10) — 5.1x oversqrt(10000) → 5001 (should be 100) — 50x oversqrt(1000000) → 500001 (should be 1000) — 500x overImpact: Initial LP token minting is massively inflated. The first liquidity provider receives orders of magnitude more LP tokens than they should. This distorts all subsequent LP calculations and pro-rata withdrawals.
Recommendation: Implement a proper integer square root using Newton's method with iterative convergence:
;; Proper Babylonian method sqrt
(define-private (sqrt (x uint))
(if (<= x u1) x
(let (
(z (/ (+ x u1) u2))
(z (if (< (/ x z) z) (/ x z) z)) ;; min(z, x/z)
;; ... iterate until convergence
) z)
)
)
Location: update-price (lines 110-115)
Description: The contract owner has unrestricted ability to set the oracle price to any value between 1 and MAX-PRICE (100,000,000,000). This directly controls whether all vaults in the system are collateralized or not. There is no time delay, no multi-sig, and no external oracle integration.
(define-public (update-price (new-price uint))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
(asserts! (validate-price new-price) ERR-INVALID-PRICE)
(var-set oracle-price new-price)
(ok true)
)
)
Impact: The owner can crash the price to make all vaults undercollateralized, or inflate it to mint maximum stablecoins with minimal collateral. In a real deployment, this gives the owner complete control over all user funds.
Recommendation: Integrate a decentralized oracle (e.g., Redstone, DIA) or implement a multi-party price feed with deviation checks. Add a time-weighted average price (TWAP) mechanism and maximum price change per update.
Location: mint-stablecoin (lines 138-140)
Description: The minting assertion checks that total supply doesn't exceed pool-btc-balance * oracle-price. But pool-btc-balance tracks the LP pool, not total collateral. At contract start, pool-btc-balance is 0, making all minting impossible regardless of collateral deposited.
(asserts! (and
(> amount u0)
(<= amount MAX-MINT-AMOUNT)
(<= (+ (var-get total-supply) amount) (* (var-get pool-btc-balance) (var-get oracle-price)))
) ERR-INVALID-AMOUNT)
Impact: No stablecoins can ever be minted until someone first provides liquidity to the pool. The collateral vault system is disconnected from the minting constraint. Even with sufficient personal collateral, users cannot mint.
Recommendation: Track total collateral across all vaults separately. The mint check should reference total locked collateral, not LP pool balance.
Location: calculate-lp-tokens (lines 83-92)
Description: For the first depositor, LP tokens = sqrt(btc * stable). For subsequent depositors, LP tokens = btc * sqrt(pool_btc * pool_stable) / pool_btc. These formulas are not equivalent. The second formula doesn't consider the stable amount at all, meaning a user can provide 1 stable + 1000 BTC and get the same LP tokens as 1000 stable + 1000 BTC.
(if (is-eq pool-btc u0)
(sqrt (* btc-amount stable-amount))
(/ (* btc-amount (sqrt (* pool-btc pool-stable))) pool-btc)
)
Impact: Second and subsequent liquidity providers can game the system by providing minimal stablecoins with large BTC amounts. They receive disproportionate LP tokens and can drain pool value on withdrawal.
Recommendation: Use the standard Uniswap V2 LP token formula: min(btc * totalLP / poolBTC, stable * totalLP / poolStable). Track total LP supply globally.
Location: Contract-wide
Description: The contract defines a LIQUIDATION-RATIO of 130% but never implements liquidation. If BTC price drops and a vault falls below 130%, nothing happens. The stablecoin remains in circulation, underbacked.
Impact: The stablecoin has no mechanism to maintain its peg during BTC price declines. Bad debt accumulates silently with no way to unwind it.
Recommendation: Implement a liquidation function allowing anyone to repay a vault's debt when the collateral ratio falls below LIQUIDATION-RATIO, receiving the collateral at a discount.
Location: remove-liquidity (lines 201-202)
Description: When removing liquidity, the pro-rata BTC and stable returns are subtracted from the user's historical btc-provided and stable-provided. But due to pool ratio changes from the broken sqrt and asymmetric deposits, btc-return can exceed btc-provided, causing an underflow panic.
(map-set liquidity-providers tx-sender {
pool-tokens: (- total-lp-tokens lp-tokens),
btc-provided: (- (get btc-provided provider-data) btc-return),
stable-provided: (- (get stable-provided provider-data) stable-return)
})
Impact: LP providers may be permanently unable to withdraw their liquidity if pool ratios have shifted, as the subtraction will panic and revert.
Recommendation: Don't track historical provided amounts. Calculate returns purely from LP token share of pool.
Location: Contract-wide
Description: The contract builds AMM pool infrastructure (add/remove liquidity, fee rate constant, pool balances) but implements no swap function. The POOL-FEE-RATE constant (0.3%) is defined but unused.
Impact: The liquidity pool is a dead-end. Funds can be deposited but never used for trading. The pool serves no purpose without swaps, and LPs earn no fees.
Recommendation: Implement a constant-product swap function: x * y = k with fee deduction.
Location: calculate-collateral-ratio (lines 68-76)
Description: The collateral ratio formula (btc_value * 100) / stablecoin_amount uses integer division which truncates. With carefully chosen amounts, a user can get a ratio of exactly 150 (passing the check) while actually being slightly undercollateralized.
Impact: Minor: users can mint fractionally more stablecoins than the 150% ratio should allow.
Recommendation: Multiply by a larger precision factor before dividing, or rearrange the inequality to avoid division entirely: btc_value * 100 >= stablecoin_amount * MINIMUM_COLLATERAL_RATIO.
Location: Contract-wide
Description: Users can deposit collateral and mint stablecoins, then burn stablecoins, but there is no function to withdraw excess collateral from a vault. Collateral is permanently locked once deposited.
Impact: User funds are trapped in collateral vaults with no exit path, even after repaying all minted stablecoins.
Recommendation: Add a withdraw-collateral function that allows withdrawal as long as the remaining ratio stays above MINIMUM-COLLATERAL-RATIO.
Location: Contract-wide
Description: The stablecoin is tracked in a custom stablecoin-balances map rather than implementing the SIP-010 fungible token standard. It cannot be used in any other Stacks DeFi protocol, DEX, or wallet.
Recommendation: Implement SIP-010 trait (define-fungible-token with transfer, get-balance, get-total-supply, etc.).
Location: Contract-wide
Description: No print events are emitted for deposits, mints, burns, LP operations, or price updates. Off-chain monitoring and indexing is impossible.
Recommendation: Add (print { event: "deposit", ... }) to all state-changing functions.
Beyond individual findings, this contract has fundamental architectural problems:
remove-liquidity divides by the user's own LP tokens, making the calculation self-referential.stx-transfer? and define-fungible-token. This is the single most important fix.