Ethereum Credit Guild - cats's results

A trust minimized pooled lending protocol.

General Information

Platform: Code4rena

Start Date: 11/12/2023

Pot Size: $90,500 USDC

Total HM: 29

Participants: 127

Period: 17 days

Judge: TrungOre

Total Solo HM: 4

Id: 310

League: ETH

Ethereum Credit Guild

Findings Distribution

Researcher Performance

Rank: 124/127

Findings: 1

Award: $3.05

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

3.0466 USDC - $3.05

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
duplicate-473

External Links

Lines of code

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

Vulnerability details

Impact

Users are not able to unstake their tokens or receive rewards for them when they should be able to.

Proof of Concept

Due to not caching the userStake info inside the function SurplusGuildMinter::getRewards before making a critical comparison, whenever a gauge takes a single loss, all user rewards will be slashed.

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)) { // @audit comparison here
            slashed = true;
        }

        // if the user is not staking, do nothing
        userStake = _stakes[user][term]; // @audit caching here

userStake.lastGaugeLoss will always be the default value of uint256 which is 0. Whenever a gauge takes a single loss, this comparison will always be true, thus slashed will be true.

Furthermore, since the getRewards function is called every time when a user tries to unstake, as long as a gauge takes a single loss, all users that have staked tokens will not be able to unstake them even if they should be able to. Clear loss of funds/tokens for the users.

function unstake(address term, uint256 amount) external {
        // apply pending rewards
        (, UserStake memory userStake, bool slashed) = getRewards(
            msg.sender,
            term
        );

        // if the user has been slashed, there is nothing to do
        if (slashed) return;

Tools Used

Manual Review

Cache the userStake before the comparison.

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];
        lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term);
        if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) { // @audit comparison here
            slashed = true;
        }

        // if the user is not staking, do nothing
-       userStake = _stakes[user][term]; // @audit caching here

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-12-29T15:18:14Z

0xSorryNotSorry marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-12-29T15:18:38Z

0xSorryNotSorry marked the issue as duplicate of #1164

#2 - c4-judge

2024-01-28T20:12:36Z

Trumpero marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter