Platform: Code4rena
Start Date: 23/02/2024
Pot Size: $92,000 USDC
Total HM: 0
Participants: 47
Period: 10 days
Judge: 0xTheC0der
Id: 336
League: ETH
Rank: 20/47
Findings: 1
Award: $694.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: CodeWasp
Also found by: 0xdice91, 0xlemon, Aamir, Al-Qa-qa, AlexCzm, BAHOZ, Bauchibred, Breeje, DadeKuma, Fassi_Security, PetarTolev, Shield, SpicyMeatball, Trust, ZanyBonzy, cheatc0d3, gesha17, haxatron, imare, jesjupyter, kutugu, lsaudit, marchev, merlinboii, nnez, osmanozdemir1, peanuts, radev_sw, twicek, visualbits
694.2987 USDC - $694.30
Take a look at https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L292-L304
function permitAndStake( uint256 _amount, address _delegatee, address _beneficiary, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external returns (DepositIdentifier _depositId) { //@audit permit STAKE_TOKEN.permit(msg.sender, address(this), _amount, _deadline, _v, _r, _s); _depositId = _stake(msg.sender, _amount, _delegatee, _beneficiary); }
function permitAndStakeMore( DepositIdentifier _depositId, uint256 _amount, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external { Deposit storage deposit = deposits[_depositId]; _revertIfNotDepositOwner(deposit, msg.sender); STAKE_TOKEN.permit(msg.sender, address(this), _amount, _deadline, _v, _r, _s); _stakeMore(deposit, _depositId, _amount); }
Evidently one can see that in both instances, permit()
is queried to verify that the caller has provided enough tokens for the new deposit to stake or to add to an existing deposit, case with this is that, protocol supports WETH and WETh is one of those tokesn that do not support this "permit" functionality, making bot h methods of depositing and staking in protocol faulty cause now an attempt to call permi()
on this token would silently fail and not revert due to it's fall back implementation, now since it doesn't revert, it means anyone can pass any _amount
for this asset and stake this amount while not sending/approving any tokens to the UniStaker
contract.
Protocol's internal accounting would get faulty cause now anyone can modify their total staked amount to any value for the WETH token and even the earning power attached of the beneficiary attached to the depositId
.
Since there is no guarantee that all tokens would implement EIP-165, reconsider the protocol's support of WETH and even if it should ever be used as a STAKE_TOKEN
UNI
is also a weird token and has this faulty logic in regards to approvals/transfersTake a look at https://github.com/d-xo/weird-erc20?tab=readme-ov-file#revert-on-large-approvals--transfers.
One can see that UNI
is weird token and has a faulty logic in regards to approvals/transfers, but no tests has been placed around this to ensure that protocol works as expected. This shouldn't be the case
constructor(IERC20Delegates _token, address _delegatee) { _token.delegate(_delegatee); _token.approve(msg.sender, type(uint256).max); }
Evidently, protocol expects the surrogate to be approved of uint(256).max, but this wouldn't work due to UNI's limitation of approvals.
More test cases have not being looked upon, limits protocols sight of contracts
Integrate more tests
Consider the implementation differences between Uniswap V2 and V3 regarding protocol fee management. In V3, the V3FactoryOwner.setFeeProtocol
method could be adapted to manage a global protocol fee, simplifying the process of enabling or updating fees across pools significantly. This approach would alleviate the governance bottleneck and streamline fee adjustments.
Take a look at https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/V3FactoryOwner.sol#L136-L150
/// @notice Passthrough method that sets the protocol fee on a v3 pool. Must be called by the /// admin. /// @param _pool The Uniswap v3 pool on which the protocol fee is being set. /// @param _feeProtocol0 The fee protocol 0 param to forward to the pool. /// @param _feeProtocol1 The fee protocol 1 parm to forward to the pool. /// @dev See docs on IUniswapV3PoolOwnerActions for more information on forwarded params. function setFeeProtocol( IUniswapV3PoolOwnerActions _pool, uint8 _feeProtocol0, uint8 _feeProtocol1 ) external { _revertIfNotAdmin(); _pool.setFeeProtocol(_feeProtocol0, _feeProtocol1); }
In Uniswap V2, the protocol fee is globally managed via the factory, presenting a streamlined approach. However, V3 introduces a more complex mechanism where setting the protocol fee for each pool requires governance approval. This complexity is magnified by the governance proposal's limitation of actions, making the activation of a global fee switch logistically challenging. Unlike V2's fixed protocol fee of 1/6th, V3 offers flexibility, allowing fees to range from 1/10th to 1/4th or vary between pool constituents. This flexibility, while advantageous, could lead to a community consensus on a universal fee strategy to avoid micromanagement of individual pools.
Introduce a global fee protocol feature within the V3FactoryOwner
contract, allowing for broad fee adjustments without individual pool governance proposals.
Or maybe establish a manual override process, possibly through a trusted entity, to address edge cases or reduce fees after significant increases, ensuring flexibility and adaptability.
#0 - wildmolasses
2024-03-12T16:36:47Z
QA-01 Not useful; STAKE_TOKEN is UNI, not WETH... QA-02 Not a problem; interesting approach but has no consequence at all QA-03 Stakeholders are not interested in global fee setting.
Disputing because none of the findings are valid.
#1 - c4-sponsor
2024-03-12T16:36:55Z
wildmolasses (sponsor) disputed
#2 - c4-judge
2024-03-14T13:34:26Z
MarioPoneder marked the issue as grade-c
#3 - MarioPoneder
2024-03-14T18:26:58Z
Overall QA grade b due to #113
#4 - c4-judge
2024-03-14T18:27:02Z
MarioPoneder marked the issue as grade-b