Repository navigation
Timelocked upgrade pattern for all three contracts - #63
Merged
codemagician1949 merged 1 commit intoAug 21, 2026
Merged
codemagician1949 merged 1 commit into
codemagician1949 merged 1 commit into
Conversation
CelestinaBeing
force-pushed
the
feat/upgrade-timelock-pattern-32
branch
from
August 19, 2026 15:08
c826028 to
a4cb559
Compare
Contributor
Author
|
Thanks for taking a swing at the conflict here @codemagician1949! I'd already rebased locally with a fix, and noticed the merge commit didn't quite resolve it cleanly — |
Adds a proxy-upgradability-equivalent pattern to pool-manager, invoice-registry, and repayment-waterfall: queue_upgrade / execute_upgrade / cancel_upgrade / set_upgrade_paused, each admin- gated (pool-manager reuses its existing require_timelock_admin/ Address+require_auth mechanism; invoice-registry and repayment-waterfall use their existing Symbol-based admin check). Design rationale (documented in each contract's own doc comments): Soroban contracts are natively upgradable in place via env.deployer().update_current_contract_wasm(hash), and persistent/ instance storage is automatically preserved across that call - storage lives at the contract's address, not in a separate delegatecall-style proxy. So the idiomatic Soroban equivalent of "proxy holds storage, logic contract swappable" is a same-contract Wasm-hash swap, not a separate proxy contract forwarding calls - that's what's implemented here, not a shortcut. "Storage layout migration" under this model means the next version's storage reads must stay backward-compatible with what the prior version wrote, since there's no separate proxy storage to explicitly migrate. Per contract: - queue_upgrade(new_wasm_hash) -> execute_after timestamp, gated by the same admin mechanism as that contract's existing timelocked actions (pool-manager: 48h, matching TIMELOCK_SECS; the other two: new UPGRADE_TIMELOCK_SECS = 48h, since neither had an existing timelock constant to reuse). - execute_upgrade() - callable by anyone once the timelock elapses; errors if nothing's queued, the timelock hasn't elapsed, or upgrades are emergency-paused. - cancel_upgrade() - admin-gated, clears the queue. - set_upgrade_paused(bool) - admin-gated; while paused, execute_upgrade is blocked (queueing/cancelling still work). Added as a separate #[contractimpl] impl block per contract (Rust allows multiple impl blocks per type) so this doesn't touch each contract's existing entrypoints region. Tests (mirrored across all three) cover: queue requires admin, queue sets the correct 48h-out execute_after, execute before the timelock elapses errors, execute with nothing queued errors, cancel clears the queue, and execute while paused errors even after the timelock has elapsed. Actually driving execute_upgrade to a successful Wasm swap needs a real uploaded Wasm hash to target, which the existing native-registered test fixtures in this workspace don't produce, so that specific path relies on Soroban's documented storage-persistence guarantee rather than being re-proven per PR (noted in the test module's doc comment). Closes IntensifierLab#32
CelestinaBeing
force-pushed
the
feat/upgrade-timelock-pattern-32
branch
from
August 19, 2026 19:06
a4cb559 to
536970f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds a proxy-upgradability-equivalent pattern to pool-manager, invoice-registry, and repayment-waterfall:
queue_upgrade/execute_upgrade/cancel_upgrade/set_upgrade_paused, admin-gated (pool-manager reuses its existingrequire_timelock_admin/Address+require_auth()mechanism; the other two use their existingSymbol-based admin check).Design rationale (documented in each contract's own doc comments)
Soroban contracts are natively upgradable in place via
env.deployer().update_current_contract_wasm(hash), and persistent/instance storage is automatically preserved across that call — storage lives at the contract's address, not in a separate delegatecall-style proxy. So the idiomatic Soroban equivalent of "proxy holds storage, logic contract swappable" is a same-contract Wasm-hash swap, not a separate proxy contract forwarding calls — that's what's implemented here, not a shortcut. "Storage layout migration" under this model means the next version's storage reads must stay backward-compatible with what the prior version wrote, since there's no separate proxy storage to explicitly migrate.API (per contract)
queue_upgrade(new_wasm_hash) -> execute_after— gated by that contract's existing admin mechanism; 48h delay.execute_upgrade()— callable by anyone once the timelock elapses; errors if nothing's queued, the timelock hasn't elapsed, or upgrades are emergency-paused.cancel_upgrade()— admin-gated, clears the queue.set_upgrade_paused(bool)— admin-gated; while paused,execute_upgradeis blocked (queueing/cancelling still work).Each added as a separate
#[contractimpl]block per contract, so this doesn't touch any contract's existing entrypoints region — keeps it independent of the other invoice-registry PRs in flight (#60 multisig, #62 NFT tokenisation).Tests
Mirrored across all three contracts: queue requires admin, queue sets the correct 48h-out
execute_after, execute before the timelock elapses errors, execute with nothing queued errors, cancel clears the queue, execute while paused errors even after the timelock has elapsed.Actually driving
execute_upgradeto a successful Wasm swap needs a real uploaded Wasm hash to target, which this workspace's native-registered test fixtures don't produce — that specific path relies on Soroban's documented storage-persistence guarantee rather than being re-proven per PR (noted in the test module's doc comment, honestly scoped rather than faked).Verification
cargo check --workspace— clean.cargo test -p pool-manager— 29/29.cargo test -p invoice-registry— 28/28.cargo test -p repayment-waterfall— 13/13.cargo test --workspace— full clean run, 0 failures.Closes #32