code-423n4 / 2023-12-ethereumcreditguild-findings

17 stars 11 forks source link

Dutch Auction Vulnerable to Last-Second Bids #234

Closed c4-bot-7 closed 10 months ago

c4-bot-7 commented 11 months ago

Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L96-L102 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L134-L161

Vulnerability details

Impact

The Dutch auction format is vulnerable to timing manipulation which hinders debt recovery. Additional bid secrecy or flexibility is needed.

Proof of Concept

The Dutch auction style allows snipers to bid high collateral for low debt at the very end. This reduces debt recovery. Timing manipulation is easy The key functions are in AuctionHouse.sol.

When an auction starts, the full debt amount is recorded: #L96-L102

auctions[loanId] = Auction({
    startTime: block.timestamp,
    endTime: 0,
    lendingTerm: msg.sender,
    collateralAmount: loan.collateralAmount,
    callDebt: callDebt
});

The getBidDetail() function calculates how much collateral vs debt is currently offered: https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L134-L161

if (block.timestamp < _startTime + midPoint) {
    // ask for the full debt
    creditAsked = auctions[loanId].callDebt;

    // compute amount of collateral received
    uint256 elapsed = block.timestamp - _startTime; // [0, midPoint[
    uint256 _collateralAmount = auctions[loanId].collateralAmount; // SLOAD
    collateralReceived = (_collateralAmount * elapsed) / midPoint;
}
// second phase of the auction, where less and less CREDIT is asked
else if (block.timestamp < _startTime + auctionDuration) {
    // receive the full collateral
    collateralReceived = auctions[loanId].collateralAmount;

    // compute amount of CREDIT to ask
    uint256 PHASE_2_DURATION = auctionDuration - midPoint;
    uint256 elapsed = block.timestamp - _startTime - midPoint; // [0, PHASE_2_DURATION[
    uint256 _callDebt = auctions[loanId].callDebt; // SLOAD
    creditAsked = _callDebt - (_callDebt * elapsed) / PHASE_2_DURATION;
}
// second phase fully elapsed, anyone can receive the full collateral and give 0 CREDIT
// in practice, somebody should have taken the arb before we reach this condition.
else {
    // receive the full collateral
    collateralReceived = auctions[loanId].collateralAmount;
    //creditAsked = 0; // implicit
}
}

Does not account for last-second bid timing manipulation.

An attacker could bid right before the end of phase 1 to snipe all collateral for less debt.

Attacker Joe borrows 10 ETH with 100 ETH collateral. Auction starts and Joe bids 1 ETH for 9 ETH debt at the very end. Joe recovers 99 ETH while only repaying 1 ETH.

Recommended Mitigation Steps

Ensuring fair bids via secrecy or time flexibility could improve debt recovery.

Assessed type

Timing

0xSorryNotSorry commented 10 months ago
Does not account for last-second bid timing manipulation.

An attacker could bid right before the end of phase 1 to snipe all collateral for less debt.

How?

c4-pre-sort commented 10 months ago

0xSorryNotSorry marked the issue as insufficient quality report

Trumpero commented 10 months ago

Invalid. Bidders need to pay full debt in phase 1 of the auction.

c4-judge commented 10 months ago

Trumpero marked the issue as unsatisfactory: Invalid