Skip to content

Security guarantees

This section states what the Smart Treasury Account guarantees, why, and which test proves each claim. A guarantee with no passing test behind it is marked as pending rather than asserted.

Audit status

The Smart Treasury Account contracts have not been independently audited by a third party. An internal/independent review pass found and fixed real defects across three rounds (see docs/SMART_CONTRACT_AUDIT_REPORT.md in the smart-contracts repository), but that is not the same thing. The OpenZeppelin Stellar libraries they build on have been professionally audited. Do not collapse these into "audited".

The two guarantees unique to STA

Most of what a treasury wallet does — multisig thresholds, an emergency pause, role-based admin — is well-trodden. Two things distinguish STA, and they are the ones worth scrutinizing:

  1. Policy-version pinning — a policy change cannot silently reinterpret an approval made under an earlier policy.
  2. Replay protection — an approved action executes exactly once.

This is also why the test suite is weighted the way it is. STA does not re-test OpenZeppelin's access control and pause primitives; those are covered by their upstream suite and audit. The tests concentrate on the STA-specific guarantees. See the coverage matrix.

Fail-closed by default

Every policy decision denies unless something explicitly permits:

SituationResult
No rule stored for the assetRejected — AssetNotAllowed
Rule stored but enabled: falseRejected — AssetNotAllowed
Recipient never allowlistedRejected — RecipientNotAllowed
Recipient allowlisted then revokedRejected — RecipientNotAllowed
Amount above the configured capRejected — AmountAboveLimit
Amount zero or negativeRejected — InvalidAmount
Operation not explicitly enabledRejected — OperationNotAllowed
Engine never initializedRejected — NotInitialized

Absence is a denial. An unknown recipient or operation reads as false against its allowlist. There is no code path where a missing record is read as permission.

Status: verified — every row is covered by a passing test, and the version-mismatch and asset/recipient/amount rejections are additionally reproducible live on testnet — see docs/TESTNET_DEPLOYMENT.md in the smart-contracts repository.

System invariants

InvariantStatus
Unsupported assets fail closedVerified
Unsupported recipients fail closedVerified
Amounts above the cap fail closedVerified
A stale pinned policy version fails closedVerified
Policy versions are strictly monotonicVerified
Treasury assets move only through SmartAccount-authorized executionVerified
Invalid or reused nonces fail closedVerified
Paused or frozen accounts reject executionVerified
A split payment cannot move funds to a disallowed recipient by hiding it inside an approved totalVerified
A scheduled payment executes the exact asset/destination/amount/adapter approved at creation, never a caller-supplied valueVerified

"Verified" means a test asserts it and that test passes — see the coverage matrix and SmartAccount for the specific tests behind each row.

Not a guaranteed invariant: threshold-vs-signer-set consistency

Earlier drafts of this page implied thresholds can't be misconfigured into an unusable state. That's not accurate. If a weighted_threshold (or simple_threshold) policy is attached to a context rule, its stored threshold is not automatically revalidated when signers are added to or removed from that rule — an explicitly documented upstream characteristic of the composed OpenZeppelin crate, not something this project adds or removes. This is an operational risk to manage (review weights/threshold before changing a signer set), not a protection the contracts enforce. See SmartAccount for detail.

What the relayer cannot do

The scheduled-payment relayer (in the separate dApp repository, out of this site's contract-only scope) submits transactions. It never custodies treasury assets, and it cannot change the asset, destination, amount, policy version, or execution window of a scheduled payment: execute_scheduled_payment takes only an intent ID and child sequence from its caller, and reads every other value back from intent_registry's canonical record rather than trusting whatever the submitter supplies. This is enforced by the contracts documented here, independent of which relayer implementation calls them.

Reporting an issue

Report suspected vulnerabilities privately, never in a public issue, through the vulnerability reporting form of the smart-contracts repository. The organization's security policy defines the scope and what to expect; Reporting issues lists what to include.

The Smart Treasury Account contracts have not been independently audited. OpenZeppelin's Stellar libraries, which they build on, have been.