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
4 changes: 4 additions & 0 deletions contracts/streaming/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,10 @@ impl StreamingContract {
// ── Write: Admin / Upgrade ───────────────────────────────────────────────

/// Upgrade the contract wasm. Only callable by the admin.
///
/// # Errors
/// - [`StreamError::NotInitialized`] — contract has not been initialized yet.
/// - [`StreamError::Unauthorized`] — caller does not match the stored admin.
pub fn upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) -> Result<(), StreamError> {
admin.require_auth();
let stored_admin: Address = env
Expand Down
22 changes: 22 additions & 0 deletions contracts/streaming/src/test_security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,28 @@ fn test_auth_non_admin_cannot_upgrade() {
ctx.client().upgrade(&ctx.attacker, &fake_hash);
}

/// Non-admin cannot call migrate.
///
/// Regression test for the bug fixed in commit 7fe0b2e where `migrate()` did
/// not check authorization at all. Presenting attacker auth for `migrate`
/// must be rejected — `require_auth()` inside `migrate` expects the stored
/// admin, so impersonating a non-admin address must panic.
#[test]
#[should_panic]
fn test_auth_non_admin_cannot_migrate() {
let ctx = Ctx::new();
ctx.env.mock_auths(&[MockAuth {
address: &ctx.attacker,
invoke: &MockAuthInvoke {
contract: &ctx.contract_id,
fn_name: "migrate",
args: ().into_val(&ctx.env),
sub_invokes: &[],
},
}]);
ctx.client().migrate();
}

/// Sender cannot withdraw from their own outgoing stream.
#[test]
#[should_panic]
Expand Down
Loading