xanoutas/solidity-security-findings huggingface dataset:
Incorrect Variable Initialization in getRewards
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
in the the getRewards function user slashing logic is executed based on an uninitialized UserStake struct. As a result, users may be falsely considered slashed before any actual loss, erroneously affecting staking rewards and other related functionalities. and that cause the incorrect variable initialization , the  The variable userStake is used to compare with lastGaugeLoss before actually fetching the UserStake struct from _stakes[user][term] and This causes the comparison if (lastGaugeLoss &gt; uint256(userStake.lastGaugeLoss)) to use a default-initialized UserStake object, which has the lastGaugeLoss field default to 0 and this problem  can result in all users being considered slashed (slashed = true), as lastGaugeLoss is usually non-zero (once any gauge loss occurs) and will always be greater than the default 0 value of an uninitialized userStake.lastGaugeLoss. This could lead to incorrect logic where users are considered slashed incorrectly, affecting the functionality of reward distribution or other contract behaviors.
here is the vulenrbale part :

```
 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;
        }

        // if the user is not staking, do nothing
        userStake = _stakes[user][term];
        if (userStake.stakeTime == 0)
            return (lastGaugeLoss, userStake, slashed);

        // compute CREDIT rewards
        ProfitManager(profitManager).claimRewards(address(this)); // this will update profit indexes
        uint256 _profitIndex = ProfitManager(profitManager)
            .userGaugeProfitIndex(address(this), term);
        uint256 _userProfitIndex = uint256(userStake.profitIndex);

        if (_profitIndex == 0) _profitIndex = 1e18;
        if (_userProfitIndex == 0) _userProfitIndex = 1e18;

        uint256 deltaIndex = _profitIndex - _userProfitIndex;

        if (deltaIndex != 0) {
            uint256 creditReward = (uint256(userStake.guild) * deltaIndex) /
                1e18;
            uint256 guildReward = (creditReward * rewardRatio) / 1e18;
            if (slashed) {
                guildReward = 0;
            }

            // forward rewards to user
            if (guildReward != 0) {
                RateLimitedMinter(rlgm).mint(user, guildReward);
                emit GuildReward(block.timestamp, user, guildReward);
            }
Incorrect slashing of users' CREDIT principal and GUILD rewards
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L229-L231
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L253-L255
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L258-L261
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L274-L284
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L216
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L114
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L158
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L293


# Vulnerability details

The `SurplusGuildMinter::getRewards()` is used to get credit and guild rewards from a staking position. This function can also be used to slash users that have outstanding unapplied losses. To verify the user's unapplied loss, the function will check the term's `lastGaugeLoss` timestamp against the user's `lastGaugeLoss` timestamp (i.e., `userStake.lastGaugeLoss`), and the function will [trigger the `slashed` flag if `lastGaugeLoss` > `userStake.lastGaugeLoss`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L229-L231).

Nevertheless, the `getRewards()` was implemented incorrectly. The `userStake.lastGaugeLoss` variable will always be 0 since the `userStake` variable's state has never been loaded from the state storage before the verification mentioned above. 

**When the user executes one of the following functions: [`getRewards()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L216), [`stake()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L114), [`unstake()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L158), or [`updateMintRatio()`](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L293), if the term has ever been slashed in the past (even before the user's staking), the `slashed` flag will always be triggered by mistake.** 

**Consequently, the user will [be mistakenly forfeited their staking credit principal](https://github.com/code-
Stable coins depegging will result in all PSM's being drained.
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
The offboarding of lending terms via the DAO might act as a good oracle for volatile collateral tokens, but might not be fast enough for sudden depegs of stable coins accepted as collateral. Like in regular arbitrage, these could potentially drain all the funds in the PSM, like any other lending term would, were it to lend more credit than the spot prices worth of the accepted collateral.

## Proof of Concept
Considering we have a lending term with the following params:
```
collateralToken: usdt
maxDebtPerCollateralToken: 0.99e18
hardCap: 6_000_000e18
```
Now let's say the credit token in question is gDAI backed by users depositing DAI into the PSM directly to obtain gDAI & stake or repay loans. So:
```
PSM balance: 2_000_000e18 // DAI
creditMultiplier: 1e18    // 1:1 exchange rate of gDAI-DAI
```
Now a sudden depeg of usdt to 0.90, could cause an arbitrage-like flashloan attack. Where the attacker has to borrow ```PSM.balanceOf() * creditMultiplier``` in this case since the multiplier is 1, the attacker has to borrow 2 million gDAI to then redeem all the backing DAI. To achieve this he would need ```PSM.balanceOf() / maxDebtPerCollateralToken``` usdt tokens. This translates to: 2020202e6 usdt to flash from uniswap. If this calculation is done in solc with the raw ```/``` symbol, a +1 has to be added because of precision loss.
Now that we know how much usdt we need, we can flashloan it from uniswap. Since usdt's spot price is currently 0.9, our borrowed dollar value would be ```1818181```. After having a smart contract call:
```
lendingTerm.borrow(2_000_000e18, 2020202e6 +1);
psm.redeem(Alice, credit.balanceOf(Alice));
```
We end up with the 2_000_000e18 DAI from the PSM, which we will use to payback our loan which was 1.8m + interest, leaving us with the difference.

## Tools Used
Foundry, manual analysis

## Recommended Mitigation Steps


## Assessed type

DoS
GuildToken transfers are not allowed if any of the initial gauges of the user has its debtCeiling fully utilized
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In GuildToken.sol a comment says "Even when a loss occur, users can still transfer tokens with which they vote for gauges that did not suffer a loss" which is perfectly logical thing to expect. Other additional comments [here](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L29) mean that the user should be able to transfer irrespective of the condition of other gauges (LendingTerms).

But this is not true for this case.

The GuildToken.transfer function calls ERC20Gauges._decrementWeightUntilFree function to free up gauge weights from all his gauges (starting from the first entry in the userGauges list) until sufficient weight has been freed such that the desired guild tokens can be transferred (provided more than the desired amount are actually existing as free from any gauge, or put in non-slashed gauges so that they can be withdrawn, or free from debtCeiling constraints : essentially freeweights).

Suppose user has 1000 GUILD tokens and he allocates them to 10 different gauges A, B, C..... ... these gauges are added to the userGauges list sequentially as the user incrementGauge for them and exist in that particular order until user decides to remove entire weight from one of them( or gets losses to a gauge) and that gauge is then removed from the list.

So right now, A, B, C all have 100 weight from the user and there are zero freeWeights. Now assume that gauge B has its debtCeiling completely utilized such that if the gaugeWeight is decreased the debtCeiling will go below the current issuance, so this will not be allowed as seen [here](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L226) => so user's guild tokens will be locked. Now other 900 guild token should be accessible to transfer etc. because they have not suffered any losses, so user should be free to free/reallocate/transfer them.

But if user wants to transfer 200 Guild tokens (basically anything > 100), then it will fail. This causes revert on the entire transfer txn and most of the user's guild tokens may get locked for arbitrary time. 

This is because in the transfer, _decrementWeightUntilFree is called which starts freeing gauge weights from the user using _decrementGaugeWeight for each gauge starting from the first gauge in the user list (which have been put into the list sequentially as the user entered those gauges). In i = 0 , 100 gauge weights gets freed, so totalFreed = 100. Now in i = 1, _decrementGaugeWeight tries to free weights from gauge B (where debtCeiling has reached its full and weights are not allowed to be freed) which will fail due to the require statement [here](https://github.com/code-423n4/2023-12-ethereumcre
Incorrect Value of userStake taken which causes everyone to be slashed even if they staked after the last gauge loss
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Causes users to slashed when they are not causing them to loose their stake and not able to earn rewards.Precisily when a gauge is onboarded again after a gaugeloss then it will always return slashed for every user even if they are not.

## Proof of Concept
Below is a get rewards function
```
  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;
        }

        // if the user is not staking, do nothing
        userStake = _stakes[user][term];
        if (userStake.stakeTime == 0)
            return (lastGaugeLoss, userStake, slashed);

        // compute CREDIT rewards
        ProfitManager(profitManager).claimRewards(address(this)); // this will update profit indexes
        uint256 _profitIndex = ProfitManager(profitManager)
            .userGaugeProfitIndex(address(this), term);
        uint256 _userProfitIndex = uint256(userStake.profitIndex);

        if (_profitIndex == 0) _profitIndex = 1e18;
        if (_userProfitIndex == 0) _userProfitIndex = 1e18;

        uint256 deltaIndex = _profitIndex - _userProfitIndex;

        if (deltaIndex != 0) {
            uint256 creditReward = (uint256(userStake.guild) * deltaIndex) /
                1e18;
            uint256 guildReward = (creditReward * rewardRatio) / 1e18;
            if (slashed) {
                guildReward = 0;
            }

            // forward rewards to user
            if (guildReward != 0) {
                RateLimitedMinter(rlgm).mint(user, guildReward);
                emit GuildReward(block.timestamp, user, guildReward);
            }
            if (creditReward != 0) {
                CreditToken(credit).transfer(user, creditReward);
            }

            // save the updated profitIndex
            userStake.profitIndex = SafeCastLib.safeCastTo160(_profitIndex);
            updateState = true;
        }

        // if a loss occurred while the user was staking, the GuildToken.applyGaugeLoss(address(this))
        // can be called by anyone to slash address(this) and decrement gauge weight etc.
        // The contribution to the surplus buffer is also forfeited.
        if (slashed) {
            emit Unstake(block.timestamp, term, uint256(userStake.credit));
            userStake = UserStake({
                stakeTime: uint48(0),
                lastGaugeLoss: uint48(0),
                profitIndex: uint160(0),
Incrementing gauge after a positive PnL make it possible to steal rewards of all gauges
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L409-L436
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L416
ttps://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L258


# Vulnerability details

## Vulnerability details
Nothing prevent a user to increment a gauge after it has notified a positive PnL, meaning its possible to get rewards even if he never took risk to vote this gauge before.

The scenario is the following:
1) A profit is made
2) Bob had no weight in that gauge
3) Bob see this and incrementGauge (not even need to MEV this into the same block)
4) Bob call claimReward and get a reward

This shouldn't happen this way. What should happen is that user entered after the profit, so he's not entilted to reward right now, but will be for the next profit. 

But something even worse happens: incrementing a gauge after a positive PnL allow the user to steal all profit held by the `ProfitManager`

This happens because of the early return if `_userGaugeWeight == 0` [line 416 in claimGaugeRewards](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L416)

What happens is that when Bob increment gauge while having no weight in this gauge, this calls `_incrementGaugeWeight`; which itself [calls claimGaugeRewards before incrementing](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/tokens/GuildToken.sol#L258)

Then, if we go back to claimGaugeRewards code, we see its `_userGaugeProfitIndex` is not updated when his gaugeWeight is 0.
So, when finally he calls himeself `claimGaugeRewards`, his `_userGaugeProfitIndex == 0`, L425 update it to 1e18
And thus `deltaIndex != 0` , allowing him to get a reward he does not deserve.

```solidity
File: src\governance\ProfitManager.sol
409:     function claimGaugeRewards(
410:         address user,
411:         address gauge
412:     ) public returns (uint256 creditEarned) {
413:         uint256 _userGaugeWeight = uint256(			
414:             GuildToken(guild).getUserGaugeWeight(user, gauge)
415:         );
416:         if (_userGaugeWeight == 0) {	//@audit when _incrementGaugeWeight is called, early return
417:             return 0;
418:         }
419:         uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge];
420:         uint256 _userGaugeProfitIndex = userGaugeProfitIndex[user][gauge];
421:         if (_gaugeProfitIndex == 0) {	
422:             _gaugeProfitIndex = 1e18;	//@audit if no profit made, this == 1e18
423:         }
424:         if (_userGaugeProfitIndex == 0) {	//@audit if first claim, user profitIndex = 1e18
425:             _userGaugeProfitIndex = 1e18;
426:         }
427:         uint256
Attacker can steal most rebasing rewards and trap anyone else who enters rebase indefinitely. 
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The credit token inherits ERC20RebaseDistributor, allowing attackers to exploit the `ERC20RebaseDistributor::transfer()` or `ERC20RebaseDistributor::transferFrom()` functions. These two functions enables them to mint free credit tokens and trap others by engaging in self-transfers.

## Proof of Concept
Temporary values are used to update state variables in the `ERC20RebaseDistributor::transfer()` function.

```solidity

    /// @notice Override of default ERC20 behavior: exit rebase before movement (if rebasing),
    /// and re-enter rebasing after movement (if rebasing).
    function transfer(
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        // if `from` is rebasing, materialize the tokens from rebase to ensure
        // proper behavior in `ERC20.transfer()`.
        RebasingState memory rebasingStateFrom = rebasingState[msg.sender];//@audit-note temporary data is used 
        RebasingState memory rebasingStateTo = rebasingState[to]; //@audit-note temporary data is used 
        uint256 fromBalanceBefore = ERC20.balanceOf(msg.sender);
        uint256 _rebasingSharePrice =  (rebasingStateFrom.isRebasing == 1 || rebasingStateTo.isRebasing == 1)
                                        ? rebasingSharePrice()
                                        : 0; // only SLOAD if at least one address is rebasing
        if (rebasingStateFrom.isRebasing == 1) {
            uint256 shares = uint256(rebasingStateFrom.nShares);
            uint256 rebasedBalance = _shares2balance(
                shares,
                _rebasingSharePrice,
                0,
                fromBalanceBefore
            );
            uint256 mintAmount = rebasedBalance - fromBalanceBefore;
            if (mintAmount != 0) {
                ERC20._mint(msg.sender, mintAmount);
                fromBalanceBefore += mintAmount;
                decreaseUnmintedRebaseRewards(mintAmount);
                emit RebaseReward(msg.sender, block.timestamp, mintAmount);
            }
        }

        // do ERC20.transfer()
        bool success = ERC20.transfer(to, amount);

        // if `from` is rebasing, update its number of shares
        int256 sharesDelta;
        if (rebasingStateFrom.isRebasing == 1) {
            uint256 fromBalanceAfter = fromBalanceBefore - amount;
            uint256 fromSharesAfter = _balance2shares(fromBalanceAfter, _rebasingSharePrice);
            uint256 sharesSpent = rebasingStateFrom.nShares - fromSharesAfter;
            sharesDelta -= int256(sharesSpent);
            rebasingState[msg.sender] = RebasingState({isRebasing: 1, nShares: uint248(fromSharesAfter)});
A user who is slashed while staking, will be slashed again if he restake 
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Vulnerability details

In order to get GuildTokens, it is possible for a user to stake its CreditToken into `SurplusGuildMinter`. Staking in this contract imply for the user to transfer an amount of `CreditToken`s to the contract. In exchange, he will get Credit & Guild shares registered to his address for the term/gauge he has chosen to stake into.

For each Guild shares he get, the same amount of GuildToken is minted, these tokens are not transferable, and are automatically put in the term's gauge.
Each time a profit is made, user will see tokens transfered to its address as a reward.
But if a loss occur in the gauge, he will get slashed from all the credit he staked for this gauge.

The issue is there's a bug present in `SurplusGuildMinter::getRewards` which make it possible for the user to get slashed multiple time for the same loss in a gauge.

This function is called by `SurplusGuildMinter::stake` and `SurplusGuildMinter::unstake` at the very beginning of the call in order to apply slashing or distribute rewards before updating the user state.

The issue is [line 229](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L228-L234) : the condition verifying if a user should be slashed will always read an empty `userStake` element, as it declared but still not be writen.
Only afterward, line 234 userStake is written to, but it is too late for the condition to be verified.

```solidity
File: src\loan\SurplusGuildMinter.sol
216:     function getRewards(
217:         address user,
218:         address term
219:     )
220:         public
221:         returns (
222:             uint256 lastGaugeLoss, // GuildToken.lastGaugeLoss(term)
223:             UserStake memory userStake, // stake state after execution of getRewards()
224:             bool slashed // true if the user has been slashed
225:         )
226:     {
227:         bool updateState;
228:         lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term);
229:         if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) { //@audit-issue H03 - userStake not even yet defined...? always returns 0
230:             slashed = true;									//this means a user can be slashed twice or more
231:         }	//note after a slash, userStake.lastGaugeLoss == 0, thus if condition not verified
232: 
233:         // if the user is not staking, do nothing
234:         userStake = _stakes[user][term]; //@audit-issue H03 should be read before the if condition L229
235:         if (userStake.stakeTime == 0)
236:             return (lastGaugeLoss, userStake, slashed);
237: 
```

## Impact
After being slashed, if a user restake, he will again be slashed on next unstake.

## Proof of Concept

This PoC shows what happens (output below) :

```