Smart Contract Fix Plan
Smart Contract Fix Plan
This is the implementation order for cleaning up the smart contract accounting model.
Status:
Implemented on 2026-04-23.
The main decision is:
Use USD-only collateral state.
That means collateral is a risk and margin value, not a payout asset selector.
Core Rules
Collateral should be tracked as:
collateral_usd
Collateral deposits should:
- Convert the deposited token amount to USD using the oracle.
- Transfer the deposited token into the matching custody.
- Increase that custody
token_owned. - Increase position
collateral_usd. - Not track separate per-position SOL and USDC collateral buckets.
Collateral withdrawals should:
- Convert the requested withdrawal token amount to USD using the oracle.
- Check the position remains healthy after reducing
collateral_usd. - Check the requested payout custody has enough free liquidity.
- Transfer the requested token to the user.
- Decrease that custody
token_owned. - Decrease position
collateral_usd.
Close and liquidation logic should treat collateral as:
margin math only
Collateral should not decide liquidation payout asset.
For liquidation:
Long liquidation pays from locked SOL.
Short liquidation pays from locked USDC.
Implementation Order
- Convert collateral model to USD-only.
- Fix open position initialization around
collateral_usd. - Fix add collateral.
- Fix remove collateral.
- Fix direct close.
- Fix limit close and cancel refunds.
- Fix TP/SL execution.
- Fix liquidation.
- Fix auto-exercise immediate payout.
- Sync IDL, backend, frontend, and docs.
Implemented Behavior
The final implementation keeps legacy collateral_custody and collateral_amount
fields for account-layout compatibility, but they are no longer the economic
source of truth.
Rules now enforced:
collateral_usdis the margin source of truth for perps and expiry futures.- Collateral deposits convert the deposited token amount to USD using the oracle.
- Collateral withdrawals convert the requested withdrawal token to USD, check post-withdraw health, and require free liquidity in the receive custody.
- Market opens check free backing liquidity before locking.
- Limit placements do not check backing liquidity because no lock is created.
- Limit executions check free backing liquidity immediately before locking.
- Same-asset closes and settlements can pay from released locked backing, bounded by the released locked amount.
- Cross-asset closes and settlements require free liquidity in the payout custody.
- Liquidations pay from locked backing: long pays SOL and short pays USDC.
- Auto-exercise immediately transfers profit from locked backing and releases the lock.
Step 1: Convert Collateral Model To USD-Only
Goal:
collateral_usd is the source of truth for position collateral.
Edits:
- Stop using
collateral_custodyas a payout selector. - Stop using
collateral_amountas the economic collateral source of truth. - Keep
collateral_usdas the value used for margin, leverage, liquidation, PnL, and health checks. - Decide whether
collateral_custodyandcollateral_amountare removed from account state or kept temporarily for backward compatibility.
Risk:
Removing account fields changes the Anchor account layout and requires IDL/backend/frontend sync.
Step 2: Fix Open Position Initialization
Open perp and open future should:
- Convert the initial collateral deposit into USD.
- Store that value in
collateral_usd. - Transfer the deposited asset into its custody.
- Increase the deposited asset custody
token_owned. - Lock backing based on side, not collateral asset.
Perp and future backing:
Long = lock SOL
Short = lock USDC
Step 3: Fix Add Collateral
Add collateral should:
- Accept SOL or USDC.
- Convert the added token amount to USD.
- Transfer the added token into the matching custody.
- Increase that custody
token_owned. - Increase
collateral_usd. - Recalculate leverage and liquidation price.
It should not:
normalize the added token into the original collateral asset
Step 4: Fix Remove Collateral
Remove collateral should:
- Accept a requested withdrawal asset, SOL or USDC.
- Convert the requested withdrawal amount into USD.
- Check that
collateral_usd - withdrawal_usdkeeps the position healthy. - Check the receive custody has free liquidity:
receive_custody.token_owned - receive_custody.token_locked >= withdrawal_tokens
- Transfer the requested token to the user.
- Decrease receive custody
token_owned. - Decrease position
collateral_usd.
Step 5: Fix Direct Close
Direct close includes:
close_option.rs
close_future.rs
close_perp_position.rs
Rules:
- Same-asset payout can use locked backing.
- Same-asset payout must be bounded by released locked amount.
- Cross-asset payout must use free liquidity.
- Token transfer must subtract
token_ownedfrom the custody that actually paid.
Step 6: Fix Limit Close And Cancel Refunds
Limit close execution should use the same payout rules as direct close.
Cancel/refund paths should:
- Not use locked backing unless the refund is explicitly from locked backing.
- Use reserved-premium or reserved-collateral accounting if such reserve exists.
- Otherwise require free liquidity before refund transfer.
- Subtract
token_ownedfrom the custody that actually paid.
Step 7: Fix TP/SL Execution
TP/SL creation and updates do not need payout checks.
TP/SL execution should use the same payout rules as direct close:
- Same-asset payout bounded by released locked amount.
- Cross-asset payout backed by free liquidity.
- Exact paying custody
token_ownedsubtraction.
Step 8: Fix Liquidation
Liquidation should not pay from collateral.
Liquidation payout asset should be based on locked backing:
Long = SOL payout from locked SOL
Short = USDC payout from locked USDC
Rules:
- Calculate liquidation payout.
- Require payout is bounded by locked backing released.
- Transfer from locked backing custody.
- Subtract payout from locked backing custody
token_owned. - Release locked backing from
token_locked. - Do not use
collateral_custodyas payout source.
Step 9: Fix Auto-Exercise Immediate Payout
Auto-exercise should immediately transfer profit.
Rules:
- Call auto-exercise pays SOL from locked SOL.
- Put auto-exercise pays USDC from locked USDC.
- Require locked custody has enough locked backing.
- Require profit payout is bounded by unlock amount.
- Transfer profit.
- Subtract transferred profit from locked custody
token_owned. - Subtract unlock amount from locked custody
token_locked. - Mark the option exercised or invalid.
There should be no claim-recording model.
Step 10: Sync IDL, Backend, Frontend, And Docs
After smart contract account or instruction changes:
- Run
anchor build. - Sync IDL to backend and frontend.
- Update backend indexers and API models.
- Update frontend transaction builders.
- Update frontend order boxes and position views.
- Update docs to match the final implementation.