The Attack Scenario
contracts/loan_pool/src/storage.rs:349-363:
pub fn get_shares_from_tokens(e: &Env, amount_tokens: i128) ->
Result<i128, LoanPoolError> {
let shares = if total_pool_tokens == 0 {
amount_tokens // First deposit: 1:1 ratio
} else {
total_pool_shares * amount_tokens / total_pool_tokens //
Integer division rounds down
};
Ok(shares)
}
Concrete Attack Example
- Alice deposits 10 stroops → Gets 10 shares
- Pool: 10 tokens, 10 shares
- 1 share = 1 token ✓
- Interest accrues over time, pool grows to 10,010 stroops
- Pool: 10,010 tokens, 10 shares
- 1 share = 1,001 tokens
- Bob deposits 2,000 stroops → (10 * 2,000) / 10,010 = 1.998... → Gets
1 share (rounds down!)
- Pool: 12,010 tokens, 11 shares
- 1 share = 1,091.8 tokens
- Bob's loss:
- Deposited: 2,000 stroops
- Share value: 1,091 stroops
- Lost: ~909 stroops (45.5%!)
Why This Happens
The resolution of shares is determined by the first deposit size. If that's tiny (10 stroops = 10 shares), each share represents a large unit, causing massive rounding errors as the pool grows. While it is unlikely for pool to accrue such large amount of tokens before more deposits flow in, this is unnecessary and easily avoidable attack vector. Here's some potential fixes:
Option 1: Minimum First Deposit
// In deposit() function
if current_contract_balance == 0 && amount < MINIMUM_INITIAL_DEPOSIT {
return Err(LoanPoolError::DepositTooSmall);
}
const MINIMUM_INITIAL_DEPOSIT: i128 = 1_000_000; // 1 token with 7
decimals
Option 2: Virtual Shares/Tokens (Like Uniswap V2)
let shares = if total_pool_tokens == 0 {
amount - MINIMUM_LIQUIDITY // Burn some shares initially
} else {
// ... normal calculation
};
Option 3: Admin Initialization
- Have the deployment script automatically seed each pool with a
reasonable amount (e.g., 100-1000 tokens)
- Located in
scripts/initialize.ts (mentioned in package.json)
The Attack Scenario
contracts/loan_pool/src/storage.rs:349-363:
Concrete Attack Example
1 share (rounds down!)
Why This Happens
The resolution of shares is determined by the first deposit size. If that's tiny (10 stroops = 10 shares), each share represents a large unit, causing massive rounding errors as the pool grows. While it is unlikely for pool to accrue such large amount of tokens before more deposits flow in, this is unnecessary and easily avoidable attack vector. Here's some potential fixes:
Option 1: Minimum First Deposit
Option 2: Virtual Shares/Tokens (Like Uniswap V2)
Option 3: Admin Initialization
reasonable amount (e.g., 100-1000 tokens)
scripts/initialize.ts(mentioned inpackage.json)