Platform: Code4rena
Start Date: 27/10/2022
Pot Size: $33,500 USDC
Total HM: 8
Participants: 96
Period: 3 days
Judge: kirk-baird
Total Solo HM: 1
Id: 176
League: ETH
Rank: 11/96
Findings: 3
Award: $552.01
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rbserver
Also found by: 0x1f8b, 0xSmartContract, Trust, cccz, codexploder, ctf_sec, hansfriese
247.5252 USDC - $247.53
Judge has assessed an item in Issue #20 as M risk. The relevant finding follows:
Affected source code:
WardenPledge.sol:18
#0 - c4-judge
2022-11-11T23:38:18Z
kirk-baird marked the issue as satisfactory
#1 - c4-judge
2022-11-11T23:38:35Z
kirk-baird marked the issue as duplicate of #70
#2 - c4-judge
2022-12-06T17:36:15Z
Simon-Busch marked the issue as duplicate of #269
🌟 Selected for report: robee
Also found by: 0x007, 0x1f8b, 0x52, 0xDjango, 0xNazgul, 0xSmartContract, 8olidity, Awesome, B2, Bnke0x0, Chom, Diana, Dravee, JTJabba, Jeiwan, Josiah, Lambda, Mathieu, Picodes, RaoulSchaffranek, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Ruhum, Sm4rty, Tricko, Trust, Waze, __141345__, a12jmx, adriro, ajtra, brgltd, c3phas, carlitox477, cccz, ch0bu, chaduke, chrisdior4, corerouter, cryptonue, csanuragjain, ctf_sec, cylzxje, delfin454000, dic0de, djxploit, horsefacts, imare, jayphbee, jwood, ktg, ladboy233, leosathya, lukris02, minhtrng, neko_nyaa, oyc_109, pashov, peritoflores, rbserver, rvierdiiev, shark, tnevler, yixxas
180.6401 USDC - $180.64
The pragma version used is:
pragma solidity 0.8.10;
The minimum required version must be 0.8.17; otherwise, contracts will be affected by the following important bug fixes:
abi.encodeCall
in place of fixed bytes arguments.calldatasize()
in all cases.bytes
arrays.Apart from these, there are several minor bug fixes and improvements.
Ownable
and Pausable
The contract WardenPledge
is Ownable
and Pausable
, so the owner could resign while the contract is paused, causing a Denial of Service. Owner resignation while the contract is paused should be avoided.
Affected source code:
supportsInterface
The EIP-165
standard helps detect that a smart contract implements the expected logic, prevents human error when configuring smart contract bindings, so it is recommended to check that the received argument is a contract and supports the expected interface.
Reference:
Affected source code:
address(0)
The following methods have a lack of checks if the received argument is an address, it's good practice in order to reduce human error to check that the address specified in the constructor or initialize is different than address(0)
.
Affected source code:
The following methods lack checks on the following integer arguments, you can see the recommendations above.
Affected source code:
minTargetVotes
is allowed to be 0
during the constructor, but when this will be changed using updateMinTargetVotes will not be allowed.
The WardenPledge
contract has a recoverERC20
method through which the owner can take an arbitrary erc20 token, this method contains a protection to prevent it from being a reward token, however, if the owner calls removeRewardToken
first, owner will have no problem in being able to subtract the balance.
Affected source code:
Suffixes like seconds
, minutes
, hours
, days
and weeks
after literal numbers can be used to specify units of time where seconds are the base unit and units are considered naively in the following way:
Reference:
Affected source code:
In the method _pledge
, when endTimestamp
is less than block.timestamp
it will fault with a non custom error.
Affected source code:
Move all structures to a different file or to the top of the contract to improve code readability.
Affected source code:
#0 - c4-judge
2022-11-08T22:33:14Z
kirk-baird marked the issue as grade-a
🌟 Selected for report: c3phas
Also found by: 0x1f8b, 0xNazgul, 0xRoxas, 0xSmartContract, 0xbepresent, Amithuddar, Awesome, B2, Bnke0x0, Dravee, KoKo, Mathieu, Picodes, RaymondFam, RedOneN, ReyAdmirado, RockingMiles, Ruhum, SadBase, SooYa, Waze, __141345__, adriro, ajtra, ballx, carlitox477, ch0bu, cylzxje, djxploit, durianSausage, emrekocak, erictee, gogo, halden, horsefacts, imare, indijanc, karanctf, leosathya, lukris02, neko_nyaa, oyc_109, peiw, sakman, shark, skyle, tnevler
123.8403 USDC - $123.84
Using compound assignment operators for state variables (like State += X
or State -= X
...) it's more expensive than using operator assignment (like State = State + X
or State = State - X
...).
Proof of concept (without optimizations):
pragma solidity 0.8.15; contract TesterA { uint private _a; function testShort() public { _a += 1; } } contract TesterB { uint private _a; function testLong() public { _a = _a + 1; } }
Gas saving executing: 13 per entry
TesterA.testShort: 43507 TesterB.testLong: 43494
Affected source code:
Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert
in case of failure, it is not necessary to return a true
, since it will never be false
. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.
Affected source code:
delete
optimizationUse delete
instead of set to default value (false
or 0
).
5 gas could be saved per entry in the following affected lines:
Affected source code:
constant
for static valueThe following variables are state variables, their value doesn't change but they weren't defined as constants
, changing the definition to constant
will save a lot of gas, because it won't use storage.
Affected source code:
_pledge
with unchecked
By applying the following changes it is possible to save gas:
... // Re-calculate the new Boost bias & slope (using Boostv2 logic) uint256 slope = amount / boostDuration; + uint256 bias; + unchecked { // It was previously checked - uint256 bias = slope * boostDuration; + bias = slope * boostDuration; + } // Rewards are set in the Pledge as reward/veToken/sec // To find the total amount of veToken delegated through the whole Boost duration // based on the Boost bias & the Boost duration, to take in account that the delegated amount decreases // each second of the Boost duration uint256 totalDelegatedAmount = ((bias * boostDuration) + bias) / 2; // Then we can calculate the total amount of rewards for this Boost uint256 rewardAmount = (totalDelegatedAmount * pledgeParams.rewardPerVote) / UNIT; if(rewardAmount > pledgeAvailableRewardAmounts[pledgeId]) revert Errors.RewardsBalanceTooLow(); + unchecked { // It was previously checked pledgeAvailableRewardAmounts[pledgeId] -= rewardAmount; + } // Send the rewards to the user IERC20(pledgeParams.rewardToken).safeTransfer(user, rewardAmount); emit Pledged(pledgeId, user, amount, endTimestamp); }
Affected source code:
createPledge
By applying the following changes it is possible to save gas by avoiding access to storage.
function createPledge( address receiver, address rewardToken, uint256 targetVotes, uint256 rewardPerVote, // reward/veToken/second uint256 endTimestamp, uint256 maxTotalRewardAmount, uint256 maxFeeAmount ) external whenNotPaused nonReentrant returns(uint256){ ... if(receiver == address(0) || rewardToken == address(0)) revert Errors.ZeroAddress(); if(targetVotes < minTargetVotes) revert Errors.TargetVoteUnderMin(); - if(minAmountRewardToken[rewardToken] == 0) revert Errors.TokenNotWhitelisted(); - if(rewardPerVote < minAmountRewardToken[rewardToken]) revert Errors.RewardPerVoteTooLow(); + if(rewardPerVote == 0 || rewardPerVote <= minAmountRewardToken[rewardToken]) revert Errors.RewardPerVoteTooLow(); ... }
Affected source code:
extendPledge
with unchecked
By applying the following changes it is possible to save gas:
function extendPledge( uint256 pledgeId, uint256 newEndTimestamp, uint256 maxTotalRewardAmount, uint256 maxFeeAmount ) external whenNotPaused nonReentrant { ... uint256 oldEndTimestamp = pledgeParams.endTimestamp; if(newEndTimestamp != _getRoundedTimestamp(newEndTimestamp) || newEndTimestamp < oldEndTimestamp) revert Errors.InvalidEndTimestamp(); + uint256 addedDuration; + unchecked { // It was previously checked - uint256 addedDuration = newEndTimestamp - oldEndTimestamp; - addedDuration = newEndTimestamp - oldEndTimestamp; + } ... }
Affected source code:
msg.sender
instead of the cached creator
Using msg.sender
will always be cheaper than using a local variable.
The reason is the following:
CALLER
operation which is reading the msg.sender global variable costs 2.MLOAD/MSTORE
operations which are memory operations (storage where local variables are stored) costs 3.Affected source code:
increasePledgeRewardPerVote
with unchecked
By applying the following changes it is possible to save gas:
function increasePledgeRewardPerVote( uint256 pledgeId, uint256 newRewardPerVote, uint256 maxTotalRewardAmount, uint256 maxFeeAmount ) external whenNotPaused nonReentrant { ... uint256 oldRewardPerVote = pledgeParams.rewardPerVote; if(newRewardPerVote <= oldRewardPerVote) revert Errors.RewardsPerVotesTooLow(); uint256 remainingDuration = pledgeParams.endTimestamp - block.timestamp; + uint256 rewardPerVoteDiff; + unchecked { // It was previously checked - uint256 rewardPerVoteDiff = newRewardPerVote - oldRewardPerVote; + rewardPerVoteDiff = newRewardPerVote - oldRewardPerVote; + } ... }
Affected source code:
updateRewardToken
It is possible to optimize the input verification order in the following way:
function updateRewardToken(address token, uint256 minRewardPerSecond) external onlyOwner { if(token == address(0)) revert Errors.ZeroAddress(); + if(minRewardPerSecond == 0) revert Errors.InvalidValue(); if(minAmountRewardToken[token] == 0) revert Errors.NotAllowedToken(); - if(minRewardPerSecond == 0) revert Errors.InvalidValue(); minAmountRewardToken[token] = minRewardPerSecond; emit UpdateRewardToken(token, minRewardPerSecond); }
Affected source code:
It's possible to emit the event before the change and save one variable creation.
function updateChest(address chest) external onlyOwner { if(chest == address(0)) revert Errors.ZeroAddress(); - address oldChest = chestAddress; + emit ChestUpdated(chestAddress, chest); chestAddress = chest; - emit ChestUpdated(oldChest, chest); } function updateMinTargetVotes(uint256 newMinTargetVotes) external onlyOwner { if(newMinTargetVotes == 0) revert Errors.InvalidValue(); - uint256 oldMinTarget = minTargetVotes; + emit MinTargetUpdated(minTargetVotes, newMinTargetVotes); minTargetVotes = newMinTargetVotes; - emit MinTargetUpdated(oldMinTarget, newMinTargetVotes); } function updatePlatformFee(uint256 newFee) external onlyOwner { if(newFee > 500) revert Errors.InvalidValue(); - uint256 oldfee = protocalFeeRatio; + emit PlatformFeeUpdated(protocalFeeRatio, newFee); protocalFeeRatio = newFee; - emit PlatformFeeUpdated(oldfee, newFee); }
Affected source code:
#0 - c4-judge
2022-11-09T06:48:57Z
kirk-baird marked the issue as grade-a