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: 53/175
Findings: 1
Award: $78.19
🌟 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-
78.1884 USDC - $78.19
Number | Issue | Instances | Total gas saved |
---|---|---|---|
[G-01] | Use uint256(1)/uint256(2) instead for true and false boolean states | 24 | 240000 |
[G-02] | The creation of an intermediary array can be avoided | 14 | |
[G-03] | Use Modifiers Instead of Functions To Save Gas | 4 | |
[G-04] | Repeatedly doing the same operation (addition) involving a state variable should be avoided | 1 | |
[G-05] | Use the cached value instead of fetching a storage value | 3 | |
[G-06] | Unused initialization of variable inside they function | 2 | |
[G-07] | Optimizing the check order for cost efficient function execution | 3 | 6600 |
[G-08] | Do not cache immutable values | 2 | |
[G-09] | Use != 0 instead of > 0 for unsigned integer comparison | 18 | 54 |
[G-10] | Can Make The Variable Outside The Loop To Save Gas | 6 | |
[G-11] | Gas saving is achieved by removing the delete keyword (~60k) | 2 | 1490 |
[G-12] | With assembly, .call (bool success) transfer can be done gas-optimized | 4 | |
[G-13] | Duplicated require()/if() checks should be refactored to a modifier or function | 14 | |
[G-14] | Use a hardcoded address instead of address(this) | 39 | |
[G-15] | abi.encode() is less efficient than abi.encodePacked() | 15 | 769755 |
[G-16] | Multiple accesses of a mapping/array should use a local variable cache | 36 | 1512 |
[G-17] | Empty blocks should be removed or emit something | 3 | 17823 |
[G-18] | Uncheck arithmetics operations that can’t underflow/overflow | 14 | 1190 |
[G-19] | Access mappings directly rather than using accessor functions | 7 | |
[G-20] | Avoid contract existence checks by using low level calls | 48 | 4800 |
[G-21] | Amounts should be checked for 0 before calling a transfer | 11 | |
[G-22] | Internal functions that are not called by the contract should be removed to save deployment gas | 6 | |
[G-23] | The result of a function call should be cached rather than re-calling the function | 14 | 1400 |
[G-24] | Using storage instead of memory for structs/arrays saves gas | 14 | 58800 |
[G-25] | x += y costs more gas than x = x + y for state variables | 5 | 15 |
[G-26] | Counting down in for statements is more gas efficient | 14 | 170394 |
[G-27] | Use assembly to write address storage values | 6 | 444 |
[G-28] | Use do while loops instead of for loops | 9 | |
[G-29] | Use assembly to make more efficient back-to-back calls | 4 | |
[G-30] | Expensive operation inside a for loop | 3 | |
[G-31] | Change public function visibility to external | 6 | |
[G-32] | Create DepositMultipleInput and GasParams variable immutable variable to avoid redundant external calls | 5 | |
[G-33] | Use assembly in place of abi.decode to extract calldata values more efficiently | 29 | |
[G-34] | Use assembly to validate msg.sender | 25 | 75 |
[G-35] | Cache external calls outside of loop to avoid re-calling function on each iteration | 3 | |
[G-36] | A modifier used only once and not being inherited should be inlined to save gas | 9 | |
[G-37] | Not using the named return variable when a function returns, wastes deployment gas only in view function | 3 | 81 |
[G-38] | Using fixed bytes is cheaper than using string | 7 | |
[G-39] | Use assembly to check for address(0) | 8 | 48 |
[G-40] | State Variable can be packed into fewer storage slots | 1 | 20000 |
[G-41] | Caching global variables is more expensive than using the actual variable (use msg.sender or block.timestamp instead of caching it) | 1 | |
[G-42] | Using ERC721A instead of ERC721 for more gas-efficient | 2 | |
[G-43] | Don't apply the same value to state variables | 2 |
If you don’t use boolean for storage you will avoid Gwarmaccess 100 gas. In addition, state changes of boolean from true to false can cost up to ~20000 gas rather than uint256(2) to uint256(1) that would cost significantly less.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L130
file: src/BranchPort.sol 130 isBridgeAgentFactory[_bridgeAgentFactory] = true; 322 isBridgeAgent[_bridgeAgent] = true; 341 isBridgeAgentFactory[_newBridgeAgentFactory] = true; 80000 369 isStrategyToken[_token] = true; 390 isPortStrategy[_portStrategy][_token] = true; 420 isBridgeAgent[_coreBranchBridgeAgent] = true;
first create mapping us uint256
- mapping(address bridgeAgentFactory => bool isActiveBridgeAgentFactory) public isBridgeAgentFactory; + mapping(address bridgeAgentFactory => uint isActiveBridgeAgentFactory) public isBridgeAgentFactory; function initialize(address _coreBranchRouter, address _bridgeAgentFactory) external virtual onlyOwner { require(coreBranchRouterAddress == address(0), "Contract already initialized"); require(!isBridgeAgentFactory[_bridgeAgentFactory], "Contract already initialized"); require(_coreBranchRouter != address(0), "CoreBranchRouter is zero address"); require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address"); coreBranchRouterAddress = _coreBranchRouter; - isBridgeAgentFactory[_bridgeAgentFactory] = true; + isBridgeAgentFactory[_bridgeAgentFactory] = 1; bridgeAgentFactories.push(_bridgeAgentFactory); }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L113
file: src/RootPort.sol 113 isChainId[_localChainId] = true; 116 _setup = true; 117 _setupCore = true; 135 isBridgeAgentFactory[_bridgeAgentFactory] = true; 249 isGlobalAddress[_globalAddress] = true; 387 isBridgeAgent[_bridgeAgent] = true; 425 isBridgeAgentFactory[_bridgeAgentFactory] = true; 465 isChainId[_chainId] = true; 467 isGlobalAddress[newGlobalToken] = true; 499 isGlobalAddress[_ecoTokenGlobalAddress] = true;
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L76
file: src/CoreRootRouter.sol 76 setup = true;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1172
file: src/RootBridgeAgent.sol 1172 isBranchBridgeAgentAllowed[_branchChainId] = true;
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L85
file: src/CoreRootRouter.sol 85 _setup = false;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L133
file: src/RootPort.sol 133 _setup = false; 157 _setupCore = false;
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L44
file: src/CoreRootRouter.sol 44 bool internal _setup;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L24
file: src/RootPort.sol 24 bool internal _setup; 27 bool internal _setupCore;
Sometimes, it’s not necessary to create an intermediate array to store values. In this case, an array of maximum size is created because we don’t yet know what size the final array will be. This is not useful, as it’s more efficient to keep this maximum-size array, fill it and then reduce its size using assembly.
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1067
file: src/RootBridgeAgent.sol 1067 address[] memory hTokens = new address[](_globalAddresses.length); 1068 address[] memory tokens = new address[](_globalAddresses.length);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgentExecutor.sol#L276
file: src/RootBridgeAgentExecutor.sol 276 address[] memory hTokens = new address[](numOfAssets); 277 address[] memory tokens = new address[](numOfAssets); 278 uint256[] memory amounts = new uint256[](numOfAssets); 279 uint256[] memory deposits = new uint256[](numOfAssets);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L507
file: src/BranchBridgeAgent.sol 507 address[] memory _hTokens = new address[](numOfAssets); 508 address[] memory _tokens = new address[](numOfAssets); 509 uint256[] memory _amounts = new uint256[](numOfAssets); 510 uint256[] memory _deposits = new uint256[](numOfAssets); 827 address[] memory addressArray = new address[](1); 828 uint256[] memory uintArray = new uint256[](1);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1007
file: src/RootBridgeAgent.sol 1007 address[] memory addressArray = new address[](1); 1008 uint256[] memory uintArray = new uint256[](1);
Example of two contracts with modifiers and internal view function:
// SPDX-License-Identifier: MIT pragma solidity 0.8.9; contract Inlined { function isNotExpired(bool _true) internal view { require(_true == true, "Exchange: EXPIRED"); } function foo(bool _test) public returns(uint){ isNotExpired(_test); return 1; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.9; contract Modifier { modifier isNotExpired(bool _true) { require(_true == true, "Exchange: EXPIRED"); _; } function foo(bool _test) public isNotExpired(_test)returns(uint){ return 1; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L518-520
file: src/MulticallRootRouter.sol 518 function _decode(bytes calldata data) internal pure virtual returns (bytes memory) { return data; }
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L604-606
file: src/MulticallRootRouter.sol 604 function _requiresExecutor() internal view { if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor(); }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgentExecutor.sol#L53-L56
file: src/BranchBridgeAgentExecutor.sol 53 function executeNoSettlement(address _router, bytes calldata _payload) external payable onlyOwner { // Execute Calldata if there is code in the destination router IRouter(_router).executeNoSettlement{value: msg.value}(_payload[PARAMS_TKN_START_SIGNED:]); }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgentExecutor.sol#L243-L246
file: src/RootBridgeAgentExecutor.sol 243 function _bridgeIn(address _recipient, DepositParams memory _dParams, uint16 _srcChainId) internal { //Request assets for decoded request. RootBridgeAgent(payable(msg.sender)).bridgeIn(_recipient, _dParams, _srcChainId); }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgentExecutor.sol#L201-L235
Gas saved on average: 548
file: src/RootBridgeAgentExecutor.sol 201 function executeSignedWithDepositMultiple( address _account, address _router, bytes calldata _payload, uint16 _srcChainId ) external payable onlyOwner { //Bridge In Assets DepositMultipleParams memory dParams = _bridgeInMultiple( _account, _payload[ PARAMS_START_SIGNED: PARAMS_END_SIGNED_OFFSET + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE ], _srcChainId ); // Check if there is additional calldata in the payload if ( _payload.length > PARAMS_END_SIGNED_OFFSET + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE ) { //Execute remote request IRouter(_router).executeSignedDepositMultiple{value: msg.value}( _payload[ PARAMS_END_SIGNED_OFFSET + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE: ], dParams, _account, _srcChainId ); } }
In the function above, the operation PARAMS_START_SIGNED: PARAMS_END_SIGNED_OFFSET + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE is being done repeatedly. the amount of gas becomes too much. We should do the operation and cache the result in a memory variable.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L144-L157
file: src/BranchPort.sol 144 function manage(address _token, uint256 _amount) external override requiresPortStrategy(_token) { // Cache Strategy Token Global Debt uint256 _strategyTokenDebt = getStrategyTokenDebt[_token]; // Check if request would surpass the tokens minimum reserves if (_amount > _excessReserves(_strategyTokenDebt, _token)) revert InsufficientReserves(); // Check if request would surpass the Port Strategy's daily limit _checkTimeLimit(_token, _amount); // Update Strategy Token Global Debt getStrategyTokenDebt[_token] = _strategyTokenDebt + _amount; // Update Port Strategy Token Debt getPortStrategyTokenDebt[msg.sender][_token] += _amount; 188 function replenishReserves(address _strategy, address _token) external override lock { // Cache Strategy Token Global Debt uint256 strategyTokenDebt = getStrategyTokenDebt[_token]; // Get current balance of _token uint256 currBalance = ERC20(_token).balanceOf(address(this)); // Get reserves lacking uint256 reservesLacking = _reservesLacking(strategyTokenDebt, _token, currBalance); // Cache Port Strategy Token Debt uint256 portStrategyTokenDebt = getPortStrategyTokenDebt[_strategy][_token]; // Calculate amount to withdraw. The lesser of reserves lacking or Strategy Token Global Debt. uint256 amountToWithdraw = portStrategyTokenDebt < reservesLacking ? portStrategyTokenDebt : reservesLacking; // Update Port Strategy Token Debt getPortStrategyTokenDebt[_strategy][_token] = portStrategyTokenDebt - amountToWithdraw; // Update Strategy Token Global Debt getStrategyTokenDebt[_token] = strategyTokenDebt - amountToWithdraw;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L485-L494
file: src/BranchPort.sol 485 function _checkTimeLimit(address _token, uint256 _amount) internal { uint256 dailyLimit = strategyDailyLimitRemaining[msg.sender][_token]; if (block.timestamp - lastManaged[msg.sender][_token] >= 1 days) { dailyLimit = strategyDailyLimitAmount[msg.sender][_token]; unchecked { lastManaged[msg.sender][_token] = (block.timestamp / 1 days) * 1 days; } } strategyDailyLimitRemaining[msg.sender][_token] = dailyLimit - _amount; }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1045-L1114
file: src/RootBridgeAgent.sol 1045 function _createSettlementMultiple( uint32 _settlementNonce, address payable _refundee, address _recipient, uint16 _dstChainId, address[] memory _globalAddresses, uint256[] memory _amounts, uint256[] memory _deposits, bytes memory _params, bool _hasFallbackToggled ) internal returns (bytes memory _payload) { // Check if valid length if (_globalAddresses.length > MAX_TOKENS_LENGTH) revert InvalidInputParamsLength(); // Check if valid length if (_globalAddresses.length != _amounts.length) revert InvalidInputParamsLength(); if (_amounts.length != _deposits.length) revert InvalidInputParamsLength(); //Update Settlement Nonce settlementNonce = _settlementNonce + 1; // Create Arrays address[] memory hTokens = new address[](_globalAddresses.length); address[] memory tokens = new address[](_globalAddresses.length); for (uint256 i = 0; i < hTokens.length;) { // Populate Addresses for Settlement hTokens[i] = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddresses[i], _dstChainId); tokens[i] = IPort(localPortAddress).getUnderlyingTokenFromLocal(hTokens[i], _dstChainId); // Avoid stack too deep uint16 destChainId = _dstChainId; // Update State to reflect bridgeOut _updateStateOnBridgeOut( msg.sender, _globalAddresses[i], hTokens[i], tokens[i], _amounts[i], _deposits[i], destChainId ); unchecked { ++i; } } // Prepare data for call with settlement of multiple assets _payload = abi.encodePacked( _hasFallbackToggled ? bytes1(0x02) & 0x0F : bytes1(0x02), _recipient, uint8(hTokens.length), _settlementNonce, hTokens, tokens, _amounts, _deposits, _params ); // Create and Save Settlement // Get storage reference Settlement storage settlement = getSettlement[_settlementNonce]; // Update Setttlement settlement.owner = _refundee; settlement.recipient = _recipient; settlement.hTokens = hTokens; settlement.tokens = tokens; settlement.amounts = _amounts; settlement.deposits = _deposits; settlement.dstChainId = _dstChainId; settlement.status = STATUS_SUCCESS; } 966 function _createSettlement( uint32 _settlementNonce, address payable _refundee, address _recipient, uint16 _dstChainId, bytes memory _params, address _globalAddress, uint256 _amount, uint256 _deposit, bool _hasFallbackToggled ) internal returns (bytes memory _payload) { // Update Settlement Nonce settlementNonce = _settlementNonce + 1; // Get Local Branch Token Address from Root Port address localAddress = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddress, _dstChainId); // Get Underlying Token Address from Root Port address underlyingAddress = IPort(localPortAddress).getUnderlyingTokenFromLocal(localAddress, _dstChainId); //Update State to reflect bridgeOut _updateStateOnBridgeOut( msg.sender, _globalAddress, localAddress, underlyingAddress, _amount, _deposit, _dstChainId ); // Prepare data for call _payload = abi.encodePacked( _hasFallbackToggled ? bytes1(0x81) : bytes1(0x01), _recipient, _settlementNonce, localAddress, underlyingAddress, _amount, _deposit, _params ); // Avoid stack too deep uint32 cachedSettlementNonce = _settlementNonce; // Get Auxiliary Dynamic Arrays address[] memory addressArray = new address[](1); uint256[] memory uintArray = new uint256[](1); // Get storage reference for new Settlement Settlement storage settlement = getSettlement[cachedSettlementNonce]; // Update Setttlement settlement.owner = _refundee; settlement.recipient = _recipient; addressArray[0] = localAddress; settlement.hTokens = addressArray; addressArray[0] = underlyingAddress; settlement.tokens = addressArray; uintArray[0] = _amount; settlement.amounts = uintArray; uintArray[0] = _deposit; settlement.deposits = uintArray; settlement.dstChainId = _dstChainId; settlement.status = STATUS_SUCCESS; }
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas) in a function that may ultimately revert, in the unhappy case.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L434-L439
file: src/BranchBridgeAgent.sol 434 function redeemDeposit(uint32 _depositNonce) external override lock { // Get storage reference Deposit storage deposit = getDeposit[_depositNonce]; // Check Deposit if (deposit.status == STATUS_SUCCESS) revert DepositRedeemUnavailable();
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L587-L646
file: src/BranchBridgeAgent.sol 587 function lzReceiveNonBlocking(address _endpoint, bytes calldata _srcAddress, bytes calldata _payload) public override requiresEndpoint(_endpoint, _srcAddress) { //Save Action Flag bytes1 flag = _payload[0] & 0x7F; // Save settlement nonce uint32 nonce; // DEPOSIT FLAG: 0 (No settlement) if (flag == 0x00) { // Get Settlement Nonce nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])); //Check if tx has already been executed if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction(); //Try to execute the remote request //Flag 0 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoSettlement(localRouterAddress, _payload) _execute( nonce, abi.encodeWithSelector( BranchBridgeAgentExecutor.executeNoSettlement.selector, localRouterAddress, _payload ) ); // DEPOSIT FLAG: 1 (Single Asset Settlement) } else if (flag == 0x01) { // Parse recipient address payable recipient = payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))); // Parse Settlement Nonce nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])); //Check if tx has already been executed if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction(); //Try to execute the remote request //Flag 1 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement(recipient, localRouterAddress, _payload) _execute( _payload[0] == 0x81, nonce, recipient, abi.encodeWithSelector( BranchBridgeAgentExecutor.executeWithSettlement.selector, recipient, localRouterAddress, _payload ) ); // DEPOSIT FLAG: 2 (Multiple Settlement) } else if (flag == 0x02) { // Parse recipient address payable recipient = payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))); // Parse deposit nonce nonce = uint32(bytes4(_payload[22:26])); //Check if tx has already been executed if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L299-L307
file: src/RootBridgeAgent.sol 299 function redeemSettlement(uint32 _settlementNonce) external override lock { // Get setttlement storage reference Settlement storage settlement = getSettlement[_settlementNonce]; // Get deposit owner. address settlementOwner = settlement.owner; // Check if Settlement is redeemable. if (settlement.status == STATUS_SUCCESS) revert SettlementRedeemUnavailable();
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L50-57
file: src/ArbitrumBranchPort.sol 50 function depositToPort(address _depositor, address _recipient, address _underlyingAddress, uint256 _deposit) external override lock requiresBridgeAgent { // Save root port address to memory address _rootPortAddress = rootPortAddress; 73 function withdrawFromPort(address _depositor, address _recipient, address _globalAddress, uint256 _amount) external override lock requiresBridgeAgent { // Save root port address to memory address _rootPortAddress = rootPortAddress;
It’s generally more gas-efficient to use != 0 instead of > 0 when comparing unsigned integer types.
This is because the Solidity compiler can optimize the != 0 comparison to a simple bitwise operation, while the > 0 comparison requires an additional subtraction operation. As a result, using != 0 can be more gas-efficient and can help to reduce the overall cost of your contract.
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L127
File: src/ArbitrumBranchPort.sol 127: if (_deposit > 0) { 132: if (_amount - _deposit > 0) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L165
File: src/BaseBranchRouter.sol 165: if (_amount - _deposit > 0) { 173: if (_deposit > 0) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L906
File: src/BranchBridgeAgent.sol 906: if (_amount - _deposit > 0) { 912: if (_deposit > 0) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L259
File: src/BranchPort.sol 259: if (_amounts[i] - _deposits[i] > 0) { 266: if (_deposits[i] > 0) { 525: if (_hTokenAmount > 0) { 531: if (_deposit > 0) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L363
File: src/RootBridgeAgent.sol 363: if (_dParams.amount > 0) { 370: if (_dParams.deposit > 0) { 1146: if (_underlyingAddress == address(0)) if (_deposit > 0) revert UnrecognizedUnderlyingAddress(); 1149: if (_amount - _deposit > 0) { 1156: if (_deposit > 0) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L284
File: src/RootPort.sol 284: if (_amount - _deposit > 0) { 290: if (_deposit > 0) if (!ERC20hTokenRoot(_hToken).mint(_recipient, _deposit, _srcChainId)) revert UnableToMint();
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L152
File: src/VirtualAccount.sol 152: return size > 0;
When you declare a variable inside a loop, Solidity creates a new instance of the variable for each iteration of the loop. This can lead to unnecessary gas costs; especially if the loop is executed frequently or iterates over a large number of elements.
By declaring the variable outside of the loop, you can avoid the creation of multiple instances of the variable and reduce the gas cost of your contract.
Here’s an example:
contract MyContract { function sum(uint256[] memory values) public pure returns (uint256) { uint256 total = 0; for (uint256 i = 0; i < values.length; i++) { total += values[i]; } return total; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L318-L340
file: src/RootBridgeAgent.sol 318 for (uint256 i = 0; i < settlement.hTokens.length;) { // Save to memory address _hToken = settlement.hTokens[i]; // Check if asset if (_hToken != address(0)) { // Save to memory uint24 _dstChainId = settlement.dstChainId; // Move hTokens from Branch to Root + Mint Sufficient hTokens to match new port deposit IPort(localPortAddress).bridgeToRoot( msg.sender, IPort(localPortAddress).getGlobalTokenFromLocal(_hToken, _dstChainId), settlement.amounts[i], settlement.deposits[i], _dstChainId ); } unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1070-1086
file: src/RootBridgeAgent.sol 1070 for (uint256 i = 0; i < hTokens.length;) { // Populate Addresses for Settlement hTokens[i] = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddresses[i], _dstChainId); tokens[i] = IPort(localPortAddress).getUnderlyingTokenFromLocal(hTokens[i], _dstChainId); // Avoid stack too deep uint16 destChainId = _dstChainId; // Update State to reflect bridgeOut _updateStateOnBridgeOut( msg.sender, _globalAddresses[i], hTokens[i], tokens[i], _amounts[i], _deposits[i], destChainId ); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L513-L529
file: src/BranchBridgeAgent.sol 513 for (uint256 i = 0; i < numOfAssets;) { // Cache common offset uint256 currentIterationOffset = PARAMS_START + i; // Parse Params _hTokens[i] = address( uint160( bytes20( bytes32( _sParams[ PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET: PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset) ] ) ) ) );
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L70-L81
file: src/VirtualAccount.sol 70 for (uint256 i = 0; i < length;) { bool success; Call calldata _call = calls[i]; if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData); if (!success) revert CallFailed(); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L90-L108
file: src/VirtualAccount.sol 90 for (uint256 i = 0; i < length;) { _call = calls[i]; uint256 val = _call.value; // Humanity will be a Type V Kardashev Civilization before this overflows - andreas // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 unchecked { valAccumulator += val; } bool success; if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData); if (!success) revert CallFailed(); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgentExecutor.sol#L218-L296
file: src/RootBridgeAgentExecutor.sol 218 for (uint256 i = 0; i < uint256(uint8(numOfAssets));) { // Cache offset uint256 currentIterationOffset = PARAMS_START + i; // Parse Params hTokens[i] = address( uint160( bytes20( bytes32( _dParams[ PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET: PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset) ] ) ) )
30k gas savings were made by removing the delete keyword. The reason for using the delete keyword here is to reset the struct values (set to default value) in every operation. However, the struct values do not need to be zero each time the function is run. Therefore, the delete keyword is unnecessary. If it is removed, around 30k gas savings will be achieved.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L456
File: src/BranchBridgeAgent.sol 456 delete getDeposit[_depositNonce];
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L343
File: src/RootBridgeAgent.sol 343 delete getSettlement[_settlementNonce];
Return data (bool success,) has to be stored due to EVM architecture. But in a usage like below, ‘out’ and ‘outsize’ values are given (0,0); this storage disappears and gas optimization is provided.
(bool success,) = dest.call{value:amount}(""); bool success; assembly { success := call(gas(), dest, amount, 0, 0) }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L717
file: src/BranchBridgeAgent.sol 717 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); 773 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L754
file: src/RootBridgeAgent.sol 754 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); 779 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);
Sign modifiers or functions can make your code more gas-efficient by reducing the overall number of operations that need to be executed. For example, if you have a complex validation check that involves multiple operations and you refactor it into a function, then the function can be executed with a single opcode, rather than having to execute each operation separately in multiple locations.
Recommendation: You can consider adding a modifier like below:
modifer check (address checkToAddress) { require(checkToAddress != address(0) && checkToAddress != SENTINEL_MODULES, "BSA101"); _; }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L354
file: src/BranchBridgeAgent.sol 354 if (deposit.owner != msg.sender) revert NotDepositOwner(); 441 if (deposit.owner != msg.sender) revert NotDepositOwner(); 604 if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction(); 624 if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction(); 646 if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L387
file: src/BranchPort.sol 387 if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken(); 560 if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L142
file: src/MulticallRootRouter.sol 142 if (funcId == 0x01) 234 if (funcId == 0x01) 323 if (funcId == 0x01) 411 if (funcId == 0x01)
file: 244 if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable(); 670 if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable(); 282 if (settlementOwner == address(0)) revert SettlementRetrieveUnavailable(); 308 if (settlementOwner == address(0)) revert SettlementRedeemUnavailable(); 285 if (msg.sender != settlementOwner) { 311 if (msg.sender != settlementOwner) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L449
file: src/RootBridgeAgent.sol 449 if (executionState[_srcChainId][nonce] != STATUS_READY) { 472 if (executionState[_srcChainId][nonce] != STATUS_READY) { 495 if (executionState[_srcChainId][nonce] != STATUS_READY) { 518 if (executionState[_srcChainId][nonce] != STATUS_READY) { 544 if (executionState[_srcChainId][nonce] != STATUS_READY) { 582 if (executionState[_srcChainId][nonce] != STATUS_READY) { 622 if (executionState[_srcChainId][nonce] != STATUS_READY) { 818 if (callee == address(0)) revert UnrecognizedBridgeAgent(); 910 if (callee == address(0)) revert UnrecognizedBridgeAgent(); 821 if (_dstChainId != localChainId) 913 if (_dstChainId != localChainId)
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L246
file: src/RootPort.sol 246 if (_localAddress == address(0)) revert InvalidLocalAddress(); 264 if (_localAddress == address(0)) revert InvalidLocalAddress(); 282 if (!isGlobalAddress[_hToken]) revert UnrecognizedToken(); 299 if (!isGlobalAddress[_hToken]) revert UnrecognizedToken(); 309 if (!isGlobalAddress[_hToken]) revert UnrecognizedToken(); 320 if (!isGlobalAddress[_hToken]) revert UnrecognizedToken(); 330 if (!isGlobalAddress[_hToken]) revert UnrecognizedToken(); 341 if (!isGlobalAddress[_hToken]) revert UnrecognizedToken(); 528 if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter(); 544 if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter(); 529 if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent(); 545 if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();
It can be more gas-efficient to use a hardcoded address instead of the address(this) expression, especially if you need to use the same address multiple times in your contract.
The reason for this is that using address(this) requires an additional EXTCODESIZE operation to retrieve the contract’s address from its bytecode, which can increase the gas cost of your contract. By pre-calculating and using a hardcoded address, you can avoid this additional operation and reduce the overall gas cost of your contract.
Here’s an example of how you can use a hardcoded address instead of address(this):
contract MyContract { address public myAddress = 0x1234567890123456789012345678901234567890; function doSomething() public { // Use myAddress instead of address(this) require(msg.sender == myAddress, "Caller is not authorized"); // Do something } }
In the above example, we have a contract, MyContract, with a public address variable myAddress. Instead of using address(this) to retrieve the contract’s address, we have pre-calculated and hardcoded the address in the variable. This can help to reduce the gas cost of our contract and make our code more efficient.
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchBridgeAgent.sol#L126
file: src/ArbitrumBranchBridgeAgent.sol 126 if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L66
file: src/ArbitrumBranchPort.sol 66 _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit); 128 _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L167
file: src/BaseBranchRouter.sol 167 _hToken.safeTransferFrom(msg.sender, address(this), _amount - _deposit); 174 _token.safeTransferFrom(msg.sender, address(this), _deposit);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L131
file: src/BranchBridgeAgent.sol 131 require(_localPortAddress != address(0), "Local Port Address cannot be the zero address."); 142 rootBridgeAgentPath = abi.encodePacked(_rootBridgeAgentAddress, address(this)); 168 address(this), 579 address(this).excessivelySafeCall( 717 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); 737 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); 787 ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}( 938 if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgentExecutor.sol#L92
file: src/BranchBridgeAgentExecutor.sol 92 _recipient.safeTransferETH(address(this).balance); 126 _recipient.safeTransferETH(address(this).balance);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L175
file: src/BranchPort.sol 175 uint256 currBalance = ERC20(_token).balanceOf(address(this)); 178 IPortStrategy(msg.sender).withdraw(address(this), _token, _amount); 181 require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed"); 193 uint256 currBalance = ERC20(_token).balanceOf(address(this)); 210 IPortStrategy(_strategy).withdraw(address(this), _token, amountToWithdraw); 214 ERC20(_token).balanceOf(address(this)) - currBalance == amountToWithdraw, "Port Strategy Withdraw Failed" 436 uint256 currBalance = ERC20(_token).balanceOf(address(this)); 526 _localAddress.safeTransferFrom(_depositor, address(this), _hTokenAmount); 532 _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L112
file: src/RootBridgeAgent.sol 112 require(_localPortAddress != address(0), "Port Address cannot be zero address"); 120 bridgeAgentExecutorAddress = DeployRootBridgeAgentExecutor.deploy(address(this)); 148 address(this), 424 (bool success,) = address(this).excessivelySafeCall( 695 address(this).balance 754 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); 779 (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); 940 ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}( 1182 getBranchBridgeAgentPath[_branchChainId] = abi.encodePacked(_newBranchBridgeAgent, address(this)); 1205 if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint(); 1233 if (msg.sender != IPort(localPortAddress).getBridgeAgentManager(address(this))) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L301
file: src/RootPort.sol 301 _hToken.safeTransferFrom(_from, address(this), _amount); 362 newAccount = new VirtualAccount{salt: keccak256(abi.encode(_user))}(_user, address(this));
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L62
file: src/VirtualAccount.sol 62 ERC721(_token).transferFrom(address(this), msg.sender, _tokenId);
https://github.com/code-423n4/2023-09-maia/blob/main/src/factories/ERC20hTokenRootFactory.sol#L83
file: src/factories/ERC20hTokenRootFactory.sol 83 address(this),
In terms of efficiency, abi.encodePacked() is generally considered to be more gas-efficient than abi.encode(), because it skips the step of adding function signatures and other metadata to the encoded data. However, this comes at the cost of reduced safety, as abi.encodePacked() does not perform any type checking or padding of data.
Before using of encodePacked gas value per one abi.encode: 405030 After using of encodePacked instead of encode gas value per one abi.encodePacked: 353713
Tools used remix
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumCoreBranchRouter.sol#L53
File: src/ArbitrumCoreBranchRouter.sol 53: bytes memory params = abi.encode( 112: bytes memory data = abi.encode(newBridgeAgent, _rootBridgeAgent);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L471
File: src/BranchBridgeAgent.sol 471: bytes memory params = abi.encode(_settlementNonce, msg.sender, _params, _gParams[1]);
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreBranchRouter.sol#L48
File: src/CoreBranchRouter.sol 48: bytes memory params = abi.encode(msg.sender, _globalAddress, _dstChainId, [_gParams[1], _gParams[2]]); 72: bytes memory params = abi.encode(_underlyingAddress, newToken, newToken.name(), newToken.symbol(), decimals); 178: bytes memory params = abi.encode(_globalAddress, newToken); 226: bytes memory params = abi.encode(newBridgeAgent, _rootBridgeAgent);
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L126
File: src/CoreRootRouter.sol 126: bytes memory params = abi.encode( 168: bytes memory params = abi.encode(_branchBridgeAgentFactory); 193: bytes memory params = abi.encode(_branchBridgeAgent); 220: bytes memory params = abi.encode(_underlyingToken, _minimumReservesRatio); 251: bytes memory params = abi.encode(_portStrategy, _underlyingToken, _dailyManagementLimit, _isUpdateDailyLimit); 281: bytes memory params = abi.encode(_coreBranchRouter, _coreBranchBridgeAgent); 424: bytes memory params = abi.encode(
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L362
File: src/RootPort.sol 362: newAccount = new VirtualAccount{salt: keccak256(abi.encode(_user))}(_user, address(this));
The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping’s value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access, due to not having to recalculate the keys keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations. Caching an array’s struct avoids recalculating the array offsets into memory/calldata.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L604
file: src/BranchBridgeAgent.sol 604 if (executionState[nonce] != STATUS_READY) 624 if (executionState[nonce] != STATUS_READY) 646 if (executionState[nonce] != STATUS_READY) 671 if (executionState[nonce] != STATUS_READY) 675 if (executionState[nonce] == STATUS_READY) executionState[nonce] = STATUS_RETRIEVE;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L734
file: src/BranchBridgeAgent.sol 734 executionState[_settlementNonce] = STATUS_DONE; 744 executionState[_settlementNonce] = STATUS_RETRIEVE;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L436
file: src/BranchBridgeAgent.sol 436 Deposit storage deposit = getDeposit[_depositNonce]; 456 delete getDeposit[_depositNonce];
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L124
file: src/BranchPort.sol 124 require(!isBridgeAgentFactory[_bridgeAgentFactory], "Contract already initialized"); 130 isBridgeAgentFactory[_bridgeAgentFactory] = true; 339 if (isBridgeAgentFactory[_newBridgeAgentFactory]) revert AlreadyAddedBridgeAgentFactory(); 341 isBridgeAgentFactory[_newBridgeAgentFactory] = true;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L487
file: src/BranchPort.sol 487 if (block.timestamp - lastManaged[msg.sender][_token] >= 1 days) 490 lastManaged[msg.sender][_token] = (block.timestamp / 1 days) * 1 days;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1207
file: src/RootBridgeAgent.sol 1207 if (_endpoint != getBranchBridgeAgent[localChainId]) { 1212 if (getBranchBridgeAgent[_srcChain] != address(uint160(bytes20(_srcAddress[PARAMS_ADDRESS_SIZE:])))) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L301
file: src/RootBridgeAgent.sol 301 Settlement storage settlement = getSettlement[_settlementNonce]; 343 delete getSettlement[_settlementNonce];
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L449
file: src/RootBridgeAgent.sol 449 if (executionState[_srcChainId][nonce] != STATUS_READY) { 472 if (executionState[_srcChainId][nonce] != STATUS_READY) { 495 if (executionState[_srcChainId][nonce] != STATUS_READY) { 518 if (executionState[_srcChainId][nonce] != STATUS_READY) { 544 if (executionState[_srcChainId][nonce] != STATUS_READY) { 582 if (executionState[_srcChainId][nonce] != STATUS_READY) { 622 if (executionState[_srcChainId][nonce] != STATUS_READY) { 704 if (executionState[_srcChainId][nonce] != STATUS_READY) { 708 if (executionState[_srcChainId][nonce] != STATUS_READY) { 709 if (executionState[_srcChainId][nonce] != STATUS_READY) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L383
file: 383 if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent(); 387 isBridgeAgent[_bridgeAgent] = true;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L422
file: src/RootPort.sol 422 if (isBridgeAgentFactory[_bridgeAgentFactory]) revert AlreadyAddedBridgeAgentFactory(); 425 isBridgeAgentFactory[_bridgeAgentFactory] = true;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L374
file: src/RootPort.sol 374 isRouterApproved[_userAccount][_router] = !isRouterApproved[_userAccount][_router];
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L485
file: src/RootPort.sol 485 if (isGlobalAddress[_ecoTokenGlobalAddress]) revert AlreadyAddedEcosystemToken(); 499 isGlobalAddress[_ecoTokenGlobalAddress] = true;
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}}).
Before removing of receive function gas value: 220636 After removing of receive function gas value: 214695
Tools used remix
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L149
file: src/BranchBridgeAgent.sol 149 receive() external payable {}
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L128
file: src/RootBridgeAgent.sol 128 receive() external payable {}
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L44
file: src/VirtualAccount.sol 44 receive() external payable {}
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block
file: uint256 value = yield(a, b, c - totalFee(c), address(this));
unchecked {uint256 value = yield(a, b, c - totalFee(c), address(this));}
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L169
file: src/BranchPort.sol 169 getPortStrategyTokenDebt[msg.sender][_token] -= _amount; 172 getStrategyTokenDebt[_token] -= _amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/token/ERC20hTokenRoot.sol#L70
file: src/token/ERC20hTokenRoot.sol 70 getTokenBalance[chainId] -= amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L157
file: src/BranchPort.sol 157 getPortStrategyTokenDebt[msg.sender][_token] += _amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/token/ERC20hTokenRoot.sol#L58
file: src/token/ERC20hTokenRoot.sol 58 getTokenBalance[chainId] += amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L205
file: src/BranchPort.sol 205 getPortStrategyTokenDebt[_strategy][_token] = portStrategyTokenDebt - amountToWithdraw; 207 getStrategyTokenDebt[_token] = strategyTokenDebt - amountToWithdraw; 493 strategyDailyLimitRemaining[msg.sender][_token] = dailyLimit - _amount; 522 uint256 _hTokenAmount = _amount - _deposit;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L515
file: src/BranchBridgeAgent.sol 515 uint256 currentIterationOffset = PARAMS_START + i; 821 depositNonce = _depositNonce + 1; 875 depositNonce = _depositNonce + 1;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgentExecutor.sol#L111
file: src/BranchBridgeAgentExecutor.sol 111 uint256 settlementEndOffset = PARAMS_START_SIGNED + PARAMS_TKN_START + assetsOffset;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L155
file: src/BranchPort.sol 155 getStrategyTokenDebt[_token] = _strategyTokenDebt + _amount;
Saves having to do two JUMP instructions, along with stack setup.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L157
file: src/BranchBridgeAgent.sol 157 return getDeposit[_depositNonce];
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L136
file: src/RootBridgeAgent.sol 136 return getSettlement[_settlementNonce];
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L196
file: src/RootPort.sol 196 return getLocalTokenFromGlobal[globalAddress][_dstChainId]; 207 return getUnderlyingTokenFromLocal[localAddress][_srcChainId]; 212 return getLocalTokenFromGlobal[_globalAddress][_srcChainId] != address(0); 217 return getGlobalTokenFromLocal[_localAddress][_srcChainId] != address(0); 231 return getLocalTokenFromUnderlying[_underlyingToken][_srcChainId] != address(0);
Prior to 0.8.10, the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence.
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L60
file: src/ArbitrumBranchPort.sol 60 address _globalToken = IRootPort(_rootPortAddress).getLocalTokenFromUnderlying(_underlyingAddress, localChainId); 69 IRootPort(_rootPortAddress).mintToLocalBranch(_recipient, _globalToken, _deposit); 87 IRootPort(_rootPortAddress).getUnderlyingTokenFromLocal(_globalAddress, localChainId); 92 IRootPort(_rootPortAddress).burnFromLocalBranch(_depositor, _globalAddress, _amount);
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumCoreBranchRouter.sol#L58
file: src/ArbitrumCoreBranchRouter.sol 58 ERC20(_underlyingAddress).decimals() 118 IBridgeAgent(localBridgeAgentAddress).callOutSystem(payable(_refundee), payload, _gParams)
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L63
file: src/BaseBranchRouter.sol 63 localPortAddress = IBridgeAgent(_localBridgeAgentAddress).localPortAddress(); 64 bridgeAgentExecutorAddress = IBridgeAgent(_localBridgeAgentAddress).bridgeAgentExecutorAddress(); 75 return IBridgeAgent(localBridgeAgentAddress).getDepositEntry(_depositNonce); 113 IBridgeAgent(localBridgeAgentAddress).callOutAndBridgeMultiple{value: msg.value}( 168 ERC20(_hToken).approve(_localPortAddress, _amount - _deposit); 175 ERC20(_token).approve(_localPortAddress, _deposit);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L568
file: src/BranchBridgeAgent.sol 568 IPort(localPortAddress).bridgeInMultiple(_recipient, _hTokens, _tokens, _amounts, _deposits); 824 IPort(localPortAddress).bridgeOut(msg.sender, _hToken, _token, _amount, _deposit); 908 IPort(localPortAddress).bridgeIn(_recipient, _hToken, _amount - _deposit); 913 IPort(localPortAddress).withdraw(_recipient, _token, _deposit);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L175
file: src/BranchPort.sol 175 uint256 currBalance = ERC20(_token).balanceOf(address(this)); 178 IPortStrategy(msg.sender).withdraw(address(this), _token, _amount); 193 uint256 currBalance = ERC20(_token).balanceOf(address(this)); 210 IPortStrategy(_strategy).withdraw(address(this), _token, amountToWithdraw); 436 uint256 currBalance = ERC20(_token).balanceOf(address(this)); 503 ERC20hTokenBranch(_localAddress).mint(_recipient, _amount); 527 ERC20hTokenBranch(_localAddress).burn(_hTokenAmount);
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreBranchRouter.sol#L64
file: src/CoreBranchRouter.sol 64 uint8 decimals = ERC20(_underlyingAddress).decimals(); 68 ERC20(_underlyingAddress).name(), ERC20(_underlyingAddress).symbol(), decimals, true 143 IPort(localPortAddress).setCoreBranchRouter(coreBranchRouter, coreBranchBridgeAgent);
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L113
file: src/MulticallRootRouter.sol 113 bridgeAgentExecutorAddress = IBridgeAgent(_bridgeAgentAddress).bridgeAgentExecutorAddress(); 139 IVirtualAccount(userAccount).call(calls); 248 IVirtualAccount(userAccount).call(calls); 255 IVirtualAccount(userAccount).userAddress(), 328 IVirtualAccount(userAccount).call(calls); 337 IVirtualAccount(userAccount).call(calls); 416 IVirtualAccount(userAccount).call(calls); 425 IVirtualAccount(userAccount).call(calls); 452 IVirtualAccount(userAccount).call(calls);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L574
file: src/RootBridgeAgent.sol 574 IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); 592 IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); 614 IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); 837 IBranchBridgeAgent(callee).lzReceive(0, "", 0, _payload); 929 IBranchBridgeAgent(callee).lzReceive(0, "", 0, payload); 981 address localAddress = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddress, _dstChainId); 984 address underlyingAddress = IPort(localPortAddress).getUnderlyingTokenFromLocal(localAddress, _dstChainId); 1072 hTokens[i] = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddresses[i], _dstChainId); 1073 tokens[i] = IPort(localPortAddress).getUnderlyingTokenFromLocal(hTokens[i], _dstChainId);
file: src/factories/ArbitrumBranchBridgeAgentFactory.sol 65 IPort(localPortAddress).addBridgeAgent(newCoreBridgeAgent);
https://github.com/code-423n4/2023-09-maia/blob/main/src/factories/BranchBridgeAgentFactory.sol#L101
file: src/factories/BranchBridgeAgentFactory.sol 101 IPort(localPortAddress).addBridgeAgent(newCoreBridgeAgent); 139 IPort(localPortAddress).addBridgeAgent(newBridgeAgent);
https://github.com/code-423n4/2023-09-maia/blob/main/src/factories/RootBridgeAgentFactory.sol#L58
file: src/factories/RootBridgeAgentFactory.sol 58 IRootPort(rootPortAddress).addBridgeAgent(msg.sender, newBridgeAgent);
It is generally a good practice to check for zero values before making any transfers in smart contract functions. This can help to avoid unnecessary external calls and save gas costs.
Checking for zero values is especially important when transferring tokens or ether, as sending these assets to an address with a zero value will result in the loss of those assets.
In Solidity, you can check whether a value is zero by using the == operator. Here’s an example of how you can check for a zero value before making a transfer:
function transfer(address payable recipient, uint256 amount) public { require(amount > 0, "Amount must be greater than zero"); recipient.transfer(amount); } `` In the above example, we check to make sure that the amount parameter is greater than zero before making the transfer to the recipient address. If the amount is zero or negative, the function will revert and the transfer will not be made. ```solidity File: src/erc-4626/ERC4626.sol 76 address(asset).safeTransfer(receiver, assets); 96 address(asset).safeTransfer(receiver, assets);
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L94
file: src/ArbitrumBranchPort.sol 94 _underlyingAddress.safeTransfer(_recipient, _amount);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L95
file: src/BaseBranchRouter.sol 95 _transferAndApproveToken(_dParams.hToken, _dParams.token, _dParams.amount, _dParams.deposit); 110 _transferAndApproveMultipleTokens(_dParams.hTokens, _dParams.tokens, _dParams.amounts, _dParams.deposits);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L160
file: src/BranchPort.sol 160 _token.safeTransfer(msg.sender, _amount); 233 _underlyingAddress.safeTransfer(_recipient, _deposit);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L301
file: src/RootPort.sol 301 _hToken.safeTransferFrom(_from, address(this), _amount); 311 _hToken.safeTransfer(_to, _amount);
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L52
file: src/VirtualAccount.sol 52 msg.sender.safeTransferETH(_amount); 57 _token.safeTransfer(msg.sender, _amount);
Internal functions in Solidity are only intended to be invoked within the contract or by other internal functions. If an internal function is not called anywhere within the contract, it serves no purpose and contributes unnecessary overhead during deployment. Removing such functions can lead to substantial gas savings.
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchBridgeAgent.sol#L99
file: File: src/ArbitrumBranchBridgeAgent.sol 99 function _performCall(address payable, bytes memory _calldata, GasParams calldata) internal override { 112 function _performFallbackCall(address payable, uint32 _settlementNonce) internal override { 125 function _requiresEndpoint(address _endpoint, bytes calldata) internal view override {
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L107
File: src/ArbitrumBranchPort.sol 107: function _bridgeIn(address _recipient, address _localAddress, uint256 _amount) internal override { 119 function _bridgeOut( address _depositor, address _localAddress, address _underlyingAddress, uint256 _amount, uint256 _deposit ) internal override {
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouterLibZip.sol#L37
File: src/MulticallRootRouterLibZip.sol 37: function _decode(bytes calldata data) internal pure override returns (bytes memory) {
External calls are expensive. Consider caching the following:
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L461-L463
file: src/CoreRootRouter.sol 461 if (IPort(rootPortAddress).isGlobalAddress(_underlyingAddress)) revert TokenAlreadyAdded(); 462 if (IPort(rootPortAddress).isLocalToken(_underlyingAddress, _srcChainId)) revert TokenAlreadyAdded(); 463 if (IPort(rootPortAddress).isUnderlyingToken(_underlyingAddress, _srcChainId)) revert TokenAlreadyAdded();
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L137-L200
file: src/MulticallRootRouter.sol 137 function execute(bytes calldata encodedData, uint16) external payable override lock requiresExecutor { // Parse funcId bytes1 funcId = encodedData[0]; /// FUNC ID: 1 (multicallNoOutput) if (funcId == 0x01) { // Decode Params (IMulticall.Call[] memory callData) = abi.decode(_decode(encodedData[1:]), (IMulticall.Call[])); // Perform Calls _multicall(callData); // @audit: _multicall function /// FUNC ID: 2 (multicallSingleOutput) } else if (funcId == 0x02) { // Decode Params ( IMulticall.Call[] memory callData, OutputParams memory outputParams, uint16 dstChainId, GasParams memory gasParams ) = abi.decode(_decode(encodedData[1:]), (IMulticall.Call[], OutputParams, uint16, GasParams)); // Perform Calls _multicall(callData); // @audit: _multicall function // Bridge Out assets _approveAndCallOut( outputParams.recipient, outputParams.recipient, outputParams.outputToken, outputParams.amountOut, outputParams.depositOut, dstChainId, gasParams ); /// FUNC ID: 3 (multicallMultipleOutput) } else if (funcId == 0x03) { // Decode Params ( IMulticall.Call[] memory callData, OutputMultipleParams memory outputParams, uint16 dstChainId, GasParams memory gasParams ) = abi.decode(_decode(encodedData[1:]), (IMulticall.Call[], OutputMultipleParams, uint16, GasParams)); // Perform Calls _multicall(callData); // @audit: _multicall function // Bridge Out assets _approveMultipleAndCallOut( outputParams.recipient, outputParams.recipient, outputParams.outputTokens, outputParams.amountsOut, outputParams.depositsOut, dstChainId, gasParams ); /// UNRECOGNIZED FUNC ID } else { revert UnrecognizedFunctionId(); } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L312-L389
file: src/MulticallRootRouter.sol 312 function executeSignedDepositSingle(bytes calldata encodedData, DepositParams calldata, address userAccount, uint16) external payable override requiresExecutor lock { // Parse funcId bytes1 funcId = encodedData[0]; /// FUNC ID: 1 (multicallNoOutput) if (funcId == 0x01) { // Decode Params Call[] memory calls = abi.decode(_decode(encodedData[1:]), (Call[])); // Make requested calls IVirtualAccount(userAccount).call(calls); /// FUNC ID: 2 (multicallSingleOutput) } else if (funcId == 0x02) { // Decode Params (Call[] memory calls, OutputParams memory outputParams, uint16 dstChainId, GasParams memory gasParams) = abi.decode(_decode(encodedData[1:]), (Call[], OutputParams, uint16, GasParams)); // Make requested calls IVirtualAccount(userAccount).call(calls); // Withdraw assets from Virtual Account IVirtualAccount(userAccount).withdrawERC20(outputParams.outputToken, outputParams.amountOut); // Bridge Out assets _approveAndCallOut( IVirtualAccount(userAccount).userAddress(), // @audit: userAddress function outputParams.recipient, outputParams.outputToken, outputParams.amountOut, outputParams.depositOut, dstChainId, gasParams ); /// FUNC ID: 3 (multicallMultipleOutput) } else if (funcId == 0x03) { // Decode Params ( Call[] memory calls, OutputMultipleParams memory outputParams, uint16 dstChainId, GasParams memory gasParams ) = abi.decode(_decode(encodedData[1:]), (Call[], OutputMultipleParams, uint16, GasParams)); // Make requested calls IVirtualAccount(userAccount).call(calls); // Withdraw assets from Virtual Account for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } } // Bridge Out assets _approveMultipleAndCallOut( IVirtualAccount(userAccount).userAddress(), // @audit: userAddress function outputParams.recipient, outputParams.outputTokens, outputParams.amountsOut, outputParams.depositsOut, dstChainId, gasParams ); /// UNRECOGNIZED FUNC ID } else { revert UnrecognizedFunctionId(); } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L423-L737
file: src/RootBridgeAgent.sol 423 function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public { (bool success,) = address(this).excessivelySafeCall( gasleft(), 150, abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcChainId, _srcAddress, _payload) ); if (!success) if (msg.sender == getBranchBridgeAgent[localChainId]) revert ExecutionFailure(); } /// @inheritdoc IRootBridgeAgent function lzReceiveNonBlocking( address _endpoint, uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload ) public override requiresEndpoint(_endpoint, _srcChainId, _srcAddress) { // Deposit Nonce uint32 nonce; // DEPOSIT FLAG: 0 (System request / response) if (_payload[0] == 0x00) { // Parse deposit nonce nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START])); // Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 0 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSystemRequest(_localRouterAddress, _payload, _srcChainId) _execute( nonce, abi.encodeWithSelector( RootBridgeAgentExecutor.executeSystemRequest.selector, localRouterAddress, _payload, srcChainId ), srcChainId ); // DEPOSIT FLAG: 1 (Call without Deposit) } else if (_payload[0] == 0x01) { // Parse Deposit Nonce nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START])); // Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 1 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoDeposit(localRouterAddress, payload, _srcChainId) _execute( nonce, abi.encodeWithSelector( RootBridgeAgentExecutor.executeNoDeposit.selector, localRouterAddress, _payload, srcChainId ), srcChainId ); // DEPOSIT FLAG: 2 (Call with Deposit) } else if (_payload[0] == 0x02) { //Parse Deposit Nonce nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START])); //Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 2 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDeposit(localRouterAddress, _payload, _srcChainId) _execute( nonce, abi.encodeWithSelector( RootBridgeAgentExecutor.executeWithDeposit.selector, localRouterAddress, _payload, srcChainId ), srcChainId ); // DEPOSIT FLAG: 3 (Call with multiple asset Deposit) } else if (_payload[0] == 0x03) { // Parse deposit nonce nonce = uint32(bytes4(_payload[2:6])); // Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 3 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDepositMultiple(localRouterAddress, _payload, _srcChainId) _execute( nonce, abi.encodeWithSelector( RootBridgeAgentExecutor.executeWithDepositMultiple.selector, localRouterAddress, _payload, srcChainId ), srcChainId ); // DEPOSIT FLAG: 4 (Call without Deposit + msg.sender) } else if (_payload[0] == 0x04) { // Parse deposit nonce nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])); //Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Get User Virtual Account VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount( // @audit: fetchVirtualAccount function address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))) ); // Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); // @audit: toggleVirtualAccountApproved function // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 4 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedNoDeposit(address(userAccount), localRouterAddress, data, _srcChainId _execute( nonce, abi.encodeWithSelector( RootBridgeAgentExecutor.executeSignedNoDeposit.selector, address(userAccount), localRouterAddress, _payload, srcChainId ), srcChainId ); // Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); // @audit: toggleVirtualAccountApproved function //DEPOSIT FLAG: 5 (Call with Deposit + msg.sender) } else if (_payload[0] & 0x7F == 0x05) { // Parse deposit nonce nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])); //Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Get User Virtual Account VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount( // @audit: fetchVirtualAccount function address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))) ); // Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); // @audit: toggleVirtualAccountApproved function // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 5 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDeposit(address(userAccount), localRouterAddress, data, _srcChainId) _execute( _payload[0] == 0x85, nonce, address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))), abi.encodeWithSelector( RootBridgeAgentExecutor.executeSignedWithDeposit.selector, address(userAccount), localRouterAddress, _payload, srcChainId ), srcChainId ); // Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); // @audit: toggleVirtualAccountApproved function // DEPOSIT FLAG: 6 (Call with multiple asset Deposit + msg.sender) } else if (_payload[0] & 0x7F == 0x06) { // Parse deposit nonce nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED + PARAMS_START:PARAMS_START_SIGNED + PARAMS_TKN_START])); // Check if tx has already been executed if (executionState[_srcChainId][nonce] != STATUS_READY) { revert AlreadyExecutedTransaction(); } // Get User Virtual Account VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount( // @audit: fetchVirtualAccount function address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))) ); // Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); // @audit: toggleVirtualAccountApproved function // Avoid stack too deep uint16 srcChainId = _srcChainId; // Try to execute remote request // Flag 6 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDepositMultiple(address(userAccount), localRouterAddress, data, _srcChainId) _execute( _payload[0] == 0x86, nonce, address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))), abi.encodeWithSelector( RootBridgeAgentExecutor.executeSignedWithDepositMultiple.selector, address(userAccount), localRouterAddress, _payload, srcChainId ), srcChainId ); // Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); // @audit: toggleVirtualAccountApproved function /// DEPOSIT FLAG: 7 (retrySettlement) } else if (_payload[0] & 0x7F == 0x07) { // Prepare Variables for decoding address owner; bytes memory params; GasParams memory gParams; // Decode Input (nonce, owner, params, gParams) = abi.decode(_payload[PARAMS_START:], (uint32, address, bytes, GasParams)); // Get storage reference Settlement storage settlementReference = getSettlement[nonce]; // Check if Settlement hasn't been redeemed. if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable(); // Check settlement owner if (owner != settlementReference.owner) { if (owner != address(IPort(localPortAddress).getUserAccount(settlementReference.owner))) { revert NotSettlementOwner(); } } //Update Settlement Staus settlementReference.status = STATUS_SUCCESS; //Retry settlement call with new params and gas _performRetrySettlementCall( _payload[0] == 0x87, settlementReference.hTokens, settlementReference.tokens, settlementReference.amounts, settlementReference.deposits, params, nonce, payable(settlementReference.owner), settlementReference.recipient, settlementReference.dstChainId, gParams, address(this).balance ); /// DEPOSIT FLAG: 8 (retrieveDeposit) } else if (_payload[0] == 0x08) { //Parse deposit nonce nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])); //Check if deposit is in retrieve mode if (executionState[_srcChainId][nonce] == STATUS_DONE) { revert AlreadyExecutedTransaction(); } else { //Set settlement to retrieve mode, if not already set. if (executionState[_srcChainId][nonce] == STATUS_READY) { executionState[_srcChainId][nonce] = STATUS_RETRIEVE; } //Trigger fallback/Retry failed fallback _performFallbackCall( payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))), nonce, _srcChainId ); } //DEPOSIT FLAG: 9 (Fallback) } else if (_payload[0] == 0x09) { // Parse nonce nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START])); // Reopen Settlement for redemption getSettlement[nonce].status = STATUS_FAILED; // Emit LogFallback emit LogFallback(nonce, _srcChainId); // return to prevent unnecessary emits/logic return; // Unrecognized Function Selector } else { revert UnknownFlag(); } emit LogExecute(nonce, _srcChainId); }
When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L507
File: src/BranchBridgeAgent.sol 507: address[] memory _hTokens = new address[](numOfAssets); 508: address[] memory _tokens = new address[](numOfAssets); 509: uint256[] memory _amounts = new uint256[](numOfAssets); 510: uint256[] memory _deposits = new uint256[](numOfAssets); 827: address[] memory addressArray = new address[](1); 828: uint256[] memory uintArray = new uint256[](1);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1007
File: src/RootBridgeAgent.sol 1007: address[] memory addressArray = new address[](1); 1008: uint256[] memory uintArray = new uint256[](1); 1067: address[] memory hTokens = new address[](_globalAddresses.length); 1068: address[] memory tokens = new address[](_globalAddresses.length);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgentExecutor.sol#L276
File: src/RootBridgeAgentExecutor.sol 276: address[] memory hTokens = new address[](numOfAssets); 277: address[] memory tokens = new address[](numOfAssets); 278: uint256[] memory amounts = new uint256[](numOfAssets); 279: uint256[] memory deposits = new uint256[](numOfAssets);
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L169
file: src/BranchPort.sol 169 getPortStrategyTokenDebt[msg.sender][_token] -= _amount; 172 getStrategyTokenDebt[_token] -= _amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/token/ERC20hTokenRoot.sol#L70
file: src/token/ERC20hTokenRoot.sol 70 getTokenBalance[chainId] -= amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L157
file: src/BranchPort.sol 157 getPortStrategyTokenDebt[msg.sender][_token] += _amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/token/ERC20hTokenRoot.sol#L58
file: src/token/ERC20hTokenRoot.sol 58 getTokenBalance[chainId] += amount;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L205
Counting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.
by changing this logic you can save 12171 gas per one for loop
Tools used Remix
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L257
file: src/BranchPort.sol 257 for (uint256 i = 0; i < length;) { 305 for (uint256 i = 0; i < length;) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L70
file: src/VirtualAccount.sol 70 for (uint256 i = 0; i < length;) { 90 for (uint256 i = 0; i < length;) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L513
file: src/BranchBridgeAgent.sol 513 for (uint256 i = 0; i < numOfAssets;) { 447 for (uint256 i = 0; i < deposit.tokens.length;) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L399
file: src/RootBridgeAgent.sol 399 for (uint256 i = 0; i < length;) { 318 for (uint256 i = 0; i < settlement.hTokens.length;) { 1070 for (uint256 i = 0; i < hTokens.length;) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L278
file: src/MulticallRootRouter.sol 278 for (uint256 i = 0; i < outputParams.outputTokens.length;) { 367 for (uint256 i = 0; i < outputParams.outputTokens.length;) { 455 for (uint256 i = 0; i < outputParams.outputTokens.length;) { 557 for (uint256 i = 0; i < outputTokens.length;) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L192
file: src/BaseBranchRouter.sol 192 for (uint256 i = 0; i < _hTokens.length;) {
file: contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L62
file: src/BaseBranchRouter.sol 62 localBridgeAgentAddress = _localBridgeAgentAddress;
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L129
file: src/BranchPort.sol 129 coreBranchRouterAddress = _coreBranchRouter; 334 coreBranchRouterAddress = _newCoreRouter; 419 coreBranchRouterAddress = _coreBranchRouter;
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L88
file: src/CoreRootRouter.sol 88 hTokenFactoryAddress = _hTokenFactory;
https://github.com/code-423n4/2023-09-maia/blob/main/src/factories/ERC20hTokenBranchFactory.sol#L74
file: src/factories/ERC20hTokenBranchFactory.sol 74 localCoreRouterAddress = _coreRouter;
https://github.com/code-423n4/2023-09-maia/blob/main/src/factories/ERC20hTokenRootFactory.sol#L51
file: src/factories/ERC20hTokenRootFactory.sol 51 coreRootRouterAddress = _coreRouter;
A do while loop will cost less gas since the condition is not being checked for the first iteration.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L192-L196
file: src/BaseBranchRouter.sol 192 for (uint256 i = 0; i < _hTokens.length;) { _transferAndApproveToken(_hTokens[i], _tokens[i], _amounts[i], _deposits[i]); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L447-L451
file: src/BranchBridgeAgent.sol 447 for (uint256 i = 0; i < deposit.tokens.length;) { _clearToken(msg.sender, deposit.hTokens[i], deposit.tokens[i], deposit.amounts[i], deposit.deposits[i]); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L257-L273
file: 257 for (uint256 i = 0; i < length;) { // Check if hTokens are being bridged in if (_amounts[i] - _deposits[i] > 0) { unchecked { _bridgeIn(_recipient, _localAddresses[i], _amounts[i] - _deposits[i]); } } // Check if underlying tokens are being cleared if (_deposits[i] > 0) { withdraw(_recipient, _underlyingAddresses[i], _deposits[i]); } unchecked { ++i; } } 305 for (uint256 i = 0; i < length;) { _bridgeOut(_depositor, _localAddresses[i], _underlyingAddresses[i], _amounts[i], _deposits[i]); unchecked { i++; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L278-283
file: src/MulticallRootRouter.sol 278 for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } } 367 for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } } 455 for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } } 557 for (uint256 i = 0; i < outputTokens.length;) { // Approve Root Port to spend output hTokens. outputTokens[i].safeApprove(_bridgeAgentAddress, amountsOut[i]); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L70-L81
file: src/VirtualAccount.sol 70 for (uint256 i = 0; i < length;) { bool success; Call calldata _call = calls[i]; if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData); if (!success) revert CallFailed(); unchecked { ++i; } }
In the instance below, two external calls, both of which take two function parameters, are performed. We can potentially avoid memory expansion costs by using assembly to utilize scratch space + free memory pointer memory offsets for the function calls. We will use the same memory space for both function calls.
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.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L63-L64
file: src/BaseBranchRouter.sol 63 localPortAddress = IBridgeAgent(_localBridgeAgentAddress).localPortAddress(); 64 bridgeAgentExecutorAddress = IBridgeAgent(_localBridgeAgentAddress).bridgeAgentExecutorAddress();
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreBranchRouter.sol#L249-L255
file: src/CoreBranchRouter.sol 249 if (IPort(_localPortAddress).isBridgeAgentFactory(_newBridgeAgentFactoryAddress)) { // If so, disable it. IPort(_localPortAddress).toggleBridgeAgentFactory(_newBridgeAgentFactoryAddress); } else { // If not, add it. IPort(_localPortAddress).addBridgeAgentFactory(_newBridgeAgentFactoryAddress); }
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L426-L429
file: src/CoreRootRouter.sol 426 ERC20(_globalAddress).name(), ERC20(_globalAddress).symbol(), ERC20(_globalAddress).decimals(), 461 if (IPort(rootPortAddress).isGlobalAddress(_underlyingAddress)) revert TokenAlreadyAdded(); if (IPort(rootPortAddress).isLocalToken(_underlyingAddress, _srcChainId)) revert TokenAlreadyAdded(); if (IPort(rootPortAddress).isUnderlyingToken(_underlyingAddress, _srcChainId)) revert TokenAlreadyAdded();
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L192-L198
file: src/BaseBranchRouter.sol 192 for (uint256 i = 0; i < _hTokens.length;) { _transferAndApproveToken(_hTokens[i], _tokens[i], _amounts[i], _deposits[i]); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/interfaces/IBranchBridgeAgent.sol#L447-L453
file: src/interfaces/IBranchBridgeAgent.sol 447 for (uint256 i = 0; i < deposit.tokens.length;) { _clearToken(msg.sender, deposit.hTokens[i], deposit.tokens[i], deposit.amounts[i], deposit.deposits[i]); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L257-L273
file: src/BranchPort.sol 257 for (uint256 i = 0; i < length;) { // Check if hTokens are being bridged in if (_amounts[i] - _deposits[i] > 0) { unchecked { _bridgeIn(_recipient, _localAddresses[i], _amounts[i] - _deposits[i]); } } // Check if underlying tokens are being cleared if (_deposits[i] > 0) { withdraw(_recipient, _underlyingAddresses[i], _deposits[i]); } unchecked { ++i; } }
Since the following public functions are not called from within the contract, their visibility can be made external for gas optimization.
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L578
File: src/BranchBridgeAgent.sol 578 function lzReceive(uint16, bytes calldata _srcAddress, uint64, bytes calldata _payload) public override { 587 function lzReceiveNonBlocking(address _endpoint, bytes calldata _srcAddress, bytes calldata _payload) public override requiresEndpoint(_endpoint, _srcAddress)
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L423
File: src/RootBridgeAgent.solChange public function visibility to external 423 function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public { 434 function lzReceiveNonBlocking( address _endpoint, uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload ) public override requiresEndpoint(_endpoint, _srcChainId, _srcAddress) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L85
File: src/VirtualAccount.sol 85 function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/token/ERC20hTokenBranch.sol#L35
File: src/token/ERC20hTokenBranch.sol 35 function burn(uint256 amount) public override onlyOwner {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L306-L312
file: src/BranchBridgeAgent.sol 306 function callOutSignedAndBridgeMultiple( address payable _refundee, bytes calldata _params, DepositMultipleInput memory _dParams, GasParams calldata _gParams, bool _hasFallbackToggled ) external payable override lock { 343 function retryDeposit( bool _isSigned, uint32 _depositNonce, bytes calldata _params, GasParams calldata _gParams, bool _hasFallbackToggled ) external payable override lock {
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreBranchRouter.sol#L175
file: src/CoreBranchRouter.sol 175 ERC20hToken newToken = ITokenFactory(hTokenFactoryAddress).createToken(_name, _symbol, _decimals, false);
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L202-L210
file: src/RootBridgeAgent.sol 202 function callOutAndBridgeMultiple( address payable _refundee, address _recipient, uint16 _dstChainId, bytes calldata _params, SettlementMultipleInput calldata _sParams, GasParams calldata _gParams, bool _hasFallbackToggled ) external payable override lock requiresRouter { 301 Settlement storage settlement = getSettlement[_settlementNonce];
Instead of using abi.decode, we can use assembly to decode our desired calldata values directly. This will allow us to avoid decoding calldata values that we will not use.
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumCoreBranchRouter.sol#L134
file: src/ArbitrumCoreBranchRouter.sol 134 ) = abi.decode(_data[1:], (address, address, address, address, address, GasParams)); 147 (address bridgeAgentFactoryAddress) = abi.decode(_data[1:], (address)); 153 (address branchBridgeAgent) = abi.decode(_data[1:], (address)); 158 (address underlyingToken, uint256 minimumReservesRatio) = abi.decode(_data[1:], (address, uint256)); 164 abi.decode(_data[1:], (address, address, uint256, bool));
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreBranchRouter.sol#L96
file: src/CoreBranchRouter.sol 96 ) = abi.decode(_params[1:], (address, string, string, uint8, address, GasParams)); 108 ) = abi.decode(_params[1:], (address, address, address, address, address, GasParams)); 116 (address bridgeAgentFactoryAddress) = abi.decode(_params[1:], (address)); 122 (address branchBridgeAgent) = abi.decode(_params[1:], (address)); 128 (address underlyingToken, uint256 minimumReservesRatio) = abi.decode(_params[1:], (address, uint256)); 135 abi.decode(_params[1:], (address, address, uint256, bool)); 141 (address coreBranchRouter, address coreBranchBridgeAgent) = abi.decode(_params[1:], (address, address));
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L309
file: src/CoreRootRouter.sol 309 = abi.decode(_encodedData[1:], (address, address, string, string, uint8)); 315 (address globalAddress, address localAddress) = abi.decode(_encodedData[1:], (address, address)); 321 (address newBranchBridgeAgent, address rootBridgeAgent) = abi.decode(_encodedData[1:], (address, address)); 339 abi.decode(_encodedData[1:], (address, address, uint16, GasParams[2]));
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L144
file: src/MulticallRootRouter.sol 144 (IMulticall.Call[] memory callData) = abi.decode(_decode(encodedData[1:]), (IMulticall.Call[])); 157 ) = abi.decode(_decode(encodedData[1:]), (IMulticall.Call[], OutputParams, uint16, GasParams)); 181 ) = abi.decode(_decode(encodedData[1:]), (IMulticall.Call[], OutputMultipleParams, uint16, GasParams)); 236 Call[] memory calls = abi.decode(_decode(encodedData[1:]), (Call[])); 245 abi.decode(_decode(encodedData[1:]), (Call[], OutputParams, uint16, GasParams)); 272 ) = abi.decode(_decode(encodedData[1:]), (Call[], OutputMultipleParams, uint16, GasParams)); 325 Call[] memory calls = abi.decode(_decode(encodedData[1:]), (Call[])); 334 abi.decode(_decode(encodedData[1:]), (Call[], OutputParams, uint16, GasParams)); 361 ) = abi.decode(_decode(encodedData[1:]), (Call[], OutputMultipleParams, uint16, GasParams)); 413 Call[] memory calls = abi.decode(_decode(encodedData[1:]), (Call[])); 422 abi.decode(_decode(encodedData[1:]), (Call[], OutputParams, uint16, GasParams)); 449 ) = abi.decode(_decode(encodedData[1:]), (Call[], OutputMultipleParams, uint16, GasParams));
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L664
file: src/RootBridgeAgent.sol 664 (nonce, owner, params, gParams) = abi.decode(_payload[PARAMS_START:], (uint32, address, bytes, GasParams));
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.
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchBridgeAgent.sol#L126
file: src/ArbitrumBranchBridgeAgent.sol 126 if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();
https://github.com/code-423n4/2023-09-maia/blob/main/src/BaseBranchRouter.sol#L207
file: src/BaseBranchRouter.sol 207 if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L247
file: src/RootBridgeAgent.sol 247 if (msg.sender != settlementReference.owner) { 248 if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementReference.owner))) { 285 if (msg.sender != settlementOwner) { 286 if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner))) { 311 if (msg.sender != settlementOwner) { 312 if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner))) { 1199 if (msg.sender != localRouterAddress) revert UnrecognizedRouter(); 1205 if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint(); 1221 if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedExecutor(); 1227 if (msg.sender != localPortAddress) revert UnrecognizedPort(); 1233 if (msg.sender != IPort(localPortAddress).getBridgeAgentManager(address(this))) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L354
file: src/BranchBridgeAgent.sol 354 if (deposit.owner != msg.sender) revert NotDepositOwner(); 424 if (getDeposit[_depositNonce].owner != msg.sender) revert NotDepositOwner(); 938 if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint(); 948 if (msg.sender != localRouterAddress) revert UnrecognizedRouter(); 954 if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L112
file: src/CoreRootRouter.sol 112 if (msg.sender != IPort(rootPortAddress).getBridgeAgentManager(_rootBridgeAgent)) { 278 require(msg.sender == rootPortAddress, "Only root port can call"); 512 if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L605
file: src/MulticallRootRouter.sol 605 if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L570
file: src/RootPort.sol 570 if (msg.sender != coreRootRouterAddress) revert UnrecognizedCoreRootRouter(); 576 if (msg.sender != localBranchPortAddress) revert UnrecognizedLocalBranchPort();
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L162
file: src/VirtualAccount.sol 162 if (msg.sender != userAddress) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L278-L284
Performing STATICCALLs that do not depend on variables incremented in loops should always try to be avoided within the loop. In the following instances, we are able to cache the external calls outside of the loop to save a STATICCALL (100 gas) per loop iteration.
file: src/MulticallRootRouter.sol 278 for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } } 367 for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } } 455 for (uint256 i = 0; i < outputParams.outputTokens.length;) { IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]); unchecked { ++i; } }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchPort.sol#L553-L556
file: src/BranchPort.sol 553 modifier requiresBridgeAgentFactory() { if (!isBridgeAgentFactory[msg.sender]) revert UnrecognizedBridgeAgentFactory(); _; } 559 modifier requiresPortStrategy(address _token) { if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken(); if (!isPortStrategy[msg.sender][_token]) revert UnrecognizedPortStrategy(); _; }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L1226-L1229
file: src/RootBridgeAgent.sol 1226 modifier requiresPort() { if (msg.sender != localPortAddress) revert UnrecognizedPort(); _; } 1232 modifier requiresManager() { if (msg.sender != IPort(localPortAddress).getBridgeAgentManager(address(this))) { revert UnrecognizedBridgeAgentManager(); } _; }
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L557-L560
file: src/RootPort.sol 557 modifier requiresBridgeAgentFactory() { if (!isBridgeAgentFactory[msg.sender]) revert UnrecognizedBridgeAgentFactory(); _; }
file: src/factories/ERC20hTokenBranchFactory.sol 112 modifier requiresCoreRouter() { if (msg.sender != localCoreRouterAddress) revert UnrecognizedCoreRouter(); _; }
file: src/factories/ERC20hTokenRootFactory.sol 96 modifier requiresCoreRouterOrPort() { if (msg.sender != coreRootRouterAddress) { if (msg.sender != rootPortAddress) { revert UnrecognizedCoreRouterOrPort(); } } _; }
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L930-L933
file: src/BranchBridgeAgent.sol 930 modifier requiresEndpoint(address _endpoint, bytes calldata _srcAddress) { _requiresEndpoint(_endpoint, _srcAddress); _; } 947 modifier requiresRouter() { if (msg.sender != localRouterAddress) revert UnrecognizedRouter(); _; }
berfore vast of gas: 115075 after of removing of they name vast of gas: 115048
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L145
file: src/RootBridgeAgent.sol 145 ) external view returns (uint256 _fee) {
https://github.com/code-423n4/2023-09-maia/blob/main/src/interfaces/IRootBridgeAgent.sol#L104
file: src/interfaces/IRootBridgeAgent.sol 104 function settlementNonce() external view returns (uint32 nonce); 154 ) external view returns (uint256 _fee);
As a rule of thumb, use bytes for arbitrary-length raw byte data and string for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.
file: /src/factories/ERC20hTokenBranchFactory.sol 26 string public chainName; 29 string public chainSymbol;
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L308
file: /src/CoreRootRouter.sol 308 (address underlyingAddress, address localAddress, string memory name, string memory symbol, uint8 decimals) 455 string memory _name, 456 string memory _symbol,
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L441-L442
file: /src/RootPort.sol 441 string memory _wrappedGasTokenName, 442 string memory _wrappedGasTokenSymbol,
Save 6 gas per instance.
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L818
file: /src/RootBridgeAgent.sol 818 if (callee == address(0)) revert UnrecognizedBridgeAgent(); 910 if (callee == address(0)) revert UnrecognizedBridgeAgent();
https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchPort.sol#L63
file: /src/ArbitrumBranchPort.sol 63 if (_globalToken == address(0)) revert UnknownGlobalToken(); 90 if (_underlyingAddress == address(0)) revert UnknownUnderlyingToken();
https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreRootRouter.sol#L120
file: /src/CoreRootRouter.sol 120 if (IBridgeAgent(_rootBridgeAgent).getBranchBridgeAgent(_dstChainId) != address(0)) revert InvalidChainId();
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L245-L247
file: /src/RootPort.sol 245 if (_globalAddress == address(0)) revert InvalidGlobalAddress(); 246 if (_localAddress == address(0)) revert InvalidLocalAddress(); 247 if (_underlyingAddress == address(0)) revert InvalidUnderlyingAddress();
If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper.
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L40-L76
file: /src/RootBridgeAgent.sol 40 uint16 public immutable localChainId; /// @notice Bridge Agent Factory Address. 43 address public immutable factoryAddress; /// @notice Local Core Root Router Address 46 address public immutable localRouterAddress; /// @notice Local Port Address where funds deposited from this chain are stored. 49 address public immutable localPortAddress; /// @notice Local Layer Zero Endpoint Address for cross-chain communication. 52 address public immutable lzEndpointAddress; /// @notice Address of Root Bridge Agent Executor. 55 address public immutable bridgeAgentExecutorAddress; /*/////////////////////////////////////////////////////////////// BRANCH BRIDGE AGENTS STATE //////////////////////////////////////////////////////////////*/ /// @notice Chain -> Branch Bridge Agent Address. For N chains, each Root Bridge Agent Address has M =< N Branch Bridge Agent Address. 62 mapping(uint256 chainId => address branchBridgeAgent) public getBranchBridgeAgent; /// @notice Message Path for each connected Branch Bridge Agent as bytes for Layzer Zero interaction = localAddress + destinationAddress abi.encodePacked() 65 mapping(uint256 chainId => bytes branchBridgeAgentPath) public getBranchBridgeAgentPath; /// @notice If true, bridge agent manager has allowed for a new given branch bridge agent to be synced/added. 68 mapping(uint256 chainId => bool allowed) public isBranchBridgeAgentAllowed; /*/////////////////////////////////////////////////////////////// SETTLEMENTS STATE //////////////////////////////////////////////////////////////*/ /// @notice Deposit nonce used for identifying transaction. 75 uint32 public settlementNonce;
https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L115
file: /src/RootBridgeAgent.sol 115 factoryAddress = msg.sender;
ERC721A is a proposed extension to the ERC721 standard that aims to improve gas efficiency and reduce the cost of deploying and using non-fungible tokens (NFTs) on the Ethereum blockchain.Â
Reference 1: https://nextrope.com/erc721-vs-erc721a-2/
Reference 2 :https://github.com/chiru-labs/ERC721A#about-the-project
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L62
file: /src/VirtualAccount.sol 62 ERC721(_token).transferFrom(address(this), msg.sender, _tokenId);
https://github.com/code-423n4/2023-09-maia/blob/main/src/interfaces/IVirtualAccount.sol#L4
file: /src/interfaces/IVirtualAccount.sol 4 import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
Assigning the same value to a state variable repeatedly can cause unnecessary gas costs, because each assignment operation incurs a gas cost. Additionally, if the value being assigned is the same as the current value of the state variable, the assignment operation does not actually change anything, which can waste gas.
https://github.com/code-423n4/2023-09-maia/blob/main/src/interfaces/BridgeAgentConstants.sol#L13-L27
file: /src/interfaces/BridgeAgentConstants.sol 13 uint8 internal constant STATUS_READY = 0; 15 uint8 internal constant STATUS_DONE = 1; 21 uint8 internal constant STATUS_FAILED = 1; 23 uint8 internal constant STATUS_SUCCESS = 0; 27 uint256 internal constant PARAMS_START = 1;
#0 - 0xA5DF
2023-10-15T17:00:48Z
G15, G20, G25, G27 are in bot report
#1 - c4-pre-sort
2023-10-15T17:00:51Z
0xA5DF marked the issue as sufficient quality report
#2 - c4-judge
2023-10-26T13:48:47Z
alcueca marked the issue as grade-a