Platform: Code4rena
Start Date: 01/08/2022
Pot Size: $50,000 USDC
Total HM: 26
Participants: 133
Period: 5 days
Judge: Jack the Pug
Total Solo HM: 6
Id: 151
League: ETH
Rank: 53/133
Findings: 2
Award: $84.74
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Lambda
Also found by: 0x1f8b, 0x52, 0xA5DF, 0xNazgul, 0xNineDec, 0xSmartContract, 0xSolus, 0xf15ers, 0xkatana, 0xsolstars, 8olidity, Aymen0909, Bahurum, Bnke0x0, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, Extropy, Funen, GalloDaSballo, Guardian, IllIllI, JC, Jujic, MEP, Noah3o6, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, SooYa, Soosh, Throne6g, TomJ, Tomio, TrungOre, Waze, Yiko, _Adam, __141345__, a12jmx, ajtra, ak1, arcoun, asutorufos, ayeslick, benbaessler, berndartmueller, bin2chen, bobirichman, brgltd, bulej93, byndooa, c3phas, codexploder, cryptonue, cryptphi, defsec, delfin454000, dipp, djxploit, erictee, exd0tpy, fatherOfBlocks, gogo, hake, hansfriese, horsefacts, hyh, ignacio, indijanc, joestakey, kaden, mics, minhquanym, neumo, obront, oyc_109, p_crypt0, pfapostol, poirots, rbserver, robee, rokinot, rotcivegaf, sach1r0, saian, samruna, saneryee, scaraven, sikorico, simon135, sseefried, supernova
62.7082 USDC - $62.71
In function returnToLender
, if the difference between lasttimestamp and current timestamp is not a multiple of 86400 the value get rounded down, so even if 1.9 days has passed the value get rounded to 1 and the communityProject.lastTimestamp
updated to the current value. So the interest is calculated on 1 days while 1.9 days is updated
uint256 _noOfDays = (block.timestamp - _communityProject.lastTimestamp) / 86400;
Multiply and divide by a value to avoid precision loss
uint256 _noOfDays = (block.timestamp - _communityProject.lastTimestamp) * VAL / 86400; // 24*60*60 /// Interest formula = (principal * APR * days) / (365 * 1000) // prettier-ignore uint256 _unclaimedInterest = _lentAmount * _communities[_communityID].projectDetails[_project].apr * _noOfDays / (365000 * VAL);
The function replaceLenderFee
does not contain input validation, so the fee can be set to a very high value, projects deployed using the value will have a high lending fee
function replaceLenderFee(uint256 _newLenderFee) external override onlyAdmin { // Revert if no change in lender fee require(lenderFee != _newLenderFee, "HomeFi::!Change"); // Reset variables lenderFee = _newLenderFee; emit LenderFeeReplaced(_newLenderFee); }
uint256 _lenderFee = (_lendingAmount * _projectInstance.lenderFee()) / (_projectInstance.lenderFee() + 1000); // Calculate amount going to project. Lending amount - lending fee. uint256 _amountToProject = _lendingAmount - _lenderFee;
Add input validation to validate the lender fee
Lenderfee updated by admin in HomeFi.sol
is not reflected in project already initialized, So lenderfee queried in Community.sol
will be the value initialized in the project
function replaceLenderFee(uint256 _newLenderFee) external override onlyAdmin { // Revert if no change in lender fee require(lenderFee != _newLenderFee, "HomeFi::!Change"); // Reset variables lenderFee = _newLenderFee; emit LenderFeeReplaced(_newLenderFee); }
function initialize( address _currency, address _sender, address _homeFiAddress ) external override initializer { ... lenderFee = homeFi.lenderFee();
Replace _projectInstance.lenderFee()
with homeFi.lenderFee()
_mint
with _safeMint
Function _mint
can be replaced with _safeMint
which checks if the receiver can handle ERC721 tokens and prevent it from being stuck in the contracts
_mint(_to, projectCount);
Owner has to make sure to send the contract address in the right order of contract names. Accidentally sending input in the wrong order will result in mismatch of name and proxy address
allContractNames.push("HF"); // HomeFi allContractNames.push("CN"); // Community allContractNames.push("DP"); // Disputes allContractNames.push("PF"); // Project Factory allContractNames.push("DA"); // rDAI allContractNames.push("US"); // rUSDC allContractNames.push("NT"); // native token rETH - rXDAI // Local instance of variable. For saving gas. uint256 _length = allContractNames.length; // Revert if _implementations length is wrong. Indicating wrong set of _implementations. require(_length == _implementations.length, "Proxy::Lengths !match"); // Mark this contract as active contractsActive[address(this)] = true; // Generate proxy for all implementation for (uint256 i = 0; i < _length; i++) { _generateProxy(allContractNames[i], _implementations[i]); }
🌟 Selected for report: c3phas
Also found by: 0x040, 0x1f8b, 0xA5DF, 0xNazgul, 0xSmartContract, 0xSolus, 0xc0ffEE, 0xkatana, 0xsam, 8olidity, Aymen0909, Bnke0x0, CertoraInc, Chinmay, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, Extropy, Fitraldys, Funen, GalloDaSballo, Guardian, IllIllI, JC, Lambda, MEP, Metatron, MiloTruck, Noah3o6, NoamYakov, PaludoX0, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, SooYa, TomJ, Tomio, Waze, _Adam, __141345__, a12jmx, ajtra, ak1, apostle0x01, asutorufos, ballx, benbaessler, bharg4v, bobirichman, brgltd, cryptonue, defsec, delfin454000, dharma09, djxploit, durianSausage, eierina, erictee, fatherOfBlocks, gerdusx, gogo, hake, hyh, ignacio, jag, kaden, kyteg, lucacez, mics, minhquanym, oyc_109, pfapostol, rbserver, ret2basic, robee, rokinot, sach1r0, saian, samruna, scaraven, sikorico, simon135, supernova, teddav, tofunmi, zeesaw
22.0296 USDC - $22.03
unchecked
to save gasIn Solidity 0.8.4 and above there is a default underflow/overflow checks on unsigned integers. it is possible to avoid this check by using unchecked
. If the expression does not cause overflow/underflow, it can be unchecked to save some gas.
require(_lentAndInterest >= _repayAmount, "Community::!Liquid"); _interest = 0; _lentAmount = _lentAndInterest - _repayAmount; L785 if (_repayAmount > _interest) { ... } else { _interest -= _repayAmount; }
if (_newCost < _taskCost) { // Find the difference between old - new. uint256 _withdrawDifference = _taskCost - _newCost;
if (_costToAllocate >= _taskCost) { // Reduce task cost from _costToAllocate _costToAllocate -= _taskCost;
if (_costToAllocate >= _taskCost) { // Reduce task cost from _costToAllocate _costToAllocate -= _taskCost;
> 0
is less efficient than != 0
for unsigned integers!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas)
require(_cost > 0, "Project::!value>0");
Pre-increment saves a small amount of gas than postfix increment because it doesnt have to store the previous value. This can be more significant in loops where this operation is done multiple times.
for (uint256 i = 0; i < _length; i++)
for (uint256 i = 0; i < _length; i++)
for (uint256 i = 0; i < _length; i++)
for (uint256 i = 0; i < _length; i++)
for (uint256 i = 0; i < _length; i++)
for (uint256 _taskID = 1; _taskID <= _length; _taskID++)
for (++j; j <= taskCount; j++) {
for (uint256 _taskID = 1; _taskID <= _length; _taskID++) {
for (uint256 i = 0; i < _length; i++) _alerts[i] = _self.alerts[i];
for (uint256 i = 0; i < _communities[_communityID].memberCount; i++)
The return value of external functions that are called multiple times can be cached and reused to save gas
tasks[_taskList[i]].acceptInvitation(_msgSender());
Function calls can be replaced with cached value in _sender
L380 address _sender = _msgSender(); _currency.safeTransferFrom(_msgSender(), homeFi.treasury(), _lenderFee); // Transfer _amountToProject to _project from lender account _currency.safeTransferFrom(_msgSender(), _project, _amountToProject);
emit ApproveHash(_hash, _msgSender());
&&
and save gasrequire statements that use &&
operator to check multiple condition can be split into multiple statements of one condition to save gas
require( _disputeID < disputeCount && disputes[_disputeID].status == Status.Active, "Disputes::!Resolvable" );
require( _actionType > 0 && _actionType <= uint8(ActionType.TaskPay), "Disputes::!ActionType" );
Storage variables that are read more than once can be cached in a temporary variable to save a SLOAD(100 gas) operation and save gas
projectCount
in
projects[projectCount] = _project; projectTokenId[_project] = projectCount; emit ProjectAdded(projectCount, _project, _sender, _currency, _hash);
projectCount += 1; // Mints NFT and set token URI _mint(_to, projectCount); _setTokenURI(projectCount, _tokenURI); emit NftCreated(projectCount, _to);
Project(_clone).initialize(_currency, _sender, homeFi);
emit ContractorInvited(contractor);
Array length can be cached in a variable and reused
L603 for (; i < _changeOrderedTask.length; i++) L635 if (i == _changeOrderedTask.length) {
taskCount
in
L650 for (++j; j <= taskCount; j++) { L681 if (j > taskCount) { lastAllocatedTask = taskCount; }
emit CommunityAdded(communityCount, _sender, _currency, _hash);
_communities[_communityID].memberCount
in
for (uint256 i = 0; i < _communities[_communityID].memberCount; i++)
As the code is already using a compiler version >= 0.8.4, there is a more convenient and gas-efficient way to handle revert strings: custom errors. Custom errors decrease both deploy and runtime gas costs. Source:Â https://blog.soliditylang.org/2021/04/21/custom-errors/
require(_address != address(0), "Proxy::0 address"); require(_length == _implementations.length, "Proxy::Lengths !match"); require( contractAddress[_contractName] == address(0), "Proxy::Name !OK" ); require(_length == _contractAddresses.length, "Proxy::Lengths !match");
require(!contractorConfirmed, "Project::GC accepted"); require(_projectAddress == address(this), "Project::!projectAddress"); require(_contractor != address(0), "Project::0 address"); require(_msgSender() == builder, "Project::!B"); require(contractor != address(0), "Project::0 address"); require(_nonce == hashChangeNonce, "Project::!Nonce"); require( _sender == builder || _sender == homeFi.communityContract(), "Project::!Builder&&!Community" ); require(_cost > 0, "Project::!value>0"); require( projectCost() >= uint256(_newTotalLent), "Project::value>required" ); require(_taskCount == taskCount, "Project::!taskCount"); require(_projectAddress == address(this), "Project::!projectAddress"); require(_length == _taskCosts.length, "Project::Lengths !match"); require(_nonce == hashChangeNonce, "Project::!Nonce"); require( _msgSender() == builder || _msgSender() == contractor, "Project::!Builder||!GC" ); require(_length == _scList.length, "Project::Lengths !match"); require(_projectAddress == address(this), "Project::!Project"); require(tasks[_taskID].getState() == 3, "Project::!Complete"); require(_project == address(this), "Project::!projectAddress"); require(_project == address(this), "Project::!projectAddress"); require( signer == builder || signer == contractor, "Project::!(GC||Builder)" ); require( signer == builder || signer == contractor || signer == tasks[_task].subcontractor, "Project::!(GC||Builder||SC)" ); require(getAlerts(_task)[2], "Project::!SCConfirmed"); require(_sc != address(0), "Project::0 address"); require( _recoveredSignature == _address || approvedHashes[_address][_hash], "Project::invalid signature" ); require( ((_amount / 1000) * 1000) == _amount, "Project::Precision>=1000" );
require(admin == _msgSender(), "HomeFi::!Admin"); require(_address != address(0), "HomeFi::0 address"); require(_oldAddress != _newAddress, "HomeFi::!Change"); require(!addrSet, "HomeFi::Set"); require(lenderFee != _newLenderFee, "HomeFi::!Change"); require( _currency == tokenCurrency1 || _currency == tokenCurrency2 || _currency == tokenCurrency3, "HomeFi::!Currency" );
require(_address != address(0), "Disputes::0 address"); require(homeFi.admin() == _msgSender(), "Disputes::!Admin"); require(homeFi.isProjectExist(_msgSender()), "Disputes::!Project"); require( _disputeID < disputeCount && disputes[_disputeID].status == Status.Active, "Disputes::!Resolvable" ); require( _actionType > 0 && _actionType <= uint8(ActionType.TaskPay), "Disputes::!ActionType" ); require(_result, "Disputes::!Member");
require(_address != address(0), "PF::0 address"); require( _msgSender() == IHomeFi(homeFi).admin(), "ProjectFactory::!Owner" ); require(_msgSender() == homeFi, "PF::!HomeFiContract");
require( communityContract == _msgSender(), "DebtToken::!CommunityContract" ); require(_communityContract != address(0), "DebtToken::0 address"); revert("DebtToken::blocked");
require(_self.subcontractor == _sc, "Task::!SC");
bool
incurs storage overheadUse uint256(0) & uint256(1) for false and true instead of the boolean variable
mapping(address => bool) internal contractsActive;
// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.