xanoutas/solidity-security-findings huggingface dataset:
A special attack idea
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

I have a relatively unique attack idea.  Taking the USDC market created in the GIP_0 contract as an example, in the first launched term, the hardCap of gUSDC is 2 million:
```solidity
address termSDAI1 = termOnboarding.createTerm(
    _lendingTermV1,
    LendingTerm.LendingTermParams({
        collateralToken: AddressLib.get("ERC20_SDAI"),
        maxDebtPerCollateralToken: 1e18, // 1 CREDIT per SDAI collateral + no decimals correction
        interestRate: SDAI_RATE, // 4%
        maxDelayBetweenPartialRepay: 0, // no periodic partial repay needed
        minPartialRepayPercent: 0, // no minimum size for partial repay
        openingFee: 0, // 0%
        hardCap: CREDIT_HARDCAP // max 2m CREDIT issued
    })
);
```

1. Assume that a malicious team uses approximately $2 million in real costs as collateral (ERC20_SDAI) and uses multiple witch accounts to mint all 2 million gUSDC.

2. In the PSM contract, assume that other users provide 1 million USDC as peg tokens.

3. A malicious team calls the redeem function in the PSM contract, burns one million gUSDC, and gets one million USDC.

4. The malicious team still has 1 million gUSDC left, and they deliberately turn all their gUSDC into bad debts.  At this time, their ERC20_SDAI collateral will be auctioned off.

5. Since malicious teams hold the most gUSDC, they are in the most advantageous position during the auction process and can buy back all ERC20_SDAI collateral at a low price in the form of an auction.

6. So far, the malicious team has earned a total of 1 million USDC!

#### Additional Context:
Auction mechanism: at first, offer 0% collateral, ask 100% debt. Then, over time, offer a larger % of the collateral and still 100% debt. At 'midpoint', 100% collateral is offered asking for 100% debt, and if nobody has bid, bad debt will be realized. In the second phase, 100% collateral is offered and less and less debt is asked. When we reach the end of the auction (100% collateral offered, 0% debt asked), nobody can bid in the auction anymore, and the loans can be automatically forgiven (marking it as a 100% loss). The first to bid wins the auction, making it a race to arbitrage (onchain MEV or otherwise).





## Assessed type

Other
The userGaugeProfitIndex is not set correctly, allowing an attacker to receive rewards without waiting
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

When `claimGaugeRewards()` is called for the first time before the user votes for a gauge, the `userGaugeProfitIndex` should be set to the current `gaugeProfitIndex`, later when the `gaugeProfitIndex` grows the user will be able to claim the rewards.

The problem here is that the first time `claimGaugeRewards()` is called the `userGaugeProfitIndex` is not set to anything because of this check:

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

```solidity
416:  if (_userGaugeWeight == 0) {
417:      return 0;
418:  }
```

So an attacker can vote for a gauge after profit is notified, `claimGaugeRewards()` will be called for the first time in `incrementGauge()` however the `userGaugeProfitIndex` will not be set and will be 0. 

The attacker will then call `claimGaugeRewards()` again and because the `userGaugeProfitIndex` == 0 it will be set to 1e18 and because the `gaugeProfitIndex` is bigger than 1e18 the attacker will receive rewards without waiting. 

## Impact

The attacker will receive rewards without waiting and he can repeat this until all rewards are stolen, other users will then fail to claim their rewards and will receive nothing.

## Proof of Concept

Add this to `ProfitManager.t.sol` and `import "@forge-std/console.sol";`

As you can see the attacker will be able to steal rewards in the same transaction.


```solidity
function testAttackClaimAfterProfit() public {
        address attacker = makeAddr("attacker");
        vm.startPrank(governor);
        core.grantRole(CoreRoles.GOVERNOR, address(this));
        core.grantRole(CoreRoles.CREDIT_MINTER, address(this));
        core.grantRole(CoreRoles.GUILD_MINTER, address(this));
        core.grantRole(CoreRoles.GAUGE_ADD, address(this));
        core.grantRole(CoreRoles.GAUGE_PARAMETERS, address(this));
        core.grantRole(CoreRoles.GAUGE_PNL_NOTIFIER, address(this));
        vm.stopPrank();

        vm.prank(governor);
        profitManager.setProfitSharingConfig(
            0, // surplusBufferSplit
            0.5e18, // creditSplit
            0.5e18, // guildSplit
            0, // otherSplit
            address(0) // otherRecipient
        );
        guild.setMaxGauges(1);
        guild.addGauge(1, gauge1);
        guild.mint(attacker, 150e18);
        guild.mint(bob, 400e18);


        vm.prank(bob);
        guild.incrementGauge(gauge1, 400e18);
        

        credit.mint(address(profitManager), 20e18);
        profitManager.notifyPnL(gauge1, 20e18);

        //Attacker votes for a gauge after it notifies profit
        //The userGaugeProfitIndex of the attacker is not set 
        vm.prank(attacker);
        guild.incrementGauge(gauge1, 150e18);
       
        //Because the userGaugeProfitI
Liquidators can short credit tokens via PSM contract for massive profits.
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

While any user can carry out this attack, liquidators are best posed since they can best structure the order of transactions necessary to carry out this exploit.

1. Assume there is a loan which is going to be in bad debt. Thus the liquidator is aware that `creditMultiplier` is going to decrease, decreasing the value of the credit tokens. The PSM contract gives an opportunity to play this trade and short the credit token with a flashloan, allowing potentially infinite leverage.

2. The liquidator can take a flashloan of credit tokens. This can be easily available since credit tokens are expected to be in some sort of a liquidity pool and DEXes use the optimistic pattern allowing flashswaps. The liquidator can then redeem these credit tokens for all peg tokens in the PSM contract. This also decreases the `totalSupply` of credit tokens since they are burnt.

3. The liquidation call is then done. Loss is realized, `creditMultiplier` decreases (assume it decreases from 1 to 0.9). This decrease is exaggerated since the `totalSupply` of credit tokens has also decreased. This can be seen in the snippet below from `notifyPnL` function in ProfitManager.sol.

```solidity
uint256 creditTotalSupply = CreditToken(_credit).totalSupply();
    uint256 newCreditMultiplier = (creditMultiplier *
        (creditTotalSupply - loss)) / creditTotalSupply;
```

4. The liquidator then sends in all the peg tokens from last step and mints credit tokens. Since the `creditMultiplier` is now 0.9, they get 11% more credit tokens than they initially flash loaned. They then repay the flashloan and keep the extra 11% credit tokens.

The attack can be carried out by a sandwich attacker on mainnet, but not on Arbitrum due to the FCFS system. However liquidators on both chains can abuse this. Liquidators may even take a bad deal on the liquidation in order to ensure they get control and offset their losses via this shorting strategy.

## Proof of Concept

The POC above can be added to `ProfitManager.t.sol` to show the same scenario of a liquidator (or a sandwich attacker) shorting credit tokens.

```solidity
function testAttackShort() public {
  // grant roles to test contract
  vm.startPrank(governor);
  core.grantRole(CoreRoles.GAUGE_PNL_NOTIFIER, address(this));
  core.grantRole(CoreRoles.CREDIT_MINTER, address(this));
  vm.stopPrank();

  // Fund the PSM contract with 1e6 pegtokens
  address funder = address(1337);
  vm.startPrank(funder);
  pegToken.mint(funder, 1_000_000e6);
  pegToken.approve(address(psm), 1_000_000e6);
  psm.mint(funder, 1_000_000e6);
  vm.stopPrank();

  // Flashloan credit tokens from a DEX in a fresh account
  credit.burn(credit.balanceOf(address(this)));
  credit.mint(address(this), 10_000e18);
  uint256 initBalance = credit.balanceOf(address(this));

  // Short cre
In the proposeOffboard function of the LendingTermOffboarding contract, there is no verification of whether the passed-in term parameter comes from the same market.  A malicious actor may use the proposeOffboard function to offboard lending terms from other different markets.
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In the `proposeOffboard` function of the `LendingTermOffboarding` contract, there is no verification of whether the passed-in term parameter comes from the same market.  A malicious actor may use the `proposeOffboard` function to offboard lending terms from other different markets.

## Proof of Concept
The code for the proposeOffboard function is as follows:
```solidity
function proposeOffboard(address term) external whenNotPaused {
    require(
        polls[block.number][term] == 0,
        "LendingTermOffboarding: poll exists"
    );
    require(
        block.number > lastPollBlock[term] + POLL_DURATION_BLOCKS,
        "LendingTermOffboarding: poll active"
    );
    // Check that the term is an active gauge
    require(
        GuildToken(guildToken).isGauge(term),
        "LendingTermOffboarding: not an active term"
    );

    polls[block.number][term] = 1; // voting power
    lastPollBlock[term] = block.number;
    emit OffboardSupport(
        block.timestamp,
        term,
        block.number,
        address(0),
        1
    );
}
```
It can be seen that the incoming term parameter is not verified to be from the same market.

Different markets have different quorum parameters.  Suppose there are two LendingTermOffboarding contracts (contract A, contract B, corresponding to the ETH and Bitcoin markets).  The quorum parameter of contract A is lower than that of contract B. Suppose:
1. The quorum of contract A is 10000, and the quorum of contract B is 50
2. Because the proposeOffboard function does not verify whether the term parameter comes from the same market, as long as there are 50 votes in contract B, the term in contract A can be offboarded, and the redemption function of the corresponding PSM in contract A will be temporarily suspended.

## Tools Used
Manual audit

## Recommended Mitigation Steps
Optimize the system logic, in the proposeOffboard function of the LendingTermOffboarding contract, verify whether the incoming term parameter comes from the same market


## Assessed type

Governance
`surplusGuildMinter.sol`:`getRewards` uses bad value for userStake
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-L234


# Vulnerability details

## Impact

The function `getReward` in `surplusGuildMinter.sol` contract is used to calculate pending rewards for the Surplus minter contract. It also calculates if a term has had negative profit, and thus if a gauge has been slashed. This is done in the following snippet.

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

The issue is that `userStake` is being used without being assigned. `userStake` is actually allocated in the `return` part of the function prototype, and is an empty struct, thus having `userStake.lastGaugeLoss` return 0. This means if a slashing has happened in the past at any point, every user will be flagged as `slashed=true` even if they had staked after the slashing.

The correct assignment actually takes place below this code block.

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

Thus the value is used before assignment, leading to incorrect results. This leads to direct loss, since in the `unstake` function, the user is not give any tokens back if they are slashed.

```solidity
if (slashed) return;
```

## Proof of Concept

The test `testUnstakeWithoutLoss` in `SurplusGuildMinter.t.sol` has been modified to show the issue. A loss is realized, and then a user stakes on to the system. The `getRewards` function shows that the user has been slashed, even though they have not.

```solidity
function testAttackSGM() public {
  // setup
  credit.mint(address(this), 150e18);
  credit.approve(address(sgm), 150e18);
  sgm.stake(term, 150e18);
  assertEq(credit.balanceOf(address(this)), 0);
  assertEq(profitManager.surplusBuffer(), 0);
  assertEq(profitManager.termSurplusBuffer(term), 150e18);
  assertEq(guild.balanceOf(address(sgm)), 300e18);
  assertEq(guild.getGaugeWeight(term), 350e18);
  assertEq(sgm.getUserStake(address(this), term).credit, 150e18);

  // the guild token earn interests
  vm.prank(governor);
  profitManager.setProfitSharingConfig(
    0.5e18, // surplusBufferSplit
    0, // creditSplit
    0.5e18, // guildSplit
    0, // otherSplit
    address(0) // otherRecipient
  );
  credit.mint(address(profitManager), 35e18);
  profitManager.notifyPnL(term, 35e18);
  assertEq(profitManager.surplusBuffer(), 17.5e18);
  assertEq(profitManager.termSurplusBuffer(term), 150e18);
  (, , uint256
Using temporary value can lead to a chain of bad debts
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Liquidating a position may take up to 30 minutes. The amount of credit tokens requested, known as `loanDebt`, is determined by the `creditMultiplier` when the `LendingTerm::call()` function is called.

When a bad debt occurs, the `creditMultiplier` decreases. This decrease affects other ongoing auctions because the `LendingTerm::onBid()` function uses the latest `creditMultiplier` to calculate the principal.

In simpler terms, when a bad debt happens, the value of credit tokens decreases and liquidators can mint same amount of credit tokens using fewer peg tokens from the PSMs.

If a bad debt occurs in an auction, other ongoing auctions should use the new `creditMultiplier` to determine their requested loan amount (`loanDebt`).

## Proof of Concept
LendingTerm::call()#L665
```solidity
        // update loan in state
        uint256 loanDebt = getLoanDebt(loanId);
```

LendingTerm::getLoanDebt:L228-L230
```solidity
        uint256 creditMultiplier = ProfitManager(refs.profitManager)
            .creditMultiplier();
        loanDebt = (loanDebt * loan.borrowCreditMultiplier) / creditMultiplier;
    }
```

## Tools Used
VS Code

## Recommended Mitigation Steps
Determine the `loanDebt` each auction should request using the new `creditMultiplier`.


## Assessed type

Context
Slashing in SurplusGuildMinter is determined incorrectly leading to user losing his GUILD stake and also the guildReward from his staking Position
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
When a user stakes his CREDIT token into Surplus Guild Minter, his GUSDC gets donated to the termSurplusBuffer and he gets minted some GUILD tokens that are automatically involved as gauge weights in the user's chosen gauge via the SurplusGuildMinter's position in that gauge. After initial staking, the user can collect any rewards ie. guildSplit (profits distributed from Lendingterms for GUILD voters in that gauge) through getRewards, which also determines if the user has to be slashed (ie. when the term accrues a loss after the user staked his GUILD here in SGM).

The problem is that this slashing is being determined incorrectly, such that every user will be slashed by default whenever he collects rewards or even if anyone else calls getRewards on his behalf. This slashing will lead to zeroing of the user's position in SGM and also deprive him of any additional guildReward he might have earned. 

This is because slashing is true if user's previously recorded lastGaugeLoss (which is recorded as the current lastGaugeLoss of that gauge at the time user stakes first) is before the current lastGaugeLoss of the term) : which is correct by design, but in code slashing does not use user's real lastGaugeLoss to compare. Instead slashing compares an uninitialized value of lastGaugeLoss in an empty userStake struct => which will always be zero and hence < gauge's current lastGaugeLoss if the gauge had accrued a loss ever. This is a problem because user should only be slashed if he staked after the gauge accrued loss last time. 

See [here](https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L229) : lastGaugeLoss is being compared with a value from an empty struct just initialized in the returns part of the function.

We can see that userStake struct has been initialized just after this as `userStake = _stakes[user][term];`

This incorrect comparison leads to slashing of a user who has staked after the lastGaugeLoss of that gauge, which means all his GUILD stake is gone and also any guildReward he might have deserved is gone.

## Proof of Concept
guildReward is zero if slashed
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L253

Staking position goes to zero if slashed :
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L274

## Tools Used
Manual review 

## Recommended Mitigation Steps
Set `userStake = _stakes[user][term];`  before comapring user's lastGaugeLoss. the userStake struct needs to be correctly initialized first. 


## Assessed type

Context
Users losing their staking record due to mistaken slashing
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-L230


# Vulnerability details

### Impact

Contract Name: SurplusGuildMinter

The vulnerability lies in the `getRewards` function, where an invalid validation process incorrectly marks users for slashing (slashed = true). As a result of this erroneous validation, users experience a severe impact, including the loss of their staked amount and the forfeiture of rewards that they were entitled to receive as shown in the following:
```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;
}

// store the updated stake, if needed
if (updateState) {
    _stakes[user][term] = userStake;
}
```

Therefore, Users' stakes are mistakenly slashed due to the invalid validation in line 229, resulting in the loss of the staked amount they contributed to the contract.


### Proof of Concept

https://gist.github.com/beber89/88795f5eeb273b5edf3828fe20df8a91

### Tools Used
Foundry

### Recommended Mitigation Steps

The validation in line 229 can be fixed just  if line 234 in the function: `userStake = _stakes[user][term];`  is moved ahead of the if condition in line 229.




## Assessed type

Invalid Validation
Different markets can prevent the execution of proposals in other markets, resulting in confusion in the system's proposal
Audit: 2023-12-ethereumcreditguild-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/GuildVetoGovernor.sol#L315-L343
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/test/proposals/gips/GIP_0.sol#L441-L448
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/test/proposals/gips/GIP_0.sol#L210-L221


# Vulnerability details

## Impact
Different markets can prevent the execution of proposals in other markets, resulting in confusion in the system's proposal

## Proof of Concept
In The Credit Guild system, a market is a 'gauge type' at the smart-contract level.

Each market has an independent GuildVetoGovernor contract and has the authority of `TIMELOCK_CANCELLER`. At the same time, the timelock variable of the GuildVetoGovernor contract points to the unique `daoTimelock` contract.  Therefore, in the `GuildVetoGovernor` contract of any market, you can call the function `createVeto(bytes32 timelockId)` to object to other markets.  

We believe this is a serious governance vulnerability.

## Tools Used
Manual audit

## Recommended Mitigation Steps
Segregate the execution authority of GuildVetoGovernor contracts in different markets



## Assessed type

Governance