DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack of updating yield before distributing

Summary

The YieldFacet::distributeYield() does not update the Vault's zethYieldRate before distributing the yield earned to shorter's ShortRecord positions.

As a result, the shorter will receive the zETH and Ditto rewards less than expected.

Vulnerability Details

The distributeYield() does not update the Vault's zethYieldRate before distributing the yield earned to the shorter's ShortRecord positions.

Once the _distributeYield() is executed, the non-updated zethYieldRate will be consumed. Subsequently, the computed yield (zETH) and dittoYieldShares will be less than expected.

Finally, the shorter will receive the zETH and Ditto rewards less than expected.

function distributeYield(address[] calldata assets) external nonReentrant {
uint256 length = assets.length;
uint256 vault = s.asset[assets[0]].vault;
// distribute yield for the first order book
@> (uint88 yield, uint256 dittoYieldShares) = _distributeYield(assets[0]); //@audit -- The distributeYield() distributes yield earned to the shorter's ShortRecord positions without updating the Vault's zethYieldRate
// distribute yield for remaining order books
for (uint256 i = 1; i < length;) {
if (s.asset[assets[i]].vault != vault) revert Errors.DifferentVaults();
@> (uint88 amtYield, uint256 amtDittoYieldShares) = _distributeYield(assets[i]); //@audit -- The distributeYield() distributes yield earned to the shorter's ShortRecord positions without updating the Vault's zethYieldRate
@> yield += amtYield;
@> dittoYieldShares += amtDittoYieldShares;
unchecked {
++i;
}
}
// claim all distributed yield
@> _claimYield(vault, yield, dittoYieldShares); //@audit -- The distributeYield() credits the zETH and Ditto rewards (less than expected) to a shorter
emit Events.DistributeYield(vault, msg.sender, yield, dittoYieldShares);
}
function _distributeYield(address asset)
private
onlyValidAsset(asset)
returns (uint88 yield, uint256 dittoYieldShares)
{
uint256 vault = s.asset[asset].vault;
// Last updated zethYieldRate for this vault
@> uint80 zethYieldRate = s.vault[vault].zethYieldRate; //@audit -- Since the Vault's zethYieldRate is not updated, the _distributeYield() will consume the non-updated zethYieldRate
...
// Loop through all shorter's shorts of this asset
while (true) {
// One short of one shorter in this market
STypes.ShortRecord storage short = s.shortRecords[asset][msg.sender][id];
// To prevent flash loans or loans where they want to deposit to claim yield immediately
bool isNotRecentlyModified =
timestamp - short.updatedAt > Constants.YIELD_DELAY_HOURS;
// Check for cancelled short
if (short.status != SR.Cancelled && isNotRecentlyModified) {
@> uint88 shortYield =
@> short.collateral.mulU88(zethYieldRate - short.zethYieldRate);
// Yield earned by this short
@> yield += shortYield; //@audit -- Consequently, the computed yield (zETH) and dittoYieldShares will be less than expected
// Update zethYieldRate for this short
short.zethYieldRate = zethYieldRate;
// Calculate CR to modify ditto rewards
uint256 cRatio = short.getCollateralRatioSpotPrice(oraclePrice);
if (cRatio <= initialCR) {
@> dittoYieldShares += shortYield; //@audit -- Consequently, the computed yield (zETH) and dittoYieldShares will be less than expected
} else {
// Reduce amount of yield credited for ditto rewards proportional to CR
@> dittoYieldShares += shortYield.mul(initialCR).div(cRatio); //@audit -- Consequently, the computed yield (zETH) and dittoYieldShares will be less than expected
}
}
// Move to next short unless this is the last one
if (short.nextId > Constants.HEAD) {
id = short.nextId;
} else {
break;
}
}
}

Impact

As a result, the shorter will receive the zETH and Ditto rewards less than expected.

Tools Used

Manual Review

Recommendations

Update the Vault's zethYieldRate before distributing the yield earned to the shorter's ShortRecord positions.

function distributeYield(address[] calldata assets) external nonReentrant {
uint256 length = assets.length;
uint256 vault = s.asset[assets[0]].vault;
+ vault.updateYield();
+ emit Events.UpdateYield(vault);
// distribute yield for the first order book
(uint88 yield, uint256 dittoYieldShares) = _distributeYield(assets[0]);
// distribute yield for remaining order books
for (uint256 i = 1; i < length;) {
if (s.asset[assets[i]].vault != vault) revert Errors.DifferentVaults();
(uint88 amtYield, uint256 amtDittoYieldShares) = _distributeYield(assets[i]);
yield += amtYield;
dittoYieldShares += amtDittoYieldShares;
unchecked {
++i;
}
}
// claim all distributed yield
_claimYield(vault, yield, dittoYieldShares);
emit Events.DistributeYield(vault, msg.sender, yield, dittoYieldShares);
}
Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
serialcoder Submitter
over 1 year ago
0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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