xanoutas/solidity-security-findings huggingface dataset:
Malicious user avoid loss in case of gauge loss 
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L180-L186
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L214-L220
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L797
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L305
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L123-L129
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L207-L234


# Vulnerability details

## Summary 
Malicious guild token holders or credit stakers can perform front-running and avoid loss before notifying loss for the given gauge. 
## Vulnerability Details 
There are 3 scenarios that notifyGaugeLoss in guild token contract is called and guild token holders in the given gauge lose their allocated tokens.
1 - when governance calls forgiveLoan in leadingTerm
2 - when collateral in an auction is not being sold at the specific time so the loan is forgiven 
3 -  when collateral is being sold at a lower price than the loan principal, then onBid function notifies negative pnl to profit manager 
a malicious user can front-run forgive, forgiveLoan and Bid (when bid amount is lower than the principal loan) functions and decrement gauge weight in the given gauge and avoid loss. This enables malicious users to take risk-free interest by exiting the gauge before every loss and depositing to another gauge.
## Impact
Attacker avoid loss 
## Tools Used
Manual Review 
## Recommended Mitigation Steps
Consider lock tokens for a specific time after the decrement gauge.


## Assessed type

Other
`Guild:applyGaugeLoss` is only decreasing the global weight of the gauge only when the gauge is not deprecated/term is not offboarded and this can cause an issue if the term is re-onboarder.
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/volt-protocol/ethereum-credit-guild/blob/4d33abf95fee69391af0652e3cbe5e0cffa25f9f/src/tokens/GuildToken.sol#L148


# Vulnerability details

## Impact
only decreasing the global gauge weight when the gauge is not deprecated can lead to the state where the gauge will have unbacked weight if it is reoponed later.
## Proof of Concept

`Guild:applyGaugeLoss` is used to decrease the user weight if the term occurs any loss. The weight can only decremented if it is not currently being used for borrowing. That is checked by this line in the `_decrement` internal function. 

```solidity
// check if gauge is currently using its allocated debt ceiling.
        // To decrement gauge weight, guild holders might have to call loans if the debt ceiling is used.
        uint256 issuance = LendingTerm(gauge).issuance();
        if (issuance != 0) {
            uint256 debtCeilingAfterDecrement = LendingTerm(gauge).debtCeiling(
                -int256(weight)
            );
            require(
                issuance <= debtCeilingAfterDecrement,
                "GuildToken: debt ceiling used"
            );
        }
```

In most cases for `applyGaugeLoss` to be successful, the term has to be offboarded and loans have to be auctioned to free up the used weights.

After that, anyone can call `applyGaugeLoss` and decrement user weight, the if block after the weight has been decremented checks if the term is not offboarded/gauge is not deprecated and this is a rare case to have because if the term incurs any loss it will get offboarded in majority case.


```solidity
>> if (!_deprecatedGauges.contains(gauge)) {
            totalTypeWeight[gaugeType[gauge]] -= _userGaugeWeight;
            totalWeight -= _userGaugeWeight;
        }


```

If this condition rarely exists, all the global weight of this term/gauge will remain as it is without any backing GUILD, and if the gauge/term gets reopened later when the market becomes favorable for that same term(reopening is possible as per the require statement in LendingTermOffbording:cleanup)
```solidity
require(
  GuildToken(guildToken).isDeprecatedGauge(term),
  "LendingTermOffboarding: re-onboarded"
);
```

With this state, the gauge/term can lend without any actual weight backed by GUILD and if the loss occurs then there will be nothing to back all of it.

## Tools Used

manual review

## Recommended Mitigation Steps
decrement global weights when the decrement weight is called even if the gauge is deprecated.

```diff 
- if (!_deprecatedGauges.contains(gauge)) {
            totalTypeWeight[gaugeType[gauge]] -= _userGaugeWeight;
            totalWeight -= _userGaugeWeight;
-       }

```


## Assessed type

Error
[H-02] Cross function reentrancy in the SurplusGuildMinter contract
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L216-L290
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L106-L111
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L114-L155
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L158-L212
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L293-L315


# Vulnerability details

## Impact
There is reentrancy in SurplusGuildMinter contract stemming from the _stakes function on line 86.

External calls are made to the functions ProfitManager(profitManager).claimRewards(address(this)) on line 239; RateLimitedMinter(rlgm).mint on line 259 and CreditToken(credit).transfer on line 263.

There are state variables written after the call named _stakes[user][term] = userStake on line 288.

The mapping named _stakes on line 86 can be used in cross function reentrancies in the following functions.

 1. The SurplusGuildMinter.getRewards function on lines 216 to 290.
 2. The SurplusGuildMinter.getUserStake function on lines 106 to 111.
 3. The SurplusGuildMinter.stake function on lines 114 to 155.
 4. The SurplusGuildMinter.unstake function on lines 158 to 212.
 5. The SurplusGuildMinter.updateMintRatio function on lines 293 to 315.

## Proof of Concept
The exploit contract called SurplusGuildMinterX I built in remix IDE is depicted below.

```sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;

import {SafeCastLib} from "../../src/external/solmate/SafeCastLib.sol";

import {CoreRef} from "../../src/core/CoreRef.sol";
import {CoreRoles} from "../../src/core/CoreRoles.sol";
import {GuildToken} from "../../src/tokens/GuildToken.sol";
import {CreditToken} from "../../src/tokens/CreditToken.sol";
import {ProfitManager} from "../../src/governance/ProfitManager.sol";
import {RateLimitedMinter} from "../../src/rate-limits/RateLimitedMinter.sol";
import {SurplusGuildMinter} from "../../src/loan/SurplusGuildMinter.sol";

contract SurplusGuildMinterX {

    SurplusGuildMinter public x1;

    constructor(SurplusGuildMinter _x1) {

        x1 = SurplusGuildMinter(_x1);

    }

    function testReen2(address _x1) external payable {

        x1.getRewards(address(_x1),address(_x1)); // (src/loan/SurplusGuildMinter.sol#216-290)
        x1.getUserStake(address(_x1),address(_x1)); // (src/loan/SurplusGuildMinter.sol#106-111)
        x1.stake(address(_x1),msg.value); // (src/loan/SurplusGuildMinter.sol#114-155)
        x1.unstake(address(_x1),msg.value); // (src/loan/SurplusGuildMinter.sol#158-212)
        x1.updateMintRatio(address(_x1),address(_x1)); // (src/loan/SurplusGuildMinter
Attacker Can Steal USDC from the Protocol by Onboarding a Lending Term with an Upgradeable ERC-20 Collateral
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
There are 3 impacts in this bug:
1) Attacker is able to steal USDC (pegged token) from protocol
2) Bad debt might occur because the borrower hasn't repaid the loan. If this loan goes to auction, there is a possibility that no one will bid for it. Even if someone wants to bid for it, their transaction will likely revert since there is no collateral available to transfer to the bidder
3) Loss of trust in the protocol

## Proof of Concept
As outlined in the README file of the contest, users can onboard lending terms with an upgradeable ERC-20 collateral, following the attack idea known as:
`Malicious collateral: onboard a lending term with an ERC20 collateral token that is an upgradeable proxy, then upgrade the proxy in a creative way to brick the ECG internal logic & prevent proper bad debt realization`

Since onboarding lending terms with upgradeable ERC20 collaterals is a standard protocol behavior, malicious users can exploit this by onboarding a lending term with a visually appealing upgradeable proxy as collateral. Subsequently, they can strategically upgrade the proxy to a malicious implementation right before initiating the borrow. This malicious implementation consistently returns `true` on every `transferFrom` without actually transferring anything. As a result, malicious users can secure a loan without providing any collateral and redeem for USDC.

According to [this line of code](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L339) within `_borrow()` , this function calls `safeTransferFrom` on every borrow to transfer the collateral from the borrower to the lending term. However, since `safeTransferFrom` only checks the boolean result of a `transferFrom` call, the attacker upgrades the proxy to consistently return `true` on every `transferFrom` without actually transferring anything, and takes a loan without providing any collateral.

### To test the scenario:
Install openzeppelin/contracts-upgradeable ver 4.9.3:
```
npm install @openzeppelin/contracts-upgradeable@4.9.3
```
Make a folder/dir named `ninja-tests` in this path: `test/unit/` and create below files in it:

**CollateralProxy.sol**:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract CollateralProxy is ERC1967Proxy {
    address _owner;

    constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data){
        _owner = msg.sender;
    }

    function upgradeTo(address _logic) public {
        require(msg.sender == _owner, "Owner Only");
        _upgradeTo(_logic);
    }
}
```

**CollateralToken.sol:**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts-upgrad
Upgraded Q -> 2 from #556 [1706974007287]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #556 as 2 risk. The relevant finding follows:

 ## [L-01] Users can frontrun `NotifyPnL` calls to earn `risk-free` rewards and avoid slashing

### Bug Description
Calls to `ProfitManager::notifyPnL` are performed by terms when a profit or loss is generated. Guild stakers who are staked into a gauge while it experiences a profit will be eligible to claim rewards for that gauge/term. First, the gauge's `gaugeProfitIndex` is updated based on the profit generated:

[ProfitManager::notifyPnL#L383-L399](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L383-L399)

```solidity
383:            if (amountForGuild != 0) {
384:                // update the gauge profit index
385:                // if the gauge has 0 weight, does not update the profit index, this is unnecessary
386:                // because the profit index is used to reattribute profit to users voting for the gauge,
387:                // and if the weigth is 0, there are no users voting for the gauge.
388:                uint256 _gaugeWeight = uint256(
389:                    GuildToken(guild).getGaugeWeight(gauge)
390:                );
391:                if (_gaugeWeight != 0) {
392:                    uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge];
393:                    if (_gaugeProfitIndex == 0) {
394:                        _gaugeProfitIndex = 1e18;
395:                    }
396:                    gaugeProfitIndex[gauge] =
397:                        _gaugeProfitIndex +
398:                        (amountForGuild * 1e18) /
399:                        _gaugeWeight;
```

Next, stakers who were staked in the gauge will be able to collect the rewards via `ProfitManager::claimGaugeRewards`:

[ProfitManager::claimGaugeRewards#L427-L434](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L427-L434)
```solidity
427:        uint256 deltaIndex = _gaugeProfitIndex - _userGaugeProfitIndex;
428:        if (deltaIndex != 0) {
429:            creditEarned = (_userGaugeWeight * deltaIndex) / 1e18;
430:            userGaugeProfitIndex[user][gauge] = _gaugeProfitIndex;
431:        }
432:        if (creditEarned != 0) {
433:            emit ClaimRewards(block.timestamp, user, gauge, creditEarned);
434:            CreditToken(credit).transfer(user, creditEarned);
```

On the other hand, stakers who are staked into a gauge when it experiences a loss will have their Guild slashed. First, the time of the loss is recorded in the gauge: 

[ProfitManager::notifyPnL#L300-L305](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L300-L305)
```solidity
300:        // handling loss
301:        if (amount < 0) {
302:            uint256 loss = uint256(-amount);
303:
304:            // save gauge loss
305:            GuildToken(guild).notifyGaugeLoss(gauge);
```

Next, the loss can be applied to any staker who was staked in
Upgraded Q -> 2 from #1212 [1706668026325]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1212 as 2 risk. The relevant finding follows:

 # 2. `_partialRepay` in `LendingTerm.sol` does not support 0 interest loans

The function `_partialRepay` is used to make partial repayments of a loan. The issue is that during repayment, the `interestRepaid` is always required to be above 0.

```solidity
uint256 interestRepaid = debtToRepay - principalRepaid;
require(
    principalRepaid != 0 && interestRepaid != 0,
    "LendingTerm: repay too small"
);
```
Upgraded Q -> 2 from #808 [1706659279669]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #808 as 2 risk. The relevant finding follows:

 ## [L&#x2011;02] Anyone can `distribute()` only 1 wei to extend rebase time to `DISTRIBUTION_PERIOD`
consider introducing a `PROFIT_DISTRIBUTION` role and adding access control on `distribute()`.

Update the visibility of `distribute()` in `ERC20RebaseDistributor`:
```diff
-   function distribute(uint256 amount) external {
+   function distribute(uint256 amount) public {
```
Add access control by overriding `distribute()` in `CreditToken`:
```solidity
    function distribute(uint256 amount) public override onlyCoreRole(CoreRoles.PROFIT_DISTRIBUTION) {
        super.distribute(amount);
    }
```
`PROFIT_DISTRIBUTION` role should be granted to `ProfitManager` when deployed.
Upgraded Q -> 2 from #808 [1706659262817]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #808 as 2 risk. The relevant finding follows:

 ## [L&#x2011;01] [`claimRewards()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SurplusGuildMinter.sol#L239) could be reverted due to Out Of GAS
There's a possibility of [`SurplusGuildMinter#getRewards()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/SurplusGuildMinter.sol#L216-L290) failing due to running out of gas, caused by [`ProfitManager#claimRewards()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L439-L449) because there's no maximum gauge limitation on `SurplusGuildMinter`. (`canExceedMaxGauges` for `SurplusGuildMinter` is set to `true` in [deployment scripts](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/test/proposals/gips/GIP_0.sol#L480-L483))

To mitigate this, consider using `ProfitManager#claimGaugeRewards()` instead:
```diff
-       ProfitManager(profitManager).claimRewards(address(this)); // this will update profit indexes
+       ProfitManager(profitManager).claimGaugeRewards(address(this), term);
```
Upgraded Q -> 2 from #765 [1706657343488]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #765 as 2 risk. The relevant finding follows:

 ### L-03

The LendingTerm.debtCeiling() function returns the debt ceiling for the given term. Unfortunately, the code is designed such that there is an off-chance that if `_hardCap < creditMinterBuffer < (_issuance + maxBorrow)`, then the creditMintBuffer will be returned. In this case, _hardCap should be returned.

This can cause GuildToken._decrementGaugeWeight to decrement when it should if the above edge case occurs and the issuance is greater than the _hardCap.

The LendingTerm.debtCeiling() runs a series of calculations to determine the Term's debt ceiling. The debt ceiling should always lean towards returning the smallest acceptable value to ensure protocol safety and prevent runaway borrowing. You can see this safety check occur throughout the function where in most scenarios the debtCeiling() returns the following logical statement:

`_hardCap < creditMinterBuffer ? _hardCap : creditMinterBuffer;`


Unfortunately, at the end of the function the final set of code logic runs:

```
// return min(creditMinterBuffer, hardCap, debtCeiling)
if (creditMinterBuffer < _debtCeiling) {
    return creditMinterBuffer;
}
if (_hardCap < _debtCeiling) {
    return _hardCap;
}
return _debtCeiling;
```

This logic dictates that as long as creditMinterBuffer is lower than _debtCeiling, then creditMinterBuffer will be returned in the function. However, it does not consider if _hardCap is lower than the creditMinterBuffer. This can lead to a scenario where the debtCeiling() returns a value larger than expected. 


We can see this in action in the following sample code below:

```solidity
function debtCeiling() public returns (uint) {
    uint creditMinterBuffer = 100;
    uint _debtCeiling = 101;
    uint _hardCap = 99;
    
    uint returnValue;
    
    
    if (creditMinterBuffer < _debtCeiling) {
        return creditMinterBuffer;
    }
    if (_hardCap < _debtCeiling) {
        return _hardCap;
    }
}

uint ceiling = debtCeiling(); // will return 100
```



The protocol should re-work the function to do the following:

```solidity
uint ceiling = _hardCap;

if (creditMinterBuffer < ceiling) {
    ceiling = creditMinterBuffer;
}
if (_debtCeiling < ceiling) {
    ceiling = _debtCeiling;
}

return ceiling;
```
Upgraded Q -> 2 from #712 [1706656573259]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #712 as 2 risk. The relevant finding follows:

 ## [L-05] In SurplusGuildMinter, Staking and Unstaking Might be Bricked at a Point

Currently, [SurplusGuildMinter::stake](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L114-L156) function calls [getRewards()](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L216) function, this function does some computation then calls [ProfitManager::claimRewards](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L439-L449) on the `SurplusGuildMinter`, the `claimRewards` function then loops through all the available terms users have staked on in the `SurplusGuildMinter`, since these terms aren't whitelisted and the number of terms a user can stake on isn't limited this number will only grow exponentially, at a point both staking and unstaking will revert due to long computation which eventually results in an OOG revert.

**RECOMMENDATION**

Consider whitelisting some selected terms, to avoid this number growing too large
Upgraded Q -> 2 from #514 [1706650157519]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #514 as 2 risk. The relevant finding follows:

 # L-01 Anyone could increase interpolation endTime by 30 days just by distributing 1 wei.

## Lines of code

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

## Impact
Anyone can call distribute with 1 Wei as amount and update the interpolation of `__rebasingSharePrice` & `__unmintedRebaseRewards` by 30 days 

## Tools Used
manual review
## Recommended Mitigation Steps
Add a minimum amount check to the distribute function
Upgraded Q -> 2 from #496 [1706648420225]
Audit: 2023-12-ethereumcreditguild-findings
Severity: MEDIUM

Judge has assessed an item in Issue #496 as 2 risk. The relevant finding follows:

 # debtCeiling() does not determine the correct minimum which results in debtCeiling being higher than it should be
### Description
At the end of the function debtCeiling in LendingTerm.sol the minimum of creditMinterBuffer, _debtCeiling and _hardCap will be determined like this:
```solidity
        // return min(creditMinterBuffer, hardCap, debtCeiling)
        if (creditMinterBuffer < _debtCeiling) {
            return creditMinterBuffer;
        }
        if (_hardCap < _debtCeiling) {
            return _hardCap;
        }
        return _debtCeiling;
```
The problem here is that if creditMinterBuffer < _debtCeiling, then it is not checked whether _hardCap is smaller than creditMinterBuffer. So it could be, although very unlikely, that the wrong debtCeiling will be returned. This would result in the wrong debtCeiling being used in the _decrementGaugeWeight function in GuildToken.sol, making it possible to decrement more tokens than you should be able to:
```solidity
if (issuance != 0) {
            uint256 debtCeilingAfterDecrement = LendingTerm(gauge).debtCeiling(-int256(weight));
            require(
                issuance <= debtCeilingAfterDecrement,
                "GuildToken: debt ceiling used"
            );
        }
```
### Recommendation
It should also be checked whether _hardCap is smaller than creditMinterBuffer. The minimum should then be determined in this way:
```diff
// return min(creditMinterBuffer, hardCap, debtCeiling)
if (creditMinterBuffer < _debtCeiling) {
+		if(_hardCap < creditMinterBuffer) return _hardCap;
		return creditMinterBuffer;
}
if (_hardCap < _debtCeiling) {
    return _hardCap;
}
return _debtCeiling;
```