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: 106/126
Findings: 1
Award: $17.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
17.4715 USDC - $17.47
unchecked{++i}
instead of i++
in loops ✅a = a + b
instead of a += b
✅a / 2^x, x > 0
✅MLOAD
<< SLOAD
✅Internal
functions can be inlined ✅private/internal
for constants/immutable
variables instead of public
✅uint
instead of uint8
, uint64
, if it's possible ✅external
instead of public
, if there are no internal calls ✅calldataload
instead of mload
✅immutable/const
✅i++
, the compiler has to to create a temp variable to return i
(if there's a need) and then i
gets incremented.++i
, the compiler just simply returns already incremented value.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [309-309] for (uint256 i = 0; i < 255; i++) {} // Lines: [717-717] for (uint256 i = 0; i < 128; i++) {} // Lines: [739-739] for (uint256 i = 0; i < 128; i++) {} // Lines: [834-834] for (uint256 i = 0; i < 255; i++) {}
Also occured in the following files:
unchecked{++i};
instead of i++;
/++i;
in loops<a name="G-02"></a>Since Solidity 0.8.0, all arithmetic operations revert on over- and underflow by default Therefore, unchecked
box can be used to prevent all unnecessary checks, if it's no a way to get a reversion.
There are ~1e80 atoms in the universe, so 2^256 is closed to that number, therefore it's no a way to be overflowed, because of the gas limit as well.
Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [309-309] for (uint256 i = 0; i < 255; i++) {} // Lines: [717-717] for (uint256 i = 0; i < 128; i++) {} // Lines: [739-739] for (uint256 i = 0; i < 128; i++) {} // Lines: [834-834] for (uint256 i = 0; i < 255; i++) {}
Also occured in the following files:
a = a + b
instead of a += b
<a name="G-03"></a>Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [418-418] locked_.amount += int128(int256(_value)); // Lines: [420-420] locked_.delegated += int128(int256(_value)); // Lines: [460-461] newLocked.amount += int128(int256(_value)); newLocked.delegated += int128(int256(_value)); // Lines: [465-465] locked_.amount += int128(int256(_value)); // Lines: [472-472] newLocked.delegated += int128(int256(_value)); // Lines: [603-603] newLocked.delegated += value; // Lines: [654-654] penaltyAccumulated += penaltyAmount; // Lines: [537-537] newLocked.delegated -= int128(int256(value)); // Lines: [612-612] newLocked.delegated -= value; // Lines: [642-642] newLocked.delegated -= int128(int256(value));
Also occured in the following files:
onlyOwner
modifier will be reverted, if the user who is not an owner invokes following methods.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [139-139] function transferOwnership(address _addr) external {} // Lines: [146-146] function updateBlocklist(address _addr) external {} // Lines: [153-153] function updatePenaltyRecipient(address _addr) external {} // Lines: [161-161] function unlock() external {} ```
Also occured in the following files:
a / 2^x, x > 0
or a * 2^x, x > 0
<a name="G-05"></a>Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [719-719] uint256 mid = (min + max + 1) / 2; // Lines: [743-743] uint256 mid = (min + max + 1) / 2; ```
MLOAD
<< SLOAD
<a name="G-06"></a>MLOAD
costs only 3 units of gas, SLOAD
(warm access) is about 100 units. Therefore, cache, when it's possible.Contracts:
file: contracts/VotingEscrow.sol ................................. // penaltyRecipient could be cached into memory to avoid warm access on state var. // Lines: [673-678] function collectPenalty() external { uint256 amount = penaltyAccumulated; penaltyAccumulated = 0; require(token.transfer(penaltyRecipient, amount), "Transfer failed"); emit CollectPenalty(amount, penaltyRecipient); } ```
Internal
functions can be inlined<a name="G-07"></a>JUMP
s to find a function and also the gas consumption for pushing arguments into memory and etc.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [662-669] function _calculatePenaltyRate(uint256 end) internal view returns (uint256) { // We know that end > block.timestamp because expired locks cannot be quitted return ((end - block.timestamp) * maxPenalty) / MAXTIME; } // Lines: [685-697] function _copyLock(LockedBalance memory _locked) internal pure returns (LockedBalance memory) { return LockedBalance({ amount: _locked.amount, end: _locked.end, delegatee: _locked.delegatee, delegated: _locked.delegated }); } // Lines: [701-703] function _floorToWeek(uint256 _t) internal pure returns (uint256) { return (_t / WEEK) * WEEK; } // Lines: [662-669]
private/internal
for constants/immutable
variables instead of public
<a name="G-08"></a>public
instance.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [45-66] IERC20 public token; uint256 public constant WEEK = 7 days; uint256 public constant MAXTIME = 365 days; uint256 public constant MULTIPLIER = 10**18; 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; // Lock state uint256 public globalEpoch; Point[1000000000000000000] public pointHistory; // 1e9 * userPointHistory-length, so sufficient for 1e9 users mapping(address => Point[1000000000]) public userPointHistory; mapping(address => uint256) public userPointEpoch; mapping(uint256 => int128) public slopeChanges; mapping(address => LockedBalance) public locked; // Voting token string public name; string public symbol; uint256 public decimals = 18;
Also occured in the following files:
Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [140; 147; 154; 162] require(msg.sender == owner, "Only owner"); // Lines: [412; 448] require(_value > 0, "Only non zero amount");
Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [653-653] uint256 penaltyAmount = (value * penaltyRate) / 10**18; // quitlock_penalty is in 18 decimals precision // Update to: // Lines: [653-653] uint256 penaltyAmount = (value * penaltyRate) / 0xDE0B6B3A7640000; // quitlock_penalty is in 18 decimals precision // Lines: [48-48] uint256 public constant MULTIPLIER = 10**18; // Lines: [51-51] uint256 public maxPenalty = 10**18; // penalty for quitters with MAXTIME remaining lock
Also occured in the following files:
uint
instead of uint8
, uint64
, if it's possible<a name="G-12"></a>error VaultAlreadyUnderAuction(bytes12 vaultId, address witch);
.error VaultAlreadyUnderAuction(bytes32 vaultId, address witch);
.bytes32
doesn't give a huge optimization.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [69-74] struct Point { int128 bias; int128 slope; uint256 ts; uint256 blk; } // Update to: // Lines: [69-74] struct Point { int bias; int slope; uint256 ts; uint256 blk; } // Lines: [75-80] struct LockedBalance { int128 amount; uint256 end; int128 delegated; address delegatee; } // Lines: [60-60] mapping(uint256 => int128) public slopeChanges; // Lines: [174-174] int128 value = locked_.amount; // And etc, you've got the point... ```
Also occured in the following files:
calldataload
instead of mload
<a name="G-14"></a>calldataload
, or replace memory
with calldata
. If the args are pretty huge, allocating args in memory will cost a lot.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [100-106] constructor( address _owner, address _penaltyRecipient, address _token, string memory _name, string memory _symbol ) {} // Update to: // Lines: [100-106] constructor( address _owner, address _penaltyRecipient, address _token, string calldata _name, string calldata _symbol ) {} // Lines: [222-226] function _checkpoint( address _addr, LockedBalance memory _oldLocked, LockedBalance memory _newLocked ) internal {} // Lines: [595-600] function _delegate( address addr, LockedBalance memory _locked, int128 value, LockAction action ) internal {} // Lines: [685-689] function _copyLock(LockedBalance memory _locked) internal pure returns (LockedBalance memory) {} // Lines: [825-829] function _supplyAt(Point memory _point, uint256 _t) internal view returns (uint256) {}
Also occured in the following files:
immutable/const
<a name="G-15"></a>SSTORE_SET_GAS
after Berlin hard fork costs around 22100 units of gas, therefore to get a huge optimization it's possible to define the following state variables as immutable/const
.SLOAD
s, since they are consts/immutable variables, which also saves sagnificant amount of gas.Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [45-45] IERC20 public token; // could be defined as immutable. // Lines: [64-66] string public name; string public symbol; uint256 public decimals = 18; ```
Also occured in the following files:
Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [140-140] require(msg.sender == owner, "Only owner"); // Lines: [171-171] require(msg.sender == blocklist, "Only Blocklist"); // And etc, you've got the point... ```
Also occured in the following files:
Contracts:
file: contracts/VotingEscrow.sol ................................. // Lines: [723-723] // Original max = mid - 1; // Updated max = mid--; // Updated --mid; max = mid; // Lines: [747-747] // Original max = mid - 1; // Updated max = mid--; // Updated --mid; max = mid;
#0 - lacoop6tu
2022-08-26T15:34:46Z
Good one