This document outlines security best practices and checklist items for Callora vault contracts to improve audit readiness and reviewer confidence.
- All privileged functions protected by
require_auth()orrequire_auth_for_args()viaAddress - Admin state stored securely (e.g., using
env.storage().instance()) - Admin rotation/transfer tested and documented
- No integer overflow/underflow possible
- Solidity ^0.8.x overflow checks relied upon or SafeMath used where required
- For Soroban/Rust:
checked_add/checked_subused for all balance mutations -
overflow-checksenabled in both dev and release profiles
All balance mutations in
callora-vault(deposit,deduct,batch_deduct,withdraw,withdraw_to) andcallora-revenue-pool(batch_distribute) usechecked_add/checked_suband panic with a descriptive message on overflow.callora-settlement(receive_payment) does the same. The workspaceCargo.tomlsetsoverflow-checks = truefor bothdevandreleaseprofiles, so even plain arithmetic would trap in debug builds — the explicit checked calls make the intent clear and guarantee the same behaviour in all build configurations.
-
initializefunction protected against multiple calls (e.g., checking if admin key exists ininstance()storage) - Contract upgrades (
env.deployer().update_current_contract_wasm()) protected byrequire_auth() - No unprotected re-init functions
-
initializevalidates all input parameters
- Emergency pause mechanism implemented via state flag in
instance()storage - Paused state blocks fund movement (e.g., reverting via
panic_with_error!) - Pause/unpause flows tested
- Ownership transfer is two-step (optional but recommended)
- Ownership transfer emits events
- Renounce ownership reviewed and justified
- Token transfers strictly rely on
soroban_sdk::token::Client - Cross-contract calls handle potential errors/panics gracefully
- State changes are persisted before making cross-contract calls to mitigate subtle state-caching issues
- Checks-effects-interactions pattern followed
The vault performs USDC transfers to configurable counterpart addresses on every
deduct and batch_deduct call. These external transfers are justified as follows:
- settlement address: set and updated exclusively by the on-chain admin via
set_settlement. Transfers to this address implement the documentedVault → Settlementrevenue flow described inSETTLEMENT_IMPLEMENTATION.md. - revenue_pool address: set and updated exclusively by the on-chain admin via
set_revenue_pool. Transfers to this address route product revenue to the designated pool contract. - Priority rule: when both are configured,
settlementtakes priority andrevenue_poolis not used in the same deduct. This prevents "half updated" routing states where funds could be split unexpectedly across two recipients. - Unset behavior: if neither address is configured the deducted amount stays inside the vault (balance is reduced but no token transfer occurs). This state is valid and explicitly documented—no funds are lost.
- Both addresses can only be changed by the admin in a single atomic storage write, ensuring no partial update is observable by other callers.
- Deposit/withdraw invariants tested
- Vault balance accounting verified
- Funds cannot be locked permanently
- Minimum deposit requirements enforced
- Maximum deduction limits enforced
- Revenue pool transfers validated
- Batch operations respect individual limits
The Revenue Pool contract (contracts/revenue_pool) operates under the following security assumptions and threat models:
-
Malicious Admin: The
adminrole has the authority to distribute funds and replace the admin address. A compromised or malicious admin could drain the pool's USDC balance.- Mitigation: The
adminshould always be a heavily guarded multisig account or a rigorously audited governance contract.
- Mitigation: The
-
Wrong USDC Token Initialization: The
usdc_tokenaddress is set once duringinit. If initialized with a malicious or incorrect token address, the pool will process the wrong asset.- Mitigation: The deployment process must verify the official Stellar USDC (or appropriate wrapped USDC) contract address before initialization. The
initfunction guards against re-initialization.
- Mitigation: The deployment process must verify the official Stellar USDC (or appropriate wrapped USDC) contract address before initialization. The
-
Operational Griefing (Balances): Anyone can effectively transfer USDC to the revenue pool. If an attacker sends unsolicited funds, it increases the
balance()but does not disrupt thedistributelogic, as distribution is explicitly controlled by the admin.- Mitigation: The pool does not rely on strict balance equality invariants for its core operations, mitigating balance-based operational griefing. Off-chain monitoring should track
receive_paymentevents and native token transfers to reconcile expected vs. actual balances.
- Mitigation: The pool does not rely on strict balance equality invariants for its core operations, mitigating balance-based operational griefing. Off-chain monitoring should track
- All amounts validated to be > 0
- Address/parameter validation on all public functions
- Boundary conditions tested (max values, zero values)
- Error messages provide clear context for debugging
- All state changes emit appropriate events
- Event schema documented and indexed
- Critical operations (deposit, withdraw, deduct) logged with full context
- Unit tests cover all public functions
- Edge cases and boundary conditions tested
- Panic scenarios tested with
#[should_panic] - Integration tests for complete user flows
- Minimum 95% test coverage maintained
Before any mainnet deployment:
-
Engage an independent third-party security auditor
- Choose auditors with experience in Soroban/Stellar smart contracts
- Ensure auditor understands vault-specific risk patterns
-
Perform a full smart contract audit
- Review all contract code for security vulnerabilities
- Analyze upgrade patterns and migration paths
- Validate mathematical correctness of balance operations
-
Address all high and medium severity findings
- Create tracking system for audit findings
- Implement fixes for all H/M severity issues
- Document rationale for any low severity findings that won't be fixed
-
Publish audit report for transparency
- Make audit report publicly available
- Include summary of findings and remediation steps
- Provide evidence of test coverage and validation
- WASM compilation verified and reproducible (
stellar contract build/cargo build --target wasm32-unknown-unknown --release) - Storage lifespan (
extend_ttl) implemented to prevent state archiving for critical data - Stellar network parameters validated (budget, CPU/RAM limits)
- Cross-contract call security and generic type usage (
Val) reviewed - Storage patterns optimized and secure (e.g., correct usage of
persistentvsinstancevstemporarykeys)
- Fee structures reviewed for economic attacks
- Revenue pool distribution validated
- Maximum loss scenarios analyzed
- Slippage and market impact considered
- Deployment process documented and automated
- Key management procedures established
- Monitoring and alerting configured
- Incident response plan prepared
- Stellar Security Best Practices
- Soroban Documentation
- Smart Contract Weakness Classification Registry
Note: This checklist should be reviewed and updated regularly as new security patterns emerge and the codebase evolves.
All privileged entrypoints across vault, revenue_pool, and settlement contracts
have been audited for require_auth() coverage as part of Issue #160.
- All privileged functions call
require_auth()on the caller before executing. ✅ - Negative tests added to each crate's
test.rsconfirming unauthenticated calls are rejected.
| Contract | Function | Reason |
|---|---|---|
| settlement | init() |
One-time initializer guarded by already-initialized panic; no auth required by design. |
| vault | require_owner() |
Internal helper using assert! for address equality. All public callers invoke caller.require_auth() before calling this helper, so host-level auth is enforced transitively. Documented gap: require_owner itself does not call require_auth(). |
- Audit branch:
test/require-auth-sweep - Tests:
contracts/vault/src/test.rs,contracts/revenue_pool/src/test.rs,contracts/settlement/src/test.rs