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: 17/133
Findings: 5
Award: $799.63
π Selected for report: 1
π Solo Findings: 0
165.6336 USDC - $165.63
In Community
contract, function escrow()
is used to reduce debt when lender comed in terms with the builder and agent to reduce debt. It checks that all lender, builder and agent are signed the data.
But the issue is there is no nonce
value in _data
which means that anyone can use the same _data
and _signature
to call escrow()
multiple times.
( uint256 _communityID, address _builder, address _lender, address _agent, address _project, uint256 _repayAmount, bytes memory _details ) = abi.decode( _data, (uint256, address, address, address, address, uint256, bytes) );
The result is attacker (may be builder) can call escrow()
multiple times to reduce all the debt and cause loss of funds for lender (which is community owner).
10000
DAI1000
DAI in Aliceβs debt. All Alice and Bob and the third party sign this term and someone call escrow()
onchain. Now Alice debt is reduce to 9000
DAIescrow()
9 more times to reduce all her debt (each time reduce 1000
DAI). This is possible because there is no mechanism like nonce
value so escrow()
can be call multiple times with the same params.Manual Review
Add a nonce value in _data
param of escrow()
function and increase it whenever someone call escrow()
#0 - horsefacts
2022-08-06T20:28:36Z
π Selected for report: minhquanym
Also found by: Chom, berndartmueller, scaraven
285.6964 USDC - $285.70
In Project
contract, the lendToProject()
function might not be available to be called if there are a lot of Task in tasks[]
list of project. It means that the project cannot be funded by either builder or community owner.
This can happen because lendToProject()
used projectCost()
function. And the loop in projectCost()
did not have a mechanism to stop, itβs only based on the length taskCount
, and may take all the gas limit. If the gas limit is reached, this transaction will fail or revert.
Same issue with toggleLendingNeeded()
function which also call projectCost()
function.
Function projectCost()
did not have a mechanism to stop, only based on the taskCount
.
function projectCost() public view override returns (uint256 _cost) { // Local instance of taskCount. To save gas. uint256 _length = taskCount; // Iterate over all tasks to sum their cost for (uint256 _taskID = 1; _taskID <= _length; _taskID++) { _cost += tasks[_taskID].cost; } }
There is no limit for builder when add task.
And function lendToProject()
used projectCost()
to check the new total lent value
require( projectCost() >= uint256(_newTotalLent), "Project::value>required" );
Manual Review
Consider keep value of projectCost()
in a storage variable and update it when a task is added or updated accordingly.
π Selected for report: MEP
Also found by: Haipls, byndooa, minhquanym
285.6964 USDC - $285.70
https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/Project.sol#L170 https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/Project.sol#L271
In updateProjectHash()
function, the _data
encoded only _hash
and _nonce
value but not the _projectAddress
. In case builder had 2 or more projects, the signature that builder used in updateProjectHash()
can also be used in other projects by attackers.
(bytes memory _hash, uint256 _nonce) = abi.decode( _data, (bytes, uint256) );
The result is wrong event emitted, which can make others application that build on top of this hash value malfunctioning.
And since there is a nonce, attacker can even front-running when builder try to updateProjectHash()
of other projects. Builder will have to sign a new signature with bigger nonce, and attacker again can use this new signature to front-running the other projects.
Same issue with updateTaskHash()
function when there is no _projectAddress
value encoded in _data
param.
Scenario
10
times. So attacker had signature for nonce value from 0 to 9
from project A0 to 9
, attacker will front-run Alice and call updateProjectHash()
with wrong hash value first (this hash is hash value of project A).10
times failed to update hash value of project B, Alice now is able to call updateProjectHash()
since attacker did not have signature with nonce = 10
.nonce = 10
and can use it to front-run other projects of Alice (A and C)Manual Review
Consider add _projectAddress
encoded in _data
param to prevent signature replay.
#0 - parv3213
2022-08-11T12:02:57Z
Duplicate of #347
π 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
40.8799 USDC - $40.88
Id | Title |
---|---|
1 | Not support fee-on-transfer tokens |
2 | No limit in lenderFee set by HomeFi admin |
3 | A year has 365.25 days in average, not 365 days |
Function Community.lendToProject
function use a _amountToProject
parameter but this parameter is not the actual transferred amount for fee-on-transfer / deflationary (or other rebasing) tokens.
Although sponsor said that they only support 3 tokens at this time, but they might consider this issue in the future before they want to add another token.
The actual lent amount might be lower than _amountToProject
. The result is project is not receive enough funds to pay subcontractor and it broke the accouting mechanism.
lenderFee
set by HomeFi
adminThere is no limit here
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); }
365.25
days in average, not 365
daysIn Community
, when function returnToLender()
calculates interest to return to lender, it assumed that a year has 365 days but in fact, it has 365.25
days in average.
π 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
21.7223 USDC - $21.72
Id | Title |
---|---|
1 | Caching returned values from function calls can save gas |
2 | Cache length in the for loop and uncheck index |
External call to other contract is gas consuming and should avoid as much as possible.
For example, in Community.lendToProject()
function, lenderFee()
of _projectInstance
is called 2 times. We should call only 1 time and cache the returned values to save gas.
At each iteration of the loop, length is read from storage. We can cache the length and save gas per iteration.
Solidity 0.8.0 check safe math in every operation. Use uncheck to increase index can save gas.
For example
uint256 length = arr.length; for (uint i; i < length; ) { // do stuff unchecked { ++i; } }