Platform: Code4rena
Start Date: 22/09/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 133
Period: 3 days
Judge: 0xean
Total Solo HM: 2
Id: 165
League: ETH
Rank: 25/133
Findings: 3
Award: $146.61
🌟 Selected for report: 0
🚀 Solo Findings: 0
103.1542 USDC - $103.15
Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point.
In you current loop you are just deleting the minter index, and the minters array will keep growing and growing.
Also if you add a minter, then you removed, and then you check minters_array.length
you will see that its 1
but you have no minter because you have delete it
Manual revision
My recommendation its to avoid the minters_array
, you could rely on events to track the minters.
If you still want to use and array please read this article; https://blog.finxter.com/how-to-delete-an-element-from-an-array-in-solidity/ And use this pattern to delete an object of the array
firstArray[index] = firstArray[firstArray.length - 1]; firstArray.pop();
#0 - FortisFortuna
2022-09-25T22:41:04Z
Technically correct, but in practice, the number of minters will always remain low. If it becomes an issue, we can designate one minter as a "pre-minter" that has a batch of tokens minted to it beforehand, then auxiliary contracts can connect to that instead of ERC20PermitPermissionedMint.sol instead.
#1 - joestakey
2022-09-26T16:18:49Z
Duplicate of #12
🌟 Selected for report: rotcivegaf
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xf15ers, 8olidity, Aymen0909, B2, Bahurum, Bnke0x0, Ch_301, CodingNameKiki, Deivitto, Diana, Funen, IllIllI, JC, JLevick, KIntern_NA, Lambda, OptimismSec, PaludoX0, RockingMiles, Rolezn, Sm4rty, Soosh, Tagir2003, Tointer, TomJ, Triangle, Trust, V_B, Waze, Yiko, __141345__, a12jmx, ajtra, asutorufos, ayeslick, aysha, bbuddha, bharg4v, bobirichman, brgltd, bytera, c3phas, cryptostellar5, cryptphi, csanuragjain, datapunk, delfin454000, durianSausage, exd0tpy, gogo, got_targ, jag, joestakey, karanctf, ladboy233, leosathya, lukris02, mics, millersplanet, natzuu, neko_nyaa, obront, oyc_109, parashar, peritoflores, rbserver, ret2basic, rokinot, ronnyx2017, rvierdiiev, sach1r0, seyni, sikorico, slowmoses, tnevler, yasir, yongskiws
30.653 USDC - $30.65
On ERC20PermitPermissionedMint.sol#L40-L43 modifier onlyByOwnGov()
, is checking for the msg.sender to be owner or timelock, so whats the point of having a timelock if you could do whatever?
Same issue on OperatorRegistry.sol#L45-L48
My recommendation its to remove this modifier and use the onlyOwner
modifier, if you want to use a timelock just transfer the ownership to the timelock.
On ERC20PermitPermissionedMint.sol#L34;
Owned(_creator_address) { timelock_address = _timelock_address; }
You should revert if _timelock_address
is address(0)
to ensure that you start with a valid timelock, if you want to start with address(0) remove this line.
Same issue on; OperatorRegistry.sol#L41
minters_array
on ERC20PermitPermissionedMint.sol
Affected lines; https://github.com/code-423n4/2022-09-frax/blob/55ea6b1ef3857a277e2f47d42029bc0f3d6f9173/src/ERC20/ERC20PermitPermissionedMint.sol#L84-L89
When you remove a minter the minters_array
its just setting the minter index into address(0)
, think on this escenario;
minters_array.length
is 1minters_array.length
still is 1, you have remove the minter!My recommendation its to avoid the minters_array
, you could rely on events to track the minters.
If you still want to use and array please read this article; https://blog.finxter.com/how-to-delete-an-element-from-an-array-in-solidity/ And use this pattern to delete an object of the array
firstArray[index] = firstArray[firstArray.length - 1]; firstArray.pop();
depositEther
will always revert if (balance / 32 ether) is greater than validators.lengthCheckout https://github.com/code-423n4/2022-09-frax/blob/55ea6b1ef3857a277e2f47d42029bc0f3d6f9173/src/frxETHMinter.sol#L136;
) = getNextValidator(); // Will revert if there are not enough free validators
Instead of reverting on this line you could change the loop guard insted of numDeposits
you will have to use numIterations
, and numIterations
is the minimum between numDeposits
and numValidators()
recoverERC20
the ERC20 will always go to the owner
What if owner
is address(0)
or a contract? Also governance could call this function, but all tokens will be send to owner.
I think you should add a parameter to specify where you want to send the funds, or change onlyByOwnGov
to onlyOwner
Lines: frxETHMinter.sol#L200
recoverEther
the ERC20 will always go to the owner
What if owner
is address(0)
or a contract that rejects ETH? Also governance could call this function, but all tokens will be send to owner.
I think you should add a parameter to specify where you want to send the funds, or change onlyByOwnGov
to onlyOwner
Lines: frxETHMinter.sol#L191
approve(0)
before approveIts consider a good practice to reset the approve before changing it. Mainly because some tokens (like USDT) do not work when changing the allowance from an existing non-zero allowance value.They must first be approved by zero and then the actual allowance must be approved. Lines: frxETHMinter.sol#L75
You could also remove this line and add an infinite approve on the constructor for gas savings.
The ERC20PermitPermissionedMint.sol
contract utilised draft-EIP712, an OpenZeppelin contract. This contract is still a draft and is not considered ready for mainnet use. OpenZeppelin contracts may be considered draft contracts if they have not received adequate security auditing or are liable to change with future development.
ERC20PermitPermissionedMint.sol#L6
Ensure the development team is aware of the risks of using a draft contract or consider waiting until the contract is finalised.
Otherwise, make sure that development team are aware of the risks of using a draft OpenZeppelin contract and accept the risk-benefit trade-off.
ERC20PermitPermissionedMint.sol#L2 frxETHMinter.sol#L2 sfrxETH.sol#L2 frxETH.sol#L2
Use this pattern
import {Contract} from "./contract.sol";
Files:
🌟 Selected for report: pfapostol
Also found by: 0x040, 0x1f8b, 0x4non, 0x5rings, 0xA5DF, 0xNazgul, 0xSmartContract, 0xmatt, 0xsam, Amithuddar, Aymen0909, B2, Ben, Bnke0x0, Chom, CodingNameKiki, Deivitto, Diana, Fitraldys, Funen, IllIllI, JAGADESH, JC, Metatron, Ocean_Sky, PaludoX0, Pheonix, RaymondFam, ReyAdmirado, RockingMiles, Rohan16, Rolezn, Satyam_Sharma, Sm4rty, SnowMan, SooYa, Tagir2003, TomJ, Tomio, Triangle, V_B, Waze, __141345__, ajtra, albincsergo, asutorufos, aysha, beardofginger, bobirichman, brgltd, bulej93, bytera, c3phas, ch0bu, cryptostellar5, cryptphi, d3e4, delfin454000, dharma09, drdr, durianSausage, emrekocak, erictee, fatherOfBlocks, gogo, got_targ, imare, jag, karanctf, ladboy233, leosathya, lukris02, medikko, mics, millersplanet, natzuu, neko_nyaa, oyc_109, peanuts, prasantgupta52, rbserver, ret2basic, rokinot, ronnyx2017, rotcivegaf, sach1r0, samruna, seyni, slowmoses, tnevler, wagmi, zishansami
12.8108 USDC - $12.81
withholdRatio
and currentWithheldETH
are already 0, avoid to set variables with 0
withholdRatio = 0; // No ETH is withheld initially currentWithheldETH = 0;
withholdRatio
and currentWithheldETH
are already 0
numValidators()
use validators.length
Save gas avoiding calling a view function on: OperatorRegistry.sol#L136 OperatorRegistry.sol#L182
!= 0
on requires instead of >0
to save gasOn: frxETHMinter.sol#L79 frxETHMinter.sol#L126
++i
instead of i++
, ++i
costs less gas than i++
On:
++i
/i++
you should use unchecked{++i}
/unchecked{i++}
(when its safe)On;
Use this pattern;
for (uint256 i = 0; i < numDeposits;) { ... code unchecked{ ++i; } }