PoolTogether - Tripathi's results

General Information

Platform: Code4rena

Start Date: 04/03/2024

Pot Size: $36,500 USDC

Total HM: 9

Participants: 80

Period: 7 days

Judge: hansfriese

Total Solo HM: 2

Id: 332

League: ETH

PoolTogether

Findings Distribution

Researcher Performance

Rank: 21/80

Findings: 3

Award: $165.52

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

1.4652 USDC - $1.47

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
:robot:_10_group
duplicate-59

External Links

Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611

Vulnerability details

Summary

PrizeVault::claimYieldFeeShares Transfers yield fee shares to the yield fee recipient. instead of decreasing transferred shares it resets the yieldFeeBalance even for partial shares

Impact

yieldFeeBalance will lost due to incorrect update

Proof of Concept

    /// @notice Transfers yield fee shares to the yield fee recipient
    /// @param _shares The shares to mint to the yield fee recipient
    /// @dev Emits a `ClaimYieldFeeShares` event
    /// @dev Will revert if the caller is not the yield fee recipient or if zero shares are withdrawn
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

        yieldFeeBalance -= _yieldFeeBalance;
        //@audit-issue it should be yieldFeeBalance -= shares

        _mint(msg.sender, _shares);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611

Transferring _shares should decrease yieldFeeBalance by _shares, while it resets the yieldFeeBalance to zero

Tools Used

Manual

-     yieldFeeBalance -= _yieldFeeBalance;
+     yieldFeeBalance -= _shares;

Assessed type

Math

#0 - c4-pre-sort

2024-03-11T21:56:19Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-03-11T21:56:24Z

raymondfam marked the issue as duplicate of #10

#2 - c4-pre-sort

2024-03-13T04:38:36Z

raymondfam marked the issue as duplicate of #59

#3 - c4-judge

2024-03-15T07:38:37Z

hansfriese marked the issue as satisfactory

Findings Information

🌟 Selected for report: Aymen0909

Also found by: 0xmystery, Abdessamed, FastChecker, Giorgio, Tripathi, btk, cheatc0d3, trachev, turvy_fuzz

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
:robot:_90_group
duplicate-274

Awards

131.1445 USDC - $131.14

External Links

Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L489

Vulnerability details

Summary

prizeVault::withdraw() is used to withdraw exact number of assets by buring shares and sent to receiver.

But this function lacks maxShares burnt param, without this caller could burn more than expected amount of shares for corresponding assets.

Impact

caller will end up burning more shares than he expect

Proof of Concept

    function withdraw(
        uint256 _assets,
        address _receiver,
        address _owner
    ) external returns (uint256) {
        uint256 _shares = previewWithdraw(_assets);
        _burnAndWithdraw(msg.sender, _receiver, _owner, _shares, _assets);
        return _shares;
    }

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L489 withdraw only takes _assets the amount of underlying assets to be withdrawn by burning whatever amount of shares which is not an optimised trade for caller.

In many case caller could end up burning more than he expected

eg. before submitting transaction Alice see that everything is fine so she submitted a tx for withdrawing assets due to some malicious actions or inconsistent behaviour vault goes into recovery mode i.e totalAssets()< totalDebt()

Now 1 share corresponds to less than 1 asset but Alice tx will be executed as normal tx and she will end up burning more shares than she expected

Tools Used

Manual

Add proper slippage checks during withdrawing

Assessed type

Other

#0 - c4-pre-sort

2024-03-11T23:47:10Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-03-11T23:47:20Z

raymondfam marked the issue as duplicate of #90

#2 - c4-pre-sort

2024-03-13T05:09:53Z

raymondfam marked the issue as duplicate of #274

#3 - c4-judge

2024-03-15T08:08:52Z

hansfriese changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-03-15T08:09:30Z

hansfriese marked the issue as satisfactory

Findings Information

🌟 Selected for report: 0xmystery

Also found by: Al-Qa-qa, Tripathi, ZanyBonzy, d3e4, dvrkzy, slvDev

Labels

bug
grade-b
insufficient quality report
QA (Quality Assurance)
Q-03

Awards

32.9145 USDC - $32.91

External Links

1 PrizeVault->Claimable->HookManager::setHooks() allows Unintended or Malicious Use of Prize Winners' Hook

The setHooks function in Vault.sol allows users to set arbitrary hooks, potentially enabling them to make external calls with unintended consequences. This vulnerability could lead to various unexpected behaviors, such as unauthorized side transactions with gas paid unbeknownst to the claimer, reentrant calls, or denial-of-service attacks on claiming transactions.

 function setHooks(VaultHooks calldata hooks) external {
        _hooks[msg.sender] = hooks;
        emit SetHooks(msg.sender, hooks);
    }

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/abstract/HookManager.sol#L29 it is possible for a hook to make an external call and modify the EVM state.

Handle these malicious calls

2 PrizeVaultFactory::deployVault allows deployment of vaults with non-authentic claimer, prizePool etc

A malicious vault can be deployed via the official PrizeVaultFactory contract. Users may lose funds while interacting with such vaults.

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVaultFactory.sol#L92

ensure proper validation or implement kind of trusted role for the deployement of new vaults

3 PrizeVault::setYieldFeeRecipient doesn't check for zero address

    function setYieldFeeRecipient(address _yieldFeeRecipient) external onlyOwner {
        _setYieldFeeRecipient(_yieldFeeRecipient);
    }
    function _setYieldFeeRecipient(address _yieldFeeRecipient) internal {
        //@audit lacks zero address check
        yieldFeeRecipient = _yieldFeeRecipient;
        emit YieldFeeRecipientSet(_yieldFeeRecipient);
    }

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L958

4 depositWithPermit doesn't server the purpose of permit function since even after sign the tx only signer can call this function

    function depositWithPermit(
        uint256 _assets,
        address _owner,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external returns (uint256) {
        if (_owner != msg.sender) {
            revert PermitCallerNotOwner(msg.sender, _owner);
        }

        // Skip the permit call if the allowance has already been set to exactly what is needed. This prevents
        // griefing attacks where the signature is used by another actor to complete the permit before this
        // function is executed.
        if (_asset.allowance(_owner, address(this)) != _assets) {
            IERC20Permit(address(_asset)).permit(_owner, address(this), _assets, _deadline, _v, _r, _s);/
        }

        uint256 _shares = previewDeposit(_assets);
        _depositAndMint(_owner, _owner, _assets, _shares);
        return _shares;
    }

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L532 fix this so that it serves the purpose of permit function or remove the function to save deployment cost

5 depositWithPermit lacks chainID which allows signatures to be re-used across forks

    function depositWithPermit(
        uint256 _assets,
        address _owner,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external returns (uint256) {
///////////////////////
        if (_asset.allowance(_owner, address(this)) != _assets) {
            IERC20Permit(address(_asset)).permit(_owner, address(this), _assets, _deadline, _v, _r, _s);/
        }
///////////////////////
    }

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L540 eg.

Scenario :: An EIP is included in an upcoming hard fork that has split the community. After the hard fork, a significant user base remains on the old chain. On the new chain, Alice approves some tokens via a call to permit. Alice, operating on both chains, replays the permit call on the old chain

Include the chainID opcode in the permit schema. This will make replay attacks impossible in the event of a post-deployment hard fork.

#0 - raymondfam

2024-03-13T06:53:04Z

1: See #18 2.: Owner is trusted per readme 3: Bot: [L-10] Missing checks for address(0x0) when updating address state variables 4, 5: See #13

#1 - c4-pre-sort

2024-03-13T06:53:08Z

raymondfam marked the issue as insufficient quality report

#2 - c4-pre-sort

2024-03-13T06:53:13Z

raymondfam marked the issue as grade-c

#3 - c4-judge

2024-03-21T05:52:46Z

hansfriese marked the issue as grade-b

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