Repaying zero repaid everything

MystenLabs/ts-sdks

#1032 ↗ TypeScript · Move

A truthy guard collapsed amount = 0 onto the “repay everything” branch, so the most conservative call a user could make triggered the most aggressive action available.

The defect

MarginManagerContract.repayBase and repayQuote built their Option<u64> argument with a truthy guard — amount ? … : null. In JavaScript 0 is falsy, so an explicit amount = 0 took the same branch as an omitted argument and became Option::None.

Why it mattered

On the Move side, margin_manager::repay reads that None as “repay as much as possible using the balance held in the balance manager.” So repayBase(mgr, 0) did not repay nothing — it silently triggered a full-debt repayment. The numeric argument suggested a no-op and the contract performed the largest transfer available. Nothing threw, so the only signal was the balance afterwards.

The fix

The guard checks amount !== undefined, matching the pattern already used in the sibling marginLiquidations.ts and marginPool.ts. Zero now means zero, a number means a partial repay, and an omitted argument still means repay-all. The argument also moved from tx.object.option to tx.pure.option, which matches the siblings and drops an extra PTB command per repay.

How it was proven

Unit tests that decode the emitted argument bytes with bcs.option(bcs.u64()) and assert on all three cases for both methods — Some(0), Some(scaled n), and None. Asserting on the encoded bytes rather than on a mock is what makes the test about the transaction actually sent.