Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 96
Period: 3 days
Judge: HardlyDifficult
Total Solo HM: 5
Id: 140
League: ETH
Rank: 63/96
Findings: 1
Award: $40.70
π Selected for report: 0
π Solo Findings: 0
π Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 8olidity, ACai, BowTiedWardens, Chandr, Chom, ElKu, Fitraldys, Funen, IgnacioB, JC, Lambda, Limbooo, MiloTruck, Noah3o6, Nyamcil, Picodes, Randyyy, SmartSek, StErMi, TerrierLover, TomJ, Tomio, UnusualTurtle, Waze, _Adam, ajtra, c3phas, cRat1st0s, catchup, codexploder, cryptphi, defsec, delfin454000, ellahi, exd0tpy, fatherOfBlocks, hansfriese, joestakey, kebabsec, kenta, m_Rassska, minhquanym, oyc_109, pashov, reassor, rfa, robee, sach1r0, saian, sashik_eth, simon135, slywaters, ych18, ynnad, zuhaibmohd
40.6957 USDC - $40.70
Test/Tokens/MaliciousFactory.sol
10. import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol";
Test/TestBancorFormula.sol
5. import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; 14. using SafeMath for uint256;
./NibblVaultFactory.sol
9. import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol";
If you add new top-level items inside "temp.sol", they automatically appear in all files that import βtemp.solβ. In some cases you've specified what you've actually needed, but in another cases - have not.
./Interfaces/IBasket.sol
// Redundant import, since ERC721.sol already imports IERC721Receiver.sol 5. import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
{module you need}
to specify the context as you did in some places. I'm not 100% sure that I cover all redundant imports according to the current PROBLEM../Basket.sol
"withdraw:not allowed"
defined in 8 different require statements within the current scope. Why we can't simply turn it into one single error which will throw some sign like: "W1". Use comments, if you want provide an info about certain sign-codes.36. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 42. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 53. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 62. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 69. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 79. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 86. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed"); 92. require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed");
The same is applicable for listed scopes as well.
./NibblVaultFactory.sol
./Test/UpgradedNibblVault.sol
./Test/Tokens/MiliciousFactory.sol
Just simply define custom errors and reverts them whenever it's nedeed.
<br/>&&
or ||
evm has AND
or OR
opcodes which cost minimum of 3 gas. Also, by braking down this complex require body into multiple require statements will improve the code readability../Bancor/BancorFormula.sol
188. require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT); 219. require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply);
The same is applicable for listed scopes as well.
./NibblVaultFactory.sol
./Test/Tokens/MiliciousFactory.sol
Replacing complex require statement by defining multiple requires.
UPD: I just realized that BancorFormula.sol is not under the scope πππ.
<br/>./Test/UpgradedNibblVault.sol
120. uint private unlocked;
This mutex could be defined as uint8 and placed after lines: 67, 61, because after reserving slot for address(20 bytes) there 12 remaining bytes where we can place our mutex. Congratulations, we saved one slot which is a pretty big deal. π₯π₯π₯
<br/>./Test/UpgradedNibblVault.sol
124. uint256 public upgradedNewVariable; ... ... 492. function upgradedFunction() external { 493. upgradedNewVariable++; 494. }
What is the reason for an allocation such a huge amount of storage memory to store vars which can be declared as immutable.
Well, the storage variables here are only initialized in initialize function, which will be invoked internally from the NabbiVaultFactory::createVault()
by passing each variables to initialize function. Then function will read directly from the calldata and assign all of these vars. This is the only place, where this vars change their states, if it's so, it makes sence to mark them as immutable.
Making free previously used storage by using delete keyword will not help to retrieve the full gas used for SSTORE.
./Test/UpgradedNibblVault.sol
55. address payable public factory; <-- declared as a storage variable 61. address public assetAddress; <-- declared as a storage variable 64. uint256 public assetID; <-- declared as a storage variable 70. uint256 public initialTokenPrice; <-- declared as a storage variable 75. uint256 private fictitiousPrimaryReserveBalance; <-- declared as a storage variable ... 184. initialTokenPrice=_initialTokenPrice; <-- here it's initialized 185. factory = payable(msg.sender); <-- here it's initialized 186. assetAddress = _assetAddress; <-- here it's initialized 187. assetID = _assetID; <-- here it's initialized 192. fictitiousPrimaryReserveBalance = _primaryReserveBalance; <-- here it's initialized ...
NabbiVaultFactory::createVault()
by passing each variables to initialize function. Then function will read from calldata and assign all of these vars. This is the only place, where this vars change their states, if it's so, it makes sence to mark them as immutable.UpgradedNibblVault::_rejectBayout()
which deletes some of them and I was so happy when I saw it, but anyway, it's a sagnificant amount of gas consumption to simply call SSTORE and SLOAD such a huge amount of time.