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: 55/133
Findings: 2
Award: $75.08
π Selected for report: 1
π Solo Findings: 0
π Selected for report: MiloTruck
Also found by: 0x52, 8olidity, Ruhum, __141345__, cccz, codexploder, cryptonue, hansfriese, sseefried
49.6901 USDC - $49.69
https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/Community.sol#L392-L394 https://github.com/code-423n4/2022-08-rigor/blob/main/contracts/HomeFi.sol#L184-L197
replaceLenderFee()
of HomeFi.sol
The admin of the HomeFi
contract can set lenderFee
to greater than 100%, forcing calls to lendToProject()
to all projects created in the future to revert.
Using the function replaceLenderFee()
, admins of the HomeFi
contract can set lenderFee
to any arbitrary uint256
value:
185: function replaceLenderFee(uint256 _newLenderFee) 186: external 187: override 188: onlyAdmin 189: { 190: // Revert if no change in lender fee 191: require(lenderFee != _newLenderFee, "HomeFi::!Change"); 192: 193: // Reset variables 194: lenderFee = _newLenderFee; 195: 196: emit LenderFeeReplaced(_newLenderFee); 197: }
New projects that are created will then get its lenderFee
from the HomeFi
contract. When communities wish to lend to these projects, it calls lendToProject()
, which has the following calculation:
392: // Calculate lenderFee 393: uint256 _lenderFee = (_lendingAmount * _projectInstance.lenderFee()) / 394: (_projectInstance.lenderFee() + 1000);
If lenderFee
a large value, such as type(uint256).max
, the calculation shown above to overflow. This prevents any community from lending to any new projects.
Consider adding a reasonable fee rate bounds checks in the replaceLenderFee()
function. This would prevent potential griefing and increase the trust of users in the contract.
π 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
25.3906 USDC - $25.39
++i
costs less gas compared to i++
or i += 1
!= 0
instead of > 0
for unsigned integers in require
statementsrequire
statements instead of &&
require()
checks should be refactored to a modifier or function<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesbool
for storage incurs overheadcalldata
instead of memory
for read-only arguments in external functionsuints
/ints
smaller than 32 bytes (256 bits) incurs overheadinternal
functions only called once can be inlined to save gasUninitialized uint
variables are assigned with a default value of 0
.
Thus, in for-loops, explicitly initializing an index with 0
costs unnecesary gas. For example, the following code:
for (uint256 i = 0; i < length; ++i) {
can be changed to:
for (uint256 i; i < length; ++i) {
Consider declaring the following lines without explicitly setting the index to 0
:
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];
Reading an array's length at each iteration has the following gas overheads:
mload
(3 gas)calldataload
(3 gas)Caching the length changes each of these to a DUP<N>
(3 gas), and gets rid of the extra DUP<N>
needed to store the stack offset. This would save around 3 gas per iteration.
For example:
for (uint256 i; i < arr.length; ++i) {}
can be changed to:
uint256 len = arr.length; for (uint256 i; i < len; ++i) {}
Consider making the following change to these lines:
contracts/Project.sol: 603: for (; i < _changeOrderedTask.length; i++) {
From Solidity v0.8 onwards, all arithmetic operations come with implicit overflow and underflow checks.
In for-loops, as it is impossible for the index to overflow, index increments can be left unchecked to save 30-40 gas per loop iteration.
For example, the code below:
for (uint256 i; i < numIterations; ++i) { // ... }
can be changed to:
for (uint256 i; i < numIterations;) { // ... unchecked { ++i; } }
Consider making the following change to these lines:
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];
++i
costs less gas compared to i++
or i += 1
++i
costs less gas compared to i++
or i += 1
for unsigned integers, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.
i++
increments i
and returns the initial value of i
. Which means:
uint i = 1; i++; // == 1 but i == 2
But ++i
returns the actual incremented value:
uint i = 1; ++i; // == 2 and i == 2 too, so no need for a temporary variable
In the first case, the compiler has to create a temporary variable (when used) for returning 1
instead of 2
, thus it costs more gas.
The same logic applies for --i
and i--
.
Consider using ++i
instead of i++
or i += 1
in the following instances:
contracts/Community.sol: 140: communityCount++; 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: 179: hashChangeNonce += 1; 248: for (uint256 i = 0; i < _length; i++) { 250: _taskCount += 1; 290: hashChangeNonce += 1; 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++) { 625: _loopCount++; 650: for (++j; j <= taskCount; j++) { 672: _loopCount++; 710: for (uint256 _taskID = 1; _taskID <= _length; _taskID++) { contracts/HomeFi.sol: 289: projectCount += 1; contracts/Disputes.sol: 121: emit DisputeRaised(disputeCount++, _reason); contracts/libraries/Tasks.sol: 181: for (uint256 i = 0; i < _length; i++) _alerts[i] = _self.alerts[i];
!= 0
instead of > 0
for unsigned integers in require
statementsuint
will never go below 0. Thus, checking if != 0
rather than > 0
is sufficient, which would save 6 gas per instance.
Consider changing > 0
to != 0
in these lines:
contracts/Community.sol: 764: require(_repayAmount > 0, "Community::!repay"); contracts/Project.sol: 195: require(_cost > 0, "Project::!value>0"); contracts/Disputes.sol: 106: require( 107: _actionType > 0 && _actionType <= uint8(ActionType.TaskPay), 108: "Disputes::!ActionType" 109: );
require
statements instead of &&
Instead of using a single require
statement with the &&
operator, use multiple require
statements. With reference to this issue, this helps to reduce runtime gas cost at the expense of a larger deployment gas cost, which becomes cheaper with enough runtime calls.
A require
statement can be split as such:
// Original code: require(a && b, 'error'); // Changed to: require(a, 'error: a'); require(b, 'error: b');
Instances where multiple require
statements should be used:
contracts/Community.sol: 353: require( 354: _lendingNeeded >= _communityProject.totalLent && 355: _lendingNeeded <= IProject(_project).projectCost(), 356: "Community::invalid lending" 357: ); contracts/Disputes.sol: 61: require( 62: _disputeID < disputeCount && 63: disputes[_disputeID].status == Status.Active, 64: "Disputes::!Resolvable" 65: ); 106: require( 107: _actionType > 0 && _actionType <= uint8(ActionType.TaskPay), 108: "Disputes::!ActionType" 109: );
require()
checks should be refactored to a modifier or functionIf a require()
statement that is used to validate function parameters or global variables is present across multiple functions in a contract, it should be rewritten into modifier if possible. This would help to reduce code deployment size, which saves gas, and improve code readability.
The following require()
statements are repeated multiple times:
contracts/Project.sol: 132: require(_projectAddress == address(this), "Project::!projectAddress"); 241: require(_projectAddress == address(this), "Project::!projectAddress"); 406: require(_project == address(this), "Project::!projectAddress"); 511: require(_project == address(this), "Project::!projectAddress");
contracts/Project.sol: 176: require(_nonce == hashChangeNonce, "Project::!Nonce"); 277: require(_nonce == hashChangeNonce, "Project::!Nonce");
Since Solidity v0.8.4, custom errors can be used rather than require
statements.
Taken from Custom Errors in Solidity:
Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g.,
revert("Insufficient funds.");
), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors reduce runtime gas costs as they save about 50 gas each time they are hit by avoiding having to allocate and store the revert string. Furthermore, not definiting error strings also help to reduce deployment gas costs.
Instances where custom errors can be used instead:
contracts/Community.sol: 69: require(_address != address(0), "Community::0 address"); 75: require(_msgSender() == homeFi.admin(), "Community::!admin"); 81: require( 82: projectPublished[_project] == _communityID, 83: "Community::!published" 84: ); 90: require( 91: _msgSender() == IProject(_project).builder(), 92: "Community::!Builder" 93: ); 131: require( 132: !restrictedToAdmin || _sender == homeFi.admin(), 133: "Community::!admin" 134: ); 159: require( 160: _communities[_communityID].owner == _msgSender(), 161: "Community::!owner" 162: ); 191: require( 192: !_community.isMember[_newMemberAddr], 193: "Community::Member Exists" 194: ); 235: require( 236: _publishNonce == _community.publishNonce, 237: "Community::invalid publishNonce" 238: ); 241: require(homeFi.isProjectExist(_project), "Community::Project !Exists"); 248: require(_community.isMember[_builder], "Community::!Member"); 251: require( 252: _projectInstance.currency() == _community.currency, 253: "Community::!Currency" 254: ); 312: require( 313: !_communityProject.publishFeePaid, 314: "Community::publish fee paid" 315: ); 347: require( 348: _communityProject.publishFeePaid, 349: "Community::publish fee !paid" 350: ); 353: require( 354: _lendingNeeded >= _communityProject.totalLent && 355: _lendingNeeded <= IProject(_project).projectCost(), 356: "Community::invalid lending" 357: ); 384: require( 385: _sender == _communities[_communityID].owner, 386: "Community::!owner" 387: ); 400: require( 401: _amountToProject <= 402: _communities[_communityID] 403: .projectDetails[_project] 404: .lendingNeeded - 405: _communities[_communityID] 406: .projectDetails[_project] 407: .totalLent, 408: "Community::lending>needed" 409: ); 491: require( 492: _msgSender() == _communities[_communityID].owner, 493: "Community::!Owner" 494: ); 536: require(_builder == _projectInstance.builder(), "Community::!Builder"); 539: require( 540: _lender == _communities[_communityID].owner, 541: "Community::!Owner" 542: ); 557: require(!restrictedToAdmin, "Community::restricted"); 568: require(restrictedToAdmin, "Community::!restricted"); 764: require(_repayAmount > 0, "Community::!repay"); 792: require(_lentAndInterest >= _repayAmount, "Community::!Liquid"); 886: require( 887: _recoveredSignature == _address || approvedHashes[_address][_hash], 888: "Community::invalid signature" 889: ); contracts/HomeFiProxy.sol: 41: require(_address != address(0), "Proxy::0 address"); 81: require(_length == _implementations.length, "Proxy::Lengths !match"); 105: require( 106: contractAddress[_contractName] == address(0), 107: "Proxy::Name !OK" 108: ); 133: require(_length == _contractAddresses.length, "Proxy::Lengths !match"); contracts/DebtToken.sol: 31: require( 32: communityContract == _msgSender(), 33: "DebtToken::!CommunityContract" 34: ); 50: require(_communityContract != address(0), "DebtToken::0 address"); 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( 190: _sender == builder || _sender == homeFi.communityContract(), 191: "Project::!Builder&&!Community" 192: ); 195: require(_cost > 0, "Project::!value>0"); 199: require( 200: projectCost() >= uint256(_newTotalLent), 201: "Project::value>required" 202: ); 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( 302: _msgSender() == builder || _msgSender() == contractor, 303: "Project::!Builder||!GC" 304: ); 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( 516: signer == builder || signer == contractor, 517: "Project::!(GC||Builder)" 518: ); 521: require( 522: signer == builder || 523: signer == contractor || 524: signer == tasks[_task].subcontractor, 525: "Project::!(GC||Builder||SC)" 526: ); 530: require(getAlerts(_task)[2], "Project::!SCConfirmed"); 753: require(_sc != address(0), "Project::0 address"); 886: require( 887: _recoveredSignature == _address || approvedHashes[_address][_hash], 888: "Project::invalid signature" 889: ); 906: require( 907: ((_amount / 1000) * 1000) == _amount, 908: "Project::Precision>=1000" 909: ); 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( 256: _currency == tokenCurrency1 || 257: _currency == tokenCurrency2 || 258: _currency == tokenCurrency3, 259: "HomeFi::!Currency" 260: ); contracts/ProjectFactory.sol: 36: require(_address != address(0), "PF::0 address"); 64: require( 65: _msgSender() == IHomeFi(homeFi).admin(), 66: "ProjectFactory::!Owner" 67: ); 84: require(_msgSender() == homeFi, "PF::!HomeFiContract"); 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( 62: _disputeID < disputeCount && 63: disputes[_disputeID].status == Status.Active, 64: "Disputes::!Resolvable" 65: ); 106: require( 107: _actionType > 0 && _actionType <= uint8(ActionType.TaskPay), 108: "Disputes::!ActionType" 109: ); 183: require(_result, "Disputes::!Member"); contracts/libraries/Tasks.sol: 44: require(_self.state == TaskStatus.Inactive, "Task::active"); 50: require(_self.state == TaskStatus.Active, "Task::!Active"); 56: require( 57: _self.alerts[uint256(Lifecycle.TaskAllocated)], 58: "Task::!funded" 59: ); 124: require(_self.subcontractor == _sc, "Task::!SC");
Uninitialized variables are assigned with a default value depending on its type:
uint
: 0
bool
: false
address
: address(0)
Thus, explicitly initializing a variable with its default value costs unnecesary gas. For example, the following code:
bool b = false; address c = address(0); uint256 a = 0;
can be changed to:
uint256 a; bool b; address c;
Consider declaring the following lines without explicitly setting a value:
contracts/Project.sol: 412: bool _unapproved = false;
If a state variable is read from storage multiple times in a function, it should be cached in a stack variable.
Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
communityCount
in createCommunity()
:
contracts/Community.sol: 143: CommunityStruct storage _community = _communities[communityCount]; 150: emit CommunityAdded(communityCount, _sender, _currency, _hash);
builder
in lendToProject()
:
contracts/Project.sol: 190: _sender == builder || _sender == homeFi.communityContract(), 204: if (_sender == builder) {
totalLent
in allocateFunds()
:
contracts/Project.sol: 579: uint256 _costToAllocate = totalLent - totalAllocated; 697: totalAllocated = totalLent - _costToAllocate;
taskCount
in allocateFunds()
:
contracts/Project.sol: 592: taskCount - j + _changeOrderedTask.length - i 648: if (j < taskCount) { 650: for (++j; j <= taskCount; j++) { // @audit This reads taskCount per iteration 681: if (j > taskCount) { 682: lastAllocatedTask = taskCount;
contractor
in checkSignature()
:
contracts/Project.sol: 798: if (contractor == address(0)) { 807: checkSignatureValidity(contractor, _hash, _signature, 0); 813: checkSignatureValidity(contractor, _hash, _signature, 1);
contractor
in checkSignatureTask()
:
contracts/Project.sol: 842: if (contractor == address(0)) { 852: checkSignatureValidity(contractor, _hash, _signature, 0); 859: checkSignatureValidity(contractor, _hash, _signature, 1);
projectCount
in createProject()
:
contracts/HomeFi.sol: 228: projects[projectCount] = _project; 229: projectTokenId[_project] = projectCount; 231: emit ProjectAdded(projectCount, _project, _sender, _currency, _hash);
projectCount
in mintNFT()
:
contracts/HomeFi.sol: 292: _mint(_to, projectCount); 293: _setTokenURI(projectCount, _tokenURI); 295: emit NftCreated(projectCount, _to); 296: return projectCount;
homeFi
in createProject()
:
contracts/ProjectFactory.sol: 84: require(_msgSender() == homeFi, "PF::!HomeFiContract"); 90: Project(_clone).initialize(_currency, _sender, homeFi);
disputeCount
in raiseDispute()
:
contracts/Disputes.sol: 112: Dispute storage _dispute = disputes[disputeCount]; 121: emit DisputeRaised(disputeCount++, _reason);
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesThe above also applies to state variables that use -=
.
contracts/Project.sol: 431: totalAllocated -= _withdrawDifference; 440: totalAllocated += _newCost - _taskCost; 456: totalAllocated -= _taskCost; 772: totalLent -= _amount;
bool
for storage incurs overheadDeclaring storage variables as bool
is more expensive compared to uint256
, as explained here:
Booleans are more expensive than
uint256
or any type that takes up a full word because each write operation emits an extraSLOAD
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.
Use uint256(1)
and uint256(2)
rather than true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD
, and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past.
Consider redefining the following state variables to use uint256
instead of bool
:
contracts/Community.sol: 55: bool public override restrictedToAdmin; 61: mapping(address => mapping(bytes32 => bool)) public override approvedHashes; contracts/HomeFiProxy.sol: 30: mapping(address => bool) internal contractsActive; contracts/Project.sol: 68: bool public override contractorConfirmed; 78: bool public override contractorDelegated; 84: mapping(address => mapping(bytes32 => bool)) public override approvedHashes; contracts/HomeFi.sol: 50: bool public override addrSet;
If the variable is only accessed once, it is cheaper to use the state variable directly.
contracts/Project.sol: 420: uint256 _totalAllocated = totalAllocated;
calldata
instead of memory
for read-only arguments in external functionsWhen an external function with a memory
array is called, the abi.decode()
step has to use a for-loop to copy each index of the calldata
to the memory
index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length
).
Using calldata directly helps to save gas as values are read directly from calldata
using calldataload
, thus removing the need for such a loop in the contract code during runtime execution.
Also, structs have the same overhead as an array of length one.
Consider changing the following from memory
to calldata
:
contracts/Community.sol: 488: bytes memory _details contracts/DebtToken.sol: 45: string memory name_, 46: string memory symbol_, contracts/HomeFi.sol: 210: function createProject(bytes memory _hash, address _currency)
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadAs seen from here:
When using elements that are smaller than 32 bytes, your contractβs gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
However, this does not apply to storage values as using reduced-size types might be beneficial to pack multiple elements into a single storage slot. Thus, where appropriate, use uint256
/int256
and downcast when needed.
Consider using uint256
/int256
instead of bool
for the following:
contracts/DebtToken.sol: 47: uint8 decimals_ 82: function decimals() public view virtual override returns (uint8) { contracts/Disputes.sol: 100: uint8 _actionType, contracts/libraries/SignatureDecoder.sol: 29: uint8 v; 65: uint8 v,
contracts/Community.sol: 906: return super._msgSender(); contracts/Project.sol: 722: return tasks[_taskID].getAlerts(); contracts/HomeFi.sol: 310: return super._msgSender(); contracts/libraries/Tasks.sol: 196: return uint256(_self.state);
internal
functions only called once can be inlined to save gasNot inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
Consider inlining the following internal
functions:
contracts/HomeFiProxy.sol: 197: function _replaceImplementation( 198: bytes2 _contractName, 199: address _contractAddress 200: ) internal nonZero(_contractAddress) { contracts/Project.sol: 770: function autoWithdraw(uint256 _amount) internal { contracts/HomeFi.sol: 284: function mintNFT(address _to, string memory _tokenURI) 285: internal 286: returns (uint256) 287: { contracts/Disputes.sol: 207: function resolveHandler(uint256 _disputeID) internal { 236: function executeTaskAdd(address _project, bytes memory _actionData) 237: internal 238: { 254: function executeTaskChange(address _project, bytes memory _actionData) 255: internal 256: { 268: function executeTaskPay(address _project, bytes memory _actionData) 269: internal 270: {
If a mapping/array is read with the same key/index multiple times in a function, it should be cached in a stack variable.
Caching a mapping's value in a local storage
variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory.
_communities[_communityID]
in lendToProject()
:
contracts/Community.sol: 385: _sender == _communities[_communityID].owner, 401: _amountToProject <= 402: _communities[_communityID] 401: _amountToProject <= 402: _communities[_communityID] 403: .projectDetails[_project] 404: .lendingNeeded - 405: _communities[_communityID] 412: IDebtToken _currency = _communities[_communityID].currency; 427: _communities[_communityID].projectDetails[_project].lentAmount > 0
_communities[_communityID]
in repayLender()
:
contracts/Community.sol: 471: address _lender = _communities[_communityID].owner; 474: _communities[_communityID].currency.safeTransferFrom(
_communities[_communityID]
in members()
:
contracts/Community.sol: 620: _communities[_communityID].memberCount 624: for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) { 625: _members[i] = _communities[_communityID].members[i];
_communities[_communityID]
in returnToLender()
:
contracts/Community.sol: 680: ProjectDetails storage _communityProject = _communities[_communityID] 691: _lentAmount * 692: _communities[_communityID].projectDetails[_project].apr *
_communities[_communityID]
in claimInterest()
:
contracts/Community.sol: 836: address _lender = _communities[_communityID].owner; 837: ProjectDetails storage _communityProject = _communities[_communityID]
tasks[_taskID]
in setComplete()
:
contracts/Project.sol: 350: tasks[_taskID].setComplete(); 354: tasks[_taskID].subcontractor, 355: tasks[_taskID].cost
tasks[_taskID]
in changeOrder()
:
contracts/Project.sol: 409: uint256 _taskCost = tasks[_taskID].cost; 423: if (tasks[_taskID].alerts[1]) { 449: tasks[_taskID].unApprove(); 452: tasks[_taskID].unAllocateFunds(); 470: if (_newSC != tasks[_taskID].subcontractor) { 474: tasks[_taskID].unApprove();
tasks[_task]
in raiseDispute()
:
contracts/Project.sol: 522: signer == builder || 523: signer == contractor || 524: signer == tasks[_task].subcontractor, 528: if (signer == tasks[_task].subcontractor) {
tasks[id]
in getTask()
:
contracts/Project.sol: 553: cost = tasks[id].cost; 554: subcontractor = tasks[id].subcontractor; 555: state = tasks[id].state;
_changeOrderedTask[i]
in allocateFunds()
:
contracts/Project.sol: 605: uint256 _taskCost = tasks[_changeOrderedTask[i]].cost; 619: tasks[_changeOrderedTask[i]].fundTask(); 622: _tasksAllocated[_loopCount] = _changeOrderedTask[i];
tasks[_changeOrderedTask[i]]
in allocateFunds()
:
contracts/Project.sol: 605: uint256 _taskCost = tasks[_changeOrderedTask[i]].cost; 619: tasks[_changeOrderedTask[i]].fundTask();
tasks[j]
in allocateFunds()
:
contracts/Project.sol: 652: uint256 _taskCost = tasks[j].cost; 666: tasks[j].fundTask();