DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: medium
Invalid

Profit Lock Period Allows Unfair Access to Yields for New Depositors

Summary

The current profit lock mechanism gradually unlocks locked shares over time, during which the share price increases as profits are distributed. However, in the initial stages of this unlocking process, the share price does not reflect the total accumulated profit. This allows new depositors to enter the strategy at a lower share price, enabling them to partake in profits that were not proportionally earned. This behavior can discourage long-term users, as opportunistic deposits made after the report call can unfairly benefit at the expense of earlier participants.

Vulnerability Details

The strategy implements a profit lock mechanism during the report function to distribute profits over time:

function report(){
...
if (sharesToBurn > sharesToLock) {
// Burn the difference
unchecked {
_burn(S, address(this), sharesToBurn - sharesToLock);
}
} else if (sharesToLock > sharesToBurn) {
// Mint the shares to lock the strategy.
unchecked {
_mint(S, address(this), sharesToLock - sharesToBurn);
}
}
...
}

Locked shares gradually unlock according to the _unlockedShares calculation:

function _unlockedShares(
StrategyData storage S
) internal view returns (uint256 unlocked) {
uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate;
if (_fullProfitUnlockDate > block.timestamp) {
unchecked {
unlocked =
(S.profitUnlockingRate * (block.timestamp - S.lastReport)) /
MAX_BPS_EXTENDED;
}
} else if (_fullProfitUnlockDate != 0) {
// All shares have been unlocked.
unlocked = S.balances[address(this)];
}
}

This gradual unlocking increases the share price over time as profits are distributed, based on the following formulas:

function _totalSupply(
StrategyData storage S
) internal view returns (uint256) {
return S.totalSupply - _unlockedShares(S);
}
function pricePerShare() external view returns (uint256) {
StrategyData storage S = _strategyStorage();
return _convertToAssets(S, 10 ** S.decimals, Math.Rounding.Down);
}
/// @dev Internal implementation of {convertToAssets}.
function _convertToAssets(
StrategyData storage S,
uint256 shares,
Math.Rounding _rounding
) internal view returns (uint256) {
// Saves an extra SLOAD if totalSupply() is non-zero.
uint256 supply = _totalSupply(S);
return
supply == 0
? shares
: shares.mulDiv(_totalAssets(S), supply, _rounding);
}

However, after the swap is performed to generate a certain profit, when the report is triggered and profits are locked, the share price reflects the locked shares and the total assets. However, in the initial stages of the unlocking process, new depositors can buy shares at a lower price and still benefit from profits as the share price increases. This creates a situation where:

  1. Opportunistic depositors can exploit lower share prices immediately after report.

  2. Long-term depositors are penalized, as they share their rightful profits with new entrants who contributed less.

Impact

This behavior undermines the fairness of the profit distribution mechanism. It may discourage loyal users from continuing their deposits, as new entrants can benefit with a short deposit period after profits are locked.

Tools Used

Manual

Recommendations

A protection strategy should be used to protect users who invest earlier: for example, Implement a delayed unlocking period for new depositors after report is called to prevent immediate access to profits.

Updates

Appeal created

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
jesjupyter Submitter
6 months ago
inallhonesty Lead Judge
6 months ago
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.