Deposits are eliminated before currently unclaimed reserves when there is no reserve auction
Summary
Reserves that were unclaimed during last reserve auction that's now ended are not utilized for bad debt coverage and are treated as liabilities despite it is the free reserve funds of the pool.
Due to that deposits are being written off when there are still reserve funds exist and deposits' turn as a last resort liquidity source aren't came yet.
Vulnerability Detail
Suppose auctioned reserves weren't taken for any reason: say no market participants were there for that particular pool in the period when reserve auction implied Ajna token price was above market. Then there is no liability, i.e. that amount is free pool funds and to be used ahead of HPB deposits to cover any deficits.
Currently that's not happening, instead unclaimed reserves are frozen and aren't used. I.e. system treats these funds as being liable (while they aren't, auction is ended), so only very last reserve funds, that weren't yet added to the reserve auctions pot, can be used to cover bad debt. When there are not enough such funds, deposits are written off.
Impact
Deposit holders take a loss when the pool in fact do have reserve funds to cover bad debt. This loss isn't a part of the declared mechanics of the protocol.
Reserve auction can end up with not all auctioned reserves taken frequently enough due to, for example:
short period of time when Ajna token were overpriced in it,
or this period intersecting with spike of gas prices that made it unprofitable in absolute terms,
or low liquidity of Ajna token at that time.
I.e. the reason can vary, the point is reserve auction not being sold out can be a regular outcome, while the expected sequence of funds to cover bad debt is typical and is stated in whitepaper (part 7.6, Settling, point 3 in the list), so it will be expected by the lenders that the reserves are covering bad debt first.
Code Snippet
settlePoolDebt() uses the reserves to cover bad debt:
Also, consider accounting for the reserves that were used to cover bad debt (otherwise next reserve auction will be frozen until new income replenishes the funds used for coverage):
// if there's still debt and no collateral
if (borrower.t0Debt != 0 && borrower.collateral == 0) {
// settle debt from reserves -- round reserves down however
+ uint256 reservesUsed = Maths.min(borrower.t0Debt, (params_.reserves / params_.inflator) * 1e18);
+ uint256 kicked = reserveAuction_.kicked;
- borrower.t0Debt -= Maths.min(borrower.t0Debt, (params_.reserves / params_.inflator) * 1e18);
+ borrower.t0Debt -= reservesUsed;
+ if (kicked != 0 && block.timestamp - kicked <= 72 hours) reserveAuction_.unclaimed -= reservesUsed;
// if there's still debt after settling from reserves then start to forgive amount from next HPB
// loop through remaining buckets if there's still debt to settle
hyh
high
Deposits are eliminated before currently unclaimed reserves when there is no reserve auction
Summary
Reserves that were unclaimed during last reserve auction that's now ended are not utilized for bad debt coverage and are treated as liabilities despite it is the free reserve funds of the pool.
Due to that deposits are being written off when there are still reserve funds exist and deposits' turn as a last resort liquidity source aren't came yet.
Vulnerability Detail
Suppose auctioned reserves weren't taken for any reason: say no market participants were there for that particular pool in the period when reserve auction implied Ajna token price was above market. Then there is no liability, i.e. that amount is free pool funds and to be used ahead of HPB deposits to cover any deficits.
Currently that's not happening, instead unclaimed reserves are frozen and aren't used. I.e. system treats these funds as being liable (while they aren't, auction is ended), so only very last reserve funds, that weren't yet added to the reserve auctions pot, can be used to cover bad debt. When there are not enough such funds, deposits are written off.
Impact
Deposit holders take a loss when the pool in fact do have reserve funds to cover bad debt. This loss isn't a part of the declared mechanics of the protocol.
Reserve auction can end up with not all auctioned reserves taken frequently enough due to, for example:
I.e. the reason can vary, the point is reserve auction not being sold out can be a regular outcome, while the expected sequence of funds to cover bad debt is typical and is stated in whitepaper (part 7.6, Settling, point 3 in the list), so it will be expected by the lenders that the reserves are covering bad debt first.
Code Snippet
settlePoolDebt() uses the reserves to cover bad debt:
https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/libraries/external/Auctions.sol#L277-L313
But this reserves do not include
reserveAuction.unclaimed
, which is treated like a liability even when there is no reserve auction:https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/ERC721Pool.sol#L353-L365
https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/ERC20Pool.sol#L356-L378
Reserve auction finishes by timer and there is no adjustments to
unclaimed
if it is not sold fully:https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/libraries/external/Auctions.sol#L642-L663
Tool used
Manual Review
Recommendation
Consider removing currently unsettled
reserveAuction.unclaimed
if reserve auction doesn't take place now as those aren't liabilities:https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/ERC721Pool.sol#L353-L382
https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/ERC20Pool.sol#L356-L378
Reserve auction state can be added as an argument to provide these fields:
https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/libraries/external/Auctions.sol#L199-L205
Also, consider accounting for the reserves that were used to cover bad debt (otherwise next reserve auction will be frozen until new income replenishes the funds used for coverage):
https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/libraries/external/Auctions.sol#L277-L283