|
1 | 1 | #![no_std] |
2 | 2 |
|
3 | | -use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, String}; |
| 3 | +use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, String}; |
4 | 4 |
|
5 | 5 | #[contracttype] |
6 | 6 | #[derive(Clone, Debug, PartialEq)] |
@@ -265,6 +265,33 @@ impl AgenticPayContract { |
265 | 265 | .get(&DataKey::ProjectCount) |
266 | 266 | .unwrap_or(0) |
267 | 267 | } |
| 268 | + |
| 269 | + /// Upgrade the contract WASM code. Admin-only. |
| 270 | + /// |
| 271 | + /// Uses Soroban's built-in upgrade mechanism which replaces the contract |
| 272 | + /// bytecode while preserving all persistent and instance storage. This |
| 273 | + /// allows the contract to be upgraded without redeploying or migrating data. |
| 274 | + /// |
| 275 | + /// # Arguments |
| 276 | + /// * `admin` - Must match the stored admin address |
| 277 | + /// * `new_wasm_hash` - SHA-256 hash of the new WASM binary (uploaded via `soroban contract install`) |
| 278 | + pub fn upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) { |
| 279 | + admin.require_auth(); |
| 280 | + |
| 281 | + let stored_admin: Address = env |
| 282 | + .storage() |
| 283 | + .instance() |
| 284 | + .get(&DataKey::Admin) |
| 285 | + .expect("Not initialized"); |
| 286 | + assert!(admin == stored_admin, "Only admin can upgrade"); |
| 287 | + |
| 288 | + env.deployer().update_current_contract_wasm(new_wasm_hash); |
| 289 | + } |
| 290 | + |
| 291 | + /// Return the contract version for tracking upgrades. |
| 292 | + pub fn version(_env: Env) -> u32 { |
| 293 | + 1 |
| 294 | + } |
268 | 295 | } |
269 | 296 |
|
270 | 297 | #[cfg(test)] |
@@ -295,4 +322,32 @@ mod test { |
295 | 322 | assert_eq!(project.amount, 1000); |
296 | 323 | assert_eq!(project.status, ProjectStatus::Created); |
297 | 324 | } |
| 325 | + |
| 326 | + #[test] |
| 327 | + fn test_version_returns_current() { |
| 328 | + let env = Env::default(); |
| 329 | + let contract_id = env.register_contract(None, AgenticPayContract); |
| 330 | + let client = AgenticPayContractClient::new(&env, &contract_id); |
| 331 | + |
| 332 | + assert_eq!(client.version(), 1); |
| 333 | + } |
| 334 | + |
| 335 | + #[test] |
| 336 | + #[should_panic(expected = "Only admin can upgrade")] |
| 337 | + fn test_upgrade_rejects_non_admin() { |
| 338 | + let env = Env::default(); |
| 339 | + env.mock_all_auths(); |
| 340 | + |
| 341 | + let contract_id = env.register_contract(None, AgenticPayContract); |
| 342 | + let client = AgenticPayContractClient::new(&env, &contract_id); |
| 343 | + |
| 344 | + let admin = Address::generate(&env); |
| 345 | + let non_admin = Address::generate(&env); |
| 346 | + |
| 347 | + client.initialize(&admin); |
| 348 | + |
| 349 | + // Non-admin attempting upgrade should panic |
| 350 | + let fake_hash = BytesN::from_array(&env, &[0u8; 32]); |
| 351 | + client.upgrade(&non_admin, &fake_hash); |
| 352 | + } |
298 | 353 | } |
0 commit comments