Skip to content

Put revenue_pool upgrade behind a propose/execute/cancel timelock with real storage-layout validation #1402

Description

@greatest0fallt1me

Summary

RevenuePool::upgrade replaces the contract's WASM immediately, in the same call the admin signs. There is no delay, no proposal visible on-chain, and no way to cancel. The storage-migration gate it runs passes zero_layout_hash as both the source and target layout, so it checks nothing beyond a non-zero hash and the version order. Vault and settlement both have a timelocked upgrade flow. The revenue pool, which holds pooled USDC on testnet, has none.

Background and evidence

  • Immediate upgrade. contracts/revenue_pool/src/lib.rs, upgrade (lines 869–912): caller.require_auth(), the emergency-pause check and require_admin, then update_current_contract_wasm(new_wasm_hash) in the same call.
  • No real layout check. let placeholder_layout = callora_storage_migration::zero_layout_hash(&env); is passed to both validate_before_upgrade and finalize_migration, and finalize_migration runs in the old code before the swap.
  • The docs promise more. docs/UPGRADE.md describes timelocked upgrades, and contracts/settlement/src/timelock.rs has UPGRADE_TIMELOCK_SECONDS = 172_800.
  • A pattern already exists in this crate. The pool already has a timelocked flow for a different action (propose_emergency_drain, execute_emergency_drain and cancel_emergency_drain, lines 1019–1150). That flow is the pattern to follow.

Scope

In scope

  1. New entrypoints.
    • propose_upgrade(caller, new_wasm_hash): rejects a zero hash; stores PendingUpgrade { wasm_hash, proposed_at, execute_after, expires_at }; emits upgrade_proposed.
    • execute_upgrade(caller): requires execute_after <= now <= expires_at.
    • cancel_upgrade(caller).
    • get_pending_upgrade().
  2. Deprecate the old entrypoint. upgrade becomes an alias for propose_upgrade, as settlement does, so existing tooling does not upgrade instantly by accident.
  3. Real layout validation.
    • Replace the zero layout with callora_storage_migration::layout_hash(&env, <schema descriptor>) for the current layout.
    • Record it with record_deployed_schema if it is absent.
    • Move finalize_migration to a post-swap migrate entrypoint that the new code calls.
  4. Pause policy. Execution is blocked while emergency-paused, but cancellation is still allowed.
  5. Docs. Update docs/UPGRADE.md and docs/EVENT_SCHEMA.md.

Out of scope

  • Changing StorageMigrationValidator itself (a separate issue).
  • Settlement and vault upgrade flows.

Design constraints

  • Admin only. Only the admin may call these, with require_auth first.
  • Checked arithmetic for deadlines; overflow returns a typed error.
  • Typed errors. No string panics in new code. Use RevenuePoolError with appended codes.
  • Size: the WASM size delta must be at most 3%. Report it.

Test plan

# Scenario Expected
1 Propose then execute before execute_after timelock error
2 Execute inside the window with an uploaded wasm upgraded; version stored; proposal cleared
3 Execute after expires_at expired error
4 Cancel, then execute no pending error
5 Non-admin propose, execute or cancel Unauthorized
6 Zero hash rejected
7 Execute while emergency-paused rejected; cancel still works
8 Legacy upgrade() call only proposes; the WASM is unchanged
9 Layout mismatch fixture SchemaMismatch surfaced

Regression proof. On main, upgrade() swaps the WASM in a single call. Row 8 asserts it no longer does.

Acceptance criteria

  • All test plan rows pass (cargo test -p callora-revenue-pool upgrade).
  • cargo fmt --all -- --check and cargo test --workspace pass, and CI is green.
  • cargo build --release --target wasm32v1-none -p callora-revenue-pool succeeds, and the size delta is reported.
  • Docs are updated, including the migration note for the deployed pool CC5KINWRSLKSSQG4ZOTP3EZIOUVC6ER7HL5GZALYY4LVE6ZIMX4PMCSE.
  • Coverage of the new functions is at least 95%.

Getting started

git checkout -b feat/revenue-pool-upgrade-timelock
cargo test -p callora-revenue-pool

Model the code on propose_emergency_drain / execute_emergency_drain in the same file and on contracts/settlement/src/timelock.rs.

PR requirements

  • Link this issue (Closes #<n>).
  • Explain the upgrade order for the deployed contract.
  • Paste the test output and the size delta.
  • Example commit: feat(revenue-pool): timelocked propose/execute/cancel upgrade.

Priority rationale

High. A single admin signature can replace the code of a deployed pool that holds funds, with no warning. The fix spans governance, storage-migration validation and wasm upgrade testing.

Activity

  1. abiolatousin-design commented on Oct 18, 2026

    @abiolatousin-design

    @abiolatousin-design has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    After considering the purpose of this issue, I believe it would be a valuable task to contribute to. I’d welcome the chance to explore the best approach and implement a suitable fix.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @abiolatousin-design to this issue.

  2. drips-wave commented on Oct 19, 2026

    @drips-wave

    Congratulations, @abiolatousin-design! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @abiolatousin-design: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions