By placing second if() statement (L132) and require() inside first if() (L128) can prevent unnecessary MLOAD if priceFound value is already set true at L126
Change to:
if (!priceFound) { //@audit-info: The function will just returning first price value with no validation if priceFound isTrue
(priceFound, price) = _getPriceFromMasterQuote(_assetOne, _assetTwo);
if (!priceFound) {
(priceFound, price) = _getPriceFromAdapters(_assetOne, _assetTwo);
require(priceFound, "PriceOracle.getPrice: Price not found."); //@audit-info: Validate that the final value is true
}
}
By removing return statement at L277, we can save 14 gas (if hasDirectOracle and hasInverseOracle is false). It is more efficient because by not returning any value, the function will just returning default var value (uint(0) and bool(false)) without set returning value with (false, 0)
By placing isLocked var next to any address var, we can pack those vars in 1 single slot. Address has 20 bytes size and bool 1 bytes size. The max bytes size storage in solidity can store is 32 bytes.
Change to:
IController public controller;
// The manager has the privelege to add modules, remove, and set a new manager
address public manager;
// A module that has locked other modules from privileged functionality, typically required
// for multi-block module actions such as auctions
address public locker;
// without interruption
bool public isLocked; //@audit-info: Place here
// List of initialized Modules; Modules extend the functionality of SetTokens
address[] public modules;
// Modules are initialized from NONE -> PENDING -> INITIALIZED through the
// addModule (called by manager) and initialize (called by module) functions
mapping(address => ISetToken.ModuleState) public moduleStates;
// When locked, only the locker (a module) can call privileged functionality
// Typically utilized if a module (e.g. Auction) needs multiple transactions to complete an action
// List of components
address[] public components;
// Mapping that stores all Default and External position information for a given component.
// Position quantities are represented as virtual units; Default positions are on the top-level,
// while external positions are stored in a module array and accessed through its externalPositions mapping
mapping(address => ISetToken.ComponentPosition) private componentPositions;
// The multiplier applied to the virtual position unit to achieve the real/actual unit.
// This multiplier is used for efficiently modifying the entire position units (e.g. streaming fee)
int256 public positionMultiplier;
We can just remove return statement at L211
Change to:
function invoke(
address _target,
uint256 _value,
bytes calldata _data
)
external
onlyModule
whenLockedOnlyLocker
returns (bytes memory _returnValue)
{
_returnValue = _target.functionCallWithValue(_data, _value);//@audit-info:The function has returned the value here
emit Invoked(_target, _value, _data, _returnValue);
//@audit-info: Removed return statement at L211
}
The if() condition (if the condition == true) in getUnderlyingToken() function has returned the value (so the line below it won't be executed). By removing else statement we can save gas
Change to:
if (asset.tokenType == TokenType.NonMintable) {
// In this case the asset token is the underlying
return (IERC20(asset.tokenAddress), asset.decimals);
}
return (IERC20(underlying.tokenAddress), underlying.decimals);
11.
Title: Cheaper way to returning in convertToShares()
Instead of running totalSupply(), using supply is cheaper
Change to:
return (assets * supply) / totalAssets();
And also use just one method to returning value (the shares var is useless since the function returning the value directly / not using shares to store the value)
Recommend to:
//@audit-info: Remove shares var
function convertToShares(uint256 assets) public view override returns (uint256)
Currently hasDirectOracle merely called once in the function. Checking that address(directOracle) =! address(0) inside the if condition can save gas by avoiding MSTORE. It also quite clear (for readability) that validate address var != address(0) is to ensure that the var is already set
Change to:
IOracle directOracle = oracles[_assetOne][_assetTwo];
//@audit-info: remove var declaration
// Check asset1 -> asset 2. If exists, then return value
if (address(directOracle) != address(0)) { //@audit-info: validate directly here
return (true, directOracle.read());
}
Same thing can be applied at L262 (hasDirectOracle)
The function has checked that the msg.sender == locker. locker is set in lock function which has onlyModule modifier (means that only module who can fill locker var).
GAS
1. Title: Using
delete
statement to set mapping value to falseOccurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L180 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L210 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L240 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L279
Instead of set the mapping value to false, using delete statement to set it to default value can save gas. Change to:
2. Title: Using prefix increment
Occurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L135-L149 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/IntegrationRegistry.sol#L112-L119 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L97-L99 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/SetValuer.sol#L89
Using prefix increment the way more effective for gas optimization in for() loop Change to:
3. Title: Using != operator
Occurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/IntegrationRegistry.sol#L108 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/SetToken.sol#L489 https://github.com/code-423n4/2022-06-notional-coop/blob/main/notional-wrapped-fcash/contracts/wfCashERC4626.sol#L23
Using != instead of < to validate than uint value is not 0 is more effective
4. Title: Using calldata to store array parameter
Occurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/IntegrationRegistry.sol#L98-L100 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L82-L85 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/SetTokenCreator.sol#L67-L69
Using
calldata
as a pointer to store array in the function can save gas Change to:5. Title: Emit
IntegrationRemoved
earlier inremoveIntegration()
function https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/IntegrationRegistry.sol#L187-L194Emitting the event earlier before deleting the
integrations
mapping value (L192) can save gas by not doing MSTORE Change to:6. Title: Efficiency if() in
getPrice()
https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L128-L134By placing second if() statement (L132) and require() inside first if() (L128) can prevent unnecessary MLOAD if
priceFound
value is already set true at L126 Change to:7. Title: Returning default value in function
Occurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L277 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L311 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L342
By removing return statement at L277, we can save 14 gas (if
hasDirectOracle
andhasInverseOracle
is false). It is more efficient because by not returning any value, the function will just returning default var value (uint(0) and bool(false)) without set returning value with (false, 0)8. Title: Packing var in
SetToken.sol
https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/SetToken.sol#L110-L141By placing
isLocked
var next to any address var, we can pack those vars in 1 single slot. Address has 20 bytes size and bool 1 bytes size. The max bytes size storage in solidity can store is 32 bytes. Change to:9. Title: Unnecessary multiple return statement https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/SetToken.sol#L207-L211
We can just remove return statement at L211 Change to:
10. Title: Unnecessary else statement
Occurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/notional-wrapped-fcash/contracts/wfCashBase.sol#L130-L132 https://github.com/code-423n4/2022-06-notional-coop/blob/main/notional-wrapped-fcash/contracts/wfCashERC4626.sol#L98 https://github.com/code-423n4/2022-06-notional-coop/blob/main/notional-wrapped-fcash/contracts/wfCashERC4626.sol#L118
The if() condition (if the condition == true) in
getUnderlyingToken()
function has returned the value (so the line below it won't be executed). By removing else statement we can save gasChange to:
11. Title: Cheaper way to returning in
convertToShares()
https://github.com/code-423n4/2022-06-notional-coop/blob/main/notional-wrapped-fcash/contracts/wfCashERC4626.sol#L60
Instead of running
totalSupply()
, usingsupply
is cheaperChange to:
And also use just one method to returning value (the
shares
var is useless since the function returning the value directly / not using shares to store the value)Recommend to:
12. Title: Unnecessary
hasInverseOracle
MSTOREhttps://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L262
Currently
hasDirectOracle
merely called once in the function. Checking thataddress(directOracle) =! address(0)
inside the if condition can save gas by avoiding MSTORE. It also quite clear (for readability) that validate address var != address(0) is to ensure that the var is already set Change to:Same thing can be applied at L262 (hasDirectOracle)
13. Title: Make string >= 32 bytes
Occurrences: https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L294 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/Controller.sol#L311 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/IntegrationRegistry.sol#L109-L110 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/IntegrationRegistry.sol#L168-L169 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L136 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L200 https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/PriceOracle.sol#L215
Strings are broken into 32 byte chunks for operations. Revert error strings over 32 bytes therefore consume extra gas than shorter strings
14. Title: Unnecessary onlyModule modifier
https://github.com/code-423n4/2022-06-notional-coop/blob/main/index-coop-notional-trade-module/contracts/protocol/SetToken.sol#L352
The function has checked that the msg.sender == locker.
locker
is set in lock function which hasonlyModule
modifier (means that only module who can fill locker var).