Platform: Code4rena
Start Date: 22/09/2023
Pot Size: $100,000 USDC
Total HM: 15
Participants: 175
Period: 14 days
Judge: alcueca
Total Solo HM: 4
Id: 287
League: ETH
Rank: 123/175
Findings: 1
Award: $17.71
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rvierdiiev
Also found by: 0x11singh99, 0xAnah, 0xta, Aamir, DavidGiladi, DevABDee, Eurovickk, JCK, K42, MrPotatoMagic, Pessimistic, Raihan, Rolezn, SM3_SS, SY_S, Sathish9098, Udsen, ayo_dev, blutorque, c3phas, clara, dharma09, hihen, hunter_w3b, jamshed, koxuan, lsaudit, marqymarq10, oualidpro, pfapostol, sivanesh_808, tabriz, wahedtalash77, zabihullahazadzoi, ziyou-
17.7101 USDC - $17.71
Number | Issue | Instances |
---|---|---|
[G-01] | State variables can be packed to save storage slots. | 5 |
[G-02] | Use calldata instead of memory for function arguments that do not get mutate | 21 |
[G-03] | Do not cache immutable values | 7 |
[G-04] | No need to explicitly initialize variables with default values | 14 |
[G-05] | Using ternary operator instead of if else saves gas | 1 |
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the state variable and subsequent reads as well, as writes have smaller gas savings. The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to eachother in storage and this will pack the values together into a single 32 byte storage slot (if the values combined are <= 32 bytes). If the variables packed together are retrieved together in functions, we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).
5 Instances - 5 Files
_unlocked
to uint8
and pack with bridgeAgentExecutorAddress
to save 1 SLOTSince _unlocked
is holding only 2 values 1 and 2 so uint8 is more than sufficient to hold them whenever it can be packed with another state variable to save 1 storage SLOT.
File : src/BaseBranchRouter.sol 39: address public override bridgeAgentExecutorAddress; //@audit reduce it to uint8 pack with above 42: uint256 internal _unlocked = 1;
_unlocked
to uint8
and pack with depositNonce
to save 1 SLOTFile : src/BranchBridgeAgent.sol 84: uint32 public depositNonce; ... 101: uint256 internal _unlocked = 1;
File : src/BranchBridgeAgent.sol 84: uint32 public depositNonce; + uint8 internal _unlocked = 1; /// @notice Mapping from Pending deposits hash to Deposit Struct. mapping(uint256 depositNonce => Deposit depositInfo) public getDeposit; /*/////////////////////////////////////////////////////////////// SETTLEMENT EXECUTION STATE //////////////////////////////////////////////////////////////*/ /// @notice If true, the bridge agent has already served a request with this nonce from a given chain. mapping(uint256 settlementNonce => uint256 state) public executionState; /*/////////////////////////////////////////////////////////////// REENTRANCY STATE //////////////////////////////////////////////////////////////*/ /// @notice Re-entrancy lock modifier state. - 101: uint256 internal _unlocked = 1;
_unlocked
to uint8
and pack with coreBranchRouterAddress
to save 1 SLOTFile : src/BranchPort.sol 25: address public coreBranchRouterAddress; 91: uint256 internal _unlocked = 1;
_unlocked
to uint8
and pack with bridgeAgentExecutorAddress
to save 1 SLOTFile : src/MulticallRootRouter.sol 77: address public bridgeAgentExecutorAddress; 80: uint256 internal _unlocked = 1;
_unlocked
to uint8
and pack with settlementNonce
to save 1 SLOTFile : src/RootBridgeAgent.sol 75: uint32 public settlementNonce; 92: uint256 internal _unlocked = 1;
calldata
instead of memory
for function arguments that do not get mutated.When you specify a data location as memory, that value will be copied into memory. When you specify the location as calldata, the value will stay static within calldata. If the value is a large, complex type, using memory may result in extra memory expansion costs.
Note: These are instances missed by the Bot Race.
21 Instances - 6 Files
<details> <summary>see instances</summary>File : src/factories/ERC20hTokenBranchFactory.sol 96: function createToken(string memory _name, string memory _symbol, uint8 _decimals, bool _addPrefix)
File : src/factories/ERC20hTokenBranchFactory.sol - function createToken(string memory _name, string memory _symbol, uint8 _decimals, bool _addPrefix) + function createToken(string calldata _name, string calldata _symbol, uint8 _decimals, bool _addPrefix)
File : src/factories/ERC20hTokenRootFactory.sol 76: function createToken(string memory _name, string memory _symbol, uint8 _decimals)
File : src/factories/ERC20hTokenRootFactory.sol - function createToken(string memory _name, string memory _symbol, uint8 _decimals) + function createToken(string calldata _name, string calldata _symbol, uint8 _decimals)
File : src/ArbitrumCoreBranchRouter.sol 85: function _receiveAddBridgeAgent( address _newBranchRouter, address _branchBridgeAgentFactory, address _rootBridgeAgent, address _rootBridgeAgentFactory, address _refundee, GasParams memory _gParams 92: ) internal override {
File : src/ArbitrumCoreBranchRouter.sol function _receiveAddBridgeAgent( address _newBranchRouter, address _branchBridgeAgentFactory, address _rootBridgeAgent, address _rootBridgeAgentFactory, address _refundee, - GasParams memory _gParams + GasParams calldata _gParams ) internal override {
File : src/BranchBridgeAgent.sol 860: function _createDepositMultiple( uint32 _depositNonce, address payable _refundee, address[] memory _hTokens, address[] memory _tokens, uint256[] memory _amounts, uint256[] memory _deposits 867: ) internal {
File : src/BranchBridgeAgent.sol function _createDepositMultiple( uint32 _depositNonce, address payable _refundee, - address[] memory _hTokens, - address[] memory _tokens, - uint256[] memory _amounts, - uint256[] memory _deposits + address[] calldata _hTokens, + address[] calldata _tokens, + uint256[] calldata _amounts, + uint256[] calldata _deposits ) internal {
File: src/BranchPort.sol 246: function bridgeInMultiple( address _recipient, address[] memory _localAddresses, address[] memory _underlyingAddresses, uint256[] memory _amounts, uint256[] memory _deposits 252: ) external override requiresBridgeAgent {
File: src/BranchPort.sol function bridgeInMultiple( address _recipient, - address[] memory _localAddresses, - address[] memory _underlyingAddresses, - uint256[] memory _amounts, - uint256[] memory _deposits + address[] memory _localAddresses, + address[] memory _underlyingAddresses, + uint256[] memory _amounts, + uint256[] memory _deposits ) external override requiresBridgeAgent {
File : src/BranchPort.sol 288: function bridgeOutMultiple( address _depositor, address[] memory _localAddresses, address[] memory _underlyingAddresses, uint256[] memory _amounts, uint256[] memory _deposits 294: ) external override lock requiresBridgeAgent {
File : function bridgeOutMultiple( address _depositor, - address[] memory _localAddresses, - address[] memory _underlyingAddresses, - uint256[] memory _amounts, - uint256[] memory _deposits + address[] calldata _localAddresses, + address[] calldata _underlyingAddresses, + uint256[] calldata _amounts, + uint256[] calldata _deposits ) external override lock requiresBridgeAgent {
File : src/RootBridgeAgent.sol 856: function _performRetrySettlementCall( bool _hasFallbackToggled, address[] memory _hTokens, address[] memory _tokens, uint256[] memory _amounts, uint256[] memory _deposits, bytes memory _params, uint32 _settlementNonce, address payable _refundee, address _recipient, uint16 _dstChainId, GasParams memory _gParams, uint256 _value 869: ) internal {
</details>File : src/RootBridgeAgent.sol function _performRetrySettlementCall( bool _hasFallbackToggled, - address[] memory _hTokens, - address[] memory _tokens, - uint256[] memory _amounts, - uint256[] memory _deposits, - bytes memory _params, + address[] calldata _hTokens, + address[] calldata _tokens, + uint256[] calldata _amounts, + uint256[] calldata _deposits, + bytes calldata _params, uint32 _settlementNonce, address payable _refundee, address _recipient, uint16 _dstChainId, - GasParams memory _gParams, + GasParams calldata _gParams, uint256 _value ) internal {
7 Instances - 1 Files
File : src/ArbitrumBranchPort.sol 50: function depositToPort(address _depositor, address _recipient, address _underlyingAddress, uint256 _deposit) 51: external 52: override 53: lock 54: requiresBridgeAgent 55: { 56: // Save root port address to memory 57: address _rootPortAddress = rootPortAddress; 58: 59: // Get global token address from root port 60: address _globalToken = IRootPort(_rootPortAddress).getLocalTokenFromUnderlying(_underlyingAddress, localChainId); 61: 62: // Check if the global token exists 63: if (_globalToken == address(0)) revert UnknownGlobalToken(); 64: 65: // Deposit Assets to Port 66: _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit); 67: 68: // Request Minting of Global Token 69: IRootPort(_rootPortAddress).mintToLocalBranch(_recipient, _globalToken, _deposit); 70: }
File : src/ArbitrumBranchPort.sol function depositToPort(address _depositor, address _recipient, address _underlyingAddress, uint256 _deposit) external override lock requiresBridgeAgent { // Save root port address to memory - address _rootPortAddress = rootPortAddress; // Get global token address from root port - address _globalToken = IRootPort(_rootPortAddress).getLocalTokenFromUnderlying(_underlyingAddress, localChainId); + address _globalToken = IRootPort(rootPortAddress).getLocalTokenFromUnderlying(_underlyingAddress, localChainId); // Check if the global token exists if (_globalToken == address(0)) revert UnknownGlobalToken(); // Deposit Assets to Port _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit); // Request Minting of Global Token - IRootPort(_rootPortAddress).mintToLocalBranch(_recipient, _globalToken, _deposit); + IRootPort(rootPortAddress).mintToLocalBranch(_recipient, _globalToken, _deposit); }
File : src/ArbitrumBranchPort.sol 73: function withdrawFromPort(address _depositor, address _recipient, address _globalAddress, uint256 _amount) external override lock requiresBridgeAgent { // Save root port address to memory address _rootPortAddress = rootPortAddress; // Check if the global token exists if (!IRootPort(_rootPortAddress).isGlobalToken(_globalAddress, localChainId)) revert UnknownGlobalToken(); // Get the underlying token address from the root port address _underlyingAddress = IRootPort(_rootPortAddress).getUnderlyingTokenFromLocal(_globalAddress, localChainId); // Check if the underlying token exists if (_underlyingAddress == address(0)) revert UnknownUnderlyingToken(); IRootPort(_rootPortAddress).burnFromLocalBranch(_depositor, _globalAddress, _amount); _underlyingAddress.safeTransfer(_recipient, _amount); 95: }
File : src/ArbitrumBranchPort.sol function withdrawFromPort(address _depositor, address _recipient, address _globalAddress, uint256 _amount) external override lock requiresBridgeAgent { // Save root port address to memory - address _rootPortAddress = rootPortAddress; // Check if the global token exists - if (!IRootPort(_rootPortAddress).isGlobalToken(_globalAddress, localChainId)) revert UnknownGlobalToken(); + if (!IRootPort(rootPortAddress).isGlobalToken(_globalAddress, localChainId)) revert UnknownGlobalToken(); // Get the underlying token address from the root port address _underlyingAddress = - IRootPort(_rootPortAddress).getUnderlyingTokenFromLocal(_globalAddress, localChainId); + IRootPort(rootPortAddress).getUnderlyingTokenFromLocal(_globalAddress, localChainId); // Check if the underlying token exists if (_underlyingAddress == address(0)) revert UnknownUnderlyingToken(); - IRootPort(_rootPortAddress).burnFromLocalBranch(_depositor, _globalAddress, _amount); + IRootPort(rootPortAddress).burnFromLocalBranch(_depositor, _globalAddress, _amount); _underlyingAddress.safeTransfer(_recipient, _amount); }
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address, etc.). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
for (uint256 i = 0; i < numIterations; ++i)
should be replaced with: for (uint256 i; i < numIterations; ++i)
14 Instances - 7 Files
<details> <summary>see instances</summary>File : src/BaseBranchRouter.sol - 192: for (uint256 i = 0; i < _hTokens.length;) { + 192: for (uint256 i; i < _hTokens.length;) {
File : src/BranchBridgeAgent.sol - 513: for (uint256 i = 0; i < numOfAssets;) { + 513: for (uint256 i; i < numOfAssets;) {
File : src/BranchPort.sol - 257: for (uint256 i = 0; i < length;) { + 257: for (uint256 i; i < length;) {
File : src/BranchPort.sol - 305: for (uint256 i = 0; i < length;) { + 305: for (uint256 i; i < length;) {
File : src/MulticallRootRouter.sol - 278: for (uint256 i = 0; i < outputParams.outputTokens.length;) { + 278: for (uint256 i; i < outputParams.outputTokens.length;) {
File : src/MulticallRootRouter.sol - 367: for (uint256 i = 0; i < outputParams.outputTokens.length;) { + 367: for (uint256 i; i < outputParams.outputTokens.length;) {
File : src/MulticallRootRouter.sol - 455: for (uint256 i = 0; i < outputParams.outputTokens.length;) { + 455: for (uint256 i; i < outputParams.outputTokens.length;) {
File : src/MulticallRootRouter.sol - 557: for (uint256 i = 0; i < outputTokens.length;) { + 557: for (uint256 i; i < outputTokens.length;) {
File : src/RootBridgeAgent.sol - 318 for (uint256 i = 0; i < settlement.hTokens.length;) { + 318 for (uint256 i; i < settlement.hTokens.length;) {
File : src/RootBridgeAgent.sol - 399: for (uint256 i = 0; i < length;) { + 399: for (uint256 i; i < length;) {
File : src/RootBridgeAgent.sol - 1070: for (uint256 i = 0; i < hTokens.length;) { + 1070: for (uint256 i; i < hTokens.length;) {
File : src/RootBridgeAgentExecutor.sol - 281: for (uint256 i = 0; i < uint256(uint8(numOfAssets));) { + 281: for (uint256 i; i < uint256(uint8(numOfAssets));) {
File : src/VirtualAccount.sol - 70: for (uint256 i = 0; i < length;) { + 70: for (uint256 i; i < length;) {
</details>File : src/VirtualAccount.sol - 90: for (uint256 i = 0; i < length;) { + 90: for (uint256 i; i < length;) {
ternary
operator instead of if else saves gasWhen using the if-else construct, Solidity generates bytecode that includes a JUMP operation. The JUMP operation requires the EVM to change the program counter to a different location in the bytecode, resulting in additional gas costs. On the other hand, the ternary operator doesn't involve a JUMP operation, as it generates bytecode that utilizes conditional stack manipulation.
1 Instance - 1 File
File :src/CoreBranchRouter.sol 284: function _manageStrategyToken(address _underlyingToken, uint256 _minimumReservesRatio) internal { // Save Port Address to memory address _localPortAddress = localPortAddress; // Check if the token is an active Strategy Token if (IPort(_localPortAddress).isStrategyToken(_underlyingToken)) { // If so, toggle it off. IPort(_localPortAddress).toggleStrategyToken(_underlyingToken); } else { // If not, add it. IPort(_localPortAddress).addStrategyToken(_underlyingToken, _minimumReservesRatio); } 296: }
File :src/CoreBranchRouter.sol function _manageStrategyToken(address _underlyingToken, uint256 _minimumReservesRatio) internal { // Save Port Address to memory address _localPortAddress = localPortAddress; // Check if the token is an active Strategy Token - if (IPort(_localPortAddress).isStrategyToken(_underlyingToken)) { - // If so, toggle it off. - IPort(_localPortAddress).toggleStrategyToken(_underlyingToken); - } else { - // If not, add it. - IPort(_localPortAddress).addStrategyToken(_underlyingToken, _minimumReservesRatio); - } + IPort(_localPortAddress).isStrategyToken(_underlyingToken) + ? IPort(_localPortAddress).toggleStrategyToken(_underlyingToken) + : IPort(_localPortAddress).addStrategyToken(_underlyingToken, _minimumReservesRatio); }
#0 - c4-pre-sort
2023-10-15T16:45:49Z
0xA5DF marked the issue as sufficient quality report
#1 - 0xA5DF
2023-10-15T16:46:01Z
G2 is in automated findings
#2 - c4-judge
2023-10-26T13:47:19Z
alcueca marked the issue as grade-b