From 19569257cc0f660e878f15832fa996cb65a9acee Mon Sep 17 00:00:00 2001 From: k2ghostyou Date: Tue, 28 Jul 2026 17:30:42 +0000 Subject: [PATCH] feat(#477): add one-shot initialiser guard to initialize() - Add AlreadyInitialised = 33 to ContractError in error.rs - Add initialised_key() returning symbol_short!("init_flg") in lib.rs - Replace the admin_key existence check with a dedicated Initialised boolean stored under initialised_key() in instance storage - Guard panics with "AlreadyInitialised" if the flag is already set, preventing front-run hijacking of the admin address - Set the flag atomically at the end of initialize() so the contract is only marked fully initialised after all state has been written - Add initialised_key to storage_snapshot.rs and update the JSON baseline Closes #477 --- contracts/split/src/error.rs | 2 ++ contracts/split/src/lib.rs | 19 +++++++++++++++---- contracts/split/src/storage_snapshot.rs | 2 ++ tests/snapshots/storage_keys.json | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/contracts/split/src/error.rs b/contracts/split/src/error.rs index b66d59e..958cc7e 100644 --- a/contracts/split/src/error.rs +++ b/contracts/split/src/error.rs @@ -53,4 +53,6 @@ pub enum ContractError { MemoMismatch = 31, /// Issue #439: Creator is in cooldown after cancelling an invoice. CreatorCooldownActive = 31, + /// Issue #477: initialize() has already been called; contract is fully initialised. + AlreadyInitialised = 33, } diff --git a/contracts/split/src/lib.rs b/contracts/split/src/lib.rs index 025b63a..ae499ef 100644 --- a/contracts/split/src/lib.rs +++ b/contracts/split/src/lib.rs @@ -79,6 +79,13 @@ fn admin_key() -> Symbol { fn admins_key() -> Symbol { symbol_short!("admins") } + +/// Issue #477: One-shot initialiser guard — instance storage. +/// Set to `true` at the end of `initialize()`; checked at the top to prevent +/// re-initialisation (front-run protection). +fn initialised_key() -> Symbol { + symbol_short!("init_flg") +} fn paused_key() -> Symbol { symbol_short!("paused") } @@ -1973,10 +1980,11 @@ impl SplitContract { rate_limit: u32, rate_window: u64, ) { - assert!( - !env.storage().instance().has(&admin_key()), - "already initialized" - ); + // Issue #477: one-shot initialiser guard using a dedicated key so the + // guard cannot be bypassed by front-running the admin write. + if env.storage().instance().get::<_, bool>(&initialised_key()).unwrap_or(false) { + panic!("AlreadyInitialised"); + } assert!(creation_fee >= 0, "creation_fee must be non-negative"); assert!( platform_fee_bps <= 10_000, @@ -2019,6 +2027,9 @@ impl SplitContract { env.storage() .instance() .set(&storage_quota_key(), &DEFAULT_INVOICE_STORAGE_QUOTA); + // Issue #477: set the initialisation flag atomically at the end so the + // contract is marked fully initialised only after all state is written. + env.storage().instance().set(&initialised_key(), &true); } /// Add a new admin with a given role. Requires SuperAdmin auth. diff --git a/contracts/split/src/storage_snapshot.rs b/contracts/split/src/storage_snapshot.rs index 7005d39..0bae294 100644 --- a/contracts/split/src/storage_snapshot.rs +++ b/contracts/split/src/storage_snapshot.rs @@ -125,6 +125,8 @@ fn storage_key_snapshot() { "upgrade_proposal_key", hex_xdr(&env, upgrade_proposal_key()), )); + // Issue #477: one-shot initialiser guard — instance storage + keys.push(("initialised_key", hex_xdr(&env, initialised_key()))); // ----------------------------------------------------------------------- // Persistent-tier keys (per-entity) diff --git a/tests/snapshots/storage_keys.json b/tests/snapshots/storage_keys.json index 46ae79a..5803213 100644 --- a/tests/snapshots/storage_keys.json +++ b/tests/snapshots/storage_keys.json @@ -48,6 +48,7 @@ "governance_contract_key": "0000000f00000007676f765f63747200", "group_key": "0000001000000001000000020000000f0000000367727000000000050000000000000001", "group_treasury_key": "0000001000000001000000020000000f000000066772705f74720000000000050000000000000001", + "initialised_key": "0000000f00000008696e69745f666c67", "invoice_compact_key": "0000001000000001000000020000000f00000007696e765f63707400000000050000000000000001", "invoice_count_key": "0000001000000001000000020000000f00000009696e765f636f756e740000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000", "invoice_ext2_key": "0000001000000001000000020000000f00000007696e765f65783200000000050000000000000001",