Platform: Code4rena
Start Date: 03/11/2022
Pot Size: $115,500 USDC
Total HM: 17
Participants: 120
Period: 7 days
Judge: LSDan
Total Solo HM: 1
Id: 174
League: ETH
Rank: 42/120
Findings: 3
Award: $115.92
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: __141345__
Also found by: 0xdeadbeef0x, 8olidity, Amithuddar, Bnke0x0, Ch_301, Deivitto, IllIllI, KingNFT, Nyx, RaymondFam, RedOneN, Satyam_Sharma, SmartSek, Tomo, adriro, bananasboys, carlitox477, cccz, cloudjunky, codexploder, corerouter, cryptonue, d3e4, datapunk, joestakey, martin, merlin, minhquanym, pashov, peanuts, rvierdiiev
5.3388 USDC - $5.34
When transferring ETH to the receiver
, LineLib.sol
uses Solidity’s transfer function. This has some notable shortcomings when the recipient is a smart contract as denoted by the comment on Line 29, which can render ETH impossible to transfer.
Specifically, the transfer will inevitably fail when the recipient is a smart contract that:
Manual inspection and read up
Consider using:
call
, which returns a boolean value indicating success or failure, in combination with re-entrancy guard is highly recommended after December 2019.
error TransferFailed(); (bool success, ) = receiver.call{ value: amount }(''); if(!success) revert TransferFailed();
Alternatively, sendValue()
available in OpenZeppelin Contract’s Address library can be used to transfer the Ether without being limited to 2300 gas units. And again, the risks of re-entrancy stemming from the use of this function can be mitigated by tightly following the “Check-effects-interactions” pattern and using OpenZeppelin Contract’s ReentrancyGuard contract.
Lastly, whenever token/fund transfer to the recipient from the contract is involved, use a pull-over-push pattern where possible. With a separate withdraw()
implemented, the onus is on the users to receive the fund/token properly.
#0 - c4-judge
2022-11-17T11:54:31Z
dmvt marked the issue as duplicate of #14
#1 - c4-judge
2022-12-06T14:52:29Z
dmvt marked the issue as satisfactory
#2 - C4-Staff
2022-12-20T05:56:22Z
liveactionllama marked the issue as duplicate of #369
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xRoxas, 0xSmartContract, Awesome, Aymen0909, B2, BClabs, Bnke0x0, Deekshith99, Deivitto, Diana, Dinesh11G, Funen, HE1M, HardlyCodeMan, Josiah, Nyx, Rahoz, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Saintcode_, TomJ, Trust, __141345__, a12jmx, adriro, ajtra, aphak5010, apostle0x01, brgltd, btk, bulej93, c3phas, carlitox477, catwhiskeys, ch0bu, chaduke, chrisdior4, cryptonue, cryptostellar5, csanuragjain, ctf_sec, delfin454000, djxploit, durianSausage, erictee, fatherOfBlocks, gogo, i_got_hacked, immeas, joestakey, jumpdest7d, lukris02, martin, mcwildy, merlin, minhquanym, oyc_109, pashov, peanuts, pedr02b2, rbserver, rotcivegaf, rvierdiiev, sakman, saneryee, seyni, shark, slowmoses, tnevler, trustindistrust, w0Lfrum, yurahod, zaskoh
61.3462 USDC - $61.35
For most source-units the compiler version pragma is very unspecific ^0.8.9. While this often makes sense for libraries to allow them to be included with multiple different versions of an application, it may be a security risk for the actual application implementation itself. A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up actually checking a different EVM compilation that is ultimately deployed on the blockchain.
Avoid floating pragmas where possible. It is highly recommend pinning a concrete compiler version (latest without security issues) in at least the top-level “deployed” contracts to make it unambiguous which compiler version is being used. Rule of thumb: a flattened source-unit should have at least one non-floating concrete solidity compiler version pragma.
Devoid of sanity/threshold/limit checks, critical parameters can be configured to invalid values, causing a variety of issues and breaking expected interactions within/between contracts. Consider adding proper uint256
validation as well as zero address checks in the constructor. A worst case scenario would render the contract needing to be re-deployed in the event of human/accidental errors that involve value assignments to immutable variables. If the validation procedure is unclear or too complex to implement on-chain, document the potential issues that could produce invalid values. These checks are generally not implemented in all of the in scope contracts.
There is no floating point in Solidity. You should keep numbers in whole number format. The following instance of constant assignment should be refactored as follows:
uint256 constant ONE_YEAR = 365 days + 6 hours;
Consider adding a less than 32 character string message to all require statements just so that a relevant message would be displayed in the event of a revert. The error message is generally not included in all of the in scope contracts.
block.timestamp
UnreliableThe use of block.timestamp
as part of the time checks can be slightly altered by miners/validators to favor them in contracts that have logic strongly dependent on them.
Consider taking into account this issue and warning the users that such a scenario could happen. If the alteration of timestamps cannot affect the protocol in any way, consider documenting the reasoning and writing tests enforcing that these guarantees will be preserved even if the code changes in the future.
Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L132 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L48-L50 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L82
Function calls made in unbounded loop are error-prone with potential resource exhaustion as it can trap the contract due to the gas limitations or failed transactions. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L179 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L203 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L520
Consider bounding the loop where possible to avoid unnecessary gas wastage and denial of service.
@ priviliges Line 45 * @param arbiter_ - A neutral party with some special priviliges on behalf of Borrower and Lender.
@ swithc ... repyment * @notice helper function to allow borrower to easily swithc collateral to a new Line after repyment
@ priviliegad *(@dev priviliegad internal function.
@ thdeposits * @notice - allows the lines arbiter to enable thdeposits of an asset
@ seeting // emit events before seeting to 0
@ bwithdrawn * @param credit - The lender position that is being bwithdrawn from
Solidity contracts can use a special form of comments, i.e., the Ethereum Natural Language Specification Format (NatSpec) to provide rich documentation for functions, return variables and more. Please visit the following link for further details:
https://docs.soliditylang.org/en/v0.8.16/natspec-format.html
Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L64-L72 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L117-L210 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L223-L409
Consider making the naming of local variables more verbose and descriptive so all other peer developers would better be able to comprehend the intended statement logic, significantly enhancing the code readability. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L123 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L185 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L55 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/LineFactoryLib.sol#L48-L50
receive()
Will Lock Ether in ContractIf the Ether received is intended to be used, the function should call another function, otherwise it should revert. Here is one instance entailed:
Critical operations not triggering events will make it difficult to review the correct behavior of the deployed contracts. Users and blockchain monitoring systems will not be able to detect suspicious behaviors at ease without events. Consider adding events where appropriate for all critical operations for better support of off-chain logging API. Here are some of the instances entailed:
constant
Variables Should be CapitalizedConstants should be named with all capital letters with underscores separating words if need be. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L12 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L14
delete
to Clear Variablesdelete a
assigns the initial value for the type to a
. i.e. for integers it is equivalent to a = 0
, but it can also be used on arrays, where it assigns a dynamic array of length zero or a static array of the same length with all elements reset. For structs, it assigns a struct with all members reset. Similarly, it can also be used to set an address to zero address. It has no effect on whole mappings though (as the keys of mappings may be arbitrary and are generally unknown). However, individual keys and what they map to can be deleted: If a
is a mapping, then delete a[x]
will delete the value stored at x.
The delete key better conveys the intention and is also more idiomatic. Consider replacing assignments of zero with delete statements. Here are some the three instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L188 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L219 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L117
payable
VisibilityreceiveTokenOrETH()
serves to receive ETH or ERC20 token at this contract from an external contract according to the comment and the function logic. However, devoid of the payable
visibility, all calls attempting to send ETH into the contract via this function call is going to be rejected.
To enhance readability whilst reducing human error, the following instance of constant should be assigned using a standard formula:
uint256 constant MAX_INT = type(uint256).max
Note: 2**256 - 1
and uint256(-1)
are two other alternatives that can used for the constant assignment. Additionally, the literally long numerical value may be commented if need be.
As an example, the code line below may be refactored to minimize the frequency of truncation:
return (_numerator + debtValue * 5) / (10 * debtValue);
Note: Comment the refactored code with the original multiple division arithmetic operation where deemed fit.
Consider having events associated with setter functions emit both the new and old values instead of just the new value. Here are some of the instances entailed:
In SpigotLib.sol
, updateOwner()
should be implemented transferring owner role to another address in a pending state. And then, acceptOwner()
should be introduced for the newly nominated owner to claim the new role. This will ensure the new owner is going to be fully aware of the privilege assigned/transferred. Additionally, the risk of having ownership transferred to an invalid address that would cause the contract to be without an owner will be avoided. The same adoption should also be applied to updateOperator()
and updateTreasury()
where possible.
Performing a low-level calls without confirming contract’s existence (not yet deployed or have been destructed) could return success even though no function call was executed. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L131 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L135
Lines in source code are typically limited to 80 characters, but it’s reasonable to stretch beyond this limit when need be as monitor screens theses days are comparatively larger. Considering the files will most likely reside in GitHub that will have a scroll bar automatically kick in when the length is over 164 characters, all code lines and comments should be split when/before hitting this length. Keep line width to max 120 characters for better readability where possible. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L35 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L43 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L47 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L36-L37 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/SecuredLine.sol#L72 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L62 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L71 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L55 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/CreditLib.sol#L101 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/CreditListLib.sol#L12 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/CreditListLib.sol#L17 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L15 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L164
It is a good practice to give time for users to react and adjust to critical changes with a mandatory time window between them. The first step merely broadcasts to users that a particular change is coming, and the second step commits that change after a suitable waiting period. This allows users that do not accept the change to withdraw within the grace period. A timelock provides more guarantees and reduces the level of trust required, thus decreasing risk for users. It also indicates that the project is legitimate (less risk of a malicious Owner making any malicious or ulterior intention). Specifically, privileged roles could use front running to make malicious changes just ahead of incoming transactions, or purely accidental negative effects could occur due to the unfortunate timing of changes. Here are some of the instances entailed:
The owner of the contracts has too many privileges relative to standard users. The consequence is disastrous if the contract owner's private key has been compromised. And, in the event the key was lost or unrecoverable, no implementation upgrades and system parameter updates will ever be possible.
For a project this grand, it increases the likelihood that the owner will be targeted by an attacker, especially given the insufficient protection on sensitive owner private keys. The concentration of privileges creates a single point of failure; and, here are some of the incidents that could possibly transpire:
Transfer ownership and mess up with all the setter functions, hijacking the entire protocol.
Consider:
Open TODOs can point to architecture or programming issues that still need to be resolved. Consider resolving them before deploying.
Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L140 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/factories/LineFactory.sol#L145
Consider adding an optional codehash check for immutable address at the constructor since the zero address check cannot guarantee a matching address has been inputted. As an example, the following code block could be refactored to:
constructor( address moduleFactory, address arbiter_, address oracle_, address swapTarget_, bytes32 arbiter_CodeHash, bytes32 oracle_CodeHash, bytes32 swapTarget_CodeHash ) { factory = IModuleFactory(moduleFactory); if (arbiter_ == address(0) && arbiter_.codehash != arbiter_CodeHash) { revert InvalidArbiterAddress(); } if (oracle_ == address(0) && oracle_.codehash != oracle_CodeHash) { revert InvalidOracleAddress(); } if (swapTarget_ == address(0) && swapTarget_.codehash != swapTarget_CodeHash) { revert InvalidSwapTargetAddress(); } arbiter = arbiter_; oracle = oracle_; swapTarget = swapTarget_; }
uint
Instead of uint256
Across the codebase, there are instances of uint
, as opposed to uint256
. In favor of explicitness, consider replacing all instances of uint
with uint256
.
Here is one of the instances entailed:
#0 - c4-judge
2022-12-06T14:31:21Z
dmvt marked the issue as grade-b
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xRajkumar, Awesome, Aymen0909, B2, Bnke0x0, Deivitto, Diana, JC, Metatron, Rahoz, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Saintcode_, TomJ, __141345__, ajtra, aphak5010, brgltd, c3phas, ch0bu, chrisdior4, cryptonue, durianSausage, emrekocak, erictee, exolorkistis, gogo, karanctf, lukris02, martin, me_na0mi, oyc_109, peanuts, rotcivegaf, saneryee, seyni, tnevler, zaskoh
49.2315 USDC - $49.23
Consider having the logic of a modifier embedded through a private (doesn't matter whether or not the contract entails any child contracts since the private visibility saves even more gas on function calls than the internal visibility) function to reduce contract size if need be. For instance, the following instance of modifier may be rewritten as follows:
function _whileActive() private view { if(status != LineLib.STATUS.ACTIVE) { revert NotActive(); } } modifier whileActive() { _whileActive(); _; }
The order of function will also have an impact on gas consumption. Because in smart contracts, there is a difference in the order of the functions. Each position will have an extra 22 gas. The order is dependent on method ID. So, if you rename the frequently accessed function to more early method ID, you can save gas cost. Please visit the following site for further information:
Before deploying your contract, activate the optimizer when compiling using “solc --optimize --bin sourceFile.sol”. By default, the optimizer will optimize the contract assuming it is called 200 times across its lifetime. If you want the initial contract deployment to be cheaper and the later function executions to be more expensive, set it to “ --optimize-runs=1”. Conversely, if you expect many transactions and do not care for higher deployment cost and output size, set “--optimize-runs” to a high number.
module.exports = { solidity: { version: "0.8.9", settings: { optimizer: { enabled: true, runs: 1000, }, }, }, };
Please visit the following site for further information:
https://docs.soliditylang.org/en/v0.5.4/using-the-compiler.html#using-the-commandline-compiler
Here's one example of instance on opcode comparison that delineates the gas saving mechanism:
for !=0 before optimization PUSH1 0x00 DUP2 EQ ISZERO PUSH1 [cont offset] JUMPI
after optimization DUP1 PUSH1 [revert offset] JUMPI
Disclaimer: There have been several bugs with security implications related to optimizations. For this reason, Solidity compiler optimizations are disabled by default, and it is unclear how many contracts in the wild actually use them. Therefore, it is unclear how well they are being tested and exercised. High-severity security issues due to optimization bugs have occurred in the past . A high-severity bug in the emscripten -generated solc-js compiler used by Truffle and Remix persisted until late 2018. The fix for this bug was not reported in the Solidity CHANGELOG. Another high-severity optimization bug resulting in incorrect bit shift results was patched in Solidity 0.5.6. Please measure the gas savings from optimizations, and carefully weigh them against the possibility of an optimization-related bug. Also, monitor the development and adoption of Solidity compiler optimizations to assess their maturity.
||
Costs Less Gas Than Its Equivalent &&
Rule of thumb: (x && y)
is (!(!x || !y))
Even with the 10k Optimizer enabled: ||
, OR conditions cost less than their equivalent &&
, AND conditions.
As an example, the following code line may be rewritten as:
if(!(msg.sender = credit.lender || msg.sender = b)) {
Note: Comment the refactored code where deemed fit to enhance readability.
if ... else
Using ternary operator instead of the if else statement saves gas. For instance the following code block may be rewritten as:
_canDeclareInsolvent() ? { _updateStatus(LineLib.STATUS.INSOLVENT); return true; } : { return false; }
+=
generally costs 22 more gas than writing out the assigned equation explicitly. The amount of gas wasted can be quite sizable when repeatedly operated in a loop. As an example, the following line of code could be rewritten as:
credit.deposit = credit.deposit + amount;
Similarly, the following code line should be refactored as:
unusedTokens[credit.token] = unusedTokens[credit.token] - amount;
"Checked" math, which is default in ^0.8.0 is not free. The compiler will add some overflow checks, somehow similar to those implemented by SafeMath
. While it is reasonable to expect these checks to be less expensive than the current SafeMath
, one should keep in mind that these checks will increase the cost of "basic math operation" that were not previously covered. This particularly concerns variable increments in for loops. When no arithmetic overflow/underflow is going to happen, unchecked { ++i ;}
to use the previous wrapping behavior further saves gas just as in the for loop below as an example:
for (uint256 i; i < len;) { id = ids[i]; Credit memory credit = credits[id]; credits[id] = _accrue(credit, id); unchecked { ++i; } }
In the EVM, there is no opcode for non-strict inequalities (>=, <=) and two operations are performed (> + = or < + =). As an example, consider replacing >= with the strict counterpart > in the following line of code:
require(uint(status) > uint( LineLib.STATUS.ACTIVE) - 1);
Similarly, as an example, consider replacing <= with the strict counterpart < in the following line of code:
require(defaultRevenueSplit_ < SpigotedLineLib.MAX_SPLIT + 1);
You can have further advantages in term of gas cost by simply using named return values as temporary local variable. As an example, the following instance of code block can refactored as follows:
function healthcheck() external returns (LineLib.STATUS healthStatus) { // can only check if the line has been initialized require(uint(status) >= uint( LineLib.STATUS.ACTIVE)); healthStatus = _updateStatus(_healthcheck()); }
A storage pointer is cheaper since copying a state struct in memory would incur as many SLOADs and MSTOREs as there are slots. In another words, this causes all fields of the struct/array to be read from storage, incurring a Gcoldsload (2100 gas) for each field of the struct/array, and then further incurring an additional MLOAD rather than a cheap stack read. As such, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables will be much cheaper, involving only Gcoldsload for all associated field reads. Read the whole struct/array into a memory variable only when it is being returned by the function, passed into a function that requires memory, or if the array/struct is being read from another memory array/struct. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L100 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/SpigotedLine.sol#L139 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L273
Although it has been commented that the logic of _canDeclareInsolvent()
is updated in Spigoted and Escrowed lines, the if condition of declareInsolvent()
is going to always return true when internally calling _canDeclareInsolvent()
. The if ... else
code block is therefore deemed not needed and may be refactored as follows:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L149-L154 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L157-L160
_updateStatus(LineLib.STATUS.INSOLVENT); return true;
That being said, a returned boolean is also not needed since it is going to always return true.
An internal function not called by the child contracts and only called by one other function in the parent contract may be embedded inline. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L157 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L167-L169
SLOADs cost 100 gas each after the 1st one whereas MLOADs/MSTOREs only incur 3 gas each. As such, storage values read multiple times should be cached in the stack memory the first time (costing only 1 SLOAD) and then re-read from this cache to avoid multiple SLOADs.
For instance, ids
should be cached as follows and correspondingly inserted before lines 172 and 201:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L172 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/credit/LineOfCredit.sol#L201
bytes32[] memory ids = ids;
This will significantly save gas in the for loops entailinmg ids[i]
other than ids.length
alone.
When running a function we could pass the function parameters as calldata or memory for variables such as strings, bytes, structs, arrays etc. If we are not modifying the passed parameter, we should pass it as calldata because calldata is more gas efficient than memory.
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. However, it alleviates the compiler from the abi.decode()
step that copies each index of the calldata to the memory index, bypassing the cost of 60 gas on each iteration. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/spigot/Spigot.sol#L125 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotLib.sol#L125
abi.encode()
Costs More Gas Than abi.encodePacked()
Changing abi.encode()
to abi.encodePacked()
can save gas considering the former pads extra null bytes at the end of the call data, which is unnecessary. Please visit the following the link delineating how abi.encodePacked()
is more gas efficient in general:
https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison
Here is one of the instances entailed:
uint256
Can be Cheaper Than uint8
and Other Unsigned Integer Type of Smaller Bit SizeWhen dealing with function arguments or memory values, there is no inherent benefit because the compiler does not pack these values. Your contract’s gas usage may be higher 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. The EVM needs to properly enforce the limits of this smaller type.
It is only more efficient when you can pack variables of uint8, uint16, uint32, uint64, ... into the same storage slot with other neighboring variables smaller than 32 bytes. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/SpigotedLineLib.sol#L169-L170 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/LineFactoryLib.sol#L51 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/spigot/Spigot.sol#L146
Consider marking functions with access control as payable
. This will save 20 gas on each call by their respective permissible callers for not needing to have the compiler check for msg.value
. Here are some of the instances entailed:
https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/interest-rate/InterestRateCredit.sol#L38 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L100-L102 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L104-L105 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/modules/escrow/Escrow.sol#L74-L76 https://github.com/debtdao/Line-of-Credit/blob/audit/code4rena-2022-11-03/contracts/utils/EscrowLib.sol#L215-L216
#0 - c4-judge
2022-11-17T22:50:17Z
dmvt marked the issue as grade-b