Decent Boysenberry Zebra
High
A potential vulnerability exists in the vault's share issuance and redemption mechanism due to rounding inconsistencies in AutoCompoundingPodLp.sol
. An attacker can repeatedly deposit and withdraw assets to systematically reduce the total number of shares in circulation while keeping the asset count unchanged. This results in an artificial increase in the asset price per share, allowing the attacker to manipulate share value and potentially extract undue profits.
In the deposit
function at AutoCompoundingPodLp.sol#L126
, assets are converted to shares using rounding down, whereas in the withdraw
function at AutoCompoundingPodLp.sol#L164
, assets are converted to shares using rounding up. This inconsistency causes the share price to increase without affecting the total asset count.
No response
No response
- The vault's deposit function converts assets into shares, rounding down.
- The withdrawal function converts assets into shares, rounding up.
- An attacker can deposit assets and receive shares.
- When withdrawing, the attacker burns more shares than they originally received due to rounding.
- Repeating this process inflates the total share supply over time.
- The exploit reduces the total number of shares in the vault while keeping the asset count unchanged.
- As a result, the price per share (assets/shares) artificially increases, benefiting the attacker.
function testIncosistentRoundingInDepositAndWithdraw() {
//set up attacker
address attacker = makeAddr("attacker");
deal(address(_asset), attacker, 15e18);
vm.prank(attacker);
_asset.approve(address(_autoCompoundingPodLp), 15e18);
uint256 depositShares = _autoCompoundingPodLp.deposit(15e18, attacker);
uint256 withdrawShares = _autoCompoundingPodLp.withdraw(15e18, attacker, attacker);
// Withdraw shares will be greater than Deposit shares due to inconsistent Rounding.
assertGT(withdrawShares, depositShares);
}
- Total Assets: 115
- Total Shares: 20
- Assets to Deposit: 15
Formula:
Math.mulDiv(depositAssets, (FACTOR * totalShares / totalAssets), FACTOR, Math.rounding.Floor);
- Computed Shares: 2.6087
- Final Shares (Rounded Down): 2
- Total Assets: 130
- Total Shares: 22
- Assets to Withdraw: 15
Formula:
Math.mulDiv(depositAssets, (FACTOR * totalShares / totalAssets), FACTOR, Math.rounding.Ceil);
- Computed Shares: 3.538
- Final Shares (Rounded Up): 4
Use the same rounding for deposit and withdraw either down or up.
function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
_processRewardsToPodLp(0, block.timestamp);
_shares = _convertToShares(_assets, Math.Rounding.Floor);
_deposit(_assets, _shares, _receiver);
}
function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
_processRewardsToPodLp(0, block.timestamp);
_shares = _convertToShares(_assets, Math.Rounding.Floor);
_withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
}
or
function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
_processRewardsToPodLp(0, block.timestamp);
_shares = _convertToShares(_assets, Math.Rounding.Ceil);
_deposit(_assets, _shares, _receiver);
}
function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
_processRewardsToPodLp(0, block.timestamp);
_shares = _convertToShares(_assets, Math.Rounding.Ceil);
_withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
}