UniStaker Infrastructure - peanuts's results

Staking infrastructure to empower Uniswap Governance.

General Information

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

Uniswap Foundation

Findings Distribution

Researcher Performance

Rank: 11/47

Findings: 2

Award: $716.32

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Awards

694.2987 USDC - $694.30

Labels

grade-b
QA (Quality Assurance)
satisfactory
duplicate-45
Q-03

External Links

Judge has assessed an item in Issue #83 as 2 risk. The relevant finding follows:

[L-04] Note that collectProtocol collects everything but 1 wei. Fee collectors who aren’t aware may get their transactions reverted

#0 - c4-judge

2024-03-14T14:14:25Z

MarioPoneder marked the issue as duplicate of #34

#1 - c4-judge

2024-03-14T14:14:29Z

MarioPoneder marked the issue as satisfactory

#2 - c4-judge

2024-03-26T22:51:31Z

MarioPoneder marked the issue as grade-b

Awards

22.023 USDC - $22.02

Labels

analysis-advanced
grade-b
A-01

External Links

Summary and Explanation

  • UNI token holders can now stake their tokens and earn rewards.

  • This rewards is actually from fees accrued in the Uniswap pool.

  • Usually, uniswap pool has 2 tokens, so it is very hard for the fees to be distributed properly to the UNI holders

  • To combat this issue, another party, the claimer, collects these two tokens and give an equivalent amount (or slightly less, to incentivize this action) of a single token to the Unistaker pool as the rewards to be given to the UNI stakers.

  • This amount to be given is set by the admin. (eg 10000 USDC) Only the admin can change this amount.

  • The claimer will wait until 10000+ USDC worth of fees is accrued and exchange their one token with the two fee tokens

  • Their one token will become rewards for the UNI token holders

  • For example, the WBTC-UNI pool has accrued some fees, eg 0.01 BTC and 100 UNI. It's very hard to give these two tokens to the users.

  • Assume 0.01 BTC and 100 UNI is worth 5000 USDC each. Assume WETH is worth 1000 each.

  • A claimer comes in, sees that the pool has 0.01 BTC and 100 UNI worth 10000 USDC. He collects these two tokens in exchange for 10 WETH, worth 10000 USDC.

  • This 10 WETH will be the reward tokens, and UNI token holders who stake their tokens will earn this 10 weth proportionately

  • Note that there will always be a claimer because if someone doesn't want to swap their WETH for the fees, then the fees will continue to accrue, and once it reaches a point where it is profitable, the claimer will want to exchange the fees for the reward amount

  • The reward distribution typically lasts for 30 days. Rewards are counted every second. A user who deposits late (eg 15 days) will not get rewards from day 1 to 15.

  • UNI token holders can exit their position anytime.

  • UNI token holders also have the capability to increase their stake, or change their reward (beneficiary) address.

  • UNI tokens can also delegate their votes to a delegatee of their choice when staking.

  • This whole protocol is an ongoing process. Claimers will wait to claim their fees in exchange for the payout token, and the UNI stakers waits until the reward tokens is transferred into the Unistaker contract to earn rewards.

  • The protocol will only not work if there are no more fees to claim from the pools that enable fees (which is highly unlikely)

Audit Approach

Codebase quality analysis

DelegationSurrogate.sol
ContractFunctionAccessComments
UniStakerconstructor-Delegates all token in the contract to the delegatee, and approves Unistaker to use all the Uni tokens.

Checks

  • Checked approve is called to the right address (The Unistaker contract).
  • Checked UNI token has a delegate function and is called correctly.

Potential Issues

  • constructor has the _token.delegate(_delegatee); call, so the delegation can only be called once. Users can stake more into the surrogate contract, but delegate will not be called. (Medium)
Unistaker.sol
ContractFunctionAccessComments
UniStakersetAdminonlyAdminNo two step transfer of ownership
UniStakersetRewardNotifieronlyAdminChecks whether contract is ready to receive reward via notifyRewardAmount
UniStakerlastTimeRewardDistributedviewCalled in global checkpoint reward. Sets lastCheckpointTime
UniStakerrewardPerTokenAccumulatedviewCalled in global checkpoint reward. Sets rewardPerTokenAccumulatedCheckpoint
UniStakerunclaimedRewardviewCalculates the unclaimed reward of the beneficiary
UniStakerstakepublicEntry point of users. Calls _stake
UniStakerstakepublicEntry point of users. Calls _stake. Sets the beneficiary
UniStakerpermitAndStakepublicEntry point of users. Calls _stake. Sets the beneficiary. Note permit frontrun issue
UniStakerstakeOnBehalfpublicHelps another person call stake. Must be a trusted signature
UniStakerstakeMorepublicMust already have a stake. Only can stake more for oneself
UniStakerpermitAndStakeMorepublicMust already have a stake. Only can stake more for oneself
UniStakerstakeMoreOnBehalfpublicMust already have a stake. Must be a trusted user
UniStakeralterDelegateepublicMust already have a stake. Surrogate contract transfers UNI tokens to other Surrogate
UniStakeralterDelegateeOnBehalfpublicSame as above. Caller must be trusted
UniStakeralterBeneficiarypublicChanges the reward recipient. Both previous and current recipient will update their rewards
UniStakeralterBeneficiaryOnBehalfpublicSame as above. Caller must be trusted
UniStakerwithdrawpublicWithdraws UNI tokens from surrogate contract to the deposit owner
UniStakerwithdrawOnBehalfpublicSame as above. Caller must be trusted
UniStakerclaimRewardpublicOnly beneficiary can call
UniStakerclaimRewardOnBehalfpublicClaims reward for the beneficiary.
UniStakernotifyRewardAmountClaimerCalled by Claimer in V3FactoryOwner. Reward tokens is deposited into Unistaker
UniStaker_fetchOrDeploySurrogateinternalDeploys a new surrogate contract if delegatee doesn't exist yet.
UniStaker_stakeTokenSafeTransferFrominternalSafe transfers token
UniStaker_fetchOrDeploySurrogateinternalDeploys a new surrogate contract if delegatee doesn't exist yet.
UniStaker_useDepositIdinternalCalled in _stake. Increments depositId value by 1
UniStaker_stakeinternalBeneficiary gets earningPower, creates a Deposit struct, call checkpoint global and reward
UniStaker_stakeMoreinternalCalls checkpoint global and reward
UniStaker_alterDelegateeinternalDeploys new surrogate if doesn't exist. Updates deposit struct with new delegatee
UniStaker_alterBeneficiaryinternalUpdates previous and current beneficiary, update deposit struct with new beneficiary
UniStaker_withdrawinternalOpposite of _stake, transfers withdraw amount to the deposit.owner
UniStaker_claimRewardinternalBeneficiary will claim the reward. Reward will be set to zero
UniStaker_checkpointGlobalRewardinternalUpdates rewardPerTokenAccumulatedCheckpoint, updates lastCheckpointTime
UniStaker_checkpointRewardinternalUpdates unclaimedRewardCheckpoint, updates beneficiaryRewardPerTokenCheckpoint

Scenarios Checked

  1. Can first depositor steal all the funds?
  • No, first depositor cannot steal all the funds
  • The reward token per uni is split evenly. If user A is first depositor, and subsequent users deposit before rewards are in, nothing will be broken.
  1. Can totalStaked be manipulated?
  • It can be changed to a huge number, but the change will not affect the collection of rewards
  • First depositor can deposit 1 wei, and when rewards are distributed, can deposit 100e18 UNI tokens. This will make rewardPerTokenAccumulatedCheckpoint extremely large.
  • This large amount does not matter because when 100e18 UNI tokens is deposited, _checkpointReward() is called and beneficiaryRewardPerTokenCheckpoint[_beneficiary] = rewardPerTokenAccumulatedCheckpoint. That means that from now on, although the number is extremely large, it doesn't matter because the calculation starts from that second.
  • The 1 wei holder also will not get extra rewards because of the unclaimedReward() calculation
  1. Is rewards distributed proportionately?
  • Yes, rewards are checked and distributed proportionately. Assume 300 Reward tokens for 30 days. User A is the only user with 100 UNI staked. 10 days later, user B stakes 100 UNI. 10 days after this, User C stakes 800 UNI.

  • First 10 days, user A gets 100 Reward tokens

  • Second 10 days, user A gets 50 reward tokens, user B gets 50 reward tokens

  • Last 10 days, user A gets 10, user B gets 10, user C gets 80.

  • This is because everytime someone stakes, they will start with rewardPerTokenAccumulatedCheckpoint as their beneficiaryRewardPerTokenCheckpoint[_beneficiary]. If they decide to claim the second they stake, rewardPerTokenAccumulated() - beneficiaryRewardPerTokenCheckpoint[_beneficiary] = 0. They will start earning their share the second they start staking

  1. What if a user doesn't claim their reward?
  • It will just be stuck in unclaimedReward. Everytime a depositor stake more, the beneficiary will get more rewards, and unclaimedReward() simplay adds the previous unclaimed rewards
  1. Can a reward caller grief another reward caller?
  • No, because of the if (_amount0 < _amount0Requested || _amount1 < _amount1Requested) { check. If the claimer do not get the fees they asked for, they will not transfer their reward tokens to the UniStaker contract.
  1. Can a staker claim more rewards than expected?
  • No, because all reward emission will end after the 30 day mark, and they will not earn anything else until the next rewards are funnelled in.
  1. Can transferring beneficiary result in double rewards?
  • No. When transferring beneficiary, checkpointglobalcheckpoint and checkpointreward is called to update their collected rewards. Transferring beneficiary, even if the beneficiary is the same address, will not affect anything.
  • earningPower[_beneficiary] * (rewardPerTokenAccumulated() - beneficiaryRewardPerTokenCheckpoint[_beneficiary]) will return zero as rewardPerTokenAccumulated() - beneficiaryRewardPerTokenCheckpoint[_beneficiary] is zero.
  1. Does transferring delegatee break anything?
  • No, old surrogate contract will simply transfer UNI to new surrogate contract.
  • Transferring delegatee, even if delegatee is ownself, does not affect the transfer of uni tokens
  1. Does any reward token get stuck in contract?
  • Not really, all reward tokens are claimable once the rewardEndTime is reached. It is up to the beneficiary to collect their rewards
  1. Does flashloaning work?
  • Nope, because flashloan happens within the second, and no rewards will be accrued within a second. Only 1 second later, which defeats the purpose of a flash loan.

Potential Issues

  • No potential issues found thus far, other than those already known eg Permit Frontrunning.
V3FactoryOwner.sol
ContractFunctionAccessComments
V3FactoryOwnersetAdminonlyAdminNo two step transfer of ownership
V3FactoryOwnersetPayoutAmountonlyAdminSensitive function, claimers will exchange payout amount for fees
V3FactoryOwnerenableFeeAmountonlyAdminEnables collection of fees, called to UniswapV3Factory.sol
V3FactoryOwnersetFeeProtocolonlyAdminSets fee amount, called to UniswapV3Pool.sol
V3FactoryOwnerclaimFeesPublicCalled by anyone who wants to collect fees in exchange for payout amount

Notes

Checks

  • Checked calls to UniswapV3Factory and UniswapV3Pool is correct
  • Checked all sensitive functions have access control enabled
  • Checked call to Unistaker contract is correct
  • Checked transferring of payout amount to unistaker is correct
  • Checked collection of fees from pool is correct

Potential Issues

  • Overhauling UniswapV3Factory as the new factory owner will affect createPool() in UniswapV3Factory (Medium)
  • Change of owner is irreversible (Low/Medium)
  • Protocols with fees enabled that doesn't earn enough fees will have their fees stuck in contract since no one wants to exchange payout amount (Medium)

Mechanism Review

For Users

Staking and withdrawing

  • Mechanism is simple to understand, staking UNI tokens is easy and withdrawing UNI tokens is also easy. There is no delay, and users can earn rewards even if tokens are just staked for a second

Altering Delegatees and Beneficiaries

  • Users can alter their delegatees and beneficiaries easily without any fees, and the change is seamless.

Collecting Rewards

  • Beneficiaries can collect rewards through the click of one function. Note that there might be some issues if the beneficiary is a smart contract.
  • Reward tokens go directly to the beneficiary which is convenient.
For Claimers
  • Claimers are protected against frontrunning attack by specifying minimum amounts which is good.
  • For pools with fee-on-transfer tokens, claimers must be careful of their amount requested (it should be the original amount, and not the fee-on-transferred amount)
  • Note that the amount of fees is calculated as totalAmount - 1 wei, not totalAmount.

Centralization Risks

  • Not much centralization risks, except two important parts.
  • In Unistaker.sol, the admin can set the reward notifier via setRewardNotifier(). If it is not set, the whole protocol will not work
  • In V3FactoryOwner.sol, the admin can change the payout amount at any time through setPayoutAmount(). Able to grief the claimers and the protocol itself.

Systemic Risks

  • Not much systemic risks, users do not really stand to lose anything. The protocol also does not face any infrastructure problem. At the most, no fees are collected and UNI stakers will not earn anything since no claimers will exchange the fee for reward tokens, but then users can simply withdraw their UNI tokens.

Time spent:

025 hours

#0 - c4-sponsor

2024-03-08T16:47:05Z

wildmolasses (sponsor) acknowledged

#1 - thebrittfactor

2024-03-08T16:50:33Z

For transparency, the sponsor inadvertently marked as acknowledged. Removing label until the team has a chance to review further.

#2 - c4-judge

2024-03-14T18:17:22Z

MarioPoneder marked the issue as grade-c

#3 - c4-judge

2024-03-14T18:17:35Z

MarioPoneder 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