diff --git a/contracts/finance/README.md b/contracts/finance/README.md index 3e127779..35156c2f 100644 --- a/contracts/finance/README.md +++ b/contracts/finance/README.md @@ -235,9 +235,12 @@ To author a new curve, follow the `vesting_wallet_linear` pattern: the witness-gated `consume_receipt` is what lets the curve run teardown logic or veto. The curve **must be monotonically non-decreasing in time and bounded above by -`balance + released`.** A curve that violates either makes `release` abort before any -state changes - funds stay safe, but the release path is bricked until the curve is -fixed. +`balance + released`** - the curve module's responsibility, not something the primitive +enforces. `release` aborts (before any state change) only when the attested cumulative +dips below what is already released (`EVestedBelowReleased`) or exceeds `balance + +released` (`EInsufficientBalance`); an in-range regression does not abort, it just pays +the smaller increment. Funds stay safe either way (no over-release, no clawback), but a +careless curve can under-pay or brick the release path. ### The `VestedAmount` attestation diff --git a/contracts/finance/sources/vesting_wallet.move b/contracts/finance/sources/vesting_wallet.move index bd057ba4..3ed619ab 100644 --- a/contracts/finance/sources/vesting_wallet.move +++ b/contracts/finance/sources/vesting_wallet.move @@ -64,9 +64,12 @@ /// beneficiary and parameters for the curve module to destructure. /// /// The curve must be monotonically non-decreasing in time and bounded above by -/// `balance + released`; violating either makes `release` abort before any state -/// mutation (funds stay safe, but the release path is bricked until the curve is -/// fixed). +/// `balance + released` - the curve module's responsibility, not something the primitive +/// enforces. `release` aborts (before any state mutation) only when the attested +/// cumulative dips below what is already released (`EVestedBelowReleased`) or exceeds +/// `balance + released` (`EInsufficientBalance`); an in-range regression does not abort, +/// it just pays the smaller increment. Funds stay safe either way (no over-release, no +/// clawback), but a careless curve can under-pay or brick the release path. /// /// # Topologies /// diff --git a/contracts/finance/sources/vesting_wallet_linear.move b/contracts/finance/sources/vesting_wallet_linear.move index ddc18e40..969abc49 100644 --- a/contracts/finance/sources/vesting_wallet_linear.move +++ b/contracts/finance/sources/vesting_wallet_linear.move @@ -43,7 +43,7 @@ /// catch-up, then resumes its regular cadence. /// - Mid-schedule: a staircase. With `k` full periods elapsed /// (`k = (now - start_ms) / period_ms`, `0 <= k < steps`), the cumulative vested -/// total is `total * k / steps`, computed with a u128 intermediate. The value is +/// total is `total * k / steps`, computed with a u256 intermediate. The value is /// flat across a period and steps up at each boundary. /// - Post-end: clamped to the wallet's total (`balance + released`). /// diff --git a/contracts/finance/tests/vesting_wallet_linear_tests.move b/contracts/finance/tests/vesting_wallet_linear_tests.move index 9b2d8860..a63489d7 100644 --- a/contracts/finance/tests/vesting_wallet_linear_tests.move +++ b/contracts/finance/tests/vesting_wallet_linear_tests.move @@ -376,10 +376,10 @@ fun vested_amount_is_nondecreasing_in_time() { test.end(); } -// The curve math uses a u128 intermediate, so the worst case +// The curve math uses a u256 intermediate, so the worst case // (total = u64::MAX, last step before the end) does not overflow and fits in u64. #[test] -fun vested_amount_uses_u128_intermediate_at_max() { +fun vested_amount_uses_u256_intermediate_at_max() { let (mut test, mut clk) = setup(0); let max = std::u64::max_value!(); @@ -828,10 +828,10 @@ fun vested_amount_continuous_is_linear_mid_schedule() { test.end(); } -// The curve math uses a u128 intermediate, so the worst case +// The curve math uses a u256 intermediate, so the worst case // (total = duration = u64::MAX) does not overflow and the final value fits in u64. #[test] -fun vested_amount_continuous_uses_u128_intermediate_at_max() { +fun vested_amount_continuous_uses_u256_intermediate_at_max() { let (mut test, mut clk) = setup(0); let max = std::u64::max_value!();