Ethereum Credit Guild - aslanbek's results

A trust minimized pooled lending protocol.

General Information

Platform: Code4rena

Start Date: 11/12/2023

Pot Size: $90,500 USDC

Total HM: 29

Participants: 127

Period: 17 days

Judge: TrungOre

Total Solo HM: 4

Id: 310

League: ETH

Ethereum Credit Guild

Findings Distribution

Researcher Performance

Rank: 33/127

Findings: 1

Award: $430.75

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 3docSec

Also found by: aslanbek, btk, ether_sky, rvierdiiev

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
edited-by-warden
duplicate-292

Awards

430.7502 USDC - $430.75

External Links

Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L331-L334

Vulnerability details

Impact

Whenever bad debt occurs, it is absorbed among all gUSDC holders by decreasing creditMultiplier, so even the last lenders to quit the market are able to redeem their credit tokens.

However, creditMultiplier formula uses totalSupply, which does not reflect the pending rebasing rewards. This will result in creditMultiplier becoming lower than needed, making a percentage of the PSM's pegToken balance unredeemable for all lenders.

Proof of Concept

  1. Alice calls psm#mintAndEnterRebase(500_000 USDC) and receives 500_000 gUSDC.

  2. Bob supplies 200_000 USDT and borrows 100_000 gUSDC from LendingTerm, redeems 100_000 gUSDC for 100_000 USDC in PSM.

  3. Charlie, same as Bob, supplies 200_000 USDT, borrows 100_000 gUSDC from LendingTerm, redeems 100_000 gUSDC for 100_000 USDC in PSM.

  4. 1 year later, just before the loan is due, Bob returns principal + interest (10%), by minting 110_000 gUSDC for 110_000 USDC in PSM and calling term#repay; of which 100_000 gUSDC is burnt, 10_000 gUSDC go to rewards (for the sake of example, the config is such that 100% of interest goes to gUSDC rebasing).

  5. On the same day, Charlie fails to repay his debt, so his loan is called and auctioned. The auction fails and the principal is forgiven (e.g. due to USDT being frozen in the contact), which results in:

creditMultiplier = (gUSDC.totalSupply() - debt) / gUSDC.totalSupply() = (500_000 - 100_000) * 1e18 / 500_000 = 0.8e18

1 month later Alice owns 510_000 gUSDC, and is able to redeem 408_000 USDC from PSM, but PSM has:

500_000 (alice) - 100_000 (bob) - 100_000 (charlie) + 110_000 (bob) = 410_000 USDC

As a result, 2_000 USDC without gUSDC representation are left in PSM. These 2_000 USDC could be temporarily taken out from the PSM by someone who mints gUSDC in a LendingTerm. But once they repay the debt, these unclaimable 2_000 USDC will be put back into the PSM. In the end of the SimplePSM's lifecycle (when all future loans are closed and all lenders left the market), these 2_000 USDC will stay in the contract.

Foundry PoC

Note that this is an oversimplified example:

  1. There could be any number of lenders instead of just Alice, and all of them would essentially lose the same percentage of pegToken rewards.
  2. creditMultiplier will be computed incorrectly as long as bad debt is marked down while there are any pending rebasing rewards, which is extremely likely.
  3. The loan does not necessarily have to be forgiven - any auction where the bid happened after the price went below the market value may create bad debt.

The issue exists because creditMultiplier formula uses totalSupply, which neglects rebasing rewards. Using targetTotalSupply will result in all gUSDC lenders being able to redeem exactly the whole PSM balance:

creditMultiplier = (targetTotalSupply() - debt) * 1e18 / targetTotalSupply() = (510_000 - 100_000) * 1e18 / 510_000 = 410_000 / 510_000 * 1e18

Therefore, use targetTotalSupply instead of totalSupply:

-               uint256 creditTotalSupply = CreditToken(_credit).totalSupply();
+               uint256 creditTotalSupply = CreditToken(_credit).targetTotalSupply();
                uint256 newCreditMultiplier = (creditMultiplier *
                    (creditTotalSupply - loss)) / creditTotalSupply;
                creditMultiplier = newCreditMultiplier;

Assessed type

Error

#0 - c4-pre-sort

2024-01-05T14:43:39Z

0xSorryNotSorry marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-01-05T14:45:30Z

0xSorryNotSorry marked the issue as duplicate of #292

#2 - c4-judge

2024-01-29T18:32:55Z

Trumpero marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter