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: 62/133
Findings: 2
Award: $62.91
🌟 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
40.8799 USDC - $40.88
Low Risk Issues
Issue | Instances | |
---|---|---|
1 | Missing modifier specified in comment | 1 |
2 | SignatureDecoder.recoverKey and ecrecover does not check for zero address | 1 |
3 | Incomplite action type control | 1 |
Non-critical Issues
Issue | Instances | |
---|---|---|
1 | Use struct in mapping instead of casting struct to uint256 | 1 |
2 | Remove tautological code | 1 |
3 | Use built-in constants instead of a number | 1 |
4 | Misleading comment | 1 |
5 | redundant cast uint256 to uint256 | 1 |
6 | Missing initializer modifier on constructor | 6 |
Total: instances over issues
Low Risk Issues
Missing modifier specified in comment
The function does not check the status of the task. You can set an inactive status and unapprove a subcontractor for a task that is already inactive or even completed.
156 * @dev modifier onlyActive ... 160 function unApprove(Task storage _self) internal {
fix:
function unApprove(Task storage _self) internal onlyActive(_self) {
SignatureDecoder.recoverKey and ecrecover does not check for zero address The solidity ecrecover function is called directly to verify the given signatures. However, the ecrecover EVM opcode allows malleable (non-unique) signatures and thus is susceptible to replay attacks. A replay attack may not be possible here, but ensuring the signatures are not malleable is considered a best practice.
90 address _signer = SignatureDecoder.recoverKey( 91 keccak256(_data), 92 _signature, 93 0 94 ); ... 116 _dispute.raisedBy = _signer;
fix:
if (_signer==0) revert...;
Incomplite action type control.
resolveHandler calls executeTaskPay if the action type is neither ActionType.TaskAdd nor ActionType.TaskChange. But apart from ActionType.TaskPay there is a possibility that the action type can be set to ActionType.None.
At least it is more readable. But it can be used(theoretically) to call executeTaskPay, which will release subcontractor payment.
200 /** 201 * @notice Given an id, attempt to execute the action to enforce the arbitration 202 203 * @notice logic for decoding and enforcing outcome of arbitration judgement 204 205 * @param _disputeID uint256 - the dispute to attempt to 206 */ 207 function resolveHandler(uint256 _disputeID) internal { 208 // Local instance of variable. For saving gas. 209 Dispute storage dispute = disputes[_disputeID]; 210 211 // If action type is add task then execute add task 212 if (dispute.actionType == ActionType.TaskAdd) { 213 executeTaskAdd(dispute.project, dispute.actionData); 214 } 215 // If action type is task change then execute task change 216 else if (dispute.actionType == ActionType.TaskChange) { 217 executeTaskChange(dispute.project, dispute.actionData); 218 } 219 // Else execute task pay 220 else { 221 executeTaskPay(dispute.project, dispute.actionData); 222 } 223 }
fix:
220 else if (dispute.actionType == ActionType.TaskPay) { 221 executeTaskPay(dispute.project, dispute.actionData); 222 } else {revert InvalidTask();}
Non-critical Issues
Use struct in mapping instead of casting struct to uint256
Optional fix, looks more informative, but slightly increases deployment gas due to enum import in the contract.
16 mapping(uint256 => bool) alerts; // Alerts of task //@audit bool usage //@audit use struct instead of casting to uint256 ... 57 _self.alerts[uint256(Lifecycle.TaskAllocated)],
fix:
mapping(Lifecycle => bool) alerts;
Remove tautological code.
266 _community.publishNonce = ++_community.publishNonce;
fix:
++_community.publishNonce;
Use built-in constants instead of a number
685 uint256 _noOfDays = (block.timestamp - 686 _communityProject.lastTimestamp) / 86400; // 24*60*60
fix:
685 uint256 _noOfDays = (block.timestamp - 686 _communityProject.lastTimestamp) / 1 days; // 24*60*60
Misleading comment
Comment probably copied from a nearby function and is misleading about the function's purpose.
226 * @dev Arbitration enforcement of task change orders ... 236 function executeTaskAdd(address _project, bytes memory _actionData) 237 internal 238 { 239 IProject(_project).addTasks(_actionData, bytes("")); 240 }
redundant cast uint256 to uint256.
198 uint256 _newTotalLent = totalLent + _cost; ... 200 projectCost() >= uint256(_newTotalLent),
Missing initializer modifier on constructor OpenZeppelin recommends that the initializer modifier be applied to constructors: The guidelines are now to make it impossible for anyone to run initialize on an implementation contract, by adding an empty constructor with the initializer modifier. So the implementation contract gets initialized automatically upon deployment.
fix: add constructor() initializer {}
🌟 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
Gas savings are estimated using existing tests and may differ depending on the implementation of the fix. I keep my version of the fix for each finding and can provide them if you need.
Gas Optimizations
Issue | Instances | Estimated gas(deployments) | |
---|---|---|---|
1 | Use Custom Errors instead of Revert/Require Strings to save Gas | 76 | 582064 |
2 | Increment optimization | 32 | 5388 / 23781 / 56517 |
3 | Using bools for storage incurs overhead | 7 | 50164 |
4 | Call to an external contract should be cached. | 1 | 23972 |
5 | The same modifier can be combined into a library | 5 | 8674 |
6 | Loop length can be cached to save gas | 2 | 4113 |
7 | The variable is cached and used only once. | 2 | 1738 |
8 | It is more gas efficient to compare uint variables with != 0 than it is with > 0 | 10 | 1533 |
9 | Splitting require() statements that use && saves gas | 3 | - |
Total: 138 instances over 9 issues
Use Custom Errors instead of Revert/Require Strings to save Gas (76 instances)
Deployment gas saved: 582064
Solidity 0.8.4 introduced custom errors. They are more gas efficient than revert strings, when it comes to deploy cost as well as runtime cost when the revert condition is met. Use custom errors instead of revert strings for gas savings.
Custom errors save ~50 gas each time they're hitby avoiding having to allocate and store the revert string. Additional gas is saved due to the lack of defining string. https://blog.soliditylang.org/2021/04/21/custom-errors/#errors-in-depth
31 require( 32 communityContract == _msgSender(), 33 "DebtToken::!CommunityContract" 34 ); //@audit replace with revert error ... 50 require(_communityContract != address(0), "DebtToken::0 address"); //@audit replace with revert error ... 96 revert("DebtToken::blocked"); //@audit use custom error ... 104 revert("DebtToken::blocked"); //@audit use custom error
36 require(_address != address(0), "PF::0 address"); //@audit replace with revert error ... 64 require( 65 _msgSender() == IHomeFi(homeFi).admin(), 66 "ProjectFactory::!Owner" 67 ); //@audit replace with revert error ... 84 require(_msgSender() == homeFi, "PF::!HomeFiContract"); //@audit replace with revert error
41 require(_address != address(0), "Proxy::0 address"); //@audit replace with revert error ... 81 require(_length == _implementations.length, "Proxy::Lengths !match"); //@audit replace with revert error ... 105 require( 106 contractAddress[_contractName] == address(0), 107 "Proxy::Name !OK" 108 ); //@audit replace with revert error ... 133 require(_length == _contractAddresses.length, "Proxy::Lengths !match"); //@audit replace with revert error
39 require(_address != address(0), "Disputes::0 address"); //@audit replace with revert error ... 46 require(homeFi.admin() == _msgSender(), "Disputes::!Admin"); //@audit replace with revert error ... 52 require(homeFi.isProjectExist(_msgSender()), "Disputes::!Project"); //@audit replace with revert error ... 61 require( 62 _disputeID < disputeCount && 63 disputes[_disputeID].status == Status.Active, 64 "Disputes::!Resolvable" 65 ); //@audit replace with revert error ... 106 require( 107 _actionType > 0 && _actionType <= uint8(ActionType.TaskPay), 108 "Disputes::!ActionType" 109 ); //@audit replace with revert error ... 183 require(_result, "Disputes::!Member"); //@audit replace with revert error
73 require(admin == _msgSender(), "HomeFi::!Admin"); //@audit replace with revert error ... 78 require(_address != address(0), "HomeFi::0 address"); //@audit replace with revert error ... 84 require(_oldAddress != _newAddress, "HomeFi::!Change"); //@audit replace with revert error ... 142 require(!addrSet, "HomeFi::Set"); //@audit replace with revert error ... 191 require(lenderFee != _newLenderFee, "HomeFi::!Change"); //@audit replace with revert error ... 255 require( 256 _currency == tokenCurrency1 || 257 _currency == tokenCurrency2 || 258 _currency == tokenCurrency3, 259 "HomeFi::!Currency" 260 ); //@audit replace with revert error
123 require(!contractorConfirmed, "Project::GC accepted"); //@audit replace with revert error ... 132 require(_projectAddress == address(this), "Project::!projectAddress"); //@audit replace with revert error ... 135 require(_contractor != address(0), "Project::0 address"); //@audit replace with revert error ... 150 require(_msgSender() == builder, "Project::!B"); //@audit replace with revert error ... 153 require(contractor != address(0), "Project::0 address"); //@audit replace with revert error ... 176 require(_nonce == hashChangeNonce, "Project::!Nonce"); //@audit replace with revert error ... 189 require( 190 _sender == builder || _sender == homeFi.communityContract(), 191 "Project::!Builder&&!Community" 192 ); //@audit replace with revert error @audit logical improvment for separate ... 195 require(_cost > 0, "Project::!value>0"); //@audit replace with revert error @audit rewrite with != ... 199 require( 200 projectCost() >= uint256(_newTotalLent), 201 "Project::value>required" 202 ); //@audit replace with revert error ... 238 require(_taskCount == taskCount, "Project::!taskCount"); //@audit replace with revert error ... 241 require(_projectAddress == address(this), "Project::!projectAddress"); //@audit replace with revert errorwith != ... 245 require(_length == _taskCosts.length, "Project::Lengths !match"); //@audit replace with revert error ... 277 require(_nonce == hashChangeNonce, "Project::!Nonce"); //@audit replace with revert error ... 301 require( 302 _msgSender() == builder || _msgSender() == contractor, 303 "Project::!Builder||!GC" 304 ); //@audit replace with revert error @audit logical improvment for separate ... 308 require(_length == _scList.length, "Project::Lengths !match"); //@audit replace with revert error ... 341 require(_projectAddress == address(this), "Project::!Project"); //@audit replace with revert error ... 369 require(tasks[_taskID].getState() == 3, "Project::!Complete"); //@audit replace with revert error ... 406 require(_project == address(this), "Project::!projectAddress"); //@audit replace with revert error ... 511 require(_project == address(this), "Project::!projectAddress"); //@audit replace with revert error ... 515 require( 516 signer == builder || signer == contractor, 517 "Project::!(GC||Builder)" 518 ); //@audit replace with revert error @audit try to optimize ... 521 require( 522 signer == builder || 523 signer == contractor || 524 signer == tasks[_task].subcontractor, 525 "Project::!(GC||Builder||SC)" 526 ); //@audit replace with revert error //@audit try to optimize ... 530 require(getAlerts(_task)[2], "Project::!SCConfirmed"); //@audit replace with revert error ... 753 require(_sc != address(0), "Project::0 address"); //@audit replace with revert error ... 886 require( 887 _recoveredSignature == _address || approvedHashes[_address][_hash], 888 "Project::invalid signature" 889 ); //@audit replace with revert error @audit separate ... 906 require( 907 ((_amount / 1000) * 1000) == _amount, 908 "Project::Precision>=1000" 909 ); //@audit replace with revert error
69 require(_address != address(0), "Community::0 address"); //@audit replace with revert error ... 75 require(_msgSender() == homeFi.admin(), "Community::!admin"); //@audit replace with revert error ... 81 require( 82 projectPublished[_project] == _communityID, 83 "Community::!published" 84 ); //@audit replace with revert error ... 90 require( 91 _msgSender() == IProject(_project).builder(), 92 "Community::!Builder" 93 ); //@audit replace with revert error ... 131 require( 132 !restrictedToAdmin || _sender == homeFi.admin(), 133 "Community::!admin" 134 ); //@audit replace with revert error ... 159 require( 160 _communities[_communityID].owner == _msgSender(), 161 "Community::!owner" 162 ); //@audit replace with revert error ... 191 require( 192 !_community.isMember[_newMemberAddr], 193 "Community::Member Exists" 194 ); //@audit replace with revert error ... 235 require( 236 _publishNonce == _community.publishNonce, 237 "Community::invalid publishNonce" 238 ); //@audit replace with revert error ... 241 require(homeFi.isProjectExist(_project), "Community::Project !Exists"); //@audit replace with revert error ... 248 require(_community.isMember[_builder], "Community::!Member"); //@audit replace with revert error ... 251 require( 252 _projectInstance.currency() == _community.currency, 253 "Community::!Currency" 254 ); //@audit replace with revert error ... 312 require( 313 !_communityProject.publishFeePaid, 314 "Community::publish fee paid" 315 ); //@audit replace with revert error ... 347 require( 348 _communityProject.publishFeePaid, 349 "Community::publish fee !paid" 350 ); //@audit replace with revert error ... 353 require( 354 _lendingNeeded >= _communityProject.totalLent && 355 _lendingNeeded <= IProject(_project).projectCost(), 356 "Community::invalid lending" 357 ); //@audit replace with revert error //@audit separate ... 384 require( 385 _sender == _communities[_communityID].owner, 386 "Community::!owner" 387 ); //@audit replace with revert error ... 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 ); //@audit replace with revert error ... 491 require( 492 _msgSender() == _communities[_communityID].owner, 493 "Community::!Owner" 494 ); //@audit replace with revert error ... 536 require(_builder == _projectInstance.builder(), "Community::!Builder"); //@audit replace with revert error ... 539 require( 540 _lender == _communities[_communityID].owner, 541 "Community::!Owner" 542 ); //@audit replace with revert error ... 557 require(!restrictedToAdmin, "Community::restricted"); //@audit replace with revert error ... 568 require(restrictedToAdmin, "Community::!restricted"); //@audit replace with revert error ... 764 require(_repayAmount > 0, "Community::!repay"); //@audit replace with revert error ... 792 require(_lentAndInterest >= _repayAmount, "Community::!Liquid"); //@audit replace with revert error ... 886 require( 887 _recoveredSignature == _address || approvedHashes[_address][_hash], 888 "Community::invalid signature" 889 ); //@audit replace with revert error
44 require(_self.state == TaskStatus.Inactive, "Task::active"); //@audit replace with revert error ... 50 require(_self.state == TaskStatus.Active, "Task::!Active"); //@audit replace with revert error ... 56 require( 57 _self.alerts[uint256(Lifecycle.TaskAllocated)], 58 "Task::!funded" 59 ); //@audit replace with revert error ... 124 require(_self.subcontractor == _sc, "Task::!SC"); //@audit replace with revert error
Increment optimization.
289 projectCount += 1; //@audit use pc = pc + 1;
87 for (uint256 i = 0; i < _length; i++) {//@audit increment and inicialize ... 136 for (uint256 i = 0; i < _length; i++) {//@audit increment and inicialize
179 hashChangeNonce += 1; //@audit revwrite hcn = hcn + 1; ... 248 for (uint256 i = 0; i < _length; i++) {//@audit increment and inicialize ... 250 _taskCount += 1; //@audit increment fix ... 290 hashChangeNonce += 1; // @audit fix increment ... 311 for (uint256 i = 0; i < _length; i++) {//@audit increment and inicialize ... 322 for (uint256 i = 0; i < _length; i++) {//@audit increment and inicialize ... 368 for (uint256 _taskID = 1; _taskID <= _length; _taskID++) {//@audit increment ... 431 totalAllocated -= _withdrawDifference; //@audit don't use short form ... 440 totalAllocated += _newCost - _taskCost; //@audit don't use short form ... 456 totalAllocated -= _taskCost; //@audit don't use short form ... 603 for (; i < _changeOrderedTask.length; i++) {//@audit increment ... 616 _costToAllocate -= _taskCost; ... 625 _loopCount++; //@audit increment ... 650 for (++j; j <= taskCount; j++) {//@audit increment ... 663 _costToAllocate -= _taskCost; //@audit increment ... 672 _loopCount++; //@audit increment ... 686 lastAllocatedTask = --j; //@audit increment ... 710 for (uint256 _taskID = 1; _taskID <= _length; _taskID++) {//@audit increment 711 _cost += tasks[_taskID].cost; //@audit increment ... 772 totalLent -= _amount; //@audit increment
140 communityCount++; //@audit increment ... 266 _community.publishNonce = ++_community.publishNonce; //@audit increment ... 423 .totalLent += _amountToProject; ... 435 .lentAmount += _lendingAmount; ... 624 for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) {//@audit increment and inicialize ... 798 _interest -= _repayAmount; //@audit increment
181 for (uint256 i = 0; i < _length; i++) _alerts[i] = _self.alerts[i]; //@audit increment and inicialize
83 v += 27;
121 emit DisputeRaised(disputeCount++, _reason);
Using bools for storage incurs overhead
Deployment gas saved: 50164
Important: this optimization affects the use of gas to call the method differently. Function call can become either cheaper or more expensive. Should be tested manually and analyzed according to which functions you consider more important.
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. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27
Use uint256 for 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
50 bool public override addrSet; //@audit bool usage//7363 deploy gas
30 mapping(address => bool) internal contractsActive; //@audit bool usage//2995 deploy gas
68 bool public override contractorConfirmed; //@audit bool usage//4785 deploy gas ... 78 bool public override contractorDelegated; //@audit bool usage//10780 deploy gas ... 84 mapping(address => mapping(bytes32 => bool)) public override approvedHashes; //@audit bool usage//3205 deploy gas
55 bool public override restrictedToAdmin; //@audit bool usage //16308 deploy gas ... 61 mapping(address => mapping(bytes32 => bool)) public override approvedHashes; //@audit bool usage//4728 deploy gas
Call to an external contract should be cached. Every call to an external contract costs a decent amount of gas. For optimization of gas usage, external call results should be cached if they are being used for more than one time. Gas saved: 23972 deploy gas
392 // Calculate lenderFee 393 uint256 _lenderFee = (_lendingAmount * _projectInstance.lenderFee()) / 394 (_projectInstance.lenderFee() + 1000);
fix:
393 uint256 _lenderFee = _projectInstance.lenderFee(); 394 _lenderFee = (_lendingAmount * _lenderFee) / (_lenderFee + 1000);
The same modifier can be combined into a library 8674 gas saved.
67 modifier nonZero(address _address) { 68 // Revert if _address zero address (0x00) (invalid) 69 require(_address != address(0), "Community::0 address"); 70 _; 71 }
37 modifier nonZero(address _address) { 38 // Revert if _address zero address (0x00) 39 require(_address != address(0), "Disputes::0 address"); 40 _; 41 }
77 modifier nonZero(address _address) { 78 require(_address != address(0), "HomeFi::0 address"); 79 _; 80 }
40 modifier nonZero(address _address) { 41 require(_address != address(0), "Proxy::0 address"); 42 _; 43 }
34 modifier nonZero(address _address) { 35 // Ensure an address is not the zero address (0x00) 36 require(_address != address(0), "PF::0 address"); 37 _; 38 }
50 require(_communityContract != address(0), "DebtToken::0 address");
753 require(_sc != address(0), "Project::0 address");
Loop length is calculated for each iteration in loop, this can be cached to save gas
Gas saved: 4113 deploy gas. + method call gas depending on loop length
592 taskCount - j + _changeOrderedTask.length - i ... 601 if (_changeOrderedTask.length > 0) { ... 603 for (; i < _changeOrderedTask.length; i++) {@audit loop length
620 _communities[_communityID].memberCount ... 624 for (uint256 i = 0; i < _communities[_communityID].memberCount; i++) {
The variable is cached and used only once. Caching variables cost extra gas. Gas saved: 1738
419 // Local variable for total cost allocated. For gas saving. 420 uint256 _totalAllocated = totalAllocated; ... 438 else if (totalLent - _totalAllocated >= _newCost - _taskCost) {
533 IProject _projectInstance = IProject(_project); ... 536 require(_builder == _projectInstance.builder(), "Community::!Builder");
It is more gas efficient to compare variables with != 0 than it is with > 0. These two comparisons are equivalent when the compared variable is an unsigned integer.
Gas saved: 1533
245 return projectTokenId[_project] > 0; //@audit !=
195 require(_cost > 0, "Project::!value>0"); ... 380 if (_leftOutTokens > 0) {//@audit replace with != ... 601 if (_changeOrderedTask.length > 0) {//@audit use != ... 691 if (_loopCount > 0) emit TaskAllocated(_tasksAllocated); //@audit !=0
261 if (projectPublished[_project] > 0) {//@audit != ... 427 _communities[_communityID].projectDetails[_project].lentAmount > 0 //@audit != ... 764 require(_repayAmount > 0, "Community::!repay"); ... 840 if (_interestEarned > 0) {//@audit !=
107 _actionType > 0 && _actionType <= uint8(ActionType.TaskPay),
Splitting require() statements that use && saves gas
Important: While two separate checks provide gas savings, deployment requires more gas due to code size and string usage. I can recommend to use this assertion logic optimization with if() revert Error()
model
353 require( 354 _lendingNeeded >= _communityProject.totalLent && 355 _lendingNeeded <= IProject(_project).projectCost(), 356 "Community::invalid lending" 357 );
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 );