Platform: Code4rena
Start Date: 18/10/2023
Pot Size: $36,500 USDC
Total HM: 17
Participants: 77
Period: 7 days
Judge: MiloTruck
Total Solo HM: 5
Id: 297
League: ETH
Rank: 72/77
Findings: 1
Award: $12.14
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0x11singh99, 0xhacksmithh, JCK, dharma09, nailkhalimov, naman1778, unique
12.1398 USDC - $12.14
address(this)
Instead of using address(this)
, it is more gas-efficient to pre-calculate and use the hardcoded address
. Foundry’s script.sol and solmate’s LibRlp.sol
contracts can help achieve this.
References: <ins>https://book.getfoundry.sh/reference/forge-std/compute-create-address</ins>
<ins>https://twitter.com/transmissions11/status/1518507047943245824</ins>
105: return _unqueuedUnauctionedDebt(safeEngine.debtBalance(address(this)));
104: _settleDebt(safeEngine.coinBalance(address(this)), safeEngine.debtBalance(address(this)), _rad);
162: uint256 _coinBalance = safeEngine.coinBalance(address(this));
169: emit CancelDebt(_rad, _coinBalance - _rad, safeEngine.debtBalance(address(this)));
178: uint256 _coinBalance = safeEngine.coinBalance(address(this)); 179: uint256 _debtBalance = safeEngine.debtBalance(address(this));
*Â External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following*
194: function unqueuedUnauctionedDebt() external view returns (uint256 __unqueuedUnauctionedDebt) { return _unqueuedUnauctionedDebt(safeEngine.debtBalance(address(this))); }
34: function camelotRelayersList() external view returns (address[] memory _camelotRelayersList) { return _camelotRelayers.values(); }
uint
use != 0
instead of > 0
224: if (_params.surplusTransferPercentage > 0) {
269: if (_coinBalance > 0) {
If you can fit your data in 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is much cheaper in solidity. Basically, Any fixed size variable in solidity is cheaper than variable size.
31: string public symbol;
30: string public symbol;
If similar external calls are performed back-to-back, we can use assembly to reuse any function signatures and function parameters that stay the same. In addition, we can also reuse the same memory space for each function call (scratch space + free memory pointer), which can potentially allow us to avoid memory expansion costs. In this case, we are also able to efficiently store the function signatures together in memory as one word, saving multiple MLOADs in the process.
Note: In order to do this optimization safely we will cache the free memory pointer value and restore it once we are done with our function calls. We will also set the zero slot back to 0 if neccessary.
45: address _token0 = ICamelotPair(camelotPair).token0(); 46: address _token1 = ICamelotPair(camelotPair).token1();
57: baseAmount = uint128(10 ** IERC20Metadata(_baseToken).decimals()); 58: multiplier = 18 - IERC20Metadata(_quoteToken).decimals();
address _token0 = IUniswapV3Pool(uniV3Pool).token0(); address _token1 = IUniswapV3Pool(uniV3Pool).token1();
msg.sender
We can use assembly to efficiently validate msg.sender for the didPay and uniswapV3SwapCallback functions with the least amount of opcodes necessary. Additionally, we can use xor() instead of iszero(eq()), saving 3 gas. We can also potentially save gas on the unhappy path by using scratch space to store the error selector, potentially avoiding memory expansion costs.
95: require(msg.sender == address(safeManager), 'V721: only safeManager');
https://github.com/open-dollar/od-contracts/blob/v1.5.5-audit/src/contracts/proxies/Vault721.sol#L95
bool
s for storage incurs overhead// 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.
Use uint256(1)
and uint256(2)
for true/false to avoid a Gwarmaccess (<ins>100 gas</ins>) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false
to true
, after having been true
in the past.
29: bool _succeeded;
https://github.com/open-dollar/od-contracts/blob/v1.5.5-audit/src/contracts/proxies/ODProxy.sol#L29
if (_coinAmount < _deltaWad * RAY) { // Calculates the needed deltaDebt so together with the existing coins in the safeEngine is enough to exit wad amount of COIN tokens _deltaDebt = ((_deltaWad * RAY - _coinAmount) / _rate).toInt(); // This is neeeded due lack of precision. It might need to sum an extra deltaDebt wei (for the given COIN wad amount) _deltaDebt = uint256(_deltaDebt) * _rate < _deltaWad * RAY ? _deltaDebt + 1 : _deltaDebt; }
_deltaWad * RAYÂ
should be cached.
Every call to an external contract costs a decent amount of gas. For optimization of gas usage, It’s better to call one function and have it return all the data you need rather than calling a separate function for every piece of data. This might go against the best coding practices for other languages, but solidity is special.
As you move forward with implementing the suggested optimizations, we urge you to exercise caution and conduct meticulous testing. It is essential to verify that these changes do not introduce any new vulnerabilities and effectively deliver the desired performance improvements. Carefully review the code modifications and perform rigorous testing to validate the security and effectiveness of the refactored code.
#0 - c4-pre-sort
2023-10-27T02:04:09Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-11-03T17:40:29Z
MiloTruck marked the issue as grade-b