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: 14/126
Findings: 3
Award: $486.06
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: CertoraInc
Also found by: 0x1f8b, 0xSky, CodingNameKiki, DecorativePineapple, Noah3o6, Waze, jonatascm, oyc_109, pedr02b2, peritoflores
In the VotingEscrow.sol constructor, the decimals are calculated based on the specified ERC20 tokens decimals. Then on the L:116 a require statement enforces that the max decimals of the ERC20 token is 18. This is not always the case, as some ERC20 tokens can have more than 18 decimals, such as the YAMv2 token, which has 24
.
decimals = IERC20(_token).decimals(); require(decimals <= 18, "Exceeds max decimals");
Manual Review
It is recommended to remove the require statement from L:116
and adjust the calculations in a way that work with ERC20 decimals.
#0 - lacoop6tu
2022-08-17T08:21:55Z
We are going to use a BalV2 Pool Token which use 18 Similar / Duplicate of #231
#1 - gititGoro
2022-08-31T02:56:48Z
Although aspect reported different, the same overall umbrella applies as 231 and so duplicate upheld.
🌟 Selected for report: CertoraInc
Also found by: 0x1f8b, DecorativePineapple, cRat1st0s, carlitox477, joestakey, ladboy233, reassor, rvierdiiev
142.1501 USDC - $142.15
In the createLock
function the amount is calculated by casting the uint256 _value
to int128
in an unsafe way.
Specifically the locked_.amount
is calculated as: locked_.amount += int128(int256(_value));
could result in a negative value for locked._amount
. For example, if _value
is 868790785326998466564056403945758400791
, the locked._amount
would be -152056315435816923826067418349546233577
Because the locked._amount
isn't checked whether it is >0
in the rest of the function, a lock can be created with negative amount. When the user that created that lock tries to withdraw the tokens (either with withdraw
or quitLock
) , he will be unable to withdraw the tokens.
This bug could happen if a user tries to create a lock with at least max(uint128)
tokens. He has to approve at least that amount of tokens to the contract, so the transferFrom
call from line L657 would succeed.
This is due to the check require(locked_.amount > 0, "No lock");
that is used in both functions.
function withdraw() external override nonReentrant { LockedBalance memory locked_ = locked[msg.sender]; // Validate inputs require(locked_.amount > 0, "No lock"); require(locked_.end <= block.timestamp, "Lock not expired"); require(locked_.delegatee == msg.sender, "Lock delegated"); // Update lock ...
and
function quitLock() external override nonReentrant { LockedBalance memory locked_ = locked[msg.sender]; // Validate inputs require(locked_.amount > 0, "No lock"); require(locked_.end > block.timestamp, "Lock expired"); require(locked_.delegatee == msg.sender, "Lock delegated"); // Update lock ...
The createLock function:
function createLock(uint256 _value, uint256 _unlockTime) external override nonReentrant checkBlocklist { uint256 unlock_time = _floorToWeek(_unlockTime); // Locktime is rounded down to weeks LockedBalance memory locked_ = locked[msg.sender]; // Validate inputs ... // Update lock and voting power (checkpoint) locked_.amount += int128(int256(_value)); locked_.end = unlock_time; locked_.delegated += int128(int256(_value)); locked_.delegatee = msg.sender; locked[msg.sender] = locked_; _checkpoint(msg.sender, LockedBalance(0, 0, 0, address(0)), locked_); // Deposit locked tokens require( token.transferFrom(msg.sender, address(this), _value), "Transfer failed" ); ... }
Manual Review
Even though Solidity 0.8.x is used, type casts do not throw an error. A SafeCast library must be used everywhere a typecast is done.
Because the locked._amount
isn't intended to receive negative values, it is better to use uint128
for the variables amount
and delegated
of the LockedBalance
struct, and adjust the typecasting accordingly.
#0 - lacoop6tu
2022-08-16T09:48:18Z
Duplicate of #228
#1 - gititGoro
2022-09-01T23:59:19Z
Duplicate label upheld
🌟 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.8918 USDC - $29.89
There is currently no input validation on the constructors of the VotingEscrow.sol
and the constructor of the Blocklist.sol
contracts. The variables manager
, ve
of the Blocklist.sol contract and the variables _owner
, _penaltyRecipient
and token
of the VotingEscrow.sol contract have to be validated. Not validating against a zero address could potentially lead to an initialized state where the contracts have no owner and the deployer needs to re-deploy the contract to have it working properly.
The comment on L:589
: require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
is wrong. In fact, it allows a delegate to both longer locks and lock that has the same end time.