Platform: Code4rena
Start Date: 21/04/2022
Pot Size: $75,000 USDC
Total HM: 7
Participants: 45
Period: 7 days
Judge: 0xean
Total Solo HM: 5
Id: 111
League: ETH
Rank: 1/45
Findings: 3
Award: $11,075.00
🌟 Selected for report: 2
🚀 Solo Findings: 1
🌟 Selected for report: IllIllI
A malicious authorized user can steal all unclaimed rewards and break the reward accounting
Even if the authorized user is benevolent the fact that there is a rug vector available may negatively impact the protocol's reputation. Furthermore since this contract is meant to be used by other projects, the trustworthiness of every project cannot be vouched for.
By setting a booster that returns zero for all calls to boostedBalanceOf()
where the user
address is not under the attacker's control, and returning arbitrary values for those under his/her control, an attacker can choose specific amounts of rewardToken
to assign to himself/herself. The attacker can then call claimRewards()
to withdraw the funds. Any amounts that the attacker assigns to himself/herself over the amount that normally would have been assigned, upon claiming, is taken from other users' unclaimed balances, since tokens are custodied by the flywheelRewards
address rather than per-user accounts.
File: flywheel-v2/src/FlywheelCore.sol 182 /// @notice swap out the flywheel booster contract 183 function setBooster(IFlywheelBooster newBooster) external requiresAuth { 184 flywheelBooster = newBooster; 185 186 emit FlywheelBoosterUpdate(address(newBooster)); 187 }
File: flywheel-v2/src/FlywheelCore.sol 258 uint256 supplierTokens = address(flywheelBooster) != address(0) 259 ? flywheelBooster.boostedBalanceOf(strategy, user) 260 : strategy.balanceOf(user); 261 262 // accumulate rewards by multiplying user tokens by rewardsPerToken index and adding on unclaimed 263 uint256 supplierDelta = (supplierTokens * deltaIndex) / ONE; 264 uint256 supplierAccrued = rewardsAccrued[user] + supplierDelta; 265 266 rewardsAccrued[user] = supplierAccrued;
File: flywheel-v2/src/FlywheelCore.sol 119 function claimRewards(address user) external { 120 uint256 accrued = rewardsAccrued[user]; 121 122 if (accrued != 0) { 123 rewardsAccrued[user] = 0; 124 125 rewardToken.safeTransferFrom(address(flywheelRewards), user, accrued);
Projects also using BaseFlywheelRewards
or its child contrats, are implicitly approving infinite transfers by the core
File: flywheel-v2/src/rewards/BaseFlywheelRewards.sol 25 constructor(FlywheelCore _flywheel) { 26 flywheel = _flywheel; 27 ERC20 _rewardToken = _flywheel.rewardToken(); 28 rewardToken = _rewardToken; 29 30 _rewardToken.safeApprove(address(_flywheel), type(uint256).max); 31 }
The attacker need not keep the booster set this way - he/she can set it, call accrue()
for his/her specific user, and unset it, all in the same block
Code inspection
Make flywheelRewards
immutable, or only allow it to change if there are no current users
#0 - Joeysantoro
2022-04-27T03:20:02Z
This is a similar issue to one which already affects the SushiSwap masterchef. If rewards are decreased without first calling the accrue
-equivalent on the masterchef, then previous rewards are lost.
If trust minimization is a desired property (in my opinion it is), then these functions should be behind timelocks.
If a user can call accrue before the booster is updated, they can lock in past rewards as they are added onto the rewardsAccrued global state var
266 rewardsAccrued[user] = supplierAccrued;
#1 - Joeysantoro
2022-04-27T03:20:30Z
I don't really see this as a vulnerability, but will leave it to the c4 judge
#2 - 0xean
2022-05-19T00:02:36Z
I do see this as a vulnerability. Essentially, there is a backdoor by which a privileged address can extract value from users. A timelock would be a potential solution to mitigate some of the risk, as well as the mitigation options presented by the warden.
2 — Med: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements.
This is a hypothetical attack path with external requirements and deserves the medium severity rating.
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0xDjango, 0xmint, CertoraInc, Dravee, MaratCerby, Ruhum, VAD37, catchup, csanuragjain, defsec, delfin454000, dipp, fatima_naz, gzeon, hake, hyh, joestakey, kebabsec, oyc_109, rayn, robee, samruna, simon135, sorrynotsorry, teryanarmen
1437.6398 USDC - $1,437.64
The nonce mapping used for permit()
calls is the same as the one used for delegateBySig()
. This should at the very least be documented so signers know that the order of operations between the two functions matters, and so that multicall()
s can be organized appropriately
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 392 require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce");
multicall()
s involving permit()
and delegateBySig()
can be DOSedAttackers monitoring the blockchain for multicalls can front-run by calling permit()
and delegateBySig()
before the multicall()
, causing it to revert. Have separate flavors of the functions where the multicall()
data is included in the hash
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 392 require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce");
File: lib/flywheel-v2/src/FlywheelCore.sol #1 82 @return the cumulative amount of rewards accrued to user (including prior)
the cumulative amount of rewards accrued to the user since the last claim https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/FlywheelCore.sol#L82
require()
should be used instead of assert()
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 196 assert(queuedRewards.storedCycle == 0 || queuedRewards.storedCycle >= lastCycle);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #2 235 assert(queuedRewards.storedCycle >= cycle);
require()
/revert()
statements should have descriptive reason stringsFile: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 114 require(rewardToken.balanceOf(address(this)) - balanceBefore >= totalQueuedForCycle);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #2 153 require(rewardToken.balanceOf(address(this)) - balanceBefore >= newRewards);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #3 154 require(newRewards <= type(uint112).max); // safe cast
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #4 195 require(queuedRewards.storedCycle < currentCycle);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #5 200 require(nextRewards <= type(uint112).max); // safe cast
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #6 345 require(_userGauges[user].remove(gauge));
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #7 266 require(_delegates[delegator].remove(delegatee));
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #8 352 require(_delegates[user].remove(delegatee)); // Remove from set. Should never fail.
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #9 393 require(signer != address(0));
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
File: lib/flywheel-v2/src/FlywheelCore.sol #1 84 function accrue(ERC20 strategy, address user) public returns (uint256) {
File: lib/flywheel-v2/src/FlywheelCore.sol #2 101 function accrue( 102 ERC20 strategy, 103 address user, 104 address secondUser 105 ) public returns (uint256, uint256) {
Use a solidity version of at least 0.8.4 to get bytes.concat()
instead of abi.encodePacked(<bytes>,<bytes>)
Use a solidity version of at least 0.8.12 to get string.concat()
instead of abi.encodePacked(<str>,<str>)
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 4 pragma solidity ^0.8.0;
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #1 47 uint32 public immutable gaugeCycleLength;
seen in lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol https://github.com/fei-protocol/flywheel-v2/tree/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/token/ERC20Gauges.sol#L47
File: lib/xTRIBE/src/xTRIBE.sol #1 4 pragma solidity ^0.8.0;
File: lib/flywheel-v2/src/FlywheelCore.sol #1 17 The Core contract maintaings three important pieces of state:
File: lib/flywheel-v2/src/FlywheelCore.sol #2 262 // accumulate rewards by multiplying user tokens by rewardsPerToken index and adding on unclaimed
rewardsPerToken https://github.com/fei-protocol/flywheel-v2/tree/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/FlywheelCore.sol#L262
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #3 230 /// @notice thrown when incremending during the freeze window.
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #4 143 /// @notice An event thats emitted when an account changes its delegate
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #5 189 * @param delegatee the receivier of votes.
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #6 364 /*/////////////////////////////////////////////////////////////// 365 EIP-712 LOGIC 366 //////////////////////////////////////////////////////////////*/
did you mean EIP-2612? https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/token/ERC20MultiVotes.sol#L364-L366
File: lib/flywheel-v2/src/FlywheelCore.sol #1 93 /** 94 @notice accrue rewards for a two users on a strategy 95 @param strategy the strategy to accrue a user's rewards on 96 @param user the first user to be accrued 97 @param user the second user to be accrued 98 @return the cumulative amount of rewards accrued to the first user (including prior) 99 @return the cumulative amount of rewards accrued to the second user (including prior) 100 */ 101 function accrue( 102 ERC20 strategy, 103 address user, 104 address secondUser 105 ) public returns (uint256, uint256) {
Missing: @param secondUser
https://github.com/fei-protocol/flywheel-v2/tree/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/FlywheelCore.sol#L93-L105
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #2 130 @param num the number of gauges to return 131 */ 132 function gauges(uint256 offset, uint256 num) external view returns (address[] memory values) {
Missing: @return
https://github.com/fei-protocol/flywheel-v2/tree/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/token/ERC20Gauges.sol#L130-L132
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #3 176 @param num the number of gauges to return. 177 */ 178 function userGauges( 179 address user, 180 uint256 offset, 181 uint256 num 182 ) external view returns (address[] memory values) {
Missing: @return
https://github.com/fei-protocol/flywheel-v2/tree/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/token/ERC20Gauges.sol#L176-L182
indexed
fieldsEach event
should use three indexed
fields if there are three or more fields
File: lib/flywheel-v2/src/FlywheelCore.sol #1 66 event AccrueRewards(ERC20 indexed strategy, address indexed user, uint256 rewardsDelta, uint256 rewardsIndex);
File: lib/flywheel-v2/src/FlywheelCore.sol #2 73 event ClaimRewards(address indexed user, uint256 amount);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #3 44 event CycleStart(uint32 indexed cycleStart, uint256 rewardAmount);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #4 47 event QueueRewards(address indexed gauge, uint32 indexed cycleStart, uint256 rewardAmount);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #5 234 event IncrementGaugeWeight(address indexed user, address indexed gauge, uint256 weight, uint32 cycleEnd);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #6 237 event DecrementGaugeWeight(address indexed user, address indexed gauge, uint256 weight, uint32 cycleEnd);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #7 440 event MaxGaugesUpdate(uint256 oldMaxGauges, uint256 newMaxGauges);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #8 443 event CanContractExceedMaxGaugesUpdate(address indexed account, bool canContractExceedMaxGauges);
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #9 102 event MaxDelegatesUpdate(uint256 oldMaxDelegates, uint256 newMaxDelegates);
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #10 105 event CanContractExceedMaxDelegatesUpdate(address indexed account, bool canContractExceedMaxDelegates);
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #11 135 event Delegation(address indexed delegator, address indexed delegate, uint256 amount);
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #12 138 event Undelegation(address indexed delegator, address indexed delegate, uint256 amount);
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #13 141 event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 380 address signer = ecrecover( 381 keccak256( 382 abi.encodePacked( 383 "\x19\x01", 384 DOMAIN_SEPARATOR(), 385 keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)) 386 ) 387 ), 388 v, 389 r, 390 s 391 ); 392 require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce"); 393 require(signer != address(0)); 394 _delegate(signer, delegatee);
#0 - 0xean
2022-05-20T18:41:37Z
The severities listed in this QA report are correct as-is
🌟 Selected for report: 0xkatana
Also found by: 0v3rf10w, 0x1f8b, 0xNazgul, 0xmint, CertoraInc, Dravee, Fitraldys, Funen, IllIllI, NoamYakov, Scocco, Tomio, catchup, csanuragjain, defsec, delfin454000, djxploit, fatima_naz, gzeon, joestakey, joshie, kebabsec, nahnah, oyc_109, rayn, robee, rotcivegaf, saian, samruna, sorrynotsorry, teryanarmen, z3s
262.36 USDC - $262.36
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 241 // Require freeVotes exceed the delegation size 242 uint256 free = freeVotes(delegator); 243 if (delegatee == address(0) || free < amount) revert DelegationError();
The address check should be split out and moved to before the call to freeVotes()
https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/token/ERC20MultiVotes.sol#L241-L243
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #2 393 require(signer != address(0));
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #3 259 if (cycle - block.timestamp <= incrementFreezeWindow) revert IncrementFreezeError();
address
mappings can be combined into a single mapping
of an address
to a struct
, where appropriateSaves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas). Reads and subsequent writes can also be cheaper
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #1 59 mapping(address => mapping(address => uint112)) public getUserGaugeWeight; 60 61 /// @notice a mapping from a user to their total allocated weight across all gauges 62 /// @dev NOTE this may contain weights for deprecated gauges 63 mapping(address => uint112) public getUserWeight;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #2 151 mapping(address => mapping(address => uint256)) private _delegatesVotesCount; 152 153 /// @notice mapping from a delegator to the total number of delegated votes. 154 mapping(address => uint256) public userDelegatedVotes;
The instances below point to the second access of a state variable within a function. Caching will replace each Gwarmaccess (100 gas) with a much cheaper stack read. Less obvious fixes/optimizations include having local storage variables of mappings within state variable mappings or mappings within state variable structs, having local storage variables of structs within mappings, or having local caches of state variable contracts/addresses.
File: lib/ERC4626/src/xERC4626.sol #1 40 rewardsCycleEnd = (block.timestamp.safeCastTo32() / rewardsCycleLength) * rewardsCycleLength;
File: lib/ERC4626/src/xERC4626.sol #2 89 uint32 end = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength;
File: lib/flywheel-v2/src/FlywheelCore.sol #3 168 rewardToken.safeTransferFrom(address(flywheelRewards), address(newFlywheelRewards), oldRewardBalance);
File: lib/flywheel-v2/src/FlywheelCore.sol #4 168 rewardToken.safeTransferFrom(address(flywheelRewards), address(newFlywheelRewards), oldRewardBalance);
File: lib/flywheel-v2/src/FlywheelCore.sol #5 221 ? flywheelBooster.boostedTotalSupply(strategy)
File: lib/flywheel-v2/src/FlywheelCore.sol #6 259 ? flywheelBooster.boostedBalanceOf(strategy, user)
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #7 90 gaugeCycle = (block.timestamp.safeCastTo32() / gaugeCycleLength) * gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #8 103 uint32 currentCycle = (block.timestamp.safeCastTo32() / gaugeCycleLength) * gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #9 135 uint32 currentCycle = (block.timestamp.safeCastTo32() / gaugeCycleLength) * gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #10 158 uint112 queued = nextCycleQueuedRewards;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #11 146 uint32 offset = paginationOffset;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #12 174 address[] memory gauges = gaugeToken.gauges(offset, numRewards);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #13 92 return (nowPlusOneCycle / gaugeCycleLength) * gaugeCycleLength; // cannot divide by zero and always <= nowPlusOneCycle so no overflow
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #14 263 if (added && _userGauges[user].length() > maxGauges && !canContractExceedMaxGauges[user])
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #15 61 return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #16 269 _delegatesVotesCount[delegator][delegatee] = newDelegates;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #17 354 _delegatesVotesCount[user][delegatee] = 0;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #18 352 require(_delegates[user].remove(delegatee)); // Remove from set. Should never fail.
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesFile: lib/ERC4626/src/xERC4626.sol #1 67 storedTotalAssets -= amount;
File: lib/ERC4626/src/xERC4626.sol #2 72 storedTotalAssets += amount;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #3 155 nextCycleQueuedRewards += uint112(newRewards); // in case a previous incomplete cycle had rewards, add on
++i
/i++
should be unchecked{++i}
/unchecked{++i}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsFile: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 189 for (uint256 i = 0; i < size; i++) {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #2 346 for (uint256 i = 0; i < size && (userFreeVotes + totalFreed) < votes; i++) {
require()
/revert()
strings longer than 32 bytes cost extra gasFile: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 379 require(block.timestamp <= expiry, "ERC20MultiVotes: signature expired");
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 231 return 0;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #2 248 return _incrementUserAndGlobalWeights(msg.sender, weight, currentCycle);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #3 317 return _incrementUserAndGlobalWeights(msg.sender, weightsSum, currentCycle);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #4 331 return _decrementUserAndGlobalWeights(msg.sender, weight, currentCycle);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #5 394 return _decrementUserAndGlobalWeights(msg.sender, weightsSum, currentCycle);
bool
s for storage incurs overhead// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27
Use uint256(1)
and uint256(2)
for true/false
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #1 450 mapping(address => bool) public canContractExceedMaxGauges;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #2 111 mapping(address => bool) public canContractExceedMaxDelegates;
Use a solidity version of at least 0.8.2 to get compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require()
strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
File: lib/ERC4626/src/xERC4626.sol #1 4 pragma solidity ^0.8.0;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #2 3 pragma solidity ^0.8.0;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #3 4 pragma solidity ^0.8.0;
File: lib/xTRIBE/src/xTRIBE.sol #4 4 pragma solidity ^0.8.0;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 189 for (uint256 i = 0; i < size; i++) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #2 134 for (uint256 i = 0; i < num; ) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #3 184 for (uint256 i = 0; i < num; ) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #4 307 for (uint256 i = 0; i < size; ) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #5 384 for (uint256 i = 0; i < size; ) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #6 564 for (uint256 i = 0; i < size && (userFreeWeight + totalFreed) < weight; ) {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #7 79 uint256 low = 0;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #8 346 for (uint256 i = 0; i < size && (userFreeVotes + totalFreed) < votes; i++) {
File: lib/xTRIBE/src/xTRIBE.sol #9 95 for (uint256 i = 0; i < size; ) {
internal
functions only called once can be inlined to save gasFile: lib/flywheel-v2/src/FlywheelCore.sol #1 146 function _addStrategyForRewards(ERC20 strategy) internal {
++i
costs less gas than ++i
, especially when it's used in for
-loops (--i
/i--
too)File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 189 for (uint256 i = 0; i < size; i++) {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #2 346 for (uint256 i = 0; i < size && (userFreeVotes + totalFreed) < votes; i++) {
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
File: lib/ERC4626/src/xERC4626.sol #1 24 uint32 public immutable rewardsCycleLength;
File: lib/ERC4626/src/xERC4626.sol #2 27 uint32 public lastSync;
File: lib/ERC4626/src/xERC4626.sol #3 30 uint32 public rewardsCycleEnd;
File: lib/ERC4626/src/xERC4626.sol #4 33 uint192 public lastRewardAmount;
File: lib/ERC4626/src/xERC4626.sol #5 37 constructor(uint32 _rewardsCycleLength) {
File: lib/ERC4626/src/xERC4626.sol #6 48 uint192 lastRewardAmount_ = lastRewardAmount;
File: lib/ERC4626/src/xERC4626.sol #7 49 uint32 rewardsCycleEnd_ = rewardsCycleEnd;
File: lib/ERC4626/src/xERC4626.sol #8 50 uint32 lastSync_ = lastSync;
File: lib/ERC4626/src/xERC4626.sol #9 79 uint192 lastRewardAmount_ = lastRewardAmount;
File: lib/ERC4626/src/xERC4626.sol #10 80 uint32 timestamp = block.timestamp.safeCastTo32();
File: lib/ERC4626/src/xERC4626.sol #11 89 uint32 end = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength;
File: lib/flywheel-v2/src/FlywheelCore.sol #12 195 uint224 index;
File: lib/flywheel-v2/src/FlywheelCore.sol #13 197 uint32 lastUpdatedTimestamp;
File: lib/flywheel-v2/src/FlywheelCore.sol #14 201 uint224 public constant ONE = 1e18;
File: lib/flywheel-v2/src/FlywheelCore.sol #15 224 uint224 deltaIndex;
File: lib/flywheel-v2/src/FlywheelCore.sol #16 244 uint224 strategyIndex = state.index;
File: lib/flywheel-v2/src/FlywheelCore.sol #17 245 uint224 supplierIndex = userIndex[strategy][user];
File: lib/flywheel-v2/src/FlywheelCore.sol #18 256 uint224 deltaIndex = strategyIndex - supplierIndex;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #19 44 event CycleStart(uint32 indexed cycleStart, uint256 rewardAmount);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #20 47 event QueueRewards(address indexed gauge, uint32 indexed cycleStart, uint256 rewardAmount);
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #21 50 uint32 public gaugeCycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #22 53 uint32 public immutable gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #23 56 uint32 internal nextCycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #24 59 uint112 internal nextCycleQueuedRewards;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #25 62 uint32 internal paginationOffset;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #26 66 uint112 priorCycleRewards;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #27 67 uint112 cycleRewards;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #28 68 uint32 storedCycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #29 103 uint32 currentCycle = (block.timestamp.safeCastTo32() / gaugeCycleLength) * gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #30 104 uint32 lastCycle = gaugeCycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #31 135 uint32 currentCycle = (block.timestamp.safeCastTo32() / gaugeCycleLength) * gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #32 136 uint32 lastCycle = gaugeCycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #33 146 uint32 offset = paginationOffset;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #34 158 uint112 queued = nextCycleQueuedRewards;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #35 181 uint32 currentCycle,
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #36 182 uint32 lastCycle,
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #37 198 uint112 completedRewards = queuedRewards.storedCycle == lastCycle ? queuedRewards.cycleRewards : 0;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #38 218 function getAccruedRewards(ERC20 gauge, uint32 lastUpdatedTimestamp)
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #39 226 uint32 cycle = gaugeCycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #40 237 uint32 cycleEnd = cycle + gaugeCycleLength;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #41 241 uint112 cycleRewardsNext = queuedRewards.cycleRewards;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #42 250 uint32 beginning = lastUpdatedTimestamp > cycle ? lastUpdatedTimestamp : cycle;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #43 253 uint32 elapsed = block.timestamp.safeCastTo32() - beginning;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #44 254 uint32 remaining = cycleEnd - beginning;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #45 36 constructor(uint32 _gaugeCycleLength, uint32 _incrementFreezeWindow) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #46 36 constructor(uint32 _gaugeCycleLength, uint32 _incrementFreezeWindow) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #47 47 uint32 public immutable gaugeCycleLength;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #48 50 uint32 public immutable incrementFreezeWindow;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #49 53 uint112 storedWeight;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #50 54 uint112 currentWeight;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #51 55 uint32 currentCycle;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #52 84 function getGaugeCycleEnd() public view returns (uint32) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #53 89 function _getGaugeCycleEnd() internal view returns (uint32) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #54 90 uint32 nowPlusOneCycle = block.timestamp.safeCastTo32() + gaugeCycleLength;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #55 97 function getGaugeWeight(address gauge) public view returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #56 102 function getStoredGaugeWeight(address gauge) public view returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #57 108 function _getStoredWeight(Weight storage gaugeWeight, uint32 currentCycle) internal view returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #58 108 function _getStoredWeight(Weight storage gaugeWeight, uint32 currentCycle) internal view returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #59 113 function totalWeight() external view returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #60 118 function storedTotalWeight() external view returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #61 210 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #62 212 uint112 total = _getStoredWeight(_totalWeight, currentCycle);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #63 213 uint112 weight = _getStoredWeight(_getGaugeWeight[gauge], currentCycle);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #64 234 event IncrementGaugeWeight(address indexed user, address indexed gauge, uint256 weight, uint32 cycleEnd);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #65 237 event DecrementGaugeWeight(address indexed user, address indexed gauge, uint256 weight, uint32 cycleEnd);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #66 245 function incrementGauge(address gauge, uint112 weight) external returns (uint112 newUserWeight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #67 245 function incrementGauge(address gauge, uint112 weight) external returns (uint112 newUserWeight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #68 246 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #69 254 uint112 weight,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #70 255 uint32 cycle
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #71 275 uint112 weight,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #72 276 uint32 cycle
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #73 277 ) internal returns (uint112 newUserWeight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #74 302 uint112 weightsSum;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #75 304 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #76 309 uint112 weight = weights[i];
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #77 326 function decrementGauge(address gauge, uint112 weight) external returns (uint112 newUserWeight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #78 326 function decrementGauge(address gauge, uint112 weight) external returns (uint112 newUserWeight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #79 327 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #80 337 uint112 weight,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #81 338 uint32 cycle
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #82 340 uint112 oldWeight = getUserGaugeWeight[user][gauge];
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #83 355 uint112 weight,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #84 356 uint32 cycle
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #85 357 ) internal returns (uint112 newUserWeight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #86 372 returns (uint112 newUserWeight)
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #87 378 uint112 weightsSum;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #88 380 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #89 386 uint112 weight = weights[i];
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #90 404 function(uint112, uint112) view returns (uint112) op,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #91 404 function(uint112, uint112) view returns (uint112) op,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #92 404 function(uint112, uint112) view returns (uint112) op,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #93 405 uint112 delta,
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #94 406 uint32 cycle
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #95 408 uint112 currentWeight = weight.currentWeight;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #96 410 uint112 stored = weight.currentCycle < cycle ? currentWeight : weight.storedWeight;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #97 411 uint112 newWeight = op(currentWeight, delta);
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #98 418 function _add(uint112 a, uint112 b) private pure returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #99 418 function _add(uint112 a, uint112 b) private pure returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #100 418 function _add(uint112 a, uint112 b) private pure returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #101 422 function _subtract(uint112 a, uint112 b) private pure returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #102 422 function _subtract(uint112 a, uint112 b) private pure returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #103 422 function _subtract(uint112 a, uint112 b) private pure returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #104 453 function addGauge(address gauge) external requiresAuth returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #105 457 function _addGauge(address gauge) internal returns (uint112 weight) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #106 463 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #107 483 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #108 486 uint112 weight = _getGaugeWeight[gauge].currentWeight;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #109 553 uint32 currentCycle = _getGaugeCycleEnd();
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #110 556 uint112 userFreed;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #111 557 uint112 totalFreed;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #112 566 uint112 userGaugeWeight = getUserGaugeWeight[user][gauge];
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #113 28 uint32 fromBlock;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #114 29 uint224 votes;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #115 36 function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #116 41 function numCheckpoints(address account) public view virtual returns (uint32) {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #117 375 uint8 v,
File: lib/xTRIBE/src/xTRIBE.sol #118 18 returns (uint96);
File: lib/xTRIBE/src/xTRIBE.sol #119 20 function getCurrentVotes(address account) external view returns (uint96);
File: lib/xTRIBE/src/xTRIBE.sol #120 37 uint32 _rewardsCycleLength,
File: lib/xTRIBE/src/xTRIBE.sol #121 38 uint32 _incrementFreezeWindow
keccak256()
, should use immutable
rather than constant
See this issue for a detail description of the issue
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 368 bytes32 public constant DELEGATION_TYPEHASH = 369 keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
private
rather than public
for constants, saves gasIf needed, the value can be read from the verified contract source code
File: lib/ERC4626/src/xERC4626.sol #1 24 uint32 public immutable rewardsCycleLength;
File: lib/flywheel-v2/src/FlywheelCore.sol #2 201 uint224 public constant ONE = 1e18;
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #3 53 uint32 public immutable gaugeCycleLength;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #4 47 uint32 public immutable gaugeCycleLength;
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #5 50 uint32 public immutable incrementFreezeWindow;
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #6 368 bytes32 public constant DELEGATION_TYPEHASH = 369 keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
<x> * 2
is equivalent to <x> << 1
and <x> / 2
is the same as <x> >> 1
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 94 return (a & b) + (a ^ b) / 2;
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #1 392 require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce");
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...}
=> if(!x){if(y){...}else{...}}
)
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #1 243 if (incompleteCycle) { 244 // If current cycle queue incomplete, do nothing to current cycle rewards or accrued 245 } else if (block.timestamp >= cycleEnd) {
revert()
/require()
strings to save deployment gasFile: lib/flywheel-v2/src/FlywheelCore.sol (various lines) #1
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol (various lines) #2
payable
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
File: lib/flywheel-v2/src/FlywheelCore.sol #1 142 function addStrategyForRewards(ERC20 strategy) external requiresAuth {
File: lib/flywheel-v2/src/FlywheelCore.sol #2 165 function setFlywheelRewards(IFlywheelRewards newFlywheelRewards) external requiresAuth {
File: lib/flywheel-v2/src/FlywheelCore.sol #3 183 function setBooster(IFlywheelBooster newBooster) external requiresAuth {
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #4 101 function queueRewardsForCycle() external requiresAuth returns (uint256 totalQueuedForCycle) {
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #5 133 function queueRewardsForCyclePaginated(uint256 numRewards) external requiresAuth {
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #6 218 function getAccruedRewards(ERC20 gauge, uint32 lastUpdatedTimestamp) 219 external 220 override 221 onlyFlywheel 222 returns (uint256 accruedRewards)
File: lib/flywheel-v2/src/rewards/FlywheelGaugeRewards.sol #7 273 function setRewardsStream(IRewardsStream newRewardsStream) external requiresAuth {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #8 453 function addGauge(address gauge) external requiresAuth returns (uint112) {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #9 475 function removeGauge(address gauge) external requiresAuth {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #10 495 function replaceGauge(address oldGauge, address newGauge) external requiresAuth {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #11 502 function setMaxGauges(uint256 newMax) external requiresAuth {
File: lib/flywheel-v2/src/token/ERC20Gauges.sol #12 510 function setContractExceedMaxGauges(address account, bool canExceedMax) external requiresAuth {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #13 114 function setMaxDelegates(uint256 newMax) external requiresAuth {
File: lib/flywheel-v2/src/token/ERC20MultiVotes.sol #14 122 function setContractExceedMaxDelegates(address account, bool canExceedMax) external requiresAuth {
File: lib/xTRIBE/src/xTRIBE.sol #15 89 function emitVotingBalances(address[] calldata accounts) 90 external 91 requiresAuth
File: lib/xTRIBE/src/xTRIBE.sol #16 108 function syncRewards() public override requiresAuth {