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():
- Get the USD price for the deposited asset from Reflector
- Compute
deposit_usd = amount * price / 10^decimals
- Add to
portfolio.total_value
In execute_rebalance():
- After trades complete, recalculate
total_value from all asset balances and their current prices
- Store the updated value
In withdraw():
- Get the USD price for the withdrawn asset
- 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
References
Affected Area
Smart Contracts
Checklist
Problem
The Soroban smart contract's
create_portfoliofunction storestotal_value: 0when 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:The
depositfunction updatesportfolio.total_value += amountbutamountis the raw token quantity, not a USD value. So if someone deposits 100 XLM and 50 USDC,total_valuebecomes 150 — which is meaningless since it's mixing units.The
check_rebalance_neededfunction usestotal_valueto 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 computetotal_valuein USD whenever a deposit, withdrawal, or rebalance happens.In
deposit():deposit_usd = amount * price / 10^decimalsportfolio.total_valueIn
execute_rebalance():total_valuefrom all asset balances and their current pricesIn
withdraw():withdraw_usdfromtotal_valueOption B: On-demand calculation
Add a
calculate_total_valueview 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.rs). If the price feed is stale, the contract should either use the last known price or revert with a clear error.total_valueshould be stored as an integer in stroops (10^-7 for USDC) to avoid floating-point issues. The currenti128type is fine for this.Files to modify
contracts/src/portfolio.rs— updatedeposit,withdraw,execute_rebalanceto compute and storetotal_valuecontracts/src/reflector.rs— ensureget_priceis callable from portfolio functionscontracts/src/lib.rs— add aget_total_valueview function if going with Option Bcontracts/src/test.rs— add tests for total_value computation after deposit, rebalance, and withdrawalAcceptance Criteria
total_valuereflects actual USD value after a deposittotal_valueis recalculated after a rebalancetotal_valuedecreases after a withdrawalReferences
contracts/src/portfolio.rscontracts/src/reflector.rscontracts/src/types.rsAffected Area
Smart Contracts
Checklist