Furry Berry Armadillo
Medium
Processing rewards before share calculation causes share dilution as users receive fewer shares when withdrawing or depositing assets after rewards are processed.
In AutoCompoundingPodLp.sol
, reward processing occurs before share calculation in both deposit
and withdraw
functions:
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);
}
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);
}
No response
No response
No response
Users suffer share dilution during withdrawal
and deposit
, receiving fewer shares for their assets due to reward processing occurring before share calculation.
Add this test in AutoCompoundingPodLpTest.t.sol
function testWithdrawShareCalculationIssue() public {
uint256 initialDeposit = 1000 * 1e18;
deal(address(mockStakingPoolToken), address(this), initialDeposit);
mockStakingPoolToken.approve(address(autoCompoundingPodLp), initialDeposit);
autoCompoundingPodLp.deposit(initialDeposit, address(this));
address[] memory rewardTokens = new address[](2);
rewardTokens[0] = address(rewardToken1);
rewardTokens[1] = address(rewardToken2);
mockTokenRewards.setProcessedRewardTokens(rewardTokens);
uint256 rewardAmount = 1000 * 1e18;
uint256 lpAmountOut = 1000 * 1e18; // 1:1 conversion for clarity
mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut * 3);
mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
emit log_named_uint("Initial total assets", autoCompoundingPodLp.totalAssets());
emit log_named_uint("Initial total supply", autoCompoundingPodLp.totalSupply());
uint256 withdrawAmount = 500 * 1e18;
uint256 expectedShares = autoCompoundingPodLp.convertToShares(withdrawAmount);
emit log_named_uint("Expected shares (pre-rewards)", expectedShares);
uint256 actualShares = autoCompoundingPodLp.withdraw(withdrawAmount, address(this), address(this));
emit log_named_uint("Final total assets", autoCompoundingPodLp.totalAssets());
emit log_named_uint("Final total supply", autoCompoundingPodLp.totalSupply());
emit log_named_uint("Actual shares burned", actualShares);
assertLt(actualShares, expectedShares, "Share calculation should be affected by reward processing");
}
[PASS] testWithdrawShareCalculationIssue() (gas: 1574426)
Logs:
Initial total assets: 1000000000000000000000
Initial total supply: 1000000000000000000000
Expected shares (pre-rewards): 500000000000000000000
Final total assets: 1500000000000000000000
Final total supply: 750000000000000000000
Actual shares burned: 250000000000000000000
The most straightforward solution would be to:
- Calculate shares before processing rewards.