xanoutas/solidity-security-findings huggingface dataset:
Underflow causes DOS of borrow function across all terms in a market
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L172-L176
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SimplePSM.sol#L86-L99
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20RebaseDistributor.sol#L451-L453


# Vulnerability details

In `ProfitManager.sol`; the function `totalBorrowedCredit()` can underflow due to an inconsistent adjustment of the parameters used to calculate it. with the `creditMultipler()`.

`totalBorrowedCredit()` is calculated by subtracting `targetTotalSupply()` from `redeemableCredit()`; where `targetTotalSupply()` is the sum of all minted CREDIT tokens and `redeemableCredit()` is the sum of CREDIT issued to user's who deposit funds via `SimplePSM.deposit()`
```
    function totalBorrowedCredit() external view returns (uint256) {
        return
            CreditToken(credit).targetTotalSupply() -
            SimplePSM(psm).redeemableCredit();
    }
```
The issue stems from the `creditMutiplier()` being applied to `redeemableCredit()` but not to `targetTotalSupply()`. This means that where a loss is absorbed by the system, reducing the `creditMuliplier(), the value for `redeemableCredit()` increases as a proportion of total supply of CREDIT. 
```
    function getRedeemAmountOut(
        uint256 amountIn
    ) public view returns (uint256) {
        uint256 creditMultiplier = ProfitManager(profitManager)
            .creditMultiplier();
        return (amountIn * creditMultiplier) / 1e18 / decimalCorrection;
    }

    /// @notice calculate the total number of CREDIT that can be redeemed
    /// at the moment, based on the pegTokenBalance.
    function redeemableCredit() public view returns (uint256) {
        return getMintAmountOut(pegTokenBalance);
    }
```
It should not be possible for `redeemableCredit()` to be greater than the total supply of CREDIT and given that it can be, it leads to an underflow where `redeemableCredit()` is larger than `targetTotalSupply()`.

## Impact
The impact will be a DOS of `LendingTerm::borrow()` across all Lending Terms in a market as the `borrow()` function will continue to revert. 
This means that although risk should be siloed per Lending Term; in actuality a large failing loan in one term can shutdown all the others in a market.
There would be no straightforward way to fix the DOS except for adjusting the CREDIT supply by minting or waiting for the market to adjust. 

## Proof of Concept
Add the following lines of code to `LendingTerm.t.sol`. 
First add the following to set-up to add a new gauge:
```diff
+   LendingTerm termTwo;

    function setUp() public {
        vm.warp(1679067867);
        vm.roll(16848497);
        core = new Core();

        profitManager = new ProfitManager(address(core));
        collateral = new MockERC20();
        c
LendingTerm#getLoanDept() More openingFee is charged to loans that are 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#L226


# Vulnerability details

## Impact
`openingFee` is a fee charged once when opening a loan. When repaying a loan, the `getLoanDebt()` is called and the `openingFee` calculations are made and added to the `loanDept`. It is added every time a user repays which does not seems to be a problem however when a user is partially repaying the accumulated fee is more then if he repayed it at once.
```js
            loanDebt += (borrowAmount * _openingFee) / 1e18;
```

If we assume a user first partial repay his loan, in the `_partialRepay()` a % of the repaid amount is transferred for the protocol for the fee. After time if he partial repay again, the `getLoanDebt()` calculates again the `openingFee` for the new borrowAmount(as it was decreased from the first repay), however more fee is calculated than expected.

## Proof of Concept
Paste the following test inside `test/unit/loan/LendingTerm.t.sol.`
```js
function testMoreOpeningFeeCharged() public {
        LendingTerm term2 = LendingTerm(
            Clones.clone(address(new LendingTerm()))
        );
        term2.initialize(
            address(core),
            term.getReferences(),
            LendingTerm.LendingTermParams({
                collateralToken: address(collateral),
                maxDebtPerCollateralToken: _CREDIT_PER_COLLATERAL_TOKEN,
                interestRate: 0,
                maxDelayBetweenPartialRepay: _MAX_DELAY_BETWEEN_PARTIAL_REPAY,
                minPartialRepayPercent: _MIN_PARTIAL_REPAY_PERCENT,
                openingFee: 0.05e18,
                hardCap: _HARDCAP
            })
        );
        vm.label(address(term2), "term2");
        guild.addGauge(1, address(term2));
        guild.decrementGauge(address(term), _HARDCAP);
        guild.incrementGauge(address(term2), _HARDCAP);
        vm.startPrank(governor);
        core.grantRole(CoreRoles.RATE_LIMITED_CREDIT_MINTER, address(term2));
        core.grantRole(CoreRoles.GAUGE_PNL_NOTIFIER, address(term2));
        vm.stopPrank();

        // prepare & borrow
        uint256 borrowAmount = 20_000e18;
        uint256 collateralAmount = 12e18;
        collateral.mint(address(this), collateralAmount);
        collateral.approve(address(term2), collateralAmount);
        bytes32 loanId = term2.borrow(borrowAmount, collateralAmount);

        vm.warp(block.timestamp + 10);
        vm.roll(block.number + 1);

        assertEq(term2.getLoanDebt(loanId), 21_000e18);

        // it should be 21 000e18
        uint256 firstLoanDeptBefore = term2.getLoanDebt(loanId);
        console.log("firstLoanDeptBefore:", firstLoanDeptBefore);

        credit.mint(address(this), 5_000e18);
        credit.approve(address(term2), 5_000e18);

        term2.partialRepay(loanId, 5_000e18);

        // it should be 16000000000000000000000 however we can see it the console log it is 160000000000
No slipage protection or expiry  on Mint and redeem function in SimplePSM
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SimplePSM.sol#L138
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SimplePSM.sol#L107
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SimplePSM.sol#L124


# Vulnerability details

## Impact
The system provided `getMintAmountOut` and `getRedeemAmountOut` function while minting and redeeming Crdeit Tokens for collateral tokens. but there is no slipplage protection as the price or amount of token minted will be effected by PNL  changes which will effect the price of Credit token.  

## Proof of Concept
 following case would occur noramly.
1). The Price of credit token is 0.7 USDC and ratio is 1-0.7. 
2). User call `getMintAmountOut(100e6)` and the contract return 142  of credit token to be minted out.
3). While the user Transaction is in mempool The System record Profit and creditMultipler got updated to Credit Token is 1-1 USDC.
4). When The user transaction got executed he will only receive 100e18 Credit token. this is not on what the users has agreed.


## Tools Used
Manual Review.
## Recommended Mitigation Steps
Add slippage protection and check if the `amountOut=getMintAmountOut(amountIn)` or `amountOut = getRedeemAmountOut(amountIn)` is less then that value just revert.
i.e : 


## Assessed type

MEV
A large enough loss will brick the protocol
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `totalBorrowedCredit` function in the `ProfitManager` contract may revert if the `creditMultiplier` is low. This is because `redeemableCredit` is scaled by the `creditMultiplier`, but `targetTotalSupply` isn't. This can potentially brick core protocol functionality as no one would be able to unstake or borrow.

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L172-L176
```solidity
    /// @notice returns the sum of all borrowed CREDIT, not including unpaid interests
    /// and creditMultiplier changes that could make debt amounts higher than the initial
    /// borrowed CREDIT amounts.
    function totalBorrowedCredit() external view returns (uint256) {
        return
            CreditToken(credit).targetTotalSupply() -
            SimplePSM(psm).redeemableCredit();
    }
```

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SimplePSM.sol
```solidity
   function redeemableCredit() public view returns (uint256) {
        return getMintAmountOut(pegTokenBalance);
    }

    function getMintAmountOut(uint256 amountIn) public view returns (uint256) {
        uint256 creditMultiplier = ProfitManager(profitManager)
            .creditMultiplier();
        return (amountIn * decimalCorrection * 1e18) / creditMultiplier;
    }
```

The `totalBorrowedCredit` function is reachable from several entry points including `borrow()`, `unstake()`, `updateMintRatio()`, and `applyGaugeLoss()`. If a large enough loss occurs, all these functions will revert, effectively bricking the protocol.

The root cause of this issue is the way `totalBorrowedCredit` is calculated. It subtracts `redeemableCredit` from `targetTotalSupply`. However, `redeemableCredit` is calculated using the `creditMultiplier`, which can decrease in the event of a loss. This is incorrect and was not the intention of the developers, as can be understood from `totalBorrowedCredit()`'s documentation. If the `creditMultiplier` is low enough, `redeemableCredit` can exceed `targetTotalSupply`, causing the subtraction to underflow and the function to revert.

## Proof of Concept
Consider the following scenario:

1. The protocol has 100 CREDIT minted on deployment.
2. Alice mints 200 CREDIT through the PSM, resulting in a `targetTotalSupply` of 300 CREDIT and a `pegTokenBalance` of 200.
3. A loss of 150 CREDIT is applied, causing the `creditMultiplier` to decrease to 0.5.
4. Now, `redeemableCredit` is 400 (≈`pegTokenBalance / creditMultiplier`), which is greater than `targetTotalSupply`.
5. Calling `totalBorrowedCredit` now causes an underflow and reverts.

This scenario is illustrated in the `testTotalBorrowedCredit_breaks` test below:
```solidity
        function testTotalBorrowedCredit_breaks() public {
        assertEq(profitManager.totalBorrowedCredit(), 100e1
users can front run the `notifyLoss` call to unstake their credit and avoid getting slashed
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SurplusGuildMinter.sol#L158-L212


# Vulnerability details

## Impact

When a gauge incurs a loss, the `notifyLoss` function is called, triggering the slashing of all Credit funds staked by users in that gauge. However, stakers can monitor the blockchain transactions. Upon detecting a `notifyLoss` call, they can front-run it to unstake their funds and avoid getting slashed. This will result in the decrease of the `surplusBuffer` reserved for all market gauges which will endup by generating more bad debt for the market (all gauges included) in the future.

## Proof of Concept

In the event of a gauge/term incurring a loss (negative PnL) after a call (liquidation), the `ProfitManager.notifyPnL` function is invoked. This function, in turn, calls `GuildToken.notifyGaugeLoss` under the hood to register the timestamp of the loss. This timestamp is used to slash all the credit staked in the gauge before the loss occurred.

Stakers are normally prohibited from unstaking when `SurplusGuildMinter.unstake` is called, as the `getRewards` function will return a variable `slashed == true`, preventing the unstaking operation. However, stakers can bypass this restriction by monitoring the chain transactions. Upon detecting that the gauge is about to suffer a loss, they can front-run the term call and execute `SurplusGuildMinter.unstake` to withdraw their credit funds, avoiding the slashing of their funds and capitalizing on the staking rewards offered by the gauge.

This action will also affects other gauges as when stakers front-run the `notifyLoss` call, withdrawing their credit and decreasing the gauge's `termSurplusBuffer`. The gauge is then forced to draw from the global market buffer `surplusBuffer` to reduce the bad debt (because stakers took the gauge Credit stored in `termSurplusBuffer` that was supposed to be slashed and used to handle the losses).Then when other gauges also incur losses, they may not have sufficient funds in the `surplusBuffer`, resulting in increased bad debt for the whole market.

If this front running scenario is used by stakers into multiple gauges it may make the global market (for example the usdc market) unstable, because of all the bad debt accrual will drop the creditMultiplier down, and the market may not be usable again.

## Tools Used

Manual review

## Recommended Mitigation Steps

One solution to address this issue is to introduce a delay for unstaking (a small delay of 5 minutes or less). Stakers would need to request unstaking and then wait for the delay to pass before withdrawing their credit funds. This approach allows the protocol to utilize the slashing mechanism without the risk of front-running.


## Assessed type

Context
Missing whenNotPaused modifier for redeem
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SimplePSM.sol#L134-L144


# Vulnerability details

## Impact

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SimplePSM.sol#L134-L144

In `SimplePSM.sol` the functions mint and mintAndEnterRebase functions consists of whenNotPaused modifier while the redeem function doesn't. 
Functions with mint, burn or safeTransfer should consist the whenNotPaused as it makes changes to the credit token supply.

## Proof of Concept

Function with mint and function which consists of safeTransferFrom contains whenNotPaused modifier but not the redeem function.

    function mint(
            address to,
            uint256 amountIn
        ) external whenNotPaused returns (uint256 amountOut) {

    function mintAndEnterRebase(
            uint256 amountIn
        ) external whenNotPaused returns (uint256 amountOut) {

But not the redeem function :

    function redeem(
            address to,
            uint256 amountIn
        ) external returns (uint256 amountOut) {

## Tools Used
VS Code

## Recommended Mitigation Steps
Add the whenNotPaused modifier to the redeem function.

    function redeem(
            address to,
            uint256 amountIn
        ) external whenNotPaused returns (uint256 amountOut) {


## Assessed type

Other
Ongoing to be slashed gaugeWeight voters can re-delegate their governance votes until applyGaugeLoss() is called
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Until the loss is realized (`applyGaugeLoss`), a user cannot transfer their locked tokens or
decrease the weight they assign to the gauge that suffered a loss.

Even so, he can `delegates` his votes to another address. 

## Proof of Concept
If a loss occurred it's expected `GuildToken.applyGaugeLoss` to be called permissionles to slash any guild weight voting for respective gauge.
Until the loss is accounted for by using the `applyGaugeLoss`, users are not allowed to move their locked tokens or reduce the weight they’ve allocated to the gauge that experienced the loss.

Nevertheless, he retains the ability to delegate his voting power to a different address.
This is possible because `lastGaugeLossApplied` is not checked when a user `_incrementDelegation()`.

Add following test in `GuildToken.t.sol` file and execute it with `forge test --mt testCanRedelegateEvenIfLossUnapplied -vvv`

```solidity
    function _setupGrantRoles() internal {
        // grant roles to test contract
        vm.startPrank(governor);
        core.createRole(CoreRoles.GUILD_MINTER, CoreRoles.GOVERNOR);
        core.grantRole(CoreRoles.GUILD_MINTER, address(this));
        core.createRole(CoreRoles.GAUGE_ADD, CoreRoles.GOVERNOR);
        core.grantRole(CoreRoles.GAUGE_ADD, address(this));
        core.createRole(CoreRoles.GAUGE_PARAMETERS, CoreRoles.GOVERNOR);
        core.grantRole(CoreRoles.GAUGE_PARAMETERS, address(this));
        core.createRole(CoreRoles.GAUGE_PNL_NOTIFIER, CoreRoles.GOVERNOR);
        core.grantRole(CoreRoles.GAUGE_PNL_NOTIFIER, address(this));
        core.grantRole(CoreRoles.GUILD_GOVERNANCE_PARAMETERS, address(this));
        // enable guild transfers
        token.enableTransfer();
        vm.stopPrank();

        // setup
        token.setMaxGauges(1);
        token.addGauge(1, gauge1);
        token.setMaxDelegates(2);
    }

    function testCanRedelegateEvenIfLossUnapplied() public {
        _setupGrantRoles();
        // token is GuildToken
        token.mint(alice, 100e18);
        vm.startPrank(alice);
        token.delegate(alice);// delegate entire balance to herself
        token.incrementGauge(gauge1, 60e18);// subject to slash
        vm.stopPrank();

        vm.warp(block.timestamp + 13);
        vm.roll(block.number + 1);
        
        assertEq(token.delegatesVotesCount(alice,alice), 100e18, "delegatesVotesCount alice->alice");


        profitManager.notifyPnL(gauge1, -10e18);
        vm.warp(block.timestamp + 13);
        vm.roll(block.number + 1);

        // alice cannot transfer tokens because of unrealized loss
        vm.expectRevert("GuildToken: pending loss");// this does revert -> OK
        vm.prank(alice);
        token.transfer(bob, 50e18);// 10 over the amount subjected to slashing

        // before applyGaugeLoss is called
`ERC20MultiVotes.sol` does not allow delegation to `multiple` addresses.
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

According to this [comment](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20MultiVotes.sol#L13) in `ERC20MultiVotes.sol`
```solidity
// @notice an ERC20 extension which allows delegations to multiple delegatees up to a user's balance on a given block.
```
This contract should allow delegations to multiple addresses but in the `_delegate()` function the amount of delegates is capped to be less than 2
```solidity
[297]         require(count < 2, "ERC20MultiVotes: delegation error");
```

```solidity
    function _delegate(
        address delegator,
        address newDelegatee
    ) internal virtual {
        uint256 count = delegateCount(delegator);


        // undefined behavior for delegateCount > 1
        require(count < 2, "ERC20MultiVotes: delegation error");


        address oldDelegatee;
        // if already delegated, undelegate first
        if (count == 1) {
            oldDelegatee = _delegates[delegator].at(0);
            _undelegate(
                delegator,
                oldDelegatee,
                _delegatesVotesCount[delegator][oldDelegatee]
            );
        }


        // redelegate only if newDelegatee is not empty
        if (newDelegatee != address(0)) {
            _incrementDelegation(delegator, newDelegatee, freeVotes(delegator));
        }
        emit DelegateChanged(delegator, oldDelegatee, newDelegatee);
    }
```

## Impact
The contract does not do what it is expected to do which is allow delegation to multiple addresses


## Tools Used
Manual Review

## Recommended Mitigation Steps
The cap on the `_delegate()` function should be removed and the function modified to allow users delegate to multiple addresses.



## Assessed type

Other
Callers can prevent ``setAuctionHouse` happening
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The current implementation of the `setAuctionHouse` function in `LendingTerm` introduces a potential limitation. The function requires that there are no auctions in progress in the current auction house, making it challenging to execute this function in lending terms with active users, especially those with short or nonexistent partial repayment intervals. Additionally, a caller could attempt to front-run the call to `setAuctionHouse` by initiating another position when there are no ongoing auctions.

## Proof of Concept
The condition checking for no auctions in progress may be difficult to satisfy in certain scenarios:

```solidity
// This condition MIGHT never be met
require(
    AuctionHouse(refs.auctionHouse).nAuctionsInProgress() == 0,
    "LendingTerm: auctions in progress"
);
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
To address the limitations and potential front-running issues associated with the `setAuctionHouse` function, it is recommended to allow the setting of a new auction house and find an alternative method to settle the old auctions. By decoupling the auction house change from the requirement of no ongoing auctions, the function becomes more flexible and resistant to potential issues related to active user positions or front-running attempts.

Consider modifying the function to allow the setting of a new auction house without the constraint of having no auctions in progress:

```solidity
// Updated setAuctionHouse function
function setAuctionHouse(address newAuctionHouse) external onlyGovernor {
    // Additional logic to settle old auctions if needed

    // Set the new auction house
    refs.auctionHouse = newAuctionHouse;
}
```

By implementing this modification, the function becomes more versatile, allowing the transition to a new auction house without being hindered by the presence of ongoing auctions in the current auction house. This adjustment provides greater flexibility in managing the auction house configuration.



## Assessed type

Timing