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: 3/133
Findings: 7
Award: $3,444.55
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: scaraven
Also found by: 0x52, Deivitto, Lambda, TrungOre, auditor0517, hansfriese, rbserver, simon135, smiling_heretic, sseefried
165.6336 USDC - $165.63
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L685-L694
When returnToLender
is called, the unclaimed interest is calculated as below.
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L685-L694
uint256 _noOfDays = (block.timestamp - _communityProject.lastTimestamp) / 86400; // 24*60*60 /// Interest formula = (principal * APR * days) / (365 * 1000) // prettier-ignore uint256 _unclaimedInterest = _lentAmount * _communities[_communityID].projectDetails[_project].apr * _noOfDays / 365000;
Based on this code, if this function is called within 24 hours since the last time that a loan is lent or interest is earned for a project, the unclaimed interest is 0
because block.timestamp - _communityProject.lastTimestamp
is less than 86400
. However, especially when the lent amount is large, the community owner's loan is in use for the time within the 24 hours and should receive interest accordingly. The community owner will find this very unfair.
The following example compares the current code to the suggested code.
For example, the variables are defined as follows.
block.timestamp - _communityProject.lastTimestamp
= 86399
_lentAmount
= 1000000000000
_communities[_communityID].projectDetails[_project].apr
= 50
Using the current code, the unclaimed interest is 0
because (block.timestamp - _communityProject.lastTimestamp) / 86400 = 86399 / 86400 = 0
per Solidity's behavior.
Using the following suggested code that combines the current code, the unclaimed interest is 136984715
, per Solidity's behavior, which is larger than 0
and close to the unrounded calculation result.
uint256 _unclaimedInterest = (_lentAmount * _communities[_communityID].projectDetails[_project].apr * (block.timestamp - _communityProject.lastTimestamp)) / (86400 * 365000);
VSCode
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L685-L694 can be changed to the suggested code used in the POC above.
#0 - horsefacts
2022-08-06T20:40:02Z
2351.4111 USDC - $2,351.41
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L267 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L690-L694
When the Community.publishProject
function is called, the following code is execute to assign apr
of the project that is published to a community.
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L267
_communityProject.apr = _apr;
Later, whenever the Community.claimInterest
function, which executes the following Community.returnToLender
code, is called, apr
of the project is used to calculate interest on the lent amount.
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L690-L694
uint256 _unclaimedInterest = _lentAmount * _communities[_communityID].projectDetails[_project].apr * _noOfDays / 365000;
After the project is published to a community, when the project is unpublished or published to another community and then is published again to the previous community, the project's apr
will be replaced with the latest new value. For the same community-project combination, after changing the value, calling Community.claimInterest
will inflate the interest on the previous lent amount if the new value is larger than the previous value and deflate the interest on the previous lent amount if the new value is smaller than the previous value. Because of the inaccurate calculations, either the builder will lose money by repaying more interest than or the community owner will lose money by receiving less interest than the correct amount.
Please add the following test in the Other tests
describe
block in test\utils\communityTests.ts
.
This test will pass to demonstrate the described scenario.
it('apr is replaced with new value when project is published again to the same community', async () => { // create project let newProject: Project; let newCommunityID = 0; ({ projectContractInstance: newProject } = await createProject( homeFiContract, tasksLibrary.address, '0x', await homeFiContract.connect(signers[2]).tokenCurrency1(), )); // create community const randomHash = '0x0a19'; let count = (await communityContract.communityCount()).toNumber(); await communityContract .connect(signers[2]) .createCommunity(randomHash, tokenCurrency1); newCommunityID = count + 1; // add member const data1 = { types: ['uint256', 'address', 'bytes'], values: [newCommunityID, signers[0].address, sampleHash], }; const [encodedData1, signature1] = await multisig(data1, [ signers[2], signers[0], ]); await communityContract.addMember(encodedData1, signature1); // publish project apr = 10; const data2 = { types: ['uint256', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], values: [ newCommunityID, newProject.address, apr, 0, (await communityContract.communities(newCommunityID)).publishNonce, sampleHash, ], }; const [encodedData2, signature2] = await multisig(data2, [ signers[2], signers[0], ]); await communityContract.publishProject(encodedData2, signature2); // toggle lendingNeeded await communityContract.toggleLendingNeeded( newCommunityID, newProject.address, (await newProject.projectCost()).toNumber(), ); const lendingPrincipal = 1020000000; const prjctLenderFee = (await newProject.lenderFee()).toNumber(); const fee = Number( ((lendingPrincipal * prjctLenderFee) / (prjctLenderFee + 1000)).toFixed( 0, ), ); const amountToProject = lendingPrincipal - fee; // setup dai mocks for lending await mockDAIContract.mock.transferFrom .withArgs(signers[2].address, treasury, fee) .returns(true); await mockDAIContract.mock.transferFrom .withArgs(signers[2].address, newProject.address, amountToProject) .returns(true); // lend to project await communityContract .connect(signers[2]) .lendToProject(newCommunityID, newProject.address, lendingPrincipal, sampleHash); // advance 7 days await ethers.provider.send('evm_increaseTime', [7 * 24 * 60 * 60]); // apr is not changed when 7 days past after project is published for first time const [apr_, , , , , , ,] = await communityContract.connect(signers[0]).projectDetails(newCommunityID, newProject.address); expect(apr_).to.be.equals('10'); // unpublish project await communityContract.connect(signers[0]).unpublishProject(newCommunityID, newProject.address); // advance 7 days again await ethers.provider.send('evm_increaseTime', [7 * 24 * 60 * 60]); // publish project again to same community apr = 5; const data3 = { types: ['uint256', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], values: [ newCommunityID, newProject.address, apr, 500, (await communityContract.communities(newCommunityID)).publishNonce, sampleHash, ], }; const [encodedData3, signature3] = await multisig(data3, [ signers[2], signers[0], ]); await communityContract.publishProject(encodedData3, signature3); // apr is replaced with new value after project is published again to same community const [apr2_, , , , , , ,] = await communityContract.connect(signers[0]).projectDetails(newCommunityID, newProject.address); expect(apr2_).to.be.equals('5'); });
VSCode
Instead of recording at the community-project level, the apr
and lent amount can be recorded at the community-project-publishNonce level. When Community.claimInterest
and Community.returnToLender
are called, the apr
value corresponding to the community-project-publishNonce level would be used to calculate the interest on the lent amount for the same level. These interests can be aggregated to get the total interest at the community-project level afterwards.
#0 - parv3213
2022-08-17T06:20:05Z
Duplicate of #83
🌟 Selected for report: hansfriese
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L577-L579 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L582-L584
The admin is able to pause many functionalities of the Community
contract by calling pause
. However, calling this function will take effect immediately and can block planned user interactions unexpectedly. For example, the builder calls the repayLender
function but the admin calls pause
just before that without any announcements. Because the admin's transaction finishes before the builder's starts, the builder's transaction reverts as the Community
contract has already been paused. As a result, the builder fails to pay the community owner and is forced to at least own more interest unexpectedly.
Please add the following test in the Other tests
describe
block in test\utils\communityTests.ts
. This test will pass to demonstrate the described scenario.
it('admin can pause Community functionalities unexpectedly to block planned user interactions, including loan repayments', async () => { // create project let newProject: Project; let newCommunityID = 0; ({ projectContractInstance: newProject } = await createProject( homeFiContract, tasksLibrary.address, '0x', await homeFiContract.connect(signers[2]).tokenCurrency1(), )); // create community const randomHash = '0x0a19'; let count = (await communityContract.communityCount()).toNumber(); await communityContract .connect(signers[2]) .createCommunity(randomHash, tokenCurrency1); newCommunityID = count + 1; // add member const data1 = { types: ['uint256', 'address', 'bytes'], values: [newCommunityID, signers[0].address, sampleHash], }; const [encodedData1, signature1] = await multisig(data1, [ signers[2], signers[0], ]); await communityContract.addMember(encodedData1, signature1); // publish project apr = 10; const data2 = { types: ['uint256', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], values: [ newCommunityID, newProject.address, apr, 0, (await communityContract.communities(newCommunityID)).publishNonce, sampleHash, ], }; const [encodedData2, signature2] = await multisig(data2, [ signers[2], signers[0], ]); await communityContract.publishProject(encodedData2, signature2); // toggle lendingNeeded await communityContract.toggleLendingNeeded( newCommunityID, newProject.address, (await newProject.projectCost()).toNumber(), ); const lendingPrincipal = 1020000000; const prjctLenderFee = (await newProject.lenderFee()).toNumber(); const fee = Number( ((lendingPrincipal * prjctLenderFee) / (prjctLenderFee + 1000)).toFixed( 0, ), ); const amountToProject = lendingPrincipal - fee; // setup dai mocks for lending await mockDAIContract.mock.transferFrom .withArgs(signers[2].address, treasury, fee) .returns(true); await mockDAIContract.mock.transferFrom .withArgs(signers[2].address, newProject.address, amountToProject) .returns(true); // lend to project await communityContract .connect(signers[2]) .lendToProject(newCommunityID, newProject.address, lendingPrincipal, sampleHash); // admin pause Community functionalities unexpectedly await communityContract.connect(signers[0]).pause(); // planned functions like repayLender cannot be called await expect( communityContract.repayLender(newCommunityID, newProject.address, 1000), ).to.be.revertedWith('Pausable: paused'); // restore pause status for other tests await communityContract.connect(signers[0]).unpause(); });
VSCode
pause
and unpause
can be changed to be time-locked functions so users would have more time to react and unexpectedness can be avoided.
#0 - parv3213
2022-08-11T17:21:16Z
154.2761 USDC - $154.28
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L206 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L331 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L366 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/HomeFi.sol#L210 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Project.sol#L219
For publishing a project, the documentation
, in Step 6, states "that you cannot submit a project with no total budget. Therefore it requires at least one task with a budget > 0."
However, it is possible to create a project without adding any tasks or cost and publish it to a community by using the publishProject
function. For this project, calling the toggleLendingNeeded
function with a positive _lendingNeeded
input and the lendToProject
function with a positive _lendingAmount
input will revert. Users, who trust the documentation, might create and publish this kind of projects and expect to add tasks later. They will waste gas and have bad user experience after encountering the unexpected reversions.
Please add the following helper code in test\utils\projectHelpers.ts
first.
export async function createProjectWithoutTasks( homeFiContract: HomeFi, tasksLibraryAddress: string, hash: string, currency: string, ): Promise<any> { const signers = await ethers.getSigners(); let tx = await homeFiContract .connect(signers[0]) .createProject(hash, currency); const receipt = await tx.wait(); const event = receipt.events?.find(x => x.event === 'ProjectAdded'); const project = event?.args?._project; const builder = signers[0].address; const contractor = signers[1].address; const ProjectContractFactory = await ethers.getContractFactory('Project', { libraries: { Tasks: tasksLibraryAddress, }, }); const data = { types: ['address', 'address'], values: [contractor, project], }; const [encodedData, signature] = await multisig(data, [ signers[0], signers[1], ]); const projectContractInstance = ProjectContractFactory.attach( project, ) as Project; await projectContractInstance.inviteContractor(encodedData, signature); return { projectContractInstance, project, builder, contractor }; }
Please add the following test in the lendToProject()
describe
block in test\utils\communityTests.ts
. This test will pass to demonstrate the described scenario.
it('project can be published with no tasks or cost but positive lendingNeeded cannot be toggled and positive amount cannot be lent to it', async () => { // create project let newProject: Project; let newCommunityID = 0; ({ projectContractInstance: newProject } = await createProjectWithoutTasks( homeFiContract, tasksLibrary.address, '0x', await homeFiContract.connect(signers[2]).tokenCurrency1(), )); // project cost is 0 because there are no tasks expect(await newProject.projectCost()).to.be.equals("0"); // create community const randomHash = '0x0a19'; let count = (await communityContract.communityCount()).toNumber(); await communityContract .connect(signers[2]) .createCommunity(randomHash, tokenCurrency1); newCommunityID = count + 1; expect(await communityContract.communityCount()).to.equal( newCommunityID, ); // add member const data1 = { types: ['uint256', 'address', 'bytes'], values: [newCommunityID, signers[0].address, sampleHash], }; const [encodedData1, signature1] = await multisig(data1, [ signers[2], signers[0], ]); const tx1 = await communityContract.addMember(encodedData1, signature1); await expect(tx1) .to.emit(communityContract, 'MemberAdded(uint256,address,bytes)') .withArgs(newCommunityID, signers[0].address, sampleHash); // publish project apr = 10; let data2 = { types: ['uint256', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], values: [ newCommunityID, newProject.address, apr, 0, (await communityContract.communities(newCommunityID)).publishNonce, sampleHash, ], }; let [encodedData2, signature2] = await multisig(data2, [ signers[2], signers[0], ]); const tx2 = await communityContract.publishProject(encodedData2, signature2); // project can be published successfully await expect(tx2) .to.emit(communityContract, 'ProjectPublished') .withArgs( newCommunityID, newProject.address, apr, 0, true, sampleHash, ); // cannot toggle lendingNeeded with positive _lendingNeeded for this project const tx3 = communityContract.toggleLendingNeeded( newCommunityID, newProject.address, (await newProject.projectCost()).add(1), ); await expect(tx3).to.be.revertedWith('Community::invalid lending'); // cannot lend with positive _lendingAmount to this project const tx4 = communityContract .connect(signers[2]) .lendToProject(newCommunityID, newProject.address, (await newProject.projectCost()).add(1), sampleHash); await expect(tx4).to.be.revertedWith('Community::lending>needed'); });
VSCode
In publishProject
, the corresponding project needs to be checked to ensure that it has at least one task and some cost. The documentation needs to be updated accordingly as well.
#0 - parv3213
2022-08-17T06:13:29Z
Duplicate of #16
🌟 Selected for report: berndartmueller
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/HomeFi.sol#L284-L297
After calling the mintNFT
function, an NFT is minted for the project to the builder. After the community owner lends to the project, the builder can sell this NFT in other markets. Someone, who believes in the value of the project, can purchase this NFT and think that she or he will take over the builder role. However, owning the NFT does not change who the builder is. As the original builder keeps the builder role, this NFT buyer loses money and does not have any builder control over the project.
Please add the following test in the Other tests
describe
block in test\utils\communityTests.ts
. This test will pass to demonstrate the described scenario.
it('Someone, who owns project NFT from original builder, does not become project builder', async () => { // create project let newProject: Project; let newCommunityID = 0; ({ projectContractInstance: newProject } = await createProject( homeFiContract, tasksLibrary.address, '0x', await homeFiContract.connect(signers[2]).tokenCurrency1(), )); // create community const randomHash = '0x0a19'; let count = (await communityContract.communityCount()).toNumber(); await communityContract .connect(signers[2]) .createCommunity(randomHash, tokenCurrency1); newCommunityID = count + 1; // add member const data1 = { types: ['uint256', 'address', 'bytes'], values: [newCommunityID, signers[0].address, sampleHash], }; const [encodedData1, signature1] = await multisig(data1, [ signers[2], signers[0], ]); await communityContract.addMember(encodedData1, signature1); // publish project apr = 10; const data2 = { types: ['uint256', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], values: [ newCommunityID, newProject.address, apr, 0, (await communityContract.communities(newCommunityID)).publishNonce, sampleHash, ], }; const [encodedData2, signature2] = await multisig(data2, [ signers[2], signers[0], ]); await communityContract.publishProject(encodedData2, signature2); // toggle lendingNeeded await communityContract.toggleLendingNeeded( newCommunityID, newProject.address, (await newProject.projectCost()).toNumber(), ); const lendingPrincipal = 1020000000; const prjctLenderFee = (await newProject.lenderFee()).toNumber(); const fee = Number( ((lendingPrincipal * prjctLenderFee) / (prjctLenderFee + 1000)).toFixed( 0, ), ); const amountToProject = lendingPrincipal - fee; // setup dai mocks for lending await mockDAIContract.mock.transferFrom .withArgs(signers[2].address, treasury, fee) .returns(true); await mockDAIContract.mock.transferFrom .withArgs(signers[2].address, newProject.address, amountToProject) .returns(true); // lend to project await communityContract .connect(signers[2]) .lendToProject(newCommunityID, newProject.address, lendingPrincipal, sampleHash); const projectTokenId_ = await homeFiContract.connect(signers[0]).projectTokenId(newProject.address); // both project builder and nft owner are the same at this moment expect(await newProject.builder()).to.be.equals(signers[0].address); expect(await homeFiContract.connect(signers[0]).ownerOf(projectTokenId_)).to.be.equals(signers[0].address); // after nft transfer, project nft owner changes await homeFiContract.connect(signers[0]).transferFrom(signers[0].address, signers[3].address, projectTokenId_); expect(await newProject.builder()).to.be.equals(signers[0].address); expect(await homeFiContract.connect(signers[0]).ownerOf(projectTokenId_)).to.be.equals(signers[3].address); // new project nft owner cannot call functions, such as unpublishProject, that can only be called by project builder const tx1 = communityContract.connect(signers[3]).unpublishProject(newCommunityID, newProject.address); await expect(tx1).to.be.revertedWith('Community::!Builder'); // original project builder can still call functions, such as unpublishProject, that can only be called by project builder const tx2 = await communityContract.connect(signers[0]).unpublishProject(newCommunityID, newProject.address); await expect(tx2) .to.emit(communityContract, 'ProjectUnpublished') .withArgs(newCommunityID, newProject.address); });
VSCode
Similar to the DebtToken
contract, the HomeFi
contract can also be updated to make the project NFT non-transferrable.
#0 - horsefacts
2022-08-06T22:27:47Z
🌟 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
42.5475 USDC - $42.55
The initialize
function of the HomeFi
contract is called to set the supported tokens for the whole protocol. Because initialize
has the initializer
modifier and cannot be re-initialized, the currently supported tokens cannot be replaced with any new tokens, and new tokens cannot be added to the existing ones, even that the HomeFiProxy
contract includes the addNewContract
function for adding new proxy contracts. In the future, if there is a need for new tokens, the HomeFi
contract needs to be refactored so it can be re-initialized with possibly more than three supported tokens.
The replaceAdmin
, replaceTreasury
, replaceLenderFee
, and setTrustedForwarder
functions in the HomeFi
contract can be time-locked to give users enough time to react and prevent unexpectedness. It would also be helpful to have communication channels for announcing any upcoming admin actions.
To prevent unintended actions, unexpected loss of assets, etc., the critical address input should be checked against address(0)
. Please consider checking the addresses in the following initialize
function.
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Project.sol#L94-L105
function initialize( address _currency, address _sender, address _homeFiAddress ) external override initializer { // Initialize variables homeFi = IHomeFi(_homeFiAddress); disputes = homeFi.disputesContract(); lenderFee = homeFi.lenderFee(); builder = _sender; currency = IDebtToken(_currency); }
To improve readability and maintainability, constants can be used instead of magic numbers. Please consider replacing the magic number in the following code with a constant.
contracts\Community.sol 394: (_projectInstance.lenderFee() + 1000); 686: _communityProject.lastTimestamp) / 86400; // 24*60*60 694: 365000; ontracts\Project.sol 576: uint256 _maxLoop = 50;
_newTotalLent
is already an uint256
type so it does not need to be cast to uint256
again in the following function.
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Project.sol#L198-L202
uint256 _newTotalLent = totalLent + _cost; require( projectCost() >= uint256(_newTotalLent), "Project::value>required" );
The following createProject
function is not an view function but is in the view function section. It can be moved to other section for consistency and maintainability.
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/ProjectFactory.sol#L73-L91
/******************************************************************************* * ------------------------------EXTERNAL VIEWS------------------------------- * *******************************************************************************/ /// @inheritdoc IProjectFactory function createProject(address _currency, address _sender) external override returns (address _clone) { // Revert if sender is not HomeFi require(_msgSender() == homeFi, "PF::!HomeFiContract"); // Create clone of Project implementation _clone = ClonesUpgradeable.clone(underlying); // Initialize project Project(_clone).initialize(_currency, _sender, homeFi); }
🌟 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.7291 USDC - $21.73
When there is no need to make a reference to a state value, the memory
keyword can be used instead of the storage
keyword to save gas. Accessing storage using sload is more expensive than accessing memory using mload. The memory
keyword can be used for _communityProject
below.
contracts\Community.sol 679-698: // Local instance of variables. For saving gas. ProjectDetails storage _communityProject = _communities[_communityID] .projectDetails[_project]; uint256 _lentAmount = _communityProject.lentAmount; // Calculate number of days difference current and last timestamp uint256 _noOfDays = (block.timestamp - _communityProject.lastTimestamp) / 86400; // 24*60*60 /// Interest formula = (principal * APR * days) / (365 * 1000) // prettier-ignore uint256 _unclaimedInterest = _lentAmount * _communities[_communityID].projectDetails[_project].apr * _noOfDays / 365000; // Old (already rTokens claimed) + new interest uint256 _totalInterest = _unclaimedInterest + _communityProject.interest;
Explicitly initializing a variable with its default value costs more gas than uninitializing it. For example, uint256 i
can be used instead of uint256 i = 0
in the following code.
contracts\Community.sol 624: for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) { contracts\HomeFiProxy.sol 87: for (uint256 i = 0; i < _length; i++) { 136: for (uint256 i = 0; i < _length; i++) { contracts\Project.sol 248: for (uint256 i = 0; i < _length; i++) { 311: for (uint256 i = 0; i < _length; i++) { 322: for (uint256 i = 0; i < _length; i++) { contracts\libraries\Tasks.sol 181: for (uint256 i = 0; i < _length; i++) _alerts[i] = _self.alerts[i];
Caching the array length outside of the loop and using the cached length in the loop costs less gas than reading the array length for each iteration. For example, _changeOrderedTask.length
in the following code can be cached outside of the loop like uint256 _changeOrderedTaskLength = _changeOrderedTask.length
, and i < _changeOrderedTaskLength
can be used for each iteration.
contracts\Community.sol 624: for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) { contracts\Project.sol 603: for (; i < _changeOrderedTask.length; i++) {
++variable costs less gas than variable++. For example, i++
can be changed to ++i
in the following code.
contracts\Community.sol 624: for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) { contracts\HomeFiProxy.sol 87: for (uint256 i = 0; i < _length; i++) { 136: for (uint256 i = 0; i < _length; i++) { contracts\Project.sol 248: for (uint256 i = 0; i < _length; i++) { 311: for (uint256 i = 0; i < _length; i++) { 322: for (uint256 i = 0; i < _length; i++) { 368: for (uint256 _taskID = 1; _taskID <= _length; _taskID++) { 603: for (; i < _changeOrderedTask.length; i++) { 650: for (++j; j <= taskCount; j++) { 710: for (uint256 _taskID = 1; _taskID <= _length; _taskID++) { contracts\libraries\Tasks.sol 181: for (uint256 i = 0; i < _length; i++) _alerts[i] = _self.alerts[i];
x = x + y or x = x - y costs less gas than x += y or x -= y. For example, _interest -= _repayAmount
can be changed to _interest = _interest - _repayAmount
in the following code.
contracts\Community.sol 423: .totalLent += _amountToProject; 435: .lentAmount += _lendingAmount; 798: _interest -= _repayAmount; contracts\HomeFi.sol 289: projectCount += 1; contracts\Project.sol 179: hashChangeNonce += 1; 250: _taskCount += 1; 290: hashChangeNonce += 1; 431: totalAllocated -= _withdrawDifference; 440: totalAllocated += _newCost - _taskCost; 456: totalAllocated -= _taskCost; 616: _costToAllocate -= _taskCost; 663: _costToAllocate -= _taskCost; 711: _cost += tasks[_taskID].cost; 772: totalLent -= _amount; contracts\libraries\SignatureDecoder.sol 83: v += 27;
Explicitly unchecking arithmetic operations that do not overflow by wrapping these in unchecked {}
costs less gas than implicitly checking these.
For the following loops, if increasing the counter variable is very unlikely to overflow, then unchecked {++i}
at the end of the loop block can be used, where i
is the counter variable.
contracts\Community.sol 624: for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) { contracts\HomeFiProxy.sol 87: for (uint256 i = 0; i < _length; i++) { 136: for (uint256 i = 0; i < _length; i++) { contracts\Project.sol 248: for (uint256 i = 0; i < _length; i++) { 311: for (uint256 i = 0; i < _length; i++) { 322: for (uint256 i = 0; i < _length; i++) { 368: for (uint256 _taskID = 1; _taskID <= _length; _taskID++) { 603: for (; i < _changeOrderedTask.length; i++) { 650: for (++j; j <= taskCount; j++) { 710: for (uint256 _taskID = 1; _taskID <= _length; _taskID++) { contracts\libraries\Tasks.sol 181: for (uint256 i = 0; i < _length; i++) _alerts[i] = _self.alerts[i];
revert
with custom error can cost less gas than require()
or revert()
with reason string. Please consider using revert
with custom error to replace the following require()
and revert()
.
contracts\Community.sol 69: require(_address != address(0), "Community::0 address"); 75: require(_msgSender() == homeFi.admin(), "Community::!admin"); 81: require( 90: require( 131: require( 159: require( 191: require( 235: require( 241: require(homeFi.isProjectExist(_project), "Community::Project !Exists"); 248: require(_community.isMember[_builder], "Community::!Member"); 251: require( 312: require( 347: require( 353: require( 384: require( 400: require( 491: require( 536: require(_builder == _projectInstance.builder(), "Community::!Builder"); 539: require( 557: require(!restrictedToAdmin, "Community::restricted"); 568: require(restrictedToAdmin, "Community::!restricted"); 764: require(_repayAmount > 0, "Community::!repay"); 792: require(_lentAndInterest >= _repayAmount, "Community::!Liquid"); 886: require( contracts\DebtToken.sol 31: require( 50: require(_communityContract != address(0), "DebtToken::0 address"); 96: revert("DebtToken::blocked"); 104: revert("DebtToken::blocked"); contracts\Disputes.sol 39: require(_address != address(0), "Disputes::0 address"); 46: require(homeFi.admin() == _msgSender(), "Disputes::!Admin"); 52: require(homeFi.isProjectExist(_msgSender()), "Disputes::!Project"); 61: require( 106: require( 183: require(_result, "Disputes::!Member"); contracts\HomeFi.sol 73: require(admin == _msgSender(), "HomeFi::!Admin"); 78: require(_address != address(0), "HomeFi::0 address"); 84: require(_oldAddress != _newAddress, "HomeFi::!Change"); 142: require(!addrSet, "HomeFi::Set"); 191: require(lenderFee != _newLenderFee, "HomeFi::!Change"); 255: require( contracts\HomeFiProxy.sol 41: require(_address != address(0), "Proxy::0 address"); 81: require(_length == _implementations.length, "Proxy::Lengths !match"); 105: require( 133: require(_length == _contractAddresses.length, "Proxy::Lengths !match"); contracts\Project.sol 123: require(!contractorConfirmed, "Project::GC accepted"); 132: require(_projectAddress == address(this), "Project::!projectAddress"); 135: require(_contractor != address(0), "Project::0 address"); 150: require(_msgSender() == builder, "Project::!B"); 153: require(contractor != address(0), "Project::0 address"); 176: require(_nonce == hashChangeNonce, "Project::!Nonce"); 189: require( 195: require(_cost > 0, "Project::!value>0"); 199: require( 238: require(_taskCount == taskCount, "Project::!taskCount"); 241: require(_projectAddress == address(this), "Project::!projectAddress"); 245: require(_length == _taskCosts.length, "Project::Lengths !match"); 277: require(_nonce == hashChangeNonce, "Project::!Nonce"); 301: require( 308: require(_length == _scList.length, "Project::Lengths !match"); 341: require(_projectAddress == address(this), "Project::!Project"); 369: require(tasks[_taskID].getState() == 3, "Project::!Complete"); 406: require(_project == address(this), "Project::!projectAddress"); 511: require(_project == address(this), "Project::!projectAddress"); 515: require( 521: require( 530: require(getAlerts(_task)[2], "Project::!SCConfirmed"); 753: require(_sc != address(0), "Project::0 address"); 886: require( 906: require( contracts\ProjectFactory.sol 36: require(_address != address(0), "PF::0 address"); 64: require( 84: require(_msgSender() == homeFi, "PF::!HomeFiContract"); contracts\libraries\Tasks.sol 44: require(_self.state == TaskStatus.Inactive, "Task::active"); 50: require(_self.state == TaskStatus.Active, "Task::!Active"); 56: require( 124: require(_self.subcontractor == _sc, "Task::!SC");
The protocol can benefit from more gas-efficient features and fixes by using a newer version of Solidity. Changes for newer Solidity versions can be viewed here
.
contracts\Community.sol 3: pragma solidity 0.8.6; contracts\DebtToken.sol 3: pragma solidity 0.8.6; contracts\Disputes.sol 3: pragma solidity 0.8.6; contracts\HomeFi.sol 3: pragma solidity 0.8.6; contracts\HomeFiProxy.sol 3: pragma solidity 0.8.6; contracts\Project.sol 3: pragma solidity 0.8.6; contracts\ProjectFactory.sol 3: pragma solidity 0.8.6; contracts\interfaces\ICommunity.sol 3: pragma solidity 0.8.6; contracts\interfaces\IDebtToken.sol 3: pragma solidity 0.8.6; contracts\interfaces\IDisputes.sol 3: pragma solidity 0.8.6; contracts\interfaces\IHomeFi.sol 3: pragma solidity 0.8.6; contracts\interfaces\IProject.sol 3: pragma solidity 0.8.6; contracts\interfaces\IProjectFactory.sol 3: pragma solidity 0.8.6; contracts\libraries\SignatureDecoder.sol 3: pragma solidity 0.8.6; contracts\libraries\Tasks.sol 3: pragma solidity 0.8.6;