feat(stablecoin): add collateralization-check helper - #336
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a reusable, spec-aligned collateralization invariant check to stablecoin_program so upcoming position-lifecycle instructions can share one canonical implementation and test suite.
Changes:
- Introduces
stablecoin_program::checks::assert_position_is_collateralized, implementing the §6.2 invariant usingU256intermediates and a zero-debt fast-path. - Adds focused unit tests covering boundary, near-boundary, and accumulator-growth scenarios.
- Wires the new module into the crate exports and adds the required direct dependency.
Reviewed changes
Copilot reviewed 3 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| programs/stablecoin/src/lib.rs | Exposes the new checks module from stablecoin_program. |
| programs/stablecoin/src/checks.rs | Implements assert_position_is_collateralized and adds unit tests for key invariant edges. |
| programs/stablecoin/Cargo.toml | Adds alloy-primitives as a direct dependency for U256 use in stablecoin_program. |
| Cargo.lock | Records the new stablecoin_program -> alloy-primitives dependency in the workspace lockfile. |
| programs/stablecoin/methods/guest/Cargo.lock | Updates the guest lockfile to reflect the new dependency in guest builds. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
64bee78 to
f0c2e9e
Compare
| .checked_div(one) | ||
| .expect("collateralization check: FIXED_POINT_ONE is non-zero"); | ||
|
|
||
| let collateral_value = multiply(multiply(U256::from(position.collateral_amount), one), one); |
There was a problem hiding this comment.
U256 is not wide enough for these cross-products over the function's valid u128 input domain. Since FIXED_POINT_ONE^2 == 10^54, the left side already overflows when collateral_amount reaches
115792089237316195423571. With that collateral, debt 1, accumulator and redemption price FIXED_POINT_ONE, and ratio 1.1 * FIXED_POINT_ONE, the position is comfortably collateralized but this multiplication panics before the comparison. Token supply, holding balance, and position collateral have no
smaller bound. Please use a width that holds the complete products (for example U512) or an overflow-free exact comparison, and add a regression at this boundary.
There was a problem hiding this comment.
Concrete trigger:
position.collateral_amount = 115792089237316195423571
position.normalized_debt_amount = 1
current_accumulator = 1000000000000000000000000000
current_redemption_price = 1000000000000000000000000000
minimum_collateralization_ratio = 1100000000000000000000000000
Adds
stablecoin_program::checks::assert_position_is_collateralized, the sharedspec §6.2 invariant used by
withdraw_collateral(post-decrement) andgenerate_debt(post-mint).Pure function over scalars — the callers project the accumulator and redemption
price forward to
now(§5.3) and pass them in, so the projection isn't computedtwice per instruction. Uses the cross-multiplied form the spec prescribes, in
U256, so no intermediate rounding enters the comparison. Zero-debt positionsshort-circuit to pass.
8 unit tests: zero debt with and without collateral, comfortable surplus, the
exact boundary and one unit below it, exactly 1.5x and one unit below it, and
accumulator growth flipping a passing position to failing.
First of eight issues in Plan 3 (#173). No
Instructionvariant and no guestchange, so the IDL is untouched.
closes #174