Platform: Code4rena
Start Date: 18/10/2023
Pot Size: $36,500 USDC
Total HM: 17
Participants: 77
Period: 7 days
Judge: MiloTruck
Total Solo HM: 5
Id: 297
League: ETH
Rank: 6/77
Findings: 1
Award: $2,000.24
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: Saintcode_
Also found by: T1MOH, falconhoof
2000.2368 USDC - $2,000.24
The AccountingEngine.sol
contract serves as the protocol's central component responsible for initializing both debt and surplus auctions. Debt auctions are consistently initiated with a predefined minimum bid referred to as debtAuctionBidSize. This is done to ensure that the protocol can only auction debt that is not currently undergoing an auction and is not locked within the debt queue, as articulated in the comment found on IAccountingEngine:248
: "It can only auction debt that has been popped from the debt queue and is not already being auctioned". This necessity prompts the check on AccountingEngine:181:
if (_params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance))
This check verifies that there is a sufficient amount of bad debt available to auction.
The issue stems in the line 183, where _settleDebt
is called, this aims to ensure that only bad debt is considered for the auction. However, if the remaining bad debt, after settlement, falls below the specified threshold (debtAuctionBidSize <= unqueuedUnauctionedDebt())
, the auction still starts with an incorrect amount of bad debt coverage, diluting the protocol Token when it is not yet needed.
Non-existent debt gets auctioned when it is not necesary which dilutes the protocol token.
The attached Proof of Concept (PoC) demonstrates two test cases:
auctionDebt
function is invoked directly. It is expected to behave in the same manner as in the first test case (because the funciton calls _settleDebt
internally). However, in this second test case, the execution follows a different path. Rather than replicating the behavior of the initial test case by reverting not starting the auction, the execution succeeds, resulting in the creation of a debt auction even when there is no existing debt.The following tests have been created using the protocol's test suite:
First test case (original two call flow):
function test_missing_insuficient_debt_check_part2() public { accountingEngine.modifyParameters("surplusTransferPercentage", abi.encode(1)); accountingEngine.modifyParameters("extraSurplusReceiver", abi.encode(1)); safeEngine.createUnbackedDebt(address(0), address(accountingEngine), rad(100 ether)); _popDebtFromQueue(100 ether); accountingEngine.settleDebt(100 ether); vm.expectRevert(); uint id = accountingEngine.auctionDebt(); }
Second test case (vulnerability):
function test_missing_insuficient_debt_check_part2() public { accountingEngine.modifyParameters("surplusTransferPercentage", abi.encode(1)); accountingEngine.modifyParameters("extraSurplusReceiver", abi.encode(1)); safeEngine.createUnbackedDebt(address(0), address(accountingEngine), rad(100 ether)); _popDebtFromQueue(100 ether); uint id = accountingEngine.auctionDebt(); }
Both test cases should revert and not let a user create a debt Auction under insufficient debt circumstances, but as stated on the report the second test case succeeds and creates the Auction.
Manual Review
To mitigate this risk, I suggest introducing the check (_params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance))
after calling _settleDebt to ensure there exists enough amount of bad in the contract after the settling.
Other
#0 - c4-pre-sort
2023-10-25T23:07:26Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-10-25T23:07:31Z
raymondfam marked the issue as primary issue
#2 - raymondfam
2023-10-25T23:13:59Z
Seems like _params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance) check is well in place prior to settling the debt in the second case, but will let the sponsor look into it.
#3 - c4-pre-sort
2023-10-27T04:14:39Z
raymondfam marked the issue as high quality report
#4 - c4-judge
2023-11-04T02:53:17Z
MiloTruck marked the issue as satisfactory
#5 - c4-judge
2023-11-04T02:53:46Z
MiloTruck marked the issue as selected for report
#6 - MiloTruck
2023-11-04T02:57:46Z
Due to the misplacement of the _params.debtAuctionBidSize > _unqueuedUnauctionedDebt(_debtBalance)
check before _settleDebt()
is called, the protocol will create debt auctions even when the amount of bad debt is below params.debtAuctionBidSize
. This leads to dilution of the protocol token as bidders can buy non-existent debt, thereby destabilizing the value of protocol's token. As such, I agree with high severity.
#7 - pi0neerpat
2023-12-18T17:42:05Z