Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub fn upgrade(env: Env, caller: Address, new_wasm_hash: BytesN<32>)
**Version Function Signature:**

```rust
pub fn version(env: Env) -> Option<BytesN<32>>
pub fn get_version(env: Env) -> Option<BytesN<32>>
```

**Behavior note (pause semantics):**
Expand All @@ -135,6 +135,7 @@ pub fn version(env: Env) -> Option<BytesN<32>>
|-----|------|-------------|
| `admin` | `Address` | Admin address; may call `distribute` and `set_admin` |
| `usdc` | `Address` | USDC token contract address |
| `ContractVersion` | `BytesN<32>` | WASM hash set by `upgrade` function |

#### Upgradeability

Expand All @@ -151,7 +152,7 @@ soroban contract invoke --contract-id <REVENUE_POOL_ID> -- upgrade \
--caller <ADMIN> --new_wasm_hash <32-byte-hex>
```

The `version()` view returns the stored WASM hash; the contract emits an `upgraded`
The `get_version()` view returns the stored WASM hash; the contract emits an `upgraded`
event with the admin as a topic and the new version as data.

**Init signature** (`lib.rs:28-39`):
Expand All @@ -168,6 +169,25 @@ pub fn init(env: Env, admin: Address, usdc_token: Address)
| `vault` | `Address` | Registered vault address |
| `developer_balances` | `Map<Address, i128>` | Per-developer balance tracking |
| `global_pool` | `GlobalPool` | Total balance and last updated timestamp |
| `ContractVersion` | `BytesN<32>` | WASM hash set by `upgrade` function |

#### Upgradeability

The settlement contract supports in-place upgrades via an admin-gated `upgrade` function.
This method calls the host deployer to update the contract WASM code while
preserving existing instance storage.

```bash
# Build new WASM
cargo build --target wasm32-unknown-unknown --release -p callora-settlement

# Compute WASM hash and call upgrade via RPC or tooling
soroban contract invoke --contract-id <SETTLEMENT_ID> -- upgrade \
--caller <ADMIN> --new_wasm_hash <32-byte-hex>
```

The `get_version()` view returns the stored WASM hash (as an `Option<BytesN<32>>`); the contract emits an `upgraded`
event with the admin as a topic and the new version as data.

**GlobalPool structure** (`lib.rs:16-19`):

Expand Down Expand Up @@ -236,7 +256,7 @@ The vault now supports admin-gated in-place upgrades that preserve all existing
4. **Verify upgrade**
```bash
# Check version marker
soroban contract invoke --contract-id <VAULT_ID> -- version
soroban contract invoke --contract-id <VAULT_ID> -- get_version
# Should return <NEW_WASM_HASH>

# Verify state preserved
Expand Down Expand Up @@ -264,7 +284,7 @@ The `upgrade` function emits an `upgraded` event with:
- Data: New WASM hash (BytesN<32>)

**Version Tracking:**
- Call `version()` to retrieve the current WASM hash
- Call `get_version()` to retrieve the current WASM hash
- Returns `None` for contracts deployed before upgrade functionality
- Returns `Some(BytesN<32>)` after first upgrade

Expand Down
5 changes: 2 additions & 3 deletions contracts/revenue_pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,12 +582,11 @@ impl RevenuePool {

/// Read the stored contract version (WASM hash) as last set by `upgrade`.
///
/// Panics if no version has been stored yet.
pub fn version(env: Env) -> BytesN<32> {
/// Returns `None` if no version has been stored yet.
pub fn get_version(env: Env) -> Option<BytesN<32>> {
env.storage()
.instance()
.get(&Symbol::new(&env, VERSION_KEY))
.expect("version not set")
}
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/revenue_pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1883,8 +1883,8 @@ fn upgrade_sets_version_and_emits_event() {
client.upgrade(&admin, &new_hash);

// version() should return stored value
let readback: BytesN<32> = client.version();
assert_eq!(readback, new_hash);
let readback: Option<BytesN<32>> = client.get_version();
assert_eq!(readback, Some(new_hash));

// An `upgraded` event should have been emitted
let events = env.events().all();
Expand Down
37 changes: 36 additions & 1 deletion contracts/settlement/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![no_std]

use soroban_sdk::{contract, contracterror, contractimpl, contracttype, token, Address, Env, Symbol, Vec};
use soroban_sdk::{contract, contracterror, contractimpl, contracttype, token, Address, BytesN, Env, Symbol, Vec};

/// Maximum number of items allowed in a single `batch_receive_payment` call.
pub const MAX_BATCH_SIZE: u32 = 50;
Expand Down Expand Up @@ -58,6 +58,7 @@ pub enum StorageKey {
DeveloperBalance(Address),
GlobalPool,
Usdc,
ContractVersion,
}

/// Developer balance record in settlement contract
Expand Down Expand Up @@ -791,6 +792,40 @@ impl CalloraSettlement {
env.panic_with_error(SettlementError::Unauthorized);
}
}

/// Admin-gated contract upgrade.
///
/// Only the current admin may call. This will instruct the host to update
/// the current contract WASM to `new_wasm_hash` and persist the version marker.
/// Emits an `upgraded` event with the admin as topic and the new version as data.
pub fn upgrade(env: Env, caller: Address, new_wasm_hash: BytesN<32>) {
caller.require_auth();
let admin = Self::get_admin(env.clone());
if caller != admin {
env.panic_with_error(SettlementError::Unauthorized);
}

// Perform the on-chain upgrade via the deployer interface.
env.deployer().update_current_contract_wasm(new_wasm_hash.clone());

// Persist the version marker for on-chain queries.
env.storage()
.instance()
.set(&StorageKey::ContractVersion, &new_wasm_hash);

// Emit an event for indexers / audit logs.
env.events()
.publish((Symbol::new(&env, "upgraded"), admin), new_wasm_hash);
}

/// Read the stored contract version (WASM hash) as last set by `upgrade`.
///
/// Returns `None` if no upgrade has been performed yet (initial deployment).
pub fn get_version(env: Env) -> Option<BytesN<32>> {
env.storage()
.instance()
.get(&StorageKey::ContractVersion)
}
}

#[cfg(test)]
Expand Down
17 changes: 17 additions & 0 deletions contracts/settlement/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1679,5 +1679,22 @@ mod settlement_tests {
pool.total_balance + sum_dev_balances,
"Conservation invariant violated: total credits must equal pool + developer balances"
);
#[test]
fn test_upgrade_and_get_version() {
let (env, addr, admin, _vault, _third_party) = setup_contract();
let client = CalloraSettlementClient::new(&env, &addr);

assert_eq!(client.get_version(), None);

let new_hash = BytesN::from_array(&env, &[1u8; 32]);
client.upgrade(&admin, &new_hash);

assert_eq!(client.get_version(), Some(new_hash.clone()));

// An `upgraded` event should have been emitted
let events = env.events().all();
let ev = events.last().unwrap();
let name = soroban_sdk::Symbol::try_from_val(&env, &ev.1.get(0).unwrap()).unwrap();
assert_eq!(name, soroban_sdk::Symbol::new(&env, "upgraded"));
}
}
2 changes: 1 addition & 1 deletion contracts/vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ impl CalloraVault {
/// Read the stored contract version (WASM hash) as last set by `upgrade`.
///
/// Returns `None` if no upgrade has been performed yet (initial deployment).
pub fn version(env: Env) -> Option<BytesN<32>> {
pub fn get_version(env: Env) -> Option<BytesN<32>> {
env.storage()
.instance()
.get(&StorageKey::ContractVersion)
Expand Down
14 changes: 7 additions & 7 deletions contracts/vault/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5880,14 +5880,14 @@ fn upgrade_sets_version_and_emits_event() {
client.init(&owner, &usdc, &Some(100), &None, &None, &None, &None);

// Version should be None before any upgrade
assert_eq!(client.version(), None);
assert_eq!(client.get_version(), None);

let new_hash = BytesN::from_array(&env, &[2u8; 32]);

client.upgrade(&owner, &new_hash);

// version() should return stored value
let readback = client.version();
let readback = client.get_version();
assert_eq!(readback, Some(new_hash.clone()));

// An `upgraded` event should have been emitted
Expand Down Expand Up @@ -5927,7 +5927,7 @@ fn upgrade_non_owner_admin_succeeds() {
// new_admin should be able to upgrade
client.upgrade(&new_admin, &new_hash);

let readback = client.version();
let readback = client.get_version();
assert_eq!(readback, Some(new_hash));
}

Expand Down Expand Up @@ -5966,7 +5966,7 @@ fn version_returns_none_before_first_upgrade() {
fund_vault(&usdc_admin, &vault_address, 100);
client.init(&owner, &usdc, &Some(100), &None, &None, &None, &None);

assert_eq!(client.version(), None);
assert_eq!(client.get_version(), None);
}

#[test]
Expand All @@ -5982,15 +5982,15 @@ fn upgrade_multiple_times_updates_version() {

let hash1 = BytesN::from_array(&env, &[5u8; 32]);
client.upgrade(&owner, &hash1);
assert_eq!(client.version(), Some(hash1.clone()));
assert_eq!(client.get_version(), Some(hash1.clone()));

let hash2 = BytesN::from_array(&env, &[6u8; 32]);
client.upgrade(&owner, &hash2);
assert_eq!(client.version(), Some(hash2.clone()));
assert_eq!(client.get_version(), Some(hash2.clone()));

let hash3 = BytesN::from_array(&env, &[7u8; 32]);
client.upgrade(&owner, &hash3);
assert_eq!(client.version(), Some(hash3));
assert_eq!(client.get_version(), Some(hash3));
}

// ---------------------------------------------------------------------------
Expand Down
Loading