|
1 | 1 | #![no_std] |
2 | 2 |
|
3 | | -use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, String, Vec}; |
| 3 | +use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, String, Vec}; |
4 | 4 |
|
5 | 5 | #[contracttype] |
6 | 6 | #[derive(Clone, Debug, PartialEq)] |
@@ -394,6 +394,33 @@ impl AgenticPayContract { |
394 | 394 | .get(&DataKey::ProjectCount) |
395 | 395 | .unwrap_or(0) |
396 | 396 | } |
| 397 | + |
| 398 | + /// Upgrade the contract WASM code. Admin-only. |
| 399 | + /// |
| 400 | + /// Uses Soroban's built-in upgrade mechanism which replaces the contract |
| 401 | + /// bytecode while preserving all persistent and instance storage. This |
| 402 | + /// allows the contract to be upgraded without redeploying or migrating data. |
| 403 | + /// |
| 404 | + /// # Arguments |
| 405 | + /// * `admin` - Must match the stored admin address |
| 406 | + /// * `new_wasm_hash` - SHA-256 hash of the new WASM binary (uploaded via `soroban contract install`) |
| 407 | + pub fn upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) { |
| 408 | + admin.require_auth(); |
| 409 | + |
| 410 | + let stored_admin: Address = env |
| 411 | + .storage() |
| 412 | + .instance() |
| 413 | + .get(&DataKey::Admin) |
| 414 | + .expect("Not initialized"); |
| 415 | + assert!(admin == stored_admin, "Only admin can upgrade"); |
| 416 | + |
| 417 | + env.deployer().update_current_contract_wasm(new_wasm_hash); |
| 418 | + } |
| 419 | + |
| 420 | + /// Return the contract version for tracking upgrades. |
| 421 | + pub fn version(_env: Env) -> u32 { |
| 422 | + 1 |
| 423 | + } |
397 | 424 | } |
398 | 425 |
|
399 | 426 | #[cfg(test)] |
@@ -689,4 +716,32 @@ mod test { |
689 | 716 | assert_eq!(id, 3); |
690 | 717 | assert_eq!(client.get_project_count(), 3); |
691 | 718 | } |
| 719 | + |
| 720 | + #[test] |
| 721 | + fn test_version_returns_current() { |
| 722 | + let env = Env::default(); |
| 723 | + let contract_id = env.register_contract(None, AgenticPayContract); |
| 724 | + let client = AgenticPayContractClient::new(&env, &contract_id); |
| 725 | + |
| 726 | + assert_eq!(client.version(), 1); |
| 727 | + } |
| 728 | + |
| 729 | + #[test] |
| 730 | + #[should_panic(expected = "Only admin can upgrade")] |
| 731 | + fn test_upgrade_rejects_non_admin() { |
| 732 | + let env = Env::default(); |
| 733 | + env.mock_all_auths(); |
| 734 | + |
| 735 | + let contract_id = env.register_contract(None, AgenticPayContract); |
| 736 | + let client = AgenticPayContractClient::new(&env, &contract_id); |
| 737 | + |
| 738 | + let admin = Address::generate(&env); |
| 739 | + let non_admin = Address::generate(&env); |
| 740 | + |
| 741 | + client.initialize(&admin); |
| 742 | + |
| 743 | + // Non-admin attempting upgrade should panic |
| 744 | + let fake_hash = BytesN::from_array(&env, &[0u8; 32]); |
| 745 | + client.upgrade(&non_admin, &fake_hash); |
| 746 | + } |
692 | 747 | } |
0 commit comments