xanoutas/solidity-security-findings huggingface dataset:
The Credit Multiplier can be set to zero because of a loss.
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The credit multiplier can be set to zero if the loss is equal to the totalSupply it's because when a loss occur the system doesn't check that the totalSupply is hire than the loss since the majority of borrowers redeem their credit to have the pegToken it can happen and it will be a disaster for the protocol the functions in the LendingTerm: borrow, repay, and partialRepay, bid in auctionHouse mint and redeem in the Simple PSM will not work anymore for all the LendingTerms.

## Proof of Concept
he 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 if the loss is equal to the credit total supply the credit Multiplier will be equal to zero. 
```
function testBidCataclysme() 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(10000e18,100e18);
        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 + 1600); 
        LendingTerm.Loan memory loan = term.getLoan(loanId);
         uint256 principal = (loan.borrowAmount * loan.borrowCreditMultiplier) / creditMultiplier;
       (uint256 collateralAsked, uint256 creditAsked)= auctionHouse.getBidDetail(loanId);
    
       vm.prank(user);
       credit.approve(address(term), creditAsked);

       credit.mint(address(this), 6330434720885811570152-6330434638496783243677);

       vm.prank(user);
       auctionHouse.bid(loanId);
       assert(profitManager.creditMultiplier()==0);
    }
```
In this test bob borrow 10000e18 in the
Public state-modifying `cancel()` function may lead to future vulnerabilties
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/GuildVetoGovernor.sol#L234-L267


# Vulnerability details

## Impact
The `GuildVetoGovernor` contract has a public state-modifying `cancel()` function  inherited from the `Governor` contract. This function sets the `proposal.canceled` boolean, leading to `Governor.state()` returning `Proposal.Canceled`. 

In `GuildVetoGovernor.state()`'s  NatSpec documentation it is stated that the state can be one of:
```
    - [...]
	- ProposalState.Canceled  (2) If a veto was created but the timelock action has been cancelled through another mean before the veto vote succeeded. The internal `_cancel()` function is not reachable by another mean (no public `cancel()` function), so this is the only case where a proposal will have Canceled status.
```
And in the inline documentation:
```
// Proposal cannot be Canceled because there is no public cancel() function.
```

```
// at this stage, status from super can be one of: Active, Succeeded, Defeated
```

These assumptions aren't true as `super.state()` can indeed return `ProposalState.Canceled` if the proposer has called the public `cancel()` function. While this does not affect the current functionality of `GuildVetoGovernor`,  it could potentially lead to vulnerabilities in future code updates, as the developers might not account for the `Proposal.Canceled` state.

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/GuildVetoGovernor.sol#L234-L267
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/fd81a96f01cc42ef1c9a5399364968d0e07e9e90/contracts/governance/Governor.sol#L348

## Proof of Concept
Consider a scenario where a future update to the `GuildVetoGovernor` contract introduces additional logic that depends on the state of a proposal returned by `super.state()`. If the developers are not aware that `Proposal.Canceled` is a possible state, they might not account for it in their new logic, leading to unexpected behavior or vulnerabilities.

## Tools Used
Manual review

## Recommended Mitigation Steps
Override the `cancel()` function in the `GuildVetoGovernor` contract to prevent its usage, as it is not intended to be used in this context. This can be done by adding a `cancel()` function in `GuildVetoGovernor` that simply reverts with an "Unimplemented" message. 


## Assessed type

Other
Potential inconsistent state in `LendingTermOffboarding` can lead to redemptions remaining paused forever
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/LendingTermOffboarding.sol#L154
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/LendingTermOffboarding.sol#L191-L195


# Vulnerability details

## Impact
The `LendingTermOffboarding` contract has a potential issue that could lead to an inconsistent state in the system. This inconsistency could brick redemptions and disrupt the normal functioning of the lending term offboarding process.

The issue arises when a lending term is offboarded and then immediately re-onboarded without the `cleanup()` function being called. This would allow anyone to immediately offboard the term again, leading to an incorrect value in `nOffboardingsInProgress`. This would in turn block the unpausing of redemptions in the `PSM` as the `nOffboardingsInProgress` variable could not be decreased down to 0 again.

## Proof of Concept
Consider the following sequence of events:

1. A `LendingTerm` is offboarded with the intention of calling all loans and immediately re-onboarding it (due to e.g. some loans being so old that the interest accrued brings them close to being underwater) 
2. The `LendingTerm` is immediately re-onboarded.
3. No one calls the `cleanup()` function during the time it is being offboarded.
4. Since `canOffboard[term]` is still `true`, anyone can call the `offboard()` function again.
5. This adds the term to the `_deprecatedGauges` set again and increases `nOffboardingsInProgress` to 2.
6. Now, `cleanup()` can only be called once as `canOffboard[term]` will be `false` on subsequent calls, which makes it impossible to unpause redemptions in the `PSM`.

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/LendingTermOffboarding.sol#L154
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/LendingTermOffboarding.sol#L191-L195

## Tools Used
Manual review

## Recommended Mitigation Steps
To mitigate this issue, consider adding a check in the `proposeOnboard()` function to ensure that a term cannot be re-onboarded if it hasn't been cleaned up. This could be done by checking if `LendingTermOffboarding.canOffboard[term]` is `false` before allowing the term to be onboarded. 


## Assessed type

Other
SurplusGuildMinter's operations can be DOS'ed if there exist enough active gauges
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Since operations on a list of gauges (for eg. claimRewards) can be DOS'ed on some chains having lower block gas limits, there is a protection in place called the maxGauges which is currently set to 10.

But the problem is that SurplusGuildMinter is being set to canExceedMaxGauges in the GIP_0 deployment script. This means that the SGM can open positions in any number of gauges. If enough active lending terms (gauges) exist, then users can stake through the SGM into many gauges such that now they get added to the userGauges reference list for the SGM address. This can make the userGauges list really long and cause DOS because of not fitting into the block gas limit. 

The userGauges list is being used in ProfitManager.claimRewards function which iterates over the complete list of gauges for an address and claims its rewards from there. This claimRewards function is also an important part of the SurplusGuildMinter.getRewards() function which is basically used in all functionalities of SGM. 

The number of gauges in userGauges[SGM address] can be increased very easily because every time a user stakes into a new gauge in SGM, the SGM will have a new gauge added to its list. Thus, this can cause DOS on some chains, which will lead to complete bricking of the Surplus Guild Minter, probably forever. 

## Proof of Concept
getRewards calls claimRewards and is used in all operations on SGM :
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L239

claimRewards iterates over the whole of userGauges list :
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L444

On incrementGauge, every new gauge is added to the userGauges list :
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20Gauges.sol#L235

## Tools Used
Manual review

## Recommended Mitigation Steps
Cant think of a straightforward way to prevent this, maybe have a hard capping on surplus guild minter's maxGauges.


## Assessed type

DoS
No check for sequencer uptime can lead to dutch auctions failing or executing at bad prices
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/rate-limits/RateLimitedMinter.sol#L52


# Vulnerability details

## Impact
The `AuctionHouse` contract implements a Dutch auction mechanism to recover debt from collateral. However, there is no check for sequencer uptime, which could lead to auctions failing or executing at unfavorable prices. 

The current deployment parameters allow auctions to succeed without a loss to the protocol for a duration of 10m 50s. If there's no bid on the auction after this period, the protocol has no other option but to take a loss or forgive the loan. This could have serious consequences in the event of a network outage, as any loss results in the slashing of all users with weight on the term. 

Network outages and large reorgs happen with relative frequency. For instance, Arbitrum suffered an hour-long outage just two weeks ago ([source](https://github.com/ArbitrumFoundation/docs/blob/50ee88b406e6e5f3866b32d147d05a6adb0ab50e/postmortems/15_Dec_2023.md)).

## Proof of Concept
Consider the following scenario:

1. A loan is called and an auction is initiated.
2. The network experiences an outage, causing the sequencer to go offline.
3. The auction fails to receive any bids within the 10m 50s window due to the outage.
4. The protocol is forced to take a loss (if there's still a bid after the `midPoint` and before the auction ends) or forgive the loan, both leading to the complete slashing of all users with weight on the term.

## Tools Used
Manual review

## Recommended Mitigation Steps
To mitigate this issue, consider integrating an external uptime feed such as [Chainlink's L2 Sequencer Feeds](https://docs.chain.link/data-feeds/l2-sequencer-feeds). This would allow the contract to invalidate an auction if the sequencer was ever offline during its duration. Alternatively, implement a mechanism to restart an auction if it has received no bids.


## Assessed type

Other
Collateral is not returned to the borrower when partially repayed
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L486-L630


# Vulnerability details



## Proof of Concept

Take a look at both functions from [LendingTerm.sol#L486-L630](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L486-L630)

```solidity

    function _repay(address repayer, bytes32 loanId) internal {
        Loan storage loan = loans[loanId];
//...ommited for brevity

    /// pull debt from the borrower and replenish the buffer of available debt that can be minted.
        CreditToken(refs.creditToken).transferFrom(
            repayer,
            address(this),
            loanDebt
        );
        if (interest != 0) {
            // forward profit portion to the ProfitManager
            CreditToken(refs.creditToken).transfer(
                refs.profitManager,
                interest
            );

            // report profit
            ProfitManager(refs.profitManager).notifyPnL(
                address(this),
                int256(interest)
            );
        }

        // burn loan principal
        CreditToken(refs.creditToken).burn(principal);
        RateLimitedMinter(refs.creditMinter).replenishBuffer(principal);

        // close the loan
        loan.closeTime = block.timestamp;
        issuance -= borrowAmount;

        // return the collateral to the borrower
        //@audit this is not present in partialrepay() 
        IERC20(params.collateralToken).safeTransfer(
            loan.borrower,
            loan.collateralAmount
        );

        // emit event
        emit LoanClose(block.timestamp, loanId, LoanCloseType.Repay, loanDebt);
    }



    function _partialRepay(
        address repayer,
        bytes32 loanId,
        uint256 debtToRepay
    ) internal {
        Loan storage loan = loans[loanId];
//...ommited for brevity
  // pull the debt from the borrower
        CreditToken(refs.creditToken).transferFrom(
            repayer,
            address(this),
            debtToRepay
        );

        // forward profit portion to the ProfitManager, burn the rest
        CreditToken(refs.creditToken).transfer(
            refs.profitManager,
            interestRepaid
        );
        ProfitManager(refs.profitManager).notifyPnL(
            address(this),
            int256(interestRepaid)
        );
        CreditToken(refs.creditToken).burn(principalRepaid);
        RateLimitedMinter(refs.creditMinter).replenishBuffer(principalRepaid);

        // emit event
        emit LoanPartialRepay(block.timestamp, loanId, repayer, debtToRepay);

}
```
As seen logic of both functions are very similar with the only difference being that `_partialRepay()` doesn't include a full repaty and as such the loan doesn't get closed.

Also evidently, we can see that both functions take the same steps from pulling the debt from the borrower to reple
A Borrower can Use Fluctuations in Collateral Price to Call his Loans and Pay less Credit than the Actual Debt.
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L339-L435
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L634-L675
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/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L166-L196


# Vulnerability details

## Impact
A Borrower can use collateral price fluctuations(e.g. During bear markets) to call his own loans when the price of collateral is less than the debt value, and use this to pay less debt and get his collateral back.

this is possible with a conjunction of two variables, and because anyone can call loans even the borrower and the borrower can bid for his own loans.

## Proof of Concept

Let's see the next full scenario:

Any contract(bot)/user can borrow credit from the ECG protocol by depositing collateral and asking to borrow against it, these loans are overcollateralized with collateral tokens, so a contract can deposit collateral to the system and ask for a debt in a lending term with partial repayments enables.

```solidity
function _borrow(
        address borrower,
        uint256 borrowAmount,
        uint256 collateralAmount
    ) internal returns (bytes32 loanId) {
        require(borrowAmount != 0, "LendingTerm: cannot borrow 0");
        require(collateralAmount != 0, "LendingTerm: cannot stake 0");

        loanId = keccak256(
            abi.encode(borrower, address(this), block.timestamp)
        );

        // check that the loan doesn't already exist
        require(loans[loanId].borrowTime == 0, "LendingTerm: loan exists");

        // check that enough collateral is provided
        uint256 creditMultiplier = ProfitManager(refs.profitManager)
            .creditMultiplier();
        uint256 maxBorrow = (collateralAmount *
            params.maxDebtPerCollateralToken) / creditMultiplier;
        require(
            borrowAmount <= maxBorrow,
            "LendingTerm: not enough collateral"
        );

        // check that enough CREDIT is borrowed
        require(
            borrowAmount >= ProfitManager(refs.profitManager).minBorrow(),
            "LendingTerm: borrow amount too low"
        );

        // check the hardcap
        uint256 _issuance = issuance;
        uint256 _postBorrowIssuance = _issuance + borrowAmount;
        require(
            _postBorrowIssuance <= params.hardCap,
            "LendingTerm: hardcap reached"
        );

        // check the debt ceiling
        uint256 totalBorrowedCredit = ProfitManager(refs.profitManager)
            .totalBorrowedCredit();
        uint256 gaugeWeightTolerance = ProfitManager(refs.profitManager)
            .gaugeWeightTolerance();
        uint256 _debtCeiling = (Guild
Inability to withdraw funds for certain users due to `whenNotPaused` modifier in `RateLimitedMinter`
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/rate-limits/RateLimitedMinter.sol#L52


# Vulnerability details

med

## Impact
From the contest documentation:
> \[The GUARDIAN role\] is expected to be able to freeze new usage of the protocol, but not prevent users from withdrawing their funds.

However, due to the `whenNotPaused` modifier in the `RateLimitedMinter.mint()` function, users who have `CREDIT` tokens staked on a gauge with a pending `guildReward` will not be able to withdraw their funds. This is because the `SurplusGuildMinter.unstake()` function attempts to mint rewards through the `RateLimitedMinter` in the `getRewards()` function prior to unstaking. If the protocol is paused, this step will fail, causing the transaction to revert and preventing users from withdrawing their funds.

## Proof of Concept
Consider a scenario where Alice has `CREDIT` tokens staked on a gauge with a pending `guildReward`. Then, the protocol is paused and Alice attempts to unstake her tokens by calling the `SurplusGuildMinter.unstake()` function, which in turn calls the `getRewards()` function. This function attempts to mint rewards through the `RateLimitedMinter.mint()` function. However, because the protocol is paused, this function cannot be executed, causing the transaction to revert and preventing Alice from unstaking and withdrawing her funds.

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/rate-limits/RateLimitedMinter.sol#L52
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SurplusGuildMinter.sol#L158-L163
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SurplusGuildMinter.sol#L259

## Tools Used
Manual review

## Recommended Mitigation Steps
Provide an `emergencyWithdraw` method allowing users to withdraw their funds while foregoing rewards when the protocol is paused. This change should be carefully reviewed and tested to ensure it does not introduce other security risks.


## Assessed type

Other
Inability to unstake if the credit minter buffer is low
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/tokens/GuildToken.sol#L226-L230


# Vulnerability details

## Impact
The `LendingTerm.debtCeiling()` function, only called from `GuildToken._decrementGaugeWeight()`, which is itself internally called when unstaking/decreasing gauge weight, contains a mistake in calculation that will prevent users from unstaking from any term if the `creditMinterBuffer` is low. This is because it will always return the `creditMinterBuffer` if it is lower than the `_hardCap` or `_debtCeiling`. The only exception is the case where `_issuance >= debtCeilingBefore`, in which case `debtCeilingBefore` will be returned, which will then fail the `require(issuance <= debtCeilingAfterDecrement)` statement in `_decrementGaugeWeight()` unless in case of strict equality.

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L290-L291
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L302-L303
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L309-L311
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L316-L317
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L323-L330
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/tokens/GuildToken.sol#L226-L230

It seems that this function was written with another purpose in mind (to limit minting) and then repurposed to fulfil another purpose (to determine how much can be unstaked).

This could lead to a situation where, under heavy protocol usage or simply after a large mint, users are unable to unstake or decrease their gauge weight, regardless of the issuance on the term they're supporting.

## Proof of Concept
Consider the following scenario:

1. The protocol experiences high usage, reducing the `creditMinterBuffer`.
2. A user tries to unstake or decrease their gauge weight from a term they're supporting, which is far from reaching its debt ceiling.
3. The `GuildToken._decrementGaugeWeight()` function is called, which internally calls the `debtCeiling()` function.
4. The `debtCeiling()` function returns the `creditMinterBuffer` as it is lower than the `_hardCap` or `_debtCeiling`.
5. The user is unable to unstake or decrease their gauge weight as the `creditMinterBuffer` is lower than the term's issuance.

Additionally, note that this also opens up a DoS vector (albeit probably beneficial to the protocol) exploitable by continually minting CREDIT and depleting the minting buffer, which will prevent anybody from unstaking or reducing their weight.

## Tools Used
Manual review

## Recommended Mitigation Steps
The  `debtCeiling()` function  needs to be re-architected to ensure it only prevents unstaking based on a term's issuance.


## Assessed type

Other