Description & Vulnerability Analysis
A comprehensive security audit of contracts/dca-module/src/lib.rs identified several critical and high-severity flaws that break the DCA execution cadence, allow creation of dead unexecutable plans with arbitrary invalid funding, risk arithmetic overflow panics, and expose the protocol to ledger storage exhaustion attacks:
1. Permanent Execution Schedule Drift (Schedule Invariant Failure)
In execute:
let current_ledger = env.ledger().sequence() as u64;
...
plan.next_execution_ledger = current_ledger + Self::frequency_to_ledgers(&plan.frequency);
-
Mechanism: In dollar-cost averaging protocols, executions are meant to occur periodically according to a fixed cadence ($T_0 + N \times \text{interval}$). However, automated keeper bots or off-chain executors rarely execute at the exact block when a transaction becomes due (e.g., due to transaction batching, gas pricing, or network latency).
- When a keeper executes at
current_ledger = plan.next_execution_ledger + \Delta, recalculating next_execution_ledger = current_ledger + interval permanently offsets future executions by $\Delta$.
- Over time, this cumulative schedule drift causes user plans to execute significantly fewer times than intended, distorting the dollar-cost average pricing and breaking scheduled automation.
-
Fix: Anchor the schedule to the planned cadence (
plan.next_execution_ledger = plan.next_execution_ledger.saturating_add(interval)), while only fast-forwarding to current_ledger.saturating_add(interval) if execution experienced severe keeper delay ($\ge \text{interval}$).
2. Unvalidated funded_amount in create_plan
In create_plan:
funded_amount was never checked against amount_per_execution or for positivity:
- A user could supply
funded_amount <= 0 (or negative).
- A user could supply
funded_amount < amount_per_execution.
- Plans with inadequate or negative funding were created and persisted into contract state, but subsequent calls to
execute() fail immediately with InsufficientFunds, creating permanently dead/zombie plans that clutter contract state and user queries.
- Fix: Validate that
funded_amount > 0 and funded_amount >= amount_per_execution, rejecting invalid plans at initialization.
3. Unchecked Arithmetic in execute and cancel
- In
execute: let remaining = plan.funded_amount - plan.spent_amount; and plan.spent_amount += plan.amount_per_execution;
- In
cancel: let refund = plan.funded_amount - plan.spent_amount;
- Unchecked subtraction and addition risk integer underflow/overflow panics in Rust.
- Fix: Employ
checked_sub and checked_add throughout all accounting operations.
4. Unbounded User Plans Vector (Storage Exhaustion DoS)
In create_plan:
let mut user_plans = Self::get_user_plans_internal(&env, &owner);
user_plans.push_back(plan_id);
Self::set_user_plans(&env, &owner, &user_plans);
- A user can repeatedly create plans, monotonically growing the
Vec<u64> entry in persistent storage.
- Soroban limits persistent ledger entries to 64 KB. Once the entry grows too large, serialization/deserialization CPU/RAM costs exceed transaction limits or exceed entry limits, rendering
create_plan and get_user_plans unusable for that account.
- Fix: Enforce a maximum plan limit per account (
MAX_USER_PLANS = 100) returning DcaError::MaxPlansReached.
5. Deprecated Event API & Missing Unit Test Suite
- Replaced legacy
env.events().publish with typed #[contractevent] structs (DcaPlanCreatedEvent, DcaExecutedEvent, DcaPausedEvent, DcaResumedEvent, DcaCancelledEvent), eliminating deprecation warnings.
- Wrote a 12-test unit testing suite in
contracts/dca-module/src/test.rs covering all execution paths, anti-drift timing invariants, refund accounting, validations, and storage limits.
Bounty Payout Information
- Bug Bounty Program: StellarLend Security Bug Bounty (
SECURITY.md)
- Severity Assessment: High / Medium (Protocol scheduling disruption, unvalidated fund accounting, storage DoS)
- Researcher:
teddyvj <teddy.vj@gmail.com>
- Payout Addresses:
- EVM (USDC / USDT):
0xf5fcb1f90f8a2e658f38f72f0156ecbec7aa964d
- Solana (USDC / SOL):
ECYrMmKpVNyvWMuLNkBuWxqkk2TNRC3qVPDgonBwamKP
- Bitcoin (BTC):
bc1qjg5lug59rn9rz2j2f9ut798g99mn2asw9jfv0s
Description & Vulnerability Analysis
A comprehensive security audit of
contracts/dca-module/src/lib.rsidentified several critical and high-severity flaws that break the DCA execution cadence, allow creation of dead unexecutable plans with arbitrary invalid funding, risk arithmetic overflow panics, and expose the protocol to ledger storage exhaustion attacks:1. Permanent Execution Schedule Drift (Schedule Invariant Failure)
In
execute:current_ledger = plan.next_execution_ledger + \Delta, recalculatingnext_execution_ledger = current_ledger + intervalpermanently offsets future executions byplan.next_execution_ledger = plan.next_execution_ledger.saturating_add(interval)), while only fast-forwarding tocurrent_ledger.saturating_add(interval)if execution experienced severe keeper delay (2. Unvalidated
funded_amountincreate_planIn
create_plan:funded_amountwas never checked againstamount_per_executionor for positivity:funded_amount <= 0(or negative).funded_amount < amount_per_execution.execute()fail immediately withInsufficientFunds, creating permanently dead/zombie plans that clutter contract state and user queries.funded_amount > 0andfunded_amount >= amount_per_execution, rejecting invalid plans at initialization.3. Unchecked Arithmetic in
executeandcancelexecute:let remaining = plan.funded_amount - plan.spent_amount;andplan.spent_amount += plan.amount_per_execution;cancel:let refund = plan.funded_amount - plan.spent_amount;checked_subandchecked_addthroughout all accounting operations.4. Unbounded User Plans Vector (Storage Exhaustion DoS)
In
create_plan:Vec<u64>entry in persistent storage.create_planandget_user_plansunusable for that account.MAX_USER_PLANS = 100) returningDcaError::MaxPlansReached.5. Deprecated Event API & Missing Unit Test Suite
env.events().publishwith typed#[contractevent]structs (DcaPlanCreatedEvent,DcaExecutedEvent,DcaPausedEvent,DcaResumedEvent,DcaCancelledEvent), eliminating deprecation warnings.contracts/dca-module/src/test.rscovering all execution paths, anti-drift timing invariants, refund accounting, validations, and storage limits.Bounty Payout Information
SECURITY.md)teddyvj <teddy.vj@gmail.com>0xf5fcb1f90f8a2e658f38f72f0156ecbec7aa964dECYrMmKpVNyvWMuLNkBuWxqkk2TNRC3qVPDgonBwamKPbc1qjg5lug59rn9rz2j2f9ut798g99mn2asw9jfv0s