Skip to content

fix: Contract total_value hardcoded to 0 — portfolio value is never computed from real balances #54

Description

@Uchechukwu-Ekezie

Problem

The Soroban smart contract's create_portfolio function stores total_value: 0 when a portfolio is created, and nothing ever updates it. The contract doesn't compute the portfolio's actual USD value from token balances and prices — it just leaves it at zero.

In contracts/src/portfolio.rs, when a portfolio is created:

let portfolio = Portfolio {
    id: id.clone(),
    owner: owner.clone(),
    assets: assets.clone(),
    target_allocations: allocations.clone(),
    drift_threshold,
    total_value: 0,  // <-- always zero
    last_rebalance: 0,
    created_at: env.ledger().timestamp(),
    rebalance_cooldown: 86400,
    is_active: true,
};

The deposit function updates portfolio.total_value += amount but amount is the raw token quantity, not a USD value. So if someone deposits 100 XLM and 50 USDC, total_value becomes 150 — which is meaningless since it's mixing units.

The check_rebalance_needed function uses total_value to calculate drift, but since it's always wrong, the drift calculations are unreliable. The backend works around this by computing its own portfolio value, but the on-chain state is fundamentally broken.

Proposed Fix

Option A: Oracle-driven total value (recommended)

Use the Reflector oracle integration (already partially implemented in contracts/src/reflector.rs) to fetch prices and compute total_value in USD whenever a deposit, withdrawal, or rebalance happens.

In deposit():

  1. Get the USD price for the deposited asset from Reflector
  2. Compute deposit_usd = amount * price / 10^decimals
  3. Add to portfolio.total_value

In execute_rebalance():

  1. After trades complete, recalculate total_value from all asset balances and their current prices
  2. Store the updated value

In withdraw():

  1. Get the USD price for the withdrawn asset
  2. Subtract withdraw_usd from total_value

Option B: On-demand calculation

Add a calculate_total_value view function that reads all asset balances, queries Reflector for prices, and returns the computed USD value. Don't store it — compute it on demand. This avoids stale values but costs more gas on every read.

Key considerations

  • Reflector prices have a staleness threshold (already checked in reflector.rs). If the price feed is stale, the contract should either use the last known price or revert with a clear error.
  • USDC has 7 decimals on Stellar, not 6 like on EVM. The conversion math needs to account for different asset decimals.
  • The total_value should be stored as an integer in stroops (10^-7 for USDC) to avoid floating-point issues. The current i128 type is fine for this.

Files to modify

  • contracts/src/portfolio.rs — update deposit, withdraw, execute_rebalance to compute and store total_value
  • contracts/src/reflector.rs — ensure get_price is callable from portfolio functions
  • contracts/src/lib.rs — add a get_total_value view function if going with Option B
  • contracts/src/test.rs — add tests for total_value computation after deposit, rebalance, and withdrawal

Acceptance Criteria

  • total_value reflects actual USD value after a deposit
  • total_value is recalculated after a rebalance
  • total_value decreases after a withdrawal
  • Stale oracle prices are handled gracefully (revert or use last known)
  • Multi-asset portfolios compute total value correctly (not mixing raw units)
  • Unit tests cover: single asset, multi-asset, stale price, zero balance

References

Affected Area

Smart Contracts

Checklist

  • I have searched existing issues and this is not a duplicate

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcontractSmart contract relatedhelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions