The index fund that held the wrong asset
I found a Sui Move contract called crypto_index_fund. You deposit SUI, it mints you an IndexFundToken recording an equal-weighted basket — BTC, ETH, XRP, ADA, MATIC, priced through the Supra oracle — and when you withdraw it pays you out in SUI at the basket’s current value. A one-click crypto index fund on-chain. Slick.
It is also insolvent the moment any price moves, and the reason is the first thing you should check in any fund: does it actually hold what it says it holds?
It holds SUI. It pays a basket.
Here’s the withdraw, trimmed:
// value the token's NOTIONAL basket at current oracle prices...
let total_usd_value = btc_usd + eth_usd + xrp_usd + ada_usd + matic_usd;
let total_sui = ((total_usd_value / adjusted_sui_usd_price) as u64);
// ...and pay that many SUI out of the shared pool
let index_token_balance = balance::split(&mut index_fund.balance, total_sui);
Deposit only ever added SUI to index_fund.balance. The contract never bought a single satoshi of BTC — the basket is a number in a struct. So when BTC goes up, your token is “worth” more SUI, and withdraw pays it to you out of the common pool, which is just everyone else’s SUI.
Play it forward with two depositors. Both put in 1,000 SUI; the pool holds 2,000. BTC rallies. Alice withdraws first: her token now values at, say, 1,400 SUI, so balance::split hands her 1,400 and leaves 600. Bob withdraws: his token also values at 1,400, balance::split(&mut pool, 1400) against a 600 balance — abort. Bob’s money is stuck. It’s a bank run, except the run is triggered by a green candle and the loser is whoever clicks second. (There’s tangled decimal math and no oracle-staleness check on top, but the insolvency is the one that empties wallets.)
Fix it by only ever paying what you hold
The solvent rebuild is a pro-rata share fund. Deposit mints shares proportional to the pool; withdraw returns pool * shares / total_shares SUI. That quantity is always ≤ the pool — so balance::split can never abort, and the fund can never owe SUI it doesn’t have. The oracle drops out of the money path entirely and becomes an informational USD NAV view (now with the staleness check the original skipped). The headline test is just: two depositors, withdraw both — the second one succeeds instead of aborting. Five Move tests, green.
And here’s the honest part I put in the README: this solvent thing isn’t really an index fund anymore. It’s a SUI pool with a NAV readout. A real on-chain index fund has to actually custody the assets — swap the deposited SUI into wrapped BTC/ETH/… through a DEX so the basket exists on-chain. That’s the part the original skipped, and skipping it is the whole bug. You can’t pay out exposure you never bought.
It’s the same lesson as the fake dice game and the bridge that paid twice: the contract isn’t where these break. They break on a promise the contract makes about value it doesn’t actually hold — a roll it never reads, a release it never recorded, a basket it never bought. Read what the pool contains before you read what it claims to owe.
Audit, fix, and the five passing tests are at github.com/0xSoftBoi/01.