stablecoin-bridge
BTC Stablecoin Bridge LP — Security Audit
promise-code/btc-stablecoin-bridge-lp ·
Commit: fb37888 (2024-10-31) ·
Audited: February 21, 2026
Overview
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.
Priority Score
| 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.
Findings Summary
| 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 |
Detailed Findings
C-01 Phantom Balance System — No Real Asset Transfers
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.
C-02 Broken Square Root Enables LP Token Miscalculation
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 over
Impact: 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)
)
)
C-03 Unilateral Oracle Manipulation — Owner Controls All Collateral
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.
H-01 Mint Guard Uses Pool Balance Instead of Total Collateral
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.
H-02 LP Token Calculation Inconsistent — Second Depositors Disadvantaged
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.
M-01 No Liquidation Mechanism for Undercollateralized Vaults
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.
M-02 Remove Liquidity Underflow on btc-provided / stable-provided
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.
M-03 No Swap Function Despite Pool Infrastructure
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.
L-01 Collateral Ratio Division Truncation Allows Over-Minting
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.
L-02 No Withdrawal Function for Deposited Collateral
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.
I-01 Missing SIP-010 Trait — Not Composable
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.).
I-02 No Events Emitted for State Changes
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.
Architectural Issues
Beyond individual findings, this contract has fundamental architectural problems:
- No real asset layer: The entire system operates on phantom internal balances with no connection to real STX, BTC, or SIP-010 tokens. This makes the contract completely non-functional.
- Circular dependency: Minting requires LP pool balance, but LP pool needs stablecoins — which can only come from minting. The system can never bootstrap.
- No global LP tracking: Total LP token supply is not tracked globally.
remove-liquiditydivides by the user's own LP tokens, making the calculation self-referential. - Missing core functions: No swap, no collateral withdrawal, no liquidation, no stablecoin transfer between users.
Recommendations
- Use real token transfers. Replace internal balance maps with
stx-transfer?anddefine-fungible-token. This is the single most important fix. - Fix the sqrt function. Implement proper Babylonian method with iterative convergence. Test with known values.
- Decentralize the oracle. Use a multi-party price feed or integrate an existing Stacks oracle. At minimum, add price change limits and time delays.
- Implement liquidation. The LIQUIDATION-RATIO constant is defined but unused. Build the actual liquidation mechanism.
- Add swap functionality. The LP pool is useless without a swap function. Implement constant-product AMM.
- Track global LP supply. Use a data-var for total LP tokens to enable correct pro-rata calculations.