Description:Description\
InvestToken contract contains a high flaw in its share price calculation mechanism. The core issue lies in the non-atomic execution of deposit and withdraw operations, where share calculations occur before state changes. This creates a window where the share price can be manipulated between calculation and execution.
This is dangerous for the protocol and users because:
The non-atomic execution between share calculation and actual token operations creates a price discrepancy window
The share price used for calculation may not reflect the actual price at execution time
This can lead to users receiving incorrect amounts of shares relative to their deposited assets
This issue violates the core ERC4626 vault principle that share/asset conversions should be accurate at the point of execution.
In a high-volume or volatile environment, this could be exploited by:
Front-running deposit transactions when share prices are changing
Manipulating the share price between calculation and execution
Creating arbitrage opportunities from price mismatches
Root Cause: The deposit function calculates shares before burning USDE tokens. This creates a potential race condition where the share price could change between share calculation and actual token burning/minting.
Impact\
Users could receive more or fewer shares than expected if the share price changes between calculation and execution
Violates the core invariant that deposit should return exactly the number of shares calculated by convertToShares()
Let's say\
User calls deposit with X assets
shares = convertToShares(X) calculates Y shares
Share price changes due to external factors
USDE tokens are burned and Y shares are minted
Y shares no longer accurately reflect the current share price
Initial state: Share price starts at 1e18 (1:1 ratio)
Bob's attack:
Deposits 1000 tokens, receiving 1000e18 shares
Manipulates price through withdrawal mechanics
Price impact:
Share price increases to 5.5e18 (5.5x increase)
Alice's loss:
Deposits 100 tokens
Receives only ~18.18 shares instead of expected 100 shares
Lost value: ~81.82 shares (about 82% loss)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import {Test} from "forge-std/Test.sol";
contract SharePriceManipulationTest is Test {
uint256 constant INITIAL_PRICE = 1e18;
uint256 public totalShares;
uint256 public totalAssets;
function testSharePriceManipulation() public {
// Initial state
emit log_named_uint("Initial price", INITIAL_PRICE);
// Bob's large deposit (1000 tokens)
uint256 bobShares = (1000e18 * 1e18) / INITIAL_PRICE;
totalShares += bobShares;
totalAssets += 1000e18;
emit log_named_uint("Bob's shares", bobShares);
// Bob withdraws 90% but manipulates price
uint256 bobWithdrawShares = (bobShares * 900) / 1000;
totalShares -= bobWithdrawShares;
totalAssets -= 450e18; // Only withdraw half the expected assets
// Calculate manipulated share price
uint256 manipulatedPrice = (totalAssets * 1e18) / totalShares;
emit log_named_uint("Manipulated price", manipulatedPrice);
// Alice's deposit with manipulated price
uint256 aliceShares = (100e18 * 1e18) / manipulatedPrice;
emit log_named_uint("Alice's shares", aliceShares);
emit log_named_uint("Expected shares", 100e18);
emit log_named_uint("Lost value", 100e18 - aliceShares);
assertLt(aliceShares, 100e18, "Share price manipulation demonstrated");
}
}
Logs:
[⠒] Compiling...
[⠢] Compiling 1 files with Solc 0.8.27
[⠆] Solc 0.8.27 finished in 917.16ms
Compiler run successful!
Ran 1 test for test/SharePriceManipulationTest.sol:SharePriceManipulationTest
[PASS] testSharePriceManipulation() (gas: 60813)
Logs:
Initial price: 1000000000000000000
Bob's shares: 1000000000000000000000
Manipulated price: 5500000000000000000
Alice's shares: 18181818181818181818
Expected shares: 100000000000000000000
Lost value: 81818181818181818182
As can be seen, an attacker can manipulate share prices to extract value from subsequent depositors. The test validates that share price manipulation can lead to significant losses for users.
Revised Code File (Optional)
A secure implementation should perform the share calculation after state changes to ensure price consistency throughout the deposit operation.
Github username: @0xbrett8571 Twitter username: 0xbrett8571 Submission hash (on-chain): 0x8247015e27cccb058c7f0df2f800d056c6e1cd9afa81474cbb7ed0541c94cc85 Severity: medium
Description: Description\ InvestToken contract contains a high flaw in its share price calculation mechanism. The core issue lies in the non-atomic execution of deposit and withdraw operations, where share calculations occur before state changes. This creates a window where the share price can be manipulated between calculation and execution.
In InvestToken.sol:deposit, InvestToken.sol:withdraw
This is dangerous for the protocol and users because:
This issue violates the core ERC4626 vault principle that share/asset conversions should be accurate at the point of execution.
In a high-volume or volatile environment, this could be exploited by:
Root Cause: The deposit function calculates shares before burning USDE tokens. This creates a potential race condition where the share price could change between share calculation and actual token burning/minting.
Impact\
convertToShares()
Let's say\
shares = convertToShares(X)
calculates Y sharesAttack Scenario\
Attachments
Proof of Concept (PoC) File
Initial state: Share price starts at 1e18 (1:1 ratio)
Bob's attack:
Price impact:
Alice's loss:
Logs:
As can be seen, an attacker can manipulate share prices to extract value from subsequent depositors. The test validates that share price manipulation can lead to significant losses for users.
A secure implementation should perform the share calculation after state changes to ensure price consistency throughout the deposit operation.
To ensure the share calculation happens after the state changes, making the operation atomic and preventing price manipulation opportunities.