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: 43/47
Findings: 1
Award: $22.02
š Selected for report: 0
š Solo Findings: 0
š Selected for report: roguereggiant
Also found by: 0xepley, Aamir, Al-Qa-qa, LinKenji, MSK, McToady, Myd, SAQ, Sathish9098, ZanyBonzy, aariiif, cudo, emerald7017, fouzantanveer, hassanshakeel13, hunter_w3b, ihtishamsudo, kaveyjoe, peanuts
22.023 USDC - $22.02
The UniStaker contract allows UNI token holders to stake their tokens and earn additional token rewards over time. It is designed to incentivize long-term locking of UNI tokens to empower Uniswap governance participation.
Contract Architecture
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā ā UniStaker Contract ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā⤠ā - Reward Distribution Logic ā ā - Deposit Management ā ā - Delegation Handling ā ā - Admin Functions ā ā ā āāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāā ā ā interfaces with ā¼ āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā ā External Reward Token ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
The UniStaker contract consists of four key components:
Reward Distribution Logic: Calculates rewards earned by each depositor based on stake amount and duration. Manages streaming of rewards over fixed time period.
Deposit Management: Allow users to stake UNI tokens, withdraw stake, and manage associated deposit metadata (beneficiary, delegatee etc.)
Delegation Handling: Facilitates delegation of voting power of staked tokens to chosen delegates. Achieved via DelegationSurrogate
contracts.
Admin Functions: Powerful control functions like setting admin, managing reward notifiers.
It interacts heavily with an external reward ERC20 token contract to distribute rewards.
Vulnerability Analysis
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā ā Admin Risks ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā⤠ā - Admin has excessive privileges ā ā - Admin functions lack access controls ā ā - Lack of transparency around admin actions ā ā - No timelocks or multisig protections ā ā ā ā => Could lead to centralization of power, fund ā ā drainage, malicious upgrades ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
The key risks stem from the unchecked admin privileges. The admin can unilaterally:
This could lead to loss of funds or make the contract unusable. There are no mitigating controls like timelocks, multisig schemes, or transparency over admin actions.
The key administrator privileges are concentrated in the V3FactoryOwner
contract
Function | Description | Access |
---|---|---|
setAdmin | Allows changing the admin address. Only callable by current admin. | Admin |
setPayoutAmount | Allows changing the payout amount. Only callable by admin. | Admin |
enableFeeAmount | Enables a fee amount on the Uniswap V3 factory. Only callable by admin. | Admin |
setFeeProtocol | Sets protocol fee percentage on a Uniswap V3 pool. Only callable by admin. | Admin |
setAdmin | Allows changing the admin address. Only callable by current admin. | Admin |
setRewardNotifier | Allows enabling/disabling reward notifier addresses. Only callable by admin. | Admin |
The contracts are highly dependent on the admin roles. A malicious admin could:
Set extremely high protocol fees in V3FactoryOwner
, draining fees from users
Disable legitimate reward notifiers or enable malicious ones in UniStaker
Change critical parameters like reward duration in UniStaker
This could lead to loss of funds or make the contracts unusable.
Authorization Flaws
The admin functions lack further access control. Any admin can take critical actions unilaterally.
Upgradeability Risks
The contracts are not upgradeable, so this is not a concern.
Lack of Transparency
Admin actions like changing fees or reward duration are logged with events. However, disabling/enabling reward notifiers has no event.
Mitigation Recommendations
Use a Timelock for admin functions. This prevents unilateral control.
Implement 2-of-3 multisig scheme for admin changes. Promotes decentralization.
Emit events for all admin actions, even enabling/disabling notifiers. Improves transparency.
Introduce role-based access control. For example, separate roles for changing fees, managing notifiers, etc. Reduces centralization of power in one admin.
Consider making admin a DAO. Allows community oversight of changes.
Simplify V3FactoryOwner
design by removing ability to change pool fees. Reduces surface area for exploits.
The unchecked admin privileges present a significant risk. Adding timelocks, multisig schemes, access roles and other mitigations can help reduce this risk and promote decentralization, transparency and accountability. The context that these contracts are meant for mainnet deployment necessitates thorough security review before launch.
Recommendations
Decentralize admin powers
Add timelocks for admin functions
Improve transparency
Conduct more testing
Conclusion
The UniStaker contract provides a mechanism to incentivize UNI staking and governance participation. However, the key risks stem from the unchecked admin privileges, which could lead to centralization and fund loss over time. Implementing recommendations around decentralizing control, adding timelocks and transparency, and enhancing testing would significantly improve sustainability and security.
The contracts do not directly rely on external price or data feeds. However, their utility depends on the overall health of the Uniswap ecosystem. Issues like manipulation of Uniswap price oracles could have indirect effects.
The main dependencies are the Uniswap V3 factory and pool contracts. Any issues with those contracts will directly impact this system. For example, a bug that freezes trading in Uniswap pools would also freeze fee accrual in this system.
There is a risk of complex interactions with flash loans. For example, a malicious actor could take a flash loan to manipulate Uniswap prices, trigger this contract to think substantial fees have accrued, claim the fees cheaply, then profit from price manipulation with the stolen fees.
Governance Risks
Centralization
Highly centralized as admin holds total control. Lack of community governance or multisig schemes is a risk.
Lack of Adaptability
No built-in way for the community to vote on or enact changes. This could prevent responding quickly to exploits or changing market conditions.
Mitigation Recommendations
Implement DAO governance to decentralize control.
Create a process for halting the contract under emergency scenarios.
Conduct extensive composability testing with other DeFi protocols.
Model impacts of different Uniswap fee settings and liquidity conditions.
Verify core invariant calculations using formal methods.
Structure incentives so that honest behavior is most profitable for users.
The codebase consists of:
The UniStaker contract, which is a Uniswap token staking contract that allows users to delegate their governance votes and earn rewards over time. It has logic for:
DelegationSurrogate contract, a helper contract to enable separating voting power while still allowing delegated governance for stakers.
Several interface contracts that define specific methods/structs used by the core staking and factory contracts.
The V3FactoryOwner contract, which facilitates transferring protocol fees accrued in Uniswap V3 pools. It:
The core concept is a Uniswap token staking system to motivate governance participation, funded through a fee claim mechanism powered by the V3 Factory owner contract.
The key risks stem from the unchecked admin privileges in the staking and factory owner contracts, creating potential for centralization of power and fund loss over time.
flowchart TD subgraph Exterior contracts Uni[Uniswap v3 Pools]-- Transfers accrued fees --> F[V3FactoryOwner] end subgraph UniStaker System F -->|Allows claiming fees|CF[ClaimFees<br>(Public function)] CF -->|Pays payoutToken amount <br>to notify rewards| ST1[StakingTokens<br>(ERC20)] ST1 -->|Notifies of <br> reward amount|SA1[StakingAndAdmin<br>(UniStaker Contract)] SA1 --> |Deposits rewards|STR[StakingTokens<br>(Reward ERC20)] SA1 --> |Distributes rewards to|SB1[Stakers/Beneficiaries] SA1 -. Delegates votes .-> DS1[DelegationSurrogate] SB1 -. Provides liquidity .-> Uni DS1 -. Votes on behalf of .-> SB1 end
Key Points:
This allows Uniswap governance participation to be incentivized via staking rewards funded through fee revenue.
The systemic risk stems from the admin privileges in V3FactoryOwner and UniStaker, allowing unchecked control.
Reducing staking rewards
Increasing payout amount
Whitelisting malicious reward notifiers
Enabling fees on manipulable pools
Reducing reward duration
Changing the reward token
Frequent parameter changes
A major consideration is ensuring governance decisions balance incentives between ordinary stakers, MEV bots, governance voters, and protocol sustainability. Dramatic changes risk unintended consequences. Decisions should promote the long-term health of the protocol.
Adding Reward Notifiers
Removing Reward Notifiers
Notified Reward Amounts
Reward Delivery
By restricting this privileged functionality, establishing strict vetting, and phasing changes gradually, risk is mitigated. The code overall appears to enforce good constraints around notifier management. But governance discretion is key.
Immutability
Handling Upgrades
Delegation Surrogates
Other Factors
Summary
The immutable approach, interface abstractions, and minimal delegate contracts help mitigate upgrade risks. But if the vote delegation method changed, surrogates may need to revoted. No active logic handles this.
Sig issues with smart contract accounts
Unexpected delegate call interactions
Obscured end user identification
Compromised smart contract wallets
Unintended token approvals
To mitigate these issues, care should be taken around integration with smart contract accounts or wallets. Usage should be restricted until confidence is built via testing and audits. Account abstraction should not allow circumventing intended business logic. And governance decisions should consider the impacts to users relying on account abstraction solutions.
The totalStaked
amount could overflow if new deposits are added without checking for overflows. This could incorrectly show more total stake than what is actually staked.
Withdrawals could underflow totalStaked
or deposit.balance
if overflow checks are missing, allowing users to withdraw more than they deposited.
Calculating scaledRewardRate
involves multiplication and division, which could overflow. This could result in an excessively high reward rate.
Accumulating rewards over time in rewardPerTokenAccumulated
could overflow without overflow protection.
Calculating a user's unclaimedReward
involves subtraction. If the global reward amount has overflowed but the user's checkpoint hasn't, it could incorrectly show they are owed a large reward.
Transferring staking tokens or reward tokens could underflow balances if checks are missing.
To prevent these, all arithmetic operations should be wrapped in SafeMath functions like add
, sub
, mul
, div
to prevent overflows and underflows. Explicit checks before state changes can also help.
Thoroughly review all math operations involving token balances, stakes, reward amounts, and accumulations over time to ensure overflows and underflows cannot occur in any scenario. This will help ensure token accounting remains accurate.
Specifically, if no notifyRewardAmount
calls are made for a duration longer than REWARD_DURATION
(30 days), the rewardEndTime
would expire.
When a new notification is finally made after a long pause like this, here is what would happen:
if (block.timestamp >= rewardEndTime) { // This code path would execute after long pause scaledRewardRate = (_amount * SCALE_FACTOR) / REWARD_DURATION; } else { // This code path expects rewards were continual uint256 _remainingReward = scaledRewardRate * (rewardEndTime - block.timestamp); // _remainingReward will be 0 after long pause scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / REWARD_DURATION; }
The contract no longer expects pauses in notifications and does not properly restart rewards after a pause. This could mess up accounting.
To fix this, I would recommend explicitly handling pausing/resuming via new methods:
function pauseRewards() external { // Pause distribution, snapshot state } function resumeRewards() external { // Calculate total elapsed pause time // Set new rewardEndTime // Adjust rates if needed // Restart distribution }
Adding this logic would allow the contract to be resilient to gaps in reward notifications.
Fee Calculation
Uniswap fees are calculated as .03% of the swap volume. Tracking fractional percentage values already introduces some rounding that could benefit users:
uint24 swapFee = .03% // Stored rounded down uint feeForSwap = swapFee * swapAmount / 100; // Rounding benefits swapper here
Payout Calculation
The payout amount may also round down fron a fractional native token value:
uint payoutAmount = 1.123 WETH; // Rounds down to 1 WETH // Benefits fee claimer
This happens again distributing rewards:
uint reward = calcFractionalReward(*); // Rounding reduces rewards to stakers
Mitigations
Use wrapper libraries that always round up fractional tokens
Introduce rounding buffers on top of exact calculations
Dynamically adjust payouts to equalize roundings
The contract should account for rounding systematically rather than benefitting a single party.
45 hours
#0 - c4-judge
2024-03-14T18:22:51Z
MarioPoneder marked the issue as grade-b