xanoutas/solidity-security-findings huggingface dataset:
Manipulable CREDIT `totalSupply` allows by flashloaning in SimplePSM
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


When a LendingTerm experiences loss, it is meant to be distributed uniformly across all users of given term. This is done by recalculating `creditMultiplier` on each `ProfitManager.notifyPnL()`:

```javascript
 function notifyPnL(
        address gauge,
        int256 amount
    ) external onlyCoreRole(CoreRoles.GAUGE_PNL_NOTIFIER) {
        // [...]
                // empty the surplus buffer
                loss -= _surplusBuffer;
                surplusBuffer = 0;
                CreditToken(_credit).burn(_surplusBuffer);
                emit SurplusBufferUpdate(block.timestamp, 0);

                // update the CREDIT multiplier
                uint256 creditTotalSupply = CreditToken(_credit).totalSupply(); // @audit manipulable totalSupply() via flashloan (or short term loan) minted via PSM.
                uint256 newCreditMultiplier = (creditMultiplier *
                    (creditTotalSupply - loss)) / creditTotalSupply;
                creditMultiplier = newCreditMultiplier;
                emit CreditMultiplierUpdate(
                    block.timestamp,
                    newCreditMultiplier
                );
```

In particular this calculation:
```javascript
uint256 newCreditMultiplier = (creditMultiplier *
                    (creditTotalSupply - loss)) / creditTotalSupply;
```

Here, the bigger `CREDIT.totalSupply()` in relation to notified loss, the smaller `creditMultiplier` change is. This allows users to game the credit token loss mechanism by arbitrary increasing CREDIT token supply just for the time of loss notification transaction. This costs very little to perform, and undermines one of the core system invariants, because the loss is not distributed across the users, but just disappears. This results in CREDIT token being overpriced in relation to how the market price should be.

## Impact
Users of the protocol can manipulate loss incurred on the gauge by a flashloan/short term leveraged loan.

## Proof of Concept
Let's say we have 1M$ credit tokens, and 100k$ loss. Old Multiplier would be 
oldcreditMultiplier * (900K$) / 1M$ => newCreditMultiplier = 0.9 * oldcreditMultiplier


While if I temporarily increase credit tokens supply to 5M$, it gives only 2% loss, not 10% as above:
oldcreditMultiplier * (4.9M$) / 5M$ => newCreditMultiplier = 0.98 * oldcreditMultiplier

## Tools Used

Manual analysis

## Recommended Mitigation Steps
1. Consider using RateLimitedMinter for SimplePSM
2. Introduce CREDIT totalSupply checkpointing to mitigate Just-In-Time liquidity issue.


## Assessed type

Math
Absence of Sequencer Uptime check risks Dutch Auctions executing at poor prices
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/AuctionHouse.sol#L118-L161


# Vulnerability details

## Impact
Since the anticipated deployment of Ethereum Credit Guild on the Ethereum mainnet and L2s like Arbitrum, we must consider the existence of the Sequencer and the risks it may pose. While using Dutch Auctions on L2s, we notice that the sequencer uptime isn't taken into account, but similar to what occurred on the [Arbitrum network on December 15th](https://dedaub.com/blog/arbitrum-sequencer-outage), the sequencer outage is a possibility for the future.

In a case where there are ongoing auctions while the sequencer is offline, the auction prices will continue to decrease during the sequencer is offline. When the sequencer comes back online, users will have the opportunity to bid these auctions at prices significantly lower than the market rate. This may actually even be the favorable outcome, as missing out entirely on the auction is also a possibility. Referring to the example mentioned in [AuctionHouse.sol#L33-L37](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/AuctionHouse.sol#L33-L37), considering a 30-minute auction with a midPoint of 10 minutes and 50 seconds, if the sequencer remains inactive for just 11 minutes (passing the `midPoint`) it could lead to bad prices and if it stays offline for the entire 30 minutes, it would lead to forced debt forgiveness through `forgive()`. The size of bad debts can be anything and can have a dramatic impact considering the importance of their liquidation to the health of the protocol.

## Proof of Concept

Read the scenario above and [you can also take a look at this report where a similar case occurred.](https://github.com/sherlock-audit/2023-06-Index-judging/issues/40)

## Tools Used

Manual Review

## Recommended Mitigation Steps

Determine the maximum tolerable delay for the sequencer (11 minutes may be a good choice) and invalidate the auction if the sequencer was down for maximum tolerable delay or more during the auction period.











## Assessed type

Context
ERC20RebaseDistributor : `burn  an be DOSed due to underflow issue.
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20RebaseDistributor.sol#L495


# Vulnerability details

## Impact
distribute can not happen for reBasing case.

## Proof of Concept

An user can call the function `distribute` and distribute the tokens equally to all the accounts.

Before the distribution, the amount of tokens are burnt by calling [_burn](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20RebaseDistributor.sol#L342) function.
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20RebaseDistributor.sol#L461-L505
```solidity
    function _burn(address account, uint256 amount) internal virtual override {
        // if `account` is rebasing, materialize the tokens from rebase first, to ensure
        // proper behavior in `ERC20._burn()`.
        RebasingState memory _rebasingState = rebasingState[account];
        uint256 balanceBefore;
        uint256 _rebasingSharePrice;
        if (_rebasingState.isRebasing == 1) {
            balanceBefore = ERC20.balanceOf(account);
            _rebasingSharePrice = rebasingSharePrice();
            uint256 rebasedBalance = _shares2balance(
                _rebasingState.nShares,
                _rebasingSharePrice,
                0,
                balanceBefore
            );
            uint256 mintAmount = rebasedBalance - balanceBefore;
            if (mintAmount != 0) {
                ERC20._mint(account, mintAmount);
                balanceBefore += mintAmount;
                decreaseUnmintedRebaseRewards(mintAmount);
                emit RebaseReward(account, block.timestamp, mintAmount);
            }
        }


        // do ERC20._burn()
        ERC20._burn(account, amount);


        // if `account` is rebasing, update its number of shares
        if (_rebasingState.isRebasing == 1) {
            uint256 balanceAfter = balanceBefore - amount;
            uint256 sharesAfter = _balance2shares(
                balanceAfter,
                _rebasingSharePrice
            );
            uint256 sharesBurnt = _rebasingState.nShares - sharesAfter; ------->>> will revert due to underflow (`nShares` is uint248 and `sharesAfter` is uint252)
            rebasingState[account] = RebasingState({
                isRebasing: 1,
                nShares: uint248(sharesAfter)
            });
            updateTotalRebasingShares(
                _rebasingSharePrice,
                -int256(sharesBurnt)
            );
        }
    }
```

As shown in above code block, in extreme market conditions, the price values would unrealistic and calculated `sharesAfter` will be greater than nShares . In this case, the above code will revert due to underflow issue.

## Tools Used

Manual review.

## Recommended Mitigation Steps

Update the codes as shown below.

https://github.com/code-423n4/
Users may avoid gauge loss penalty by frontrunning the LendingTerm notifying loss
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L725-L825
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L695-L722
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L292-L405


# Vulnerability details

In case of `LendingTerm.onBid()`, or `LendingTerm.forgive()`, `ProfitManager.notifyPnL()` is called to signal loss. If the loss is signalled, any holder of losing gauge has to apply the loss. Currently, the loss may be very bad for the user, because all guild tokens voted on the gauge are burned with even slightest loss. Naturally, noone wants that happening to them, hence anyone can watch the mempool for transactions incurring losses and frontrun it, saving all their GUILD tokens.

## Impact
Users are able to game gauge loss penalty by frontrunning the LendingTerm notifying loss.

## Proof of Concept
1. Transaction signalling loss to the protocol is put into mempool.
2. User observes it and frontruns the transaction to withdraw their stake.
3. The user backruns the transaction and stakes again.
4. The user skipped the punishment completely.

## Tools Used

Manual analysis

## Recommended Mitigation Steps
Consider introducing lock time for gauge staking and unstaking.


## Assessed type

Error
Offboarded terms can still update PnL and mint credit tokens
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/LendingTermOffboarding.sol#L153-L170
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/LendingTermOnboarding.sol#L246-L262


# Vulnerability details

When a LendingTerm is offboarded, it cannot be used until it's onboarded again. There are multiple occasions when a term is offboarded, one of them being temporary diverse market condition, or deprecating it due to a security vulnerability. The second case is interesting, because LendingTerm posseses very important roles in the system: `GAUGE_PNL_NOTIFIER` and `RATE_LIMITED_CREDIT_MINTER`. Those allow to mint CREDIT tokens and notify about profit & loss. Misusing those roles is critical threat to the system.

## Impact
Ability to misuse `GAUGE_PNL_NOTIFIER` and `RATE_LIMITED_CREDIT_MINTER` roles by offboarded terms.

## Proof of Concept
1. New version of LendingTerm is deployed. It contains new features.
2. Security vulnerability allowing a malicious actor to notify forged loss is found, hence the term is being offboardded
3. Because during offboartding, the `GAUGE_PNL_NOTIFIER` role is not removed, the contract can still `notifyPnL()`

## Tools Used

Manual analysis

## Recommended Mitigation Steps
Remove `GAUGE_PNL_NOTIFIER` and `RATE_LIMITED_CREDIT_MINTER` roles from LendingTerm upon offboarding process.


## Assessed type

Error
The bid function can DOS on the second phase of the auction because of an underflow 
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L751-L797
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L331-L333


# Vulnerability details

## Impact
The bid can revert because an underflow since when a loss occur the profitManager compute the new credit multiplier by this formula: 
```
 uint256 newCreditMultiplier = (creditMultiplier * (creditTotalSupply - loss)) / creditTotalSupply;
```
When a user take a loan and the loan is called there is two phase. One phase where the collateral offered increase and the creditAsked is the callDebt and another phase after the midpoint where less and less credit are asked and the collateral is offered to the bidder. 
After a bidder call bid during the second phase the AuctionHouse will call the lendingTerm 'onBid'. In this function the bid of the bidder will be burn (L-629) and the LendingTerm will call the ProfitManager function ``notifyPnL``(L-292) the ProfitManager will burn the surplusBuffer amount in order to decrement the loss.
And will use the loss to compute the new credit multiplier. but the loss can be hire than the credit totalSupply.

 for example if bob mint 100e18 with the SimplePSM and Alice borrow 100e18 and redeem them to have the pegToken amount of bob the totalSupply is 0 if the loan of Alice the partialRepay delay passed and the loan is called. During the second phase the system will ask les and les credit and it will be impossible to bid because if a bidder mint credit with the simplePSM in order to repay the debt of Alice this amount will be burn and the totalSupply is Zero put not the loss. so the call will revert until someone borrow or mint with the SimplePSM the loss will be increase until the end of the auction where we will have to forgive and it will be impossible is no one mint credits

## Proof of Concept

````
function testBidDOS() external {
         address user= makeAddr("user"); 
        uint256 creditMultiplier = profitManager.creditMultiplier();
        collateral.mint(address(user), 1000e18);
        vm.startPrank(user); 
        collateral.approve(address(term),1000e18);
       bytes32 loanId = term.borrow((1000e18 * _CREDIT_PER_COLLATERAL_TOKEN) / creditMultiplier,1000e18);
        vm.stopPrank();
        vm.warp(block.timestamp+13);
        uint256 loanDebt = term.getLoanDebt(loanId);
         vm.prank(user); 
       credit.approve(address(term), (loanDebt * _MIN_PARTIAL_REPAY_PERCENT) / 1e18);
         vm.prank(user); 
       term.partialRepay(loanId,(loanDebt * _MIN_PARTIAL_REPAY_PERCENT) / 1e18);
       vm.warp(block.timestamp+_MAX_DELAY_BETWEEN_PARTIAL_REPAY+1); 
       vm.prank(msg.sender);
       term.call(loanId);
       vm.warp(block.timestamp + 900); 
        LendingTerm.Loan memory loan = term.getLoan(loanId);
         uint256 principal = (loan.borrowAmount * loan.borrowCreditMultiplier) / creditMultiplier;
       (uint256 collateralAsked,
Extending distribution period , delays distribution of unminted rebase rewards 
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20RebaseDistributor.sol#L364-L374


# Vulnerability details

## Summary 
distribute credit tokens extends the distribution period, this leads to delay distribution of unminted rebase rewards, which is not preferred and can harm some users.
## Impact
improper calculation of rebase rewards 

## Proof of Concept

```
function testExtendDistributionPeriod() public {

    // initial state: 2 addresses rebasing, both of them have 100 tokens
        vm.prank(alice);
        token.enterRebase();
        vm.prank(bobby);
        token.enterRebase();
        token.mint(alice, 100);
        token.mint(bobby, 100);

        // distribute 120 profits and then jump to targetTimestamp( end of the distribution period ) 
        token.mint(address(this), 120);
        token.approve(address(token), 120);
        token.distribute(120);
        // t1 = first distribution time
        uint256 t1 = block.timestamp;
        // t2 = middle of t1 and end of distribution period , where we perform second distribution 
        uint256 t2 = block.timestamp + token.DISTRIBUTION_PERIOD()/2;
        // t3 = end of the distribution period (first distribution)
        uint256 t3 = block.timestamp + token.DISTRIBUTION_PERIOD();
        vm.warp(t3);

        // as we expected after the distribution period at t3, each rebasing address has 160 tokens 
        // in this scenario no one calls distribute from last distribution 
        // scenario 1 : balance in t3 = 160 
        assertEq(token.balanceOf(alice), 160);
        assertEq(token.balanceOf(bobby), 160);

        // now consider a scenario where a user perform distribute before end of the distribution period  
        // now we go back sometime ago to t2 (in the middle, between start and end of the distribution period) 
        // in this scenario someone distributes 12 token at this time
        vm.warp(t2);
        token.mint(address(this), 12);
        token.approve(address(token), 12);  
        token.distribute(12);

        // now we go forward to t3
        // as we see every address has 148  tokens, unlike the first scenario 
        // If we don't perform distribution at t2 
        // scenario 2 : balance = 148
        // this clearly demonstrates that extending distribution period delays token distribution which is not preferred since some users may exit rebase early and they don't get all the distributed rewards in their holding time . 
        vm.warp(block.timestamp + token.DISTRIBUTION_PERIOD()/2);

        assertEq(token.balanceOf(alice), 148);
        assertEq(token.balanceOf(bobby), 148);
    })
```

## Tools Used
Manual Review 
## Recommended Mitigation Steps
Consider using a distribution rate for calculating rebase rewards, and adjust rate based on the distribution amount in different periods.


## Assessed type

Other
Missing Input Validation:
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/AuctionHouse.sol#L75


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
Missing Input Validation: There is no validation on the callDebt parameter in startAuction, which could potentially be set to an incorrect value if the calling contract has a bug.


## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
  function startAuction(bytes32 loanId, uint256 callDebt) external {
        // check that caller is a lending term that still has PnL reporting role
        require(
            core().hasRole(CoreRoles.GAUGE_PNL_NOTIFIER, msg.sender),
            "AuctionHouse: invalid caller"
        );

        // check the loan exists in calling lending term and has been called in the current block
        LendingTerm.Loan memory loan = LendingTerm(msg.sender).getLoan(loanId);
        require(
            loan.callTime == block.timestamp,
            "AuctionHouse: loan previously called"
        );

        // check auction for this loan has not already been created
        require(
            auctions[loanId].startTime == 0,
            "AuctionHouse: auction exists"
        );

        // save auction in state
        auctions[loanId] = Auction({
            startTime: block.timestamp,
            endTime: 0,
            lendingTerm: msg.sender,
            collateralAmount: loan.collateralAmount,
    @>        callDebt: callDebt
        });
        nAuctionsInProgress++;

        // emit event
        emit AuctionStart(
            block.timestamp,
            loanId,
            LendingTerm(msg.sender).collateralToken(),
            loan.collateralAmount,
            callDebt
        );
    }
## Tools Used
Manual Analysis
## Recommended Mitigation Steps


## Assessed type

Invalid Validation
Credit Multiplier DoS
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If the `creditMultiplier` reaches 0 it will be DoS-ed since it won't never increase again. This would happen if a loan that has all the credit of a loan token is forgiven(e.g. auction could not be settled) and therefore a 'loss' equal to `totalCreditSupply` is notified to the `ProfitManager`, resulting in a `creditMultiplier` equal to 0 if there is no surpluss buffer, that will DoS all the lending terms for that token, and brick them permanently since the `CreditMultiplier` is used as a divisor, causing a `division or modulo by 0` error for any borrow. This scenario is very unlikely but still possible, and malicious upgradeable tokens could contribute to this happening. 

## Proof of Concept
**Note**: For this PoC to work remove the "renounceRole" line in the 'setUp' function so we can grant more roles in the test
```solidity
function testCreditMultiplierDoS() public {
        // create a second term for this same loan token
        LendingTerm term2 = LendingTerm(Clones.clone(address(new LendingTerm())));
        term2.initialize(
            address(core),
            LendingTerm.LendingTermReferences({
                profitManager: address(profitManager),
                guildToken: address(guild),
                auctionHouse: address(auctionHouse),
                creditMinter: address(rlcm),
                creditToken: address(credit)
            }),
            LendingTerm.LendingTermParams({
                collateralToken: address(0x1),// another collataral token
                maxDebtPerCollateralToken: 2000e18, // 2000, same decimals
                interestRate: 0.10e18, // 10% APR
                maxDelayBetweenPartialRepay: 0,
                minPartialRepayPercent: 0,
                openingFee: 0,
                hardCap: 20_000_000e18 // 20M CREDIT
            })
        );
        core.grantRole(CoreRoles.GAUGE_PNL_NOTIFIER, address(term2));
        core.grantRole(CoreRoles.RATE_LIMITED_CREDIT_MINTER, address(term2));

        // add gauge and vote for it
        guild.addGauge(1, address(term2));
        guild.mint(address(this), 1e18);
        guild.incrementGauge(address(term2), 1e18);


        // prepare
        uint256 borrowAmount = 500e18;
        uint256 collateralAmount = 15e18;
        collateral.mint(address(borrower), collateralAmount * 2);
    
        // borrow
        vm.startPrank(borrower);
        collateral.approve(address(term), collateralAmount);
        bytes32 loanId = term.borrow(borrowAmount, collateralAmount);
        vm.stopPrank();

        // 1 year later, interest accrued
        // assume it was not fully repaid because the collateral is frozen
        vm.warp(block.timestamp + term.YEAR());
        vm.roll(block.number + 1);

        // the debt is forgiven
        vm.prank(governor);
        term.for