From ccc782b3f4374e4f36b26a49f6d0a42e389fe5d6 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 5 Aug 2026 11:57:13 -0400 Subject: [PATCH 1/4] fix: bump admin-sep to 0.2.0 so release-plz can determine next version The repo has a v0.1.0 git tag and GitHub release, but admin-sep@0.1.0 was never published to crates.io. release-plz refuses to proceed on that mismatch. Bumping to 0.2.0 (correct anyway after the breaking soroban-sdk v27 update) lets release-plz treat it as a first release. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 2 +- admin_sep/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a3f756..5e8eba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "admin-sep" -version = "0.1.0" +version = "0.2.0" dependencies = [ "soroban-sdk", ] diff --git a/admin_sep/Cargo.toml b/admin_sep/Cargo.toml index 21f772c..e55ba70 100644 --- a/admin_sep/Cargo.toml +++ b/admin_sep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "admin-sep" -version = "0.1.0" +version = "0.2.0" edition = "2024" publish = true rust-version = "1.91.0" From d91c02cf22347ba48f6b442436cff0684dc67dcf Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 5 Aug 2026 13:29:48 -0400 Subject: [PATCH 2/4] fix: bump admin-sep to 0.27.0 to match soroban-sdk major version Co-Authored-By: Claude Fable 5 --- Cargo.lock | 2 +- admin_sep/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e8eba5..2dca0ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "admin-sep" -version = "0.2.0" +version = "0.27.0" dependencies = [ "soroban-sdk", ] diff --git a/admin_sep/Cargo.toml b/admin_sep/Cargo.toml index e55ba70..bfc1ec4 100644 --- a/admin_sep/Cargo.toml +++ b/admin_sep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "admin-sep" -version = "0.2.0" +version = "0.27.0" edition = "2024" publish = true rust-version = "1.91.0" From 47be631235560557125bd02e0becbadeff9d92dd Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 17 Aug 2026 10:51:42 -0400 Subject: [PATCH 3/4] fix: defined panic instead of UB when admin was never set; flux gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Administratable::admin() used unsafe unwrap_unchecked on the storage read — if no admin was ever set (constructor never called set_admin), that is undefined behavior in wasm and could hand back a garbage Address for require_admin to authorize against. Replace with .expect("admin-sep: admin not set"): a defined, loud panic. Also enable Flux refinement checking (metadata-only, no dependency — inert for normal builds and crates.io): cargo flux -p admin-sep checks 19/19 functions including the contracttrait-generated code. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01M52tZC7sppbbo1HDBpj9KK --- admin_sep/Cargo.toml | 6 ++++++ admin_sep/src/administratable.rs | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/admin_sep/Cargo.toml b/admin_sep/Cargo.toml index bfc1ec4..f23f92e 100644 --- a/admin_sep/Cargo.toml +++ b/admin_sep/Cargo.toml @@ -20,3 +20,9 @@ soroban-sdk = { workspace = true } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } + +# Verified by the Flux refinement checker (github.com/flux-rs/flux) via +# `cargo flux -p admin-sep` — metadata only, no dependency; inert for +# normal builds and for crates.io. +[package.metadata.flux] +enabled = true diff --git a/admin_sep/src/administratable.rs b/admin_sep/src/administratable.rs index e4f0b0a..2e17f78 100644 --- a/admin_sep/src/administratable.rs +++ b/admin_sep/src/administratable.rs @@ -4,7 +4,12 @@ use soroban_sdk::{Address, Env, Symbol, contracttrait, symbol_short}; #[contracttrait] pub trait Administratable { fn admin(env: &Env) -> soroban_sdk::Address { - unsafe { admin_from_storage(env).unwrap_unchecked() } + // A defined panic, not `unwrap_unchecked`: if no admin was ever set + // (constructor never ran `set_admin`), the old unsafe path was + // undefined behavior in wasm — it could hand back a garbage Address + // that `require_admin` would then happily `require_auth` against. + // Fail loudly instead. + admin_from_storage(env).expect("admin-sep: admin not set") } fn set_admin(env: &Env, new_admin: soroban_sdk::Address) { From e21a4417abf39e0b93f749d893f81bb1206274f2 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 17 Aug 2026 12:34:37 -0400 Subject: [PATCH 4/4] revert to unwrap_unchecked; document the constructor-sets-admin invariant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: set_admin runs in every consuming contract's constructor, so the storage entry exists before any admin() read — the unsafe read is sound in intended use. Keep it, and encode the requirement as a trait-level safety invariant + SAFETY comment instead of a runtime check, so future consumers know what they must uphold. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01M52tZC7sppbbo1HDBpj9KK --- admin_sep/src/administratable.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/admin_sep/src/administratable.rs b/admin_sep/src/administratable.rs index 2e17f78..9287455 100644 --- a/admin_sep/src/administratable.rs +++ b/admin_sep/src/administratable.rs @@ -1,15 +1,22 @@ use soroban_sdk::{Address, Env, Symbol, contracttrait, symbol_short}; /// Trait for using an admin address to control access. +/// +/// # Safety invariant +/// +/// The default [`Self::admin`] assumes the admin storage entry exists. +/// Consuming contracts uphold this by calling [`Self::set_admin`] from their +/// constructor (its first call skips the auth check for exactly this +/// purpose), so the entry is written before any entry point can read it. +/// A contract that adopts this trait without constructor wiring makes +/// `admin()` undefined behavior. #[contracttrait] pub trait Administratable { fn admin(env: &Env) -> soroban_sdk::Address { - // A defined panic, not `unwrap_unchecked`: if no admin was ever set - // (constructor never ran `set_admin`), the old unsafe path was - // undefined behavior in wasm — it could hand back a garbage Address - // that `require_admin` would then happily `require_auth` against. - // Fail loudly instead. - admin_from_storage(env).expect("admin-sep: admin not set") + // SAFETY: every consuming contract stores the admin in its + // constructor (see the trait-level safety invariant), so the entry + // exists before any read. + unsafe { admin_from_storage(env).unwrap_unchecked() } } fn set_admin(env: &Env, new_admin: soroban_sdk::Address) {