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: 46/126
Findings: 2
Award: $54.49
🌟 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
31.2723 USDC - $31.27
address(0x0)
when assigning values to address
state variables:-File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 15):
manager = _manager;
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 16):
ve = _ve;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 118):
name = _name;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 119):
symbol = _symbol;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 120):
owner = _owner;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 121):
penaltyRecipient = _penaltyRecipient;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 141):
owner = _addr;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 148):
blocklist = _addr;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 155):
penaltyRecipient = _addr;
return
statement when the function defines a named return variable, is redundant:-File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 34):
return _blocklist[addr];
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 42):
return size > 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 193):
return locked[_addr].end;
indexed
fields (Each event
should use three indexed
fields if there are three or more fields):-File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 25-31):
event Deposit( address indexed provider, uint256 value, uint256 locktime, LockAction indexed action, uint256 ts );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 32-37):
event Withdraw( address indexed provider, uint256 value, LockAction indexed action, uint256 ts );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 41):
event CollectPenalty(uint256 amount, address recipient);
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 29):
event Transfer(address indexed from, address indexed to, uint256 value);
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 30-34):
event Approval( address indexed owner, address indexed spender, uint256 value );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 48):
uint256 public constant MULTIPLIER = 10**18;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 51):
uint256 public maxPenalty = 10**18;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 57-58):
Point[1000000000000000000] public pointHistory; // 1e9 * userPointHistory-length, so sufficient for 1e9 users mapping(address => Point[1000000000]) public userPointHistory;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 66):
uint256 public decimals = 18;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 116):
require(decimals <= 18, "Exceeds max decimals");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 309):
for (uint256 i = 0; i < 255; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 653):
uint256 penaltyAmount = (value * penaltyRate) / 10**18;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 717):
for (uint256 i = 0; i < 128; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 739):
for (uint256 i = 0; i < 128; i++) {
public
functions not called by the contract should be declared external
instead (Contracts are allowed to override their parents’ functions and change the visibility from external
to public
.):-File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 33):
function isBlocked(address addr) public view returns (bool) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 754):
function balanceOf(address _owner) public view override returns (uint256) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 770-775):
function balanceOfAt(address _owner, uint256 _blockNumber) public view override returns (uint256) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 864):
function totalSupply() public view override returns (uint256) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 871-876):
function totalSupplyAt(uint256 _blockNumber) public view override returns (uint256) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 46):
uint256 public constant WEEK = 7 days;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 47):
uint256 public constant MAXTIME = 365 days;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 48):
uint256 public constant MULTIPLIER = 10**18;
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/interfaces/IBlocklist.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/interfaces/IVotingEscrow.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 2):
pragma solidity ^0.8.3;
🌟 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
23.2218 USDC - $23.22
immutable
(Avoids a Gsset (20000 gas)):-File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 11):
address public manager;
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 12):
address public ve;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 45):
IERC20 public token;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 49-53):
address public owner; address public penaltyRecipient; // receives collected penalty payments uint256 public maxPenalty = 10**18; // penalty for quitters with MAXTIME remaining lock uint256 public penaltyAccumulated; // accumulated and unwithdrawn penalty payments address public blocklist;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 64-65):
string public name; string public symbol;
function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper):-
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 11):
address public manager;
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 12):
address public ve;
x = x + y
is cheaper than x += y
:-File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 537):
newLocked.delegated -= int128(int256(value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 612):
newLocked.delegated -= value;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol(line 642):
newLocked.delegated -= int128(int256(value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 418):
locked_.amount += int128(int256(_value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 420):
locked_.delegated += int128(int256(_value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 460-461):
newLocked.amount += int128(int256(_value)); newLocked.delegated += int128(int256(_value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 465):
locked_.amount += int128(int256(_value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 472):
newLocked.delegated += int128(int256(_value));
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 603):
newLocked.delegated += value;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 654):
penaltyAccumulated += penaltyAmount;
require()
/revert()
strings longer than 32 bytes cost extra gas:-File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 34):
return _blocklist[addr];
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 42):
return size > 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 193):
return locked[_addr].end;
> 0
costs more gas than != 0
when used on a uint in a require()
statement:-File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 412):
require(_value > 0, "Only non zero amount");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 448):
require(_value > 0, "Only non zero amount");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 449):
require(locked_.amount > 0, "No lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 469):
require(locked_.amount > 0, "Delegatee has no lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 502):
require(locked_.amount > 0, "No lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 529):
require(locked_.amount > 0, "No lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 564):
require(locked_.amount > 0, "No lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 587):
require(toLocked.amount > 0, "Delegatee has no lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 635):
require(locked_.amount > 0, "No lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 163):
maxPenalty = 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 229-230):
int128 oldSlopeDelta = 0; int128 newSlopeDelta = 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 298):
uint256 blockSlope = 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 309):
for (uint256 i = 0; i < 255; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 313):
int128 dSlope = 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 326):
lastPoint.bias = 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 330):
lastPoint.slope = 0;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 717):
for (uint256 i = 0; i < 128; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 739):
for (uint256 i = 0; i < 128; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 834):
for (uint256 i = 0; i < 255; i++) {
++i
costs less gas than i++
, especially when it’s used in forloops (--i/i-- too)
:-File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 309):
for (uint256 i = 0; i < 255; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 717):
for (uint256 i = 0; i < 128; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 739):
for (uint256 i = 0; i < 128; i++) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 834):
for (uint256 i = 0; i < 255; i++) {
uints/ints
smaller than 32 bytes (256 bits) incurs overhead (When using elements that are smaller than 32 bytes, yourcontract’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):-
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 10):
function decimals() external view returns (uint8);
require()
/revert()
checks should be refactored to a modifier or function:-File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 162):
require(msg.sender == owner, "Only owner");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 637):
require(locked_.delegatee == msg.sender, "Lock delegated");
require()
or revert()
statements that check input arguments should be at the top of the function (Checks that involve constants should come before checks that involve state variables):-File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 565):
require(locked_.delegatee != _addr, "Already delegated");
revert()
/require()
strings to save deployment gas:-File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 24-25):
require(msg.sender == manager, "Only manager"); require(_isContract(addr), "Only contracts");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 116):
require(decimals <= 18, "Exceeds max decimals");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 125-128):
require( !IBlocklist(blocklist).isBlocked(msg.sender), "Blocked contract" );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 140):
require(msg.sender == owner, "Only owner");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 147):
require(msg.sender == owner, "Only owner");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 154):
require(msg.sender == owner, "Only owner");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 162):
require(msg.sender == owner, "Only owner");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 171):
require(msg.sender == blocklist, "Only Blocklist");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 412-416):
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");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 425-428):
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 448-450):
require(_value > 0, "Only non zero amount"); require(locked_.amount > 0, "No lock"); require(locked_.end > block.timestamp, "Lock expired");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 469-670):
require(locked_.amount > 0, "Delegatee has no lock"); require(locked_.end > block.timestamp, "Delegatee lock expired");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 485-488):
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 502-504):
require(locked_.amount > 0, "No lock"); require(unlock_time > locked_.end, "Only increase lock end"); require(unlock_time <= block.timestamp + MAXTIME, "Exceeds maxtime");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 511):
require(oldUnlockTime > block.timestamp, "Lock expired");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 529-531):
require(locked_.amount > 0, "No lock"); require(locked_.end <= block.timestamp, "Lock not expired"); require(locked_.delegatee == msg.sender, "Lock delegated");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 546):
require(token.transfer(msg.sender, value), "Transfer failed");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 563-565):
require(!IBlocklist(blocklist).isBlocked(_addr), "Blocked contract"); require(locked_.amount > 0, "No lock"); require(locked_.delegatee != _addr, "Already delegated");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 587-689):
require(toLocked.amount > 0, "Delegatee has no lock"); require(toLocked.end > block.timestamp, "Delegatee lock expired"); require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 635-637):
require(locked_.amount > 0, "No lock"); require(locked_.end > block.timestamp, "Lock expired"); require(locked_.delegatee == msg.sender, "Lock delegated");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 657):
require(token.transfer(msg.sender, remainingAmount), "Transfer failed");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 676):
require(token.transfer(penaltyRecipient, amount), "Transfer failed");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 776):
require(_blockNumber <= block.number, "Only past block number");
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 877):
require(_blockNumber <= block.number, "Only past block number");
0.8.15
to have external calls skipcontract existence checks if the external call has a return value):-
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/interfaces/IBlocklist.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/interfaces/IVotingEscrow.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 2):
pragma solidity ^0.8.3;
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 100-106):
constructor( address _owner, address _penaltyRecipient, address _token, string memory _name, string memory _symbol ) {
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 222-226):
function _checkpoint( address _addr, LockedBalance memory _oldLocked, LockedBalance memory _newLocked )
File: 2022-08-fiatdao/contracts/interfaces/IBlocklist.sol (line 595-600):
function _delegate( address addr, LockedBalance memory _locked, int128 value, LockAction action )
File: 2022-08-fiatdao/contracts/interfaces/IVotingEscrow.sol (line 685):
function _copyLock(LockedBalance memory _locked)
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 825):
function _supplyAt(Point memory _point, uint256 _t)
While this is done at some places, it’s not consistently done in the solution.
I suggest adding a non-zero-value check here:):-
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 425-428):
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 485-488):
require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" );
File: 2022-08-fiatdao/contracts/interfaces/IBlocklist.sol (line 546):
require(token.transfer(msg.sender, value), "Transfer failed");
File: 2022-08-fiatdao/contracts/interfaces/IVotingEscrow.sol (line 657):
require(token.transfer(msg.sender, remainingAmount), "Transfer failed");
File: 2022-08-fiatdao/contracts/interfaces/IERC20.sol (line 676):
require(token.transfer(penaltyRecipient, amount), "Transfer failed");
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).
While this is done at some places, it’s not consistently done in the solution.
I suggest adding a non-zero-value check here:):-
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 58-61):
mapping(address => Point[1000000000]) public userPointHistory; mapping(address => uint256) public userPointEpoch; mapping(uint256 => int128) public slopeChanges; mapping(address => LockedBalance) public locked;
File: 2022-08-fiatdao/contracts/features/Blocklist.sol (line 10):
mapping(address => bool) private _blocklist;
code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table):-
File: 2022-08-fiatdao/contracts/VotingEscrow.sol (line 46-48):
uint256 public constant WEEK = 7 days; uint256 public constant MAXTIME = 365 days; uint256 public constant MULTIPLIER = 10**18;