Summary
Add emergency pause for deposits/withdrawals under incident response — buffer_pool has no circuit-breaker switch, so a compromised carbon-asset contract or governance key cannot be contained without redeploying or abandoning the pool.
Social Media Link
Let's collaborate on Discord. And ensure to star our repo.
Problem Statement
Confirmed in stellar-core/carbon-asset-factory/contracts/buffer_pool/src/lib.rs, stellar-core/carbon-asset-factory/contracts/buffer_pool/src/storage.rs, and stellar-core/carbon-asset-factory/contracts/buffer_pool/src/errors.rs:
-
No pause flag exists in storage: storage.rs defines ADMIN, GOVERNANCE, CARBON_CONTRACT, REPLENISH_PCT, TVL, and CUSTODY symbols — there is no PAUSED key or equivalent boolean gate.
-
deposit() has no pause check: lib.rs:51-85 validates caller identity (caller != admin && caller != carbon_contract) and duplicate custody, but nothing would stop a deposit from proceeding during an active incident.
-
withdraw_to_replace() has no pause check: lib.rs:89-131 performs a cross-contract transfer call unconditionally once governance auth passes — there is no way to freeze this path while investigating a suspected exploit in the linked carbon_asset_contract.
-
auto_deposit() has no pause check: lib.rs:133-181 is invoked by the carbon_asset contract on every qualifying mint (per the modulo-based sampling logic) and cannot be temporarily disabled without changing set_replenishment_rate to 0, which the code explicitly rejects — set_replenishment_rate requires 1..=10000 (lib.rs:214-220) and initialize rejects initial_percentage == 0 (lib.rs:32-34), so there is no supported way to fully halt auto-deposits even temporarily.
-
No admin/governance-gated toggle function exists at all: the only state-mutating admin/governance functions are set_governance_address and set_replenishment_rate (lib.rs:183-225) — neither is designed as, or reusable as, an incident-response kill switch.
-
No Error variant for a paused-state rejection: whatever this contract's errors.rs defines (Error::Unauthorized, Error::AlreadyExists, Error::ZeroPercentage, Error::InvalidPercentage, Error::TokenNotFound, per usages seen in lib.rs), there is no Error::ContractPaused to reject calls cleanly during a freeze.
-
No event exists for a pause/unpause transition: events.rs provides emit_deposit_event, emit_withdraw_event, emit_auto_deposit_event, and emit_duplicate_auto_deposit_event, but nothing to signal an incident-response action to off-chain monitors.
-
get_total_value_locked, get_custody_record, and is_token_in_pool are read-only and unaffected, so a pause implementation must be careful to gate only state-mutating entry points (deposit, withdraw_to_replace, auto_deposit) and not the view functions.
Required Changes
-
Add a PAUSED: Symbol constant and get_paused(env) -> bool / set_paused(env, paused: &bool) storage helpers to storage.rs, defaulting to false when unset.
-
Add Error::ContractPaused to errors.rs.
-
Add pause(env, caller: Address) -> Result<(), Error> restricted to governance (matching the existing governance-gated pattern used by set_governance_address/set_replenishment_rate), setting PAUSED to true.
-
Add unpause(env, caller: Address) -> Result<(), Error> restricted to governance, setting PAUSED to false.
-
Add a pause check as the first guard in deposit(), withdraw_to_replace(), and auto_deposit(), returning Err(Error::ContractPaused) immediately when paused — before any auth or storage checks, to minimize wasted budget during an incident.
-
Add PauseEvent { paused_by: Address } and UnpauseEvent { unpaused_by: Address } to events.rs and publish them from the new functions.
-
Add is_paused(env) -> bool as a public view function.
-
Ensure auto_deposit's silent-false-return behavior for the zero-percentage case (lib.rs:150-152) is not confused with the new paused-rejection path — paused must be a distinct Err, not a silent Ok(false), so callers (the carbon_asset contract) can distinguish "no auto-deposit triggered" from "pool is frozen."
-
Add unit tests: pause() by non-governance fails; pause() by governance succeeds and subsequent deposit/withdraw_to_replace/auto_deposit calls all return ContractPaused; unpause() restores normal operation; view functions (get_total_value_locked, get_custody_record, is_token_in_pool) remain callable while paused.
Acceptance Criteria
deposit(), withdraw_to_replace(), and auto_deposit() all reject with ContractPaused when the pool is paused.
- Only the governance address can call
pause() or unpause().
PauseEvent and UnpauseEvent are emitted on each transition.
is_paused() accurately reflects current state.
- View-only functions remain callable regardless of pause state.
- Pausing does not affect already-stored custody records or TVL accounting.
- Existing deposit/withdraw/auto-deposit tests continue to pass when the pool is not paused.
- Test coverage exists for pause/unpause authorization and for each gated function's rejection while paused.
Directory to Work on:
stellar-core/carbon-asset-factory/
Summary
Add emergency pause for deposits/withdrawals under incident response —
buffer_poolhas no circuit-breaker switch, so a compromised carbon-asset contract or governance key cannot be contained without redeploying or abandoning the pool.Social Media Link
Let's collaborate on Discord. And ensure to star our repo.
Problem Statement
Confirmed in
stellar-core/carbon-asset-factory/contracts/buffer_pool/src/lib.rs,stellar-core/carbon-asset-factory/contracts/buffer_pool/src/storage.rs, andstellar-core/carbon-asset-factory/contracts/buffer_pool/src/errors.rs:No pause flag exists in storage:
storage.rsdefinesADMIN,GOVERNANCE,CARBON_CONTRACT,REPLENISH_PCT,TVL, andCUSTODYsymbols — there is noPAUSEDkey or equivalent boolean gate.deposit()has no pause check:lib.rs:51-85validates caller identity (caller != admin && caller != carbon_contract) and duplicate custody, but nothing would stop a deposit from proceeding during an active incident.withdraw_to_replace()has no pause check:lib.rs:89-131performs a cross-contracttransfercall unconditionally once governance auth passes — there is no way to freeze this path while investigating a suspected exploit in the linkedcarbon_asset_contract.auto_deposit()has no pause check:lib.rs:133-181is invoked by thecarbon_assetcontract on every qualifying mint (per the modulo-based sampling logic) and cannot be temporarily disabled without changingset_replenishment_rateto0, which the code explicitly rejects —set_replenishment_raterequires1..=10000(lib.rs:214-220) andinitializerejectsinitial_percentage == 0(lib.rs:32-34), so there is no supported way to fully halt auto-deposits even temporarily.No admin/governance-gated toggle function exists at all: the only state-mutating admin/governance functions are
set_governance_addressandset_replenishment_rate(lib.rs:183-225) — neither is designed as, or reusable as, an incident-response kill switch.No
Errorvariant for a paused-state rejection: whatever this contract'serrors.rsdefines (Error::Unauthorized,Error::AlreadyExists,Error::ZeroPercentage,Error::InvalidPercentage,Error::TokenNotFound, per usages seen inlib.rs), there is noError::ContractPausedto reject calls cleanly during a freeze.No event exists for a pause/unpause transition:
events.rsprovidesemit_deposit_event,emit_withdraw_event,emit_auto_deposit_event, andemit_duplicate_auto_deposit_event, but nothing to signal an incident-response action to off-chain monitors.get_total_value_locked,get_custody_record, andis_token_in_poolare read-only and unaffected, so a pause implementation must be careful to gate only state-mutating entry points (deposit,withdraw_to_replace,auto_deposit) and not the view functions.Required Changes
Add a
PAUSED: Symbolconstant andget_paused(env) -> bool/set_paused(env, paused: &bool)storage helpers tostorage.rs, defaulting tofalsewhen unset.Add
Error::ContractPausedtoerrors.rs.Add
pause(env, caller: Address) -> Result<(), Error>restricted to governance (matching the existing governance-gated pattern used byset_governance_address/set_replenishment_rate), settingPAUSEDtotrue.Add
unpause(env, caller: Address) -> Result<(), Error>restricted to governance, settingPAUSEDtofalse.Add a pause check as the first guard in
deposit(),withdraw_to_replace(), andauto_deposit(), returningErr(Error::ContractPaused)immediately when paused — before any auth or storage checks, to minimize wasted budget during an incident.Add
PauseEvent { paused_by: Address }andUnpauseEvent { unpaused_by: Address }toevents.rsand publish them from the new functions.Add
is_paused(env) -> boolas a public view function.Ensure
auto_deposit's silent-false-return behavior for the zero-percentage case (lib.rs:150-152) is not confused with the new paused-rejection path — paused must be a distinctErr, not a silentOk(false), so callers (thecarbon_assetcontract) can distinguish "no auto-deposit triggered" from "pool is frozen."Add unit tests:
pause()by non-governance fails;pause()by governance succeeds and subsequentdeposit/withdraw_to_replace/auto_depositcalls all returnContractPaused;unpause()restores normal operation; view functions (get_total_value_locked,get_custody_record,is_token_in_pool) remain callable while paused.Acceptance Criteria
deposit(),withdraw_to_replace(), andauto_deposit()all reject withContractPausedwhen the pool is paused.pause()orunpause().PauseEventandUnpauseEventare emitted on each transition.is_paused()accurately reflects current state.Directory to Work on:
stellar-core/carbon-asset-factory/