Platform: Code4rena
Start Date: 12/08/2022
Pot Size: $35,000 USDC
Total HM: 10
Participants: 126
Period: 3 days
Judge: Justin Goro
Total Solo HM: 3
Id: 154
League: ETH
Rank: 59/126
Findings: 2
Award: $45.07
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: oyc_109
Also found by: 0x1f8b, 0x52, 0xDjango, 0xLovesleep, 0xNazgul, 0xNineDec, 0xbepresent, 0xmatt, 0xsolstars, Aymen0909, Bahurum, Bnke0x0, CertoraInc, Chom, CodingNameKiki, DecorativePineapple, Deivitto, Dravee, ElKu, Funen, GalloDaSballo, IllIllI, JC, JohnSmith, Junnon, KIntern_NA, Lambda, LeoS, MiloTruck, Noah3o6, PaludoX0, RedOneN, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, Sm4rty, TomJ, Vexjon, Waze, Yiko, __141345__, a12jmx, ajtra, ak1, apostle0x01, asutorufos, auditor0517, bin2chen, bobirichman, brgltd, bulej93, byndooa, c3phas, cRat1st0s, cryptphi, csanuragjain, d3e4, defsec, delfin454000, djxploit, durianSausage, ellahi, erictee, exd0tpy, fatherOfBlocks, gogo, jonatascm, ladboy233, medikko, mics, natzuu, neumo, p_crypt0, paribus, pfapostol, rbserver, reassor, ret2basic, robee, rokinot, rvierdiiev, sach1r0, saneryee, seyni, sikorico, simon135, sseefried, wagmi, wastewa
29.9022 USDC - $29.90
Finding | Instances | |
---|---|---|
[L-01] | Floating Pragma | 1 |
[L-02] | Unsafe call to optional decimals ERC20 method | 1 |
[L-03] | Ownership transfer should be a two-step process | 1 |
[L-04] | Irreversible action to erase penalty mechanism might require redeploy | 1 |
Finding | Instances | |
---|---|---|
[N-01] | It is recommend to use scientific notation (1e18 ) instead of exponential (10**18 ) | 3 |
[N-02] | 2**<n> - 1 should be factored as type(uint<n>).max | 1 |
Contract | Total Instances | Total Findings | Low Findings | Low Instances | NC Findings | NC Instances |
---|---|---|---|---|---|---|
VotingEscrow.sol | 7 | 5 | 3 | 3 | 2 | 4 |
Blocklist.sol | 1 | 1 | 1 | 1 | 0 | 0 |
A floating pragma might result in contract being tested/deployed with different or inconsistent compiler versions possibly leading to unexpected behaviour. 1 instance of this issue has been found:
[L-01] Blocklist.sol#L2-L3
pragma solidity ^0.8.3;
decimals
ERC20 methodThis function was optional in the initial ERC-20 and might fail for old tokens that have not implemented it. 1 instance of this issue has been found:
[L-02] VotingEscrow.sol#L113-L114
decimals = IERC20(_token).decimals();
If ownership is accidentally transferred to an inactive account all functionalities depending on it will be lost. 1 instance of this issue has been found:
[L-03] VotingEscrow.sol#L137-L141
function transferOwnership(address _addr) external { require(msg.sender == owner, "Only owner"); owner = _addr; emit TransferOwnership(_addr); }
If unlock()
function is accidentally called the contract might have to be redeployed.
Perhaps making it reversible could be a sensible choice.
1 instance of this issue has been found:
[L-04] VotingEscrow.sol#L159-L163
function unlock() external { require(msg.sender == owner, "Only owner"); maxPenalty = 0; emit Unlock(); }
1e18
) instead of exponential (10**18
)Improves readbility. 3 instances of this issue have been found:
[N-01] VotingEscrow.sol#L51-L52
uint256 public maxPenalty = 10**18; // penalty for quitters with MAXTIME remaining lock
[N-01b] VotingEscrow.sol#L48-L49
uint256 public constant MULTIPLIER = 10**18;
[N-01c] VotingEscrow.sol#L653-L654
uint256 penaltyAmount = (value * penaltyRate) / 10**18; // quitlock_penalty is in 18 decimals precision
2**<n> - 1
should be factored as type(uint<n>).max
2**<n> - 1 should be re-written as type(uint<n>).max 1 instance of this issue has been found:
[N-02] VotingEscrow.sol#L310-L311
for (uint256 i = 0; i < 255; i++) {
🌟 Selected for report: IllIllI
Also found by: 0x040, 0x1f8b, 0xDjango, 0xHarry, 0xLovesleep, 0xNazgul, 0xNineDec, 0xSmartContract, 0xackermann, 0xbepresent, 2997ms, Amithuddar, Aymen0909, Bnke0x0, CRYP70, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, Fitraldys, Funen, GalloDaSballo, JC, JohnSmith, Junnon, LeoS, Metatron, MiloTruck, Noah3o6, NoamYakov, PaludoX0, RedOneN, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, SooYa, SpaceCake, TomJ, Tomio, Waze, Yiko, __141345__, a12jmx, ajtra, ak1, apostle0x01, asutorufos, bobirichman, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, chrisdior4, csanuragjain, d3e4, defsec, delfin454000, djxploit, durianSausage, ellahi, erictee, fatherOfBlocks, gerdusx, gogo, ignacio, jag, ladboy233, m_Rassska, medikko, mics, natzuu, newfork01, oyc_109, paribus, pfapostol, rbserver, reassor, ret2basic, robee, rokinot, rvierdiiev, sach1r0, saian, sashik_eth, sikorico, simon135
15.1721 USDC - $15.17
Finding | Instances | |
---|---|---|
[G-01] | Setting variable to default value is redundant | 3 |
[G-02] | Using prefix(++i ) instead of postfix (i++ ) saves gas | 3 |
[G-03] | for loop increments should be unchecked{} if overflow is not possible | 3 |
[G-04] | Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate | 1 |
[G-05] | Variable should be immutable | 1 |
[G-06] | immutable variable missing zero address check | 1 |
[G-07] | require() /revert() checks used multiple times should be turned into a function or modifier | 20 |
[G-08] | Use custom errors rather than revert() /require() strings to save gas | 27 |
[G-09] | MULTIPLIER constant not used | 1 |
Contract | Instances | Gas Ops |
---|---|---|
VotingEscrow.sol | 58 | 7 |
Blocklist.sol | 2 | 2 |
Setting variable to default value is redundant. 3 instances of this issue have been found:
[G-01] VotingEscrow.sol#L717-L718
for (uint256 i = 0; i < 128; i++) {
[G-01b] VotingEscrow.sol#L739-L740
for (uint256 i = 0; i < 128; i++) {
[G-01c] VotingEscrow.sol#L310-L311
for (uint256 i = 0; i < 255; i++) {
++i
) instead of postfix (i++
) saves gasIt saves 6 gas per iteration. 3 instances of this issue have been found:
[G-02] VotingEscrow.sol#L739-L740
for (uint256 i = 0; i < 128; i++) {
[G-02b] VotingEscrow.sol#L717-L718
for (uint256 i = 0; i < 128; i++) {
[G-02c] VotingEscrow.sol#L310-L311
for (uint256 i = 0; i < 255; i++) {
for
loop increments should be unchecked{}
if overflow is not possibleFrom Solidity 0.8.0
onwards using the unchecked
keyword saves 30 to 40 gas per loop.
Example:
uint length = array.length; for (uint i; i < length;){ uncheck { ++i } }
3 instances of this issue have been found:
[G-03] VotingEscrow.sol#L717-L718
for (uint256 i = 0; i < 128; i++) {
[G-03b] VotingEscrow.sol#L739-L740
for (uint256 i = 0; i < 128; i++) {
[G-03c] VotingEscrow.sol#L310-L311
for (uint256 i = 0; i < 255; i++) {
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operatio 1 instance of this issue has been found:
[G-04] VotingEscrow.sol#L56-L59
mapping(address => Point[1000000000]) public userPointHistory; mapping(address => uint256) public userPointEpoch; mapping(uint256 => int128) public slopeChanges; mapping(address => LockedBalance) public locked;
Variables set in the constructor
that are not able to be changed by other functions should be set as immutable
in order to save gas.
1 instance of this issue has been found:
[G-05] Blocklist.sol#L11-L12
address public manager; address public ve;
immutable
variable missing zero address checkIf variable is accidentally set to 0 the contract will have to be redeployed, spending unnecessary gas. 1 instance of this issue has been found:
[G-06] Blocklist.sol#L14-L17
constructor(address _manager, address _ve) { manager = _manager; ve = _ve; }
require()
/revert()
checks used multiple times should be turned into a function or modifierDoing so increases code readability decreases number of instructions for the compiler. 20 instances of this issue have been found:
[G-07] VotingEscrow.sol#L676-L677
require(token.transfer(penaltyRecipient, amount), "Transfer failed");
[G-07b] VotingEscrow.sol#L657-L658
require(token.transfer(msg.sender, remainingAmount), "Transfer failed");
[G-07c] VotingEscrow.sol#L546-L547
require(token.transfer(msg.sender, value), "Transfer failed");
[G-07d] VotingEscrow.sol#L485-L488
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
[G-07e] VotingEscrow.sol#L425-L428
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
[G-07f] VotingEscrow.sol#L504-L505
require(unlock_time <= block.timestamp + MAXTIME, "Exceeds maxtime");
[G-07g] VotingEscrow.sol#L416-L417
require(unlock_time <= block.timestamp + MAXTIME, "Exceeds maxtime");
[G-07h] VotingEscrow.sol#L503-L504
require(unlock_time > locked_.end, "Only increase lock end");
[G-07i] VotingEscrow.sol#L414-L415
require(unlock_time >= locked_.end, "Only increase lock end"); // from using quitLock, user should increaseAmount instead
[G-07j] VotingEscrow.sol#L412-L413
require(_value > 0, "Only non zero amount");
[G-07k] VotingEscrow.sol#L563-L564
require(!IBlocklist(blocklist).isBlocked(_addr), "Blocked contract");
[G-07l] VotingEscrow.sol#L123-L128
require( !IBlocklist(blocklist).isBlocked(msg.sender), "Blocked contract" ); _; }
[G-07m] VotingEscrow.sol#L448-L449
require(_value > 0, "Only non zero amount");
[G-07n] VotingEscrow.sol#L412-L413
require(_value > 0, "Only non zero amount");
[G-07o] VotingEscrow.sol#L138-L139
require(msg.sender == owner, "Only owner");
[G-07p] VotingEscrow.sol#L145-L146
require(msg.sender == owner, "Only owner");
[G-07q] VotingEscrow.sol#L152-L153
require(msg.sender == owner, "Only owner");
[G-07r] VotingEscrow.sol#L160-L161
require(msg.sender == owner, "Only owner");
[G-07s] VotingEscrow.sol#L877-L878
require(_blockNumber <= block.number, "Only past block number");
[G-07t] VotingEscrow.sol#L776-L777
require(_blockNumber <= block.number, "Only past block number");
revert()
/require()
strings to save gasCustom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. 27 instances of this issue have been found:
[G-08] VotingEscrow.sol#L114-L115
require(decimals <= 18, "Exceeds max decimals");
[G-08b] VotingEscrow.sol#L123-L127
require( !IBlocklist(blocklist).isBlocked(msg.sender), "Blocked contract" ); _;
[G-08c] VotingEscrow.sol#L138-L139
require(msg.sender == owner, "Only owner");
[G-08d] VotingEscrow.sol#L152-L153
require(msg.sender == owner, "Only owner");
[G-08e] VotingEscrow.sol#L160-L161
require(msg.sender == owner, "Only owner");
[G-08f] VotingEscrow.sol#L169-L170
require(msg.sender == blocklist, "Only Blocklist");
[G-08g] VotingEscrow.sol#L412-L416
require(_value > 0, "Only non zero amount"); require(locked_.amount == 0, "Lock exists"); require(unlock_time >= locked_.end, "Only increase lock end"); // from using quitLock, user should increaseAmount instead require(unlock_time > block.timestamp, "Only future lock end"); require(unlock_time <= block.timestamp + MAXTIME, "Exceeds maxtime");
[G-08h] VotingEscrow.sol#L425-L427
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed"
[G-08i] VotingEscrow.sol#L448-L450
require(_value > 0, "Only non zero amount"); require(locked_.amount > 0, "No lock"); require(locked_.end > block.timestamp, "Lock expired");
[G-08j] VotingEscrow.sol#L469-L470
require(locked_.amount > 0, "Delegatee has no lock"); require(locked_.end > block.timestamp, "Delegatee lock expired");
[G-08k] VotingEscrow.sol#L485-L488
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
[G-08l] VotingEscrow.sol#L502-L504
require(locked_.amount > 0, "No lock"); require(unlock_time > locked_.end, "Only increase lock end"); require(unlock_time <= block.timestamp + MAXTIME, "Exceeds maxtime");
[G-08m] VotingEscrow.sol#L511-L512
require(oldUnlockTime > block.timestamp, "Lock expired");
[G-08n] VotingEscrow.sol#L529-L531
require(locked_.amount > 0, "No lock"); require(locked_.end <= block.timestamp, "Lock not expired"); require(locked_.delegatee == msg.sender, "Lock delegated");
[G-08o] VotingEscrow.sol#L563-L564
require(!IBlocklist(blocklist).isBlocked(_addr), "Blocked contract");
[G-08p] VotingEscrow.sol#L564-L565
require(locked_.amount > 0, "No lock");
[G-08q] VotingEscrow.sol#L565-L566
require(locked_.delegatee != _addr, "Already delegated");
[G-08r] VotingEscrow.sol#L587-L588
require(toLocked.amount > 0, "Delegatee has no lock");
[G-08s] VotingEscrow.sol#L588-L589
require(toLocked.end > block.timestamp, "Delegatee lock expired");
[G-08t] VotingEscrow.sol#L589-L590
require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
[G-08u] VotingEscrow.sol#L635-L636
require(locked_.amount > 0, "No lock");
[G-08v] VotingEscrow.sol#L636-L637
require(locked_.end > block.timestamp, "Lock expired");
[G-08w] VotingEscrow.sol#L637-L638
require(locked_.delegatee == msg.sender, "Lock delegated");
[G-08x] VotingEscrow.sol#L657-L658
require(token.transfer(msg.sender, remainingAmount), "Transfer failed");
[G-08y] VotingEscrow.sol#L676-L677
require(token.transfer(penaltyRecipient, amount), "Transfer failed");
[G-08z] VotingEscrow.sol#L776-L777
require(_blockNumber <= block.number, "Only past block number");
[G-08{] VotingEscrow.sol#L877-L878
require(_blockNumber <= block.number, "Only past block number");
MULTIPLIER
constant not usedUse already existent MULTIPLIER
constant to avoid waisting gas on calculation.
1 instance of this issue has been found:
[G-09] VotingEscrow.sol#L653-L654
uint256 penaltyAmount = (value * penaltyRate) / 10**18; // quitlock_penalty is in 18 decimals precision