DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: high
Valid

Shorters can avoid being liquidated by front running the liquidation or flagging call and transfering there shortRecord to another EOA

Summary

Shorter's can avoid being liquidated by front running the liquidation or flagging call and transferring their shortRecord to another EOA, as the address of the shorter is part of the reference to the shortRecord. Therefore, the call to flag or liquidation will revert after transferring the shortRecord to another EOA, as the given one is no longer a valid short record.

Vulnerability Details

When trying to liquidate a shortRecord, or flagging it for liquidation, it is first checked if the given shortRecord is valid:

function flagShort(address asset, address shorter, uint8 id, uint16 flaggerHint)
external
isNotFrozen(asset)
nonReentrant
**onlyValidShortRecord(asset, shorter, id)**
{
...
}
function liquidate(
address asset,
address shorter,
uint8 id,
uint16[] memory shortHintArray
)
external
isNotFrozen(asset)
nonReentrant
**onlyValidShortRecord(asset, shorter, id)**
returns (uint88, uint88)
{
...
}

The address of the shorter is passed to onlyValidShortRecord and used to check if the shortRecord is valid:

function _onlyValidShortRecord(address asset, address shorter, uint8 id)
internal
view
{
uint8 maxId = s.assetUser[asset][shorter].shortRecordId;
if (id >= maxId) revert Errors.InvalidShortId();
if (id < Constants.SHORT_STARTING_ID) revert Errors.InvalidShortId();
if (s.shortRecords[asset][shorter][id].status == SR.Cancelled) {
revert Errors.InvalidShortId();
}
}
modifier onlyValidShortRecord(address asset, address shorter, uint8 id) {
_onlyValidShortRecord(asset, shorter, id);
_;
}

Therefore, if the address of the shorter changes right before the flagShort, or liquidate call, the function would revert. As there is no check inside transferFrom and transferShortRecord, which would prevent the user from front running a flagShort, or liquidate call, it would be possible for shorters to front run margin callers and transfer the shortRecord with unhealthy CR to another EOA account so that the margin callers attempt to liquidate the shortRecord is reverted.

function transferFrom(address from, address to, uint256 tokenId) public {
// @dev ensure the tokenId can be downcasted to 40 bits
if (tokenId > type(uint40).max) revert Errors.InvalidTokenId();
if (
msg.sender != from && !s.isApprovedForAll[from][msg.sender]
&& msg.sender != s.getApproved[tokenId]
) revert Errors.ERC721InsufficientApproval(msg.sender, tokenId);
address owner = ownerOf(tokenId);
if (owner != from) {
revert Errors.ERC721IncorrectOwner(from, tokenId, owner);
}
if (to == address(0)) {
revert Errors.ERC721InvalidReceiver(address(0));
}
STypes.NFT memory nft = s.nftMapping[tokenId];
address asset = s.assetMapping[nft.assetId];
//@dev If NFT does not exist, ERC721NonexistentToken() will trigger
LibShortRecord.transferShortRecord(asset, from, to, uint40(tokenId), nft);
delete s.getApproved[tokenId];
emit Events.Transfer(from, to, tokenId);
}

There is a check inside transferShortRecord which prevents transferring the shortRecord if it is already flagged, but this does not prevent front running the flagShort call:

function transferShortRecord(
address asset,
address from,
address to,
uint40 tokenId,
STypes.NFT memory nft
) internal {
AppStorage storage s = appStorage();
STypes.ShortRecord storage short = s.shortRecords[asset][from][nft.shortRecordId];
if (short.status == SR.Cancelled) revert Errors.OriginalShortRecordCancelled();
if (short.flaggerId != 0) revert Errors.CannotTransferFlaggedShort();
deleteShortRecord(asset, from, nft.shortRecordId);
uint8 id = createShortRecord(
asset,
to,
SR.FullyFilled,
short.collateral,
short.ercDebt,
short.ercDebtRate,
short.zethYieldRate,
tokenId
);
if (id == Constants.SHORT_MAX_ID) {
revert Errors.ReceiverExceededShortRecordLimit();
}
s.nftMapping[tokenId].owner = to;
s.nftMapping[tokenId].shortRecordId = id;
}

Impact

Shorter's can avoid being liquidated without providing further collateral to keep the assets in a healthy CR.

Tools Used

Manual Review

Recommendations

Revert in transferShortRecord if the shortRecord is in an unhealthy CR.

Updates

Lead Judging Commences

0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-610

Support

FAQs

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