Skip to content

Instantly share code, notes, and snippets.

@itsmetechjay
Created September 23, 2023 20:48
Show Gist options
  • Save itsmetechjay/9bcacd6e8beea0abaf9590abc6b1d10e to your computer and use it in GitHub Desktop.
Save itsmetechjay/9bcacd6e8beea0abaf9590abc6b1d10e to your computer and use it in GitHub Desktop.
Best bot finding from 2023-09-maia-bot-findings

Winning bot race submission

This is the top-ranked automated findings report, from IllIllI-bot bot. All findings in this report will be considered known issues for the purposes of your C4 audit.

Note: There is a section for disputed findings below the usual findings sections

Summary

Medium Risk Issues

Issue Instances
[M‑01] Unchecked return value of low-level call()/delegatecall() 3
[M‑02] Use of transferFrom() rather than safeTransferFrom() for NFTs in will lead to the loss of NFTs 1
[M‑03] The owner is a single point of failure and a centralization risk 38
[M‑04] Return values of approve() not checked 1

Total: 43 instances over 4 issues

Low Risk Issues

Issue Instances
[L‑01] addStrategyToken() does note remove old entries before adding new ones 2
[L‑02] Unsafe downcast 8
[L‑03] Array lengths not checked 1
[L‑04] receive()/payable fallback() function does not authorize requests 3
[L‑05] safeApprove() is deprecated 2
[L‑06] Use Ownable2Step rather than Ownable 12
[L‑07] Empty receive()/fallback() function 3
[L‑08] External calls in an un-bounded for-loop may result in a DOS 4
[L‑09] Consider implementing two-step procedure for updating protocol addresses 10
[L‑10] name() is not a part of the ERC-20 standard 5
[L‑11] symbol() is not a part of the ERC-20 standard 5
[L‑12] decimals() is not a part of the ERC-20 standard 4
[L‑13] SafeTransferLib does not ensure that the token contract exists 2
[L‑14] Functions calling contracts/addresses with transfer hooks are missing reentrancy guards 1
[L‑15] approve()/safeApprove() may revert if the current approval is not zero 3
[L‑16] External call recipient may consume all transaction gas 8
[L‑17] Missing contract-existence checks before low-level calls 4
[L‑18] Code does not follow the best practice of check-effects-interaction 43
[L‑19] Missing checks for address(0x0) in the constructor/initializer 7
[L‑20] Missing checks for address(0x0) when updating address state variables 1
[L‑21] State variables not capped at reasonable values 2
[L‑22] Tokens may be minted to address(0x0) 2
[L‑23] Initialization can be front-run 8

Total: 140 instances over 23 issues

Non-critical Issues

Issue Instances
[N‑01] public functions not called by the contract should be declared external instead 6
[N‑02] Event is not properly indexed 1
[N‑03] Duplicated require()/revert() checks should be refactored to a modifier or function 12
[N‑04] Common functions should be refactored to a common base contract 1
[N‑05] Import declarations should import specific identifiers, rather than the whole file 1
[N‑06] Events that mark critical parameter changes should contain both the old and the new value 6
[N‑07] Constant redefined elsewhere 25
[N‑08] Inconsistent spacing in comments 145
[N‑09] Lines are too long 32
[N‑10] Long functions should be refactored into multiple, smaller, functions 13
[N‑11] Inconsistent method of specifying a floating pragma 3
[N‑12] Using >/>= without specifying an upper bound is unsafe 3
[N‑13] Typos 37
[N‑14] File is missing NatSpec 3
[N‑15] Function definition modifier order does not follow Solidity style guide 2
[N‑16] Assembly blocks should have extensive comments 1
[N‑17] Visibility should be set explicitly rather than defaulting to internal 3
[N‑18] Function ordering does not follow the Solidity style guide 11
[N‑19] Contract does not follow the Solidity style guide's suggested layout ordering 15
[N‑20] Add inline comments for unnamed variables 18
[N‑21] Invalid NatSpec comment style 67
[N‑22] Contracts should have full test coverage 1
[N‑23] Large or complicated code bases should implement invariant tests 1
[N‑24] Consider adding formal verification proofs 1
[N‑25] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, for readability 3
[N‑26] Custom errors should be used rather than revert()/require() 54
[N‑27] Use abi.encodeCall() instead of abi.encodeWithSignature()/abi.encodeWithSelector() 12
[N‑28] Style guide: Non-external/public variable names should begin with an underscore 23
[N‑29] Variable names for immutables should use CONSTANT_CASE 39
[N‑30] Array indicies should be referenced via enums rather than via numeric literals 66
[N‑31] Consider using named mappings 40
[N‑32] Use of override is unnecessary 117
[N‑33] Consider using descriptive constants when passing zero as a function argument 4
[N‑34] Variables need not be initialized to zero 17
[N‑35] require()/revert() statements should have descriptive reason strings 13
[N‑36] Contracts/libraries should each be defined in separate files 4
[N‑37] Contract uses both require()/revert() as well as custom errors 10
[N‑38] Contract doesn't handle all NFT types 2
[N‑39] Style guide: Non-external/public function names should begin with an underscore 2
[N‑40] constants should be defined rather than using magic numbers 125
[N‑41] Events are missing sender information 27
[N‑42] Consider adding a block/deny-list 12
[N‑43] Consider using SafeTransferLib.safeTransferETH() or Address.sendValue() for clearer semantic meaning 3
[N‑44] Unused import 15
[N‑45] Array is push()ed but not pop()ed 12
[N‑46] Unused struct definition 1
[N‑47] Unused error definition 7
[N‑48] NatSpec: Contract declarations should have NatSpec descriptions 4
[N‑49] NatSpec: Contract declarations should have @notice tags 4
[N‑50] NatSpec: Error declarations should have NatSpec descriptions 90
[N‑51] NatSpec: Event declarations should have NatSpec descriptions 28
[N‑52] NatSpec: State variable declarations should have NatSpec descriptions 22
[N‑53] NatSpec: Function declarations should have NatSpec descriptions 37
[N‑54] NatSpec: Function declarations should have @notice tags 37
[N‑55] Constants in comparisons should appear on the left side 62
[N‑56] Custom error has no error details 90
[N‑57] NatSpec: Contract declarations should have @author tags 9
[N‑58] NatSpec: Contract declarations should have @dev tags 34
[N‑59] NatSpec: Contract declarations should have @title tags 4
[N‑60] Empty function body 1
[N‑61] Use bytes.concat() on bytes instead of abi.encodePacked() for clearer semantic meaning 14
[N‑62] if-statement can be converted to a ternary 2
[N‑63] Overflows in unchecked blocks 15
[N‑64] Consider splitting long calculations 8
[N‑65] Contract should expose an interface 47
[N‑66] else-block not required 2
[N‑67] Missing event and or timelock for critical parameter change 1
[N‑68] Setters should prevent re-setting of the same value 11
[N‑69] Polymorphic functions make security audits more time-consuming and error-prone 6
[N‑70] Imports could be organized more systematically 4
[N‑71] Use the latest solidity (prior to 0.8.20 if on L2s) for deployment 23
[N‑72] Consider moving msg.sender checks to a common authorization modifier 7
[N‑73] Non-library/interface files should use fixed compiler versions, not floating ones 23
[N‑74] Consider adding emergency-stop functionality 13
[N‑75] NatSpec: Function @return is missing 46
[N‑76] NatSpec: Function @param is missing 111
[N‑77] Consider bounding input array length 7
[N‑78] Named imports of parent contracts are missing 1
[N‑79] Complex casting 59
[N‑80] Events may be emitted out of order due to reentrancy 7
[N‑81] Non-assembly method available 1
[N‑82] Missing checks constructor/initializer assignments 9

Total: 1855 instances over 82 issues

Gas Optimizations

Issue Instances Total Gas Saved
[G‑01] Enable IR-based code generation 1 -
[G‑02] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 5 -
[G‑03] State variables only set in the constructor should be declared immutable 3 6291
[G‑04] Avoid contract existence checks by using low-level calls 52 5200
[G‑05] State variables should be cached in stack variables rather than re-reading them from storage 1 97
[G‑06] internal functions only called once can be inlined to save gas 14 280
[G‑07] <array>.length should not be looked up in every loop of a for-loop 8 24
[G‑08] require()/revert() strings longer than 32 bytes cost extra gas 22 66
[G‑09] Optimize names to save gas 24 528
[G‑10] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 1 5
[G‑11] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 21 462
[G‑12] Using private rather than public for constants, saves gas 12 -
[G‑13] Inverting the condition of an if-else-statement wastes gas 1 -
[G‑14] require() or revert() statements that check input arguments should be at the top of the function 11 -
[G‑15] Use custom errors rather than revert()/require() strings to save gas 49 -
[G‑16] Functions guaranteed to revert when called by normal users can be marked payable 21 441
[G‑17] Constructors can be marked payable 22 462
[G‑18] Reduce gas usage by moving to Solidity 0.8.19 or later 44 -
[G‑19] >= costs less gas than > 33 99
[G‑20] Nesting if-statements is cheaper than using && 1 6
[G‑21] Use assembly to emit events, in order to save gas 19 722
[G‑22] Use += for mappings 5 200
[G‑23] Using bools for storage incurs overhead 13 1300
[G‑24] Use uint256(1)/uint256(2) instead of true/false to save gas for changes 13 222300
[G‑25] Simple checks for zero can be done using assembly to save gas 25 150
[G‑26] Avoid updating storage when the value hasn't changed 11 8800
[G‑27] unchecked {} can be used on the division of two uints in order to save gas 1 20
[G‑28] Using this to access functions results in an external call, wasting gas 2 200
[G‑29] Use assembly for small keccak256 hashes, in order to save gas 1 80
[G‑30] Using calldata instead of memory for read-only arguments in external functions saves gas 23 2760
[G‑31] Avoid transferring amounts of zero in order to save gas 10 1000
[G‑32] Avoid fetching a low-level call's return data by using assembly 9 1431

Total: 478 instances over 32 issues with 252924 gas saved

Gas totals are estimates based on data from the Ethereum Yellowpaper. The estimates use the lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Disputed Issues

The issues below may be reported by other bots/wardens, but can be penalized/ignored since either the rule or the specified instances are invalid

Issue Instances
[D‑01] Unchecked return value of low-level call()/delegatecall() 9
[D‑02] Return values of approve() not checked 1
[D‑03] Unsafe downcast 5
[D‑04] Loss of precision 1
[D‑05] Solidity version 0.8.20 may not work on other chains due to PUSH0 44
[D‑06] approve()/safeApprove() may revert if the current approval is not zero 1
[D‑07] Missing contract-existence checks before low-level calls 27
[D‑08] Duplicated require()/revert() checks should be refactored to a modifier Or function to save gas 12
[D‑09] Duplicated require()/revert() checks should be refactored to a modifier or function 56
[D‑10] SPDX identifier should be the in the first line of a solidity file 44
[D‑11] Overly complicated arithmetic 18
[D‑12] Prefer double quotes for string quoting 3
[D‑13] Public functions not used internally can be marked as external to save gas 6
[D‑14] require() / revert() statements should have descriptive reason strings 155
[D‑15] Solmate's SafeTransferLib doesn't check whether the ERC20 contract exists 3
[D‑16] NatSpec: Contract declarations should have @notice tags 44
[D‑17] Event names should use CamelCase 28
[D‑18] Unnecessary look up in if condition 3
[D‑19] NatSpec: Function declarations should have @notice tags 305
[D‑20] Timestamp may be manipulation 2
[D‑21] Using bitmap to store bool states can save gas 10
[D‑22] All interfaces used within a project should be imported 21
[D‑23] It's not standard to end and begin a code object on the same line 99
[D‑24] Save gas with the use of specific import statements 1
[D‑25] Use safeApprove() 2
[D‑26] Use assembly to write address/contract type storage values 25
[D‑27] Use multiple require() and if statements instead of && 1
[D‑28] It is standard for all external and public functions to be override from an interface 117
[D‑29] Must approve or increase allowance first 9
[D‑30] Cast to bytes or bytes32 for clearer semantic meaning 42
[D‑31] Return values of transfer()/transferFrom() not checked 1
[D‑32] Array lengths not checked 6
[D‑33] Use bytes.concat() on bytes instead of abi.encodePacked() for clearer semantic meaning 28
[D‑34] Use @inheritdoc rather than using a non-standard annotation 10
[D‑35] Empty function body 4
[D‑36] Operator += costs more gas than = + for state variables 5
[D‑37] Using named function calls is a much safer 4
[D‑38] selfbalance() is cheaper than address(this).balance 9
[D‑39] Contract should expose an interface 4
[D‑40] Storage Write Removal Bug On Conditional Early Termination 1
[D‑41] Low level calls with Solidity before 0.8.14 result in an optimiser bug 1
[D‑42] abi.encode() is less efficient than abi.encodepacked() 15
[D‑43] Large approvals may not work with some ERC20 tokens 4
[D‑44] Not initializing local variables to zero saves gas 15
[D‑45] Using this to access functions results in an external call, wasting gas 5
[D‑46] Using storage instead of memory for structs/arrays saves gas 14
[D‑47] Use replace and pop instead of the delete keyword to removing an item from an array 2
[D‑48] Events that mark critical parameter changes should contain both the old and the new value 15
[D‑49] Style guide: Function Names Not in mixedCase 49
[D‑50] Bad bot rules 1
[D‑51] The result of function calls should be cached rather than re-calling the function 4
[D‑52] Contracts do not work with fee-on-transfer tokens 17
[D‑53] Re-org attack 10
[D‑54] SafeTransferLib does not ensure that the token contract exists 6
[D‑55] Use _safeMint instead of _mint for ERC721 2
[D‑56] Default bool values are manually reset 21
[D‑57] Interfaces should be defined in separate files from their usage 21
[D‑58] Non-library/interface files should use fixed compiler versions, not floating ones 21
[D‑59] Using calldata instead of memory for read-only arguments in external functions saves gas 8
[D‑60] Unusual loop variable 15
[D‑61] Missing contract-existence checks before low-level calls 3
[D‑62] Shorten the array rather than copying to a new one 14
[D‑63] Avoid fetching a low-level call's return data by using assembly 4
[D‑64] Change public function visibility to external to save gas 2
[D‑65] Use != 0 instead of > 0 for unsigned integer comparison 18
[D‑66] Unused function parameter 1
[D‑67] State variables not capped at reasonable values 2
[D‑68] Unused import 187
[D‑69] safeMint should be used in place of mint 3
[D‑70] Unused event definition 28
[D‑71] Unused error definition 73
[D‑72] Unused struct definition 15
[D‑73] internal functions not called by the contract should be removed 6
[D‑74] Unused contract variables 20
[D‑75] Initialization can be front-run 1

Total: 1789 instances over 75 issues

Medium Risk Issues

[M‑01] Unchecked return value of low-level call()/delegatecall()

The function being called may revert, which will be indicated by the return value to call()/delegatecall(). If the return value is not checked, the code will continue on as if there was no error, rather than reverting with the error encountered.

There are 3 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

103:          _rootBridgeAgentAddress.call{value: msg.value}("");

GitHub: 103

File: src/RootBridgeAgent.sol

835:              callee.call{value: msg.value}("");

927:              callee.call{value: _value}("");

GitHub: 835, 927

[M‑02] Use of transferFrom() rather than safeTransferFrom() for NFTs in will lead to the loss of NFTs

The EIP-721 standard says the following about transferFrom():

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

https://github.com/ethereum/EIPs/blob/78e2c297611f5e92b6a5112819ab71f74041ff25/EIPS/eip-721.md?plain=1#L103-L113

Code must use the safeTransferFrom() flavor if it hasn't otherwise verified that the receiving address can handle it

There is one instance of this issue:

File: src/VirtualAccount.sol

62:           ERC721(_token).transferFrom(address(this), msg.sender, _tokenId);

GitHub: 62

[M‑03] The owner is a single point of failure and a centralization risk

Having a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary, or the single owner can become malicious and perform a rug-pull. Consider changing to a multi-signature setup, and or having a role-based authorization model.

There are 38 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

60:      function initialize(address _localBridgeAgentAddress) external onlyOwner {

GitHub: 60

File: src/BranchBridgeAgentExecutor.sol

53:      function executeNoSettlement(address _router, bytes calldata _payload) external payable onlyOwner {

66       function executeWithSettlement(address _recipient, address _router, bytes calldata _payload)
67           external
68           payable
69           onlyOwner
70:      {

104      function executeWithSettlementMultiple(address _recipient, address _router, bytes calldata _payload)
105          external
106          payable
107          onlyOwner
108:     {

GitHub: 53, 66, 104

File: src/BranchPort.sol

122:     function initialize(address _coreBranchRouter, address _bridgeAgentFactory) external virtual onlyOwner {

135:     function renounceOwnership() public payable override onlyOwner {

GitHub: 122, 135

File: src/CoreRootRouter.sol

83:      function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

156      function toggleBranchBridgeAgentFactory(
157          address _rootBridgeAgentFactory,
158          address _branchBridgeAgentFactory,
159          address _refundee,
160          uint16 _dstChainId,
161          GasParams calldata _gParams
162:     ) external payable onlyOwner {

186      function removeBranchBridgeAgent(
187          address _branchBridgeAgent,
188          address _refundee,
189          uint16 _dstChainId,
190          GasParams calldata _gParams
191:     ) external payable onlyOwner {

212      function manageStrategyToken(
213          address _underlyingToken,
214          uint256 _minimumReservesRatio,
215          address _refundee,
216          uint16 _dstChainId,
217          GasParams calldata _gParams
218:     ) external payable onlyOwner {

241      function managePortStrategy(
242          address _portStrategy,
243          address _underlyingToken,
244          uint256 _dailyManagementLimit,
245          bool _isUpdateDailyLimit,
246          address _refundee,
247          uint16 _dstChainId,
248          GasParams calldata _gParams
249:     ) external payable onlyOwner {

GitHub: 83, 156, 186, 212, 241

File: src/MulticallRootRouter.sol

109:     function initialize(address _bridgeAgentAddress) external onlyOwner {

GitHub: 109

File: src/RootBridgeAgentExecutor.sol

50       function executeSystemRequest(address _router, bytes calldata _payload, uint16 _srcChainId)
51           external
52           payable
53           onlyOwner
54:      {

66       function executeNoDeposit(address _router, bytes calldata _payload, uint16 _srcChainId)
67           external
68           payable
69           onlyOwner
70:      {

82       function executeWithDeposit(address _router, bytes calldata _payload, uint16 _srcChainId)
83           external
84           payable
85           onlyOwner
86:      {

115      function executeWithDepositMultiple(address _router, bytes calldata _payload, uint16 _srcChainId)
116          external
117          payable
118          onlyOwner
119:     {

150      function executeSignedNoDeposit(address _account, address _router, bytes calldata _payload, uint16 _srcChainId)
151          external
152          payable
153          onlyOwner
154:     {

167      function executeSignedWithDeposit(address _account, address _router, bytes calldata _payload, uint16 _srcChainId)
168          external
169          payable
170          onlyOwner
171:     {

201      function executeSignedWithDepositMultiple(
202          address _account,
203          address _router,
204          bytes calldata _payload,
205          uint16 _srcChainId
206:     ) external payable onlyOwner {

GitHub: 50, 66, 82, 115, 150, 167, 201

File: src/RootPort.sol

129:     function initialize(address _bridgeAgentFactory, address _coreRootRouter) external onlyOwner {

147      function initializeCore(
148          address _coreRootBridgeAgent,
149          address _coreLocalBranchBridgeAgent,
150          address _localBranchPortAddress
151:     ) external onlyOwner {

166:     function renounceOwnership() public payable override onlyOwner {

414:     function toggleBridgeAgent(address _bridgeAgent) external override onlyOwner {

421:     function addBridgeAgentFactory(address _bridgeAgentFactory) external override onlyOwner {

431:     function toggleBridgeAgentFactory(address _bridgeAgentFactory) external override onlyOwner {

438      function addNewChain(
439          address _coreBranchBridgeAgentAddress,
440          uint256 _chainId,
441          string memory _wrappedGasTokenName,
442          string memory _wrappedGasTokenSymbol,
443          uint8 _wrappedGasTokenDecimals,
444          address _newLocalBranchWrappedNativeTokenAddress,
445          address _newUnderlyingBranchWrappedNativeTokenAddress
446:     ) external override onlyOwner {

483:     function addEcosystemToken(address _ecoTokenGlobalAddress) external override onlyOwner {

509:     function setCoreRootRouter(address _coreRootRouter, address _coreRootBridgeAgent) external override onlyOwner {

521      function setCoreBranchRouter(
522          address _refundee,
523          address _coreBranchRouter,
524          address _coreBranchBridgeAgent,
525          uint16 _dstChainId,
526          GasParams calldata _gParams
527:     ) external payable override onlyOwner {

539      function syncNewCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent, uint16 _dstChainId)
540          external
541          override
542          onlyOwner
543:     {

GitHub: 129, 147, 166, 414, 421, 431, 438, 483, 509, 521, 539

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

56:      function initialize(address _coreRootBridgeAgent) external override onlyOwner {

GitHub: 56

File: src/factories/BranchBridgeAgentFactory.sol

87:      function initialize(address _coreRootBridgeAgent) external virtual onlyOwner {

GitHub: 87

File: src/factories/ERC20hTokenBranchFactory.sol

60:      function initialize(address _wrappedNativeTokenAddress, address _coreRouter) external onlyOwner {

GitHub: 60

File: src/factories/ERC20hTokenRootFactory.sol

49:      function initialize(address _coreRouter) external onlyOwner {

GitHub: 49

File: src/token/ERC20hTokenBranch.sol

29:      function mint(address account, uint256 amount) external override onlyOwner returns (bool) {

35:      function burn(uint256 amount) public override onlyOwner {

GitHub: 29, 35

File: src/token/ERC20hTokenRoot.sol

57:      function mint(address to, uint256 amount, uint256 chainId) external onlyOwner returns (bool) {

69:      function burn(address from, uint256 amount, uint256 chainId) external onlyOwner {

GitHub: 57, 69

[M‑04] Return values of approve() not checked

Not all IERC20 implementations revert() when there's a failure in approve(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything

There is one instance of this issue:

File: src/BaseBranchRouter.sol

175:             ERC20(_token).approve(_localPortAddress, _deposit);

GitHub: 175

Low Risk Issues

[L‑01] addStrategyToken() does note remove old entries before adding new ones

Each time addStrategyToken() is called, new entries are added to the array, but doing so does not remove any old entries. By calling the function multiple times, an attacker can can increase their voting power indefinitely, without having to acquire new tokens.

There are 2 instances of this issue:

File: src/BranchPort.sol

361      /// @inheritdoc IBranchPort
362      function addStrategyToken(address _token, uint256 _minimumReservesRatio) external override requiresCoreRouter {
363          if (_minimumReservesRatio >= DIVISIONER || _minimumReservesRatio < MIN_RESERVE_RATIO) {
364              revert InvalidMinimumReservesRatio();
365          }
366  
367          strategyTokens.push(_token);
368          getMinimumTokenReserveRatio[_token] = _minimumReservesRatio;
369          isStrategyToken[_token] = true;
370: 

413      /// @inheritdoc IBranchPort
414      function setCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent)
415          external
416          override
417          requiresCoreRouter
418      {
419          coreBranchRouterAddress = _coreBranchRouter;
420          isBridgeAgent[_coreBranchBridgeAgent] = true;
421          bridgeAgents.push(_coreBranchBridgeAgent);
422  
423          emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);
424:     }

GitHub: 361, 413

[L‑02] Unsafe downcast

When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs

There are 8 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit uint8
243:              uint8(_dParams.hTokens.length),

/// @audit uint8
320:              uint8(_dParams.hTokens.length),

/// @audit uint8
359:          if (uint8(deposit.hTokens.length) == 1) {

/// @audit uint8
383:          } else if (uint8(deposit.hTokens.length) > 1) {

/// @audit uint8
389:                      uint8(deposit.hTokens.length),

/// @audit uint8
400:                      uint8(deposit.hTokens.length),

GitHub: 243, 320, 359, 383, 389, 400

File: src/RootBridgeAgent.sol

/// @audit uint8
896:                  uint8(_hTokens.length),

/// @audit uint8
1092:             uint8(hTokens.length),

GitHub: 896, 1092

[L‑03] Array lengths not checked

If the length of the arrays are not required to be of the same length, user operations may not be fully executed due to a mismatch in the number of items iterated over, versus the number of items provided in the second array

There is one instance of this issue:

File: src/BranchPort.sol

246       function bridgeInMultiple(
247           address _recipient,
248           address[] memory _localAddresses,
249           address[] memory _underlyingAddresses,
250           uint256[] memory _amounts,
251           uint256[] memory _deposits
252:      ) external override requiresBridgeAgent {

GitHub: 246

[L‑04] receive()/payable fallback() function does not authorize requests

Having no access control on the function (e.g. require(msg.sender == address(weth))) means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds. If the concern is having to spend a small amount of gas to check the sender against an immutable address, the code should at least have a function to rescue mistakenly-sent Ether.

There are 3 instances of this issue:

File: src/BranchBridgeAgent.sol

149:      receive() external payable {}

GitHub: 149

File: src/RootBridgeAgent.sol

128:      receive() external payable {}

GitHub: 128

File: src/VirtualAccount.sol

44:       receive() external payable {}

GitHub: 44

[L‑05] safeApprove() is deprecated

Deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance(). If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead. The function may currently work, but if a bug is found in this version of OpenZeppelin, and the version that you're forced to upgrade to no longer has this function, you'll encounter unnecessary delays in porting and testing replacement contracts.

There are 2 instances of this issue:

File: src/MulticallRootRouter.sol

521:          outputToken.safeApprove(_bridgeAgentAddress, amountOut);

559:              outputTokens[i].safeApprove(_bridgeAgentAddress, amountsOut[i]);

GitHub: 521, 559

[L‑06] Use Ownable2Step rather than Ownable

Ownable2Step and Ownable2StepUpgradeable prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.

There are 12 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

25:   contract BaseBranchRouter is IBranchRouter, Ownable {

GitHub: 25

File: src/BranchBridgeAgentExecutor.sol

29:   contract BranchBridgeAgentExecutor is Ownable, BridgeAgentConstants {

GitHub: 29

File: src/BranchPort.sol

17:   contract BranchPort is Ownable, IBranchPort {

GitHub: 17

File: src/CoreRootRouter.sol

38    contract CoreRootRouter is IRootRouter, Ownable {
39        /*///////////////////////////////////////////////////////////////
40                        CORE ROOT ROUTER STATE
41        //////////////////////////////////////////////////////////////*/
42    
43:       /// @notice Boolean to indicate if the contract is in set up mode.

GitHub: 38

File: src/MulticallRootRouter.sol

57:   contract MulticallRootRouter is IRootRouter, Ownable {

GitHub: 57

File: src/RootBridgeAgentExecutor.sol

27    contract RootBridgeAgentExecutor is Ownable, BridgeAgentConstants {
28        /*///////////////////////////////////////////////////////////////
29                                    CONSTRUCTOR
30        //////////////////////////////////////////////////////////////*/
31        /**
32         * @notice Constructor for Root Bridge Agent Executor.
33         * @param _rootBridgeAgent the owner of the contract in charge of calling the different execution functions.
34         */
35        constructor(address _rootBridgeAgent) {
36:           _initializeOwner(_rootBridgeAgent);

GitHub: 27

File: src/RootPort.sol

16:   contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/factories/BranchBridgeAgentFactory.sol

19    contract BranchBridgeAgentFactory is Ownable, IBranchBridgeAgentFactory {
20:       /// @notice Local Chain Id.

GitHub: 19

File: src/factories/ERC20hTokenBranchFactory.sol

12    contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {
13:       /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

12    contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {
13:       /// @notice Local Network Identifier.

GitHub: 12

File: src/token/ERC20hTokenBranch.sol

12    contract ERC20hTokenBranch is ERC20, Ownable, IERC20hTokenBranch {
13        constructor(
14            string memory chainName,
15            string memory chainSymbol,
16            string memory _name,
17            string memory _symbol,
18            uint8 _decimals,
19            address _owner
20        ) ERC20(string(string.concat(chainName, _name)), string(string.concat(chainSymbol, _symbol)), _decimals) {
21:           _initializeOwner(_owner);

GitHub: 12

File: src/token/ERC20hTokenRoot.sol

12    contract ERC20hTokenRoot is ERC20, Ownable, IERC20hTokenRoot {
13:       /// @inheritdoc IERC20hTokenRoot

GitHub: 12

[L‑07] Empty receive()/fallback() function

If the intention is for Ether sent by a caller to be used for an actual purpose (i.e. the function is not just a WETH withdraw() handler), the function should call another function (e.g. call weth.deposit() and use the token on the caller's behalf) or at least emit an event to track that funds were sent directly to it.

There are 3 instances of this issue:

File: src/BranchBridgeAgent.sol

149:     receive() external payable {}

GitHub: 149

File: src/RootBridgeAgent.sol

128:     receive() external payable {}

GitHub: 128

File: src/VirtualAccount.sol

44:      receive() external payable {}

GitHub: 44

[L‑08] External calls in an un-bounded for-loop may result in a DOS

Consider limiting the number of iterations in for-loops that make external calls

There are 4 instances of this issue:

File: src/RootBridgeAgent.sol

328                  IPort(localPortAddress).bridgeToRoot(
329                      msg.sender,
330                      IPort(localPortAddress).getGlobalTokenFromLocal(_hToken, _dstChainId),
331                      settlement.amounts[i],
332                      settlement.deposits[i],
333                      _dstChainId
334:                 );

330:                     IPort(localPortAddress).getGlobalTokenFromLocal(_hToken, _dstChainId),

1072:            hTokens[i] = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddresses[i], _dstChainId);

1073:            tokens[i] = IPort(localPortAddress).getUnderlyingTokenFromLocal(hTokens[i], _dstChainId);

GitHub: 328, 330, 1072, 1073

[L‑09] Consider implementing two-step procedure for updating protocol addresses

A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must 'accept' the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement EIP-165, and to have the 'set' functions ensure that the recipient is of the right interface type.

There are 10 instances of this issue:

File: src/BranchPort.sol

331      function setCoreRouter(address _newCoreRouter) external override requiresCoreRouter {
332          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");
333          require(_newCoreRouter != address(0), "New CoreRouter address is zero");
334          coreBranchRouterAddress = _newCoreRouter;
335:     }

362      function addStrategyToken(address _token, uint256 _minimumReservesRatio) external override requiresCoreRouter {
363          if (_minimumReservesRatio >= DIVISIONER || _minimumReservesRatio < MIN_RESERVE_RATIO) {
364              revert InvalidMinimumReservesRatio();
365          }
366  
367          strategyTokens.push(_token);
368          getMinimumTokenReserveRatio[_token] = _minimumReservesRatio;
369          isStrategyToken[_token] = true;
370  
371          emit StrategyTokenAdded(_token, _minimumReservesRatio);
372:     }

382      function addPortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
383          external
384          override
385          requiresCoreRouter
386      {
387          if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();
388          portStrategies.push(_portStrategy);
389          strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;
390          isPortStrategy[_portStrategy][_token] = true;
391  
392          emit PortStrategyAdded(_portStrategy, _token, _dailyManagementLimit);
393:     }

403      function updatePortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
404          external
405          override
406          requiresCoreRouter
407      {
408          strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;
409  
410          emit PortStrategyUpdated(_portStrategy, _token, _dailyManagementLimit);
411:     }

414      function setCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent)
415          external
416          override
417          requiresCoreRouter
418      {
419          coreBranchRouterAddress = _coreBranchRouter;
420          isBridgeAgent[_coreBranchBridgeAgent] = true;
421          bridgeAgents.push(_coreBranchBridgeAgent);
422  
423          emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);
424:     }

GitHub: 331, 362, 382, 403, 414

File: src/RootPort.sol

239      function setAddresses(
240          address _globalAddress,
241          address _localAddress,
242          address _underlyingAddress,
243          uint256 _srcChainId
244      ) external override requiresCoreRootRouter {
245          if (_globalAddress == address(0)) revert InvalidGlobalAddress();
246          if (_localAddress == address(0)) revert InvalidLocalAddress();
247          if (_underlyingAddress == address(0)) revert InvalidUnderlyingAddress();
248  
249          isGlobalAddress[_globalAddress] = true;
250          getGlobalTokenFromLocal[_localAddress][_srcChainId] = _globalAddress;
251          getLocalTokenFromGlobal[_globalAddress][_srcChainId] = _localAddress;
252          getLocalTokenFromUnderlying[_underlyingAddress][_srcChainId] = _localAddress;
253          getUnderlyingTokenFromLocal[_localAddress][_srcChainId] = _underlyingAddress;
254  
255          emit LocalTokenAdded(_underlyingAddress, _localAddress, _globalAddress, _srcChainId);
256:     }

259      function setLocalAddress(address _globalAddress, address _localAddress, uint256 _srcChainId)
260          external
261          override
262          requiresCoreRootRouter
263      {
264          if (_localAddress == address(0)) revert InvalidLocalAddress();
265  
266          getGlobalTokenFromLocal[_localAddress][_srcChainId] = _globalAddress;
267          getLocalTokenFromGlobal[_globalAddress][_srcChainId] = _localAddress;
268  
269          emit GlobalTokenAdded(_localAddress, _globalAddress, _srcChainId);
270:     }

382      function addBridgeAgent(address _manager, address _bridgeAgent) external override requiresBridgeAgentFactory {
383          if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent();
384  
385          bridgeAgents.push(_bridgeAgent);
386          getBridgeAgentManager[_bridgeAgent] = _manager;
387          isBridgeAgent[_bridgeAgent] = true;
388  
389          emit BridgeAgentAdded(_bridgeAgent, _manager);
390:     }

483      function addEcosystemToken(address _ecoTokenGlobalAddress) external override onlyOwner {
484          // Check if token already added
485          if (isGlobalAddress[_ecoTokenGlobalAddress]) revert AlreadyAddedEcosystemToken();
486  
487          // Check if token is already a underlying token in current chain
488          if (getUnderlyingTokenFromLocal[_ecoTokenGlobalAddress][localChainId] != address(0)) {
489              revert AlreadyAddedEcosystemToken();
490          }
491  
492          // Check if token is already a local branch token in current chain
493          if (getLocalTokenFromUnderlying[_ecoTokenGlobalAddress][localChainId] != address(0)) {
494              revert AlreadyAddedEcosystemToken();
495          }
496  
497          // Update State
498          // 1. Add new global token to global address mapping
499          isGlobalAddress[_ecoTokenGlobalAddress] = true;
500          // 2. Add new branch local token address to global token mapping
501          getGlobalTokenFromLocal[_ecoTokenGlobalAddress][localChainId] = _ecoTokenGlobalAddress;
502          // 3. Add new global token to branch local token address mapping
503          getLocalTokenFromGlobal[_ecoTokenGlobalAddress][localChainId] = _ecoTokenGlobalAddress;
504  
505          emit EcosystemTokenAdded(_ecoTokenGlobalAddress);
506:     }

509      function setCoreRootRouter(address _coreRootRouter, address _coreRootBridgeAgent) external override onlyOwner {
510          if (_coreRootRouter == address(0)) revert InvalidCoreRootRouter();
511          if (_coreRootBridgeAgent == address(0)) revert InvalidCoreRootBridgeAgent();
512  
513          coreRootRouterAddress = _coreRootRouter;
514          coreRootBridgeAgentAddress = _coreRootBridgeAgent;
515          getBridgeAgentManager[_coreRootBridgeAgent] = owner();
516  
517          emit CoreRootSet(_coreRootRouter, _coreRootBridgeAgent);
518:     }

GitHub: 239, 259, 382, 483, 509

[L‑10] name() is not a part of the ERC-20 standard

The name() function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.

There are 5 instances of this issue:

File: src/ArbitrumCoreBranchRouter.sol

56:              string.concat("Arbitrum Ulysses ", ERC20(_underlyingAddress).name()),

GitHub: 56

File: src/CoreBranchRouter.sol

68:              ERC20(_underlyingAddress).name(), ERC20(_underlyingAddress).symbol(), decimals, true

72:          bytes memory params = abi.encode(_underlyingAddress, newToken, newToken.name(), newToken.symbol(), decimals);

GitHub: 68, 72

File: src/CoreRootRouter.sol

426:             ERC20(_globalAddress).name(),

GitHub: 426

File: src/factories/ERC20hTokenBranchFactory.sol

66:              ERC20(_wrappedNativeTokenAddress).name(),

GitHub: 66

[L‑11] symbol() is not a part of the ERC-20 standard

The symbol() function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.

There are 5 instances of this issue:

File: src/ArbitrumCoreBranchRouter.sol

57:              string.concat("arb-u", ERC20(_underlyingAddress).symbol()),

GitHub: 57

File: src/CoreBranchRouter.sol

68:              ERC20(_underlyingAddress).name(), ERC20(_underlyingAddress).symbol(), decimals, true

72:          bytes memory params = abi.encode(_underlyingAddress, newToken, newToken.name(), newToken.symbol(), decimals);

GitHub: 68, 72

File: src/CoreRootRouter.sol

427:             ERC20(_globalAddress).symbol(),

GitHub: 427

File: src/factories/ERC20hTokenBranchFactory.sol

67:              ERC20(_wrappedNativeTokenAddress).symbol(),

GitHub: 67

[L‑12] decimals() is not a part of the ERC-20 standard

The decimals() function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.

There are 4 instances of this issue:

File: src/ArbitrumCoreBranchRouter.sol

58:              ERC20(_underlyingAddress).decimals()

GitHub: 58

File: src/CoreBranchRouter.sol

64:          uint8 decimals = ERC20(_underlyingAddress).decimals();

GitHub: 64

File: src/CoreRootRouter.sol

428:             ERC20(_globalAddress).decimals(),

GitHub: 428

File: src/factories/ERC20hTokenBranchFactory.sol

68:              ERC20(_wrappedNativeTokenAddress).decimals(),

GitHub: 68

[L‑13] SafeTransferLib does not ensure that the token contract exists

SafeTransferLib.sol has functions named similarly to functions that OpenZeppelin has, but they act differently. At the top of the file is the following comment:

/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.

https://github.com/transmissions11/solmate/blob/bfc9c25865a274a7827fea5abf6e4fb64fc64e6c/src/utils/SafeTransferLib.sol#L9

This means that if one of the transfer functions is called on a contract that doesn't exist or that got selfdestructed, the call will succeede without any warning/revert, which may cause implicit token holdings validations to be ineffective. Consider using the OpenZeppelin version instead.

There are 2 instances of this issue:

File: src/ArbitrumBranchPort.sol

66:          _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit);

128:             _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit);

GitHub: 66, 128

[L‑14] Functions calling contracts/addresses with transfer hooks are missing reentrancy guards

Even if the function follows the best practice of check-effects-interaction, not using a reentrancy guard when there may be transfer hooks will open the users of this protocol up to read-only reentrancies with no way to protect against it, except by block-listing the whole protocol.

There is one instance of this issue:

File: src/VirtualAccount.sol

/// @audit `withdrawERC721()`
62:          ERC721(_token).transferFrom(address(this), msg.sender, _tokenId);

GitHub: 62

[L‑15] approve()/safeApprove() may revert if the current approval is not zero

Calling approve() without first calling approve(0) if the current approval is non-zero will revert with some tokens, such as Tether (USDT). While Tether is known to do this, it applies to other tokens as well, which are trying to protect against this attack vector. safeApprove() itself also implements this protection. Always reset the approval to zero before changing it to a new value (SafeERC20.forceApprove() does this for you), or use safeIncreaseAllowance()/safeDecreaseAllowance()

There are 3 instances of this issue:

File: src/BaseBranchRouter.sol

175:             ERC20(_token).approve(_localPortAddress, _deposit);

GitHub: 175

File: src/MulticallRootRouter.sol

521:         outputToken.safeApprove(_bridgeAgentAddress, amountOut);

559:             outputTokens[i].safeApprove(_bridgeAgentAddress, amountsOut[i]);

GitHub: 521, 559

[L‑16] External call recipient may consume all transaction gas

There is no limit specified on the amount of gas used, so the recipient can use up all of the transaction's gas, causing it to revert. Use addr.call{gas: <amount>}("") or this library instead.

There are 8 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit `_execute()`
717:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

/// @audit `_execute()`
737:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

GitHub: 717, 737

File: src/RootBridgeAgent.sol

/// @audit `_execute()`
754:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

/// @audit `_execute()`
779:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

/// @audit `_performCall()`
835:             callee.call{value: msg.value}("");

/// @audit `_performRetrySettlementCall()`
927:             callee.call{value: _value}("");

GitHub: 754, 779, 835, 927

File: src/VirtualAccount.sol

/// @audit `call()`
74:              if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData);

/// @audit `payableCall()`
101:             if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);

GitHub: 74, 101

[L‑17] Missing contract-existence checks before low-level calls

Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that <address>.code.length > 0

There are 4 instances of this issue:

File: src/ArbitrumCoreBranchRouter.sol

65           IBridgeAgent(localBridgeAgentAddress).callOutSystem(payable(msg.sender), payload, GasParams(0, 0));
66:      }

118          IBridgeAgent(localBridgeAgentAddress).callOutSystem(payable(_refundee), payload, _gParams);
119:     }

GitHub: 65, 118

File: src/BaseBranchRouter.sol

84           IBridgeAgent(localBridgeAgentAddress).callOut{value: msg.value}(payable(msg.sender), _params, _gParams);
85:      }

GitHub: 84

File: src/BranchBridgeAgent.sol

737          (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);
738  
739          //Update tx state if execution failed
740          if (!success) {
741              //Read the fallback flag and perform the fallback call if necessary. If not, allow for retrying deposit.
742:             if (_hasFallbackToggled) {

GitHub: 737

[L‑18] Code does not follow the best practice of check-effects-interaction

Code should follow the best-practice of check-effects-interaction, where state variables are updated before any external calls are made. Doing so prevents a large class of reentrancy bugs.

There are 43 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

/// @audit approve() called prior to this assignment
64:          bridgeAgentExecutorAddress = IBridgeAgent(_localBridgeAgentAddress).bridgeAgentExecutorAddress();

GitHub: 64

File: src/BranchBridgeAgent.sol

/// @audit bridgeOut() called prior to this assignment
832:         deposit.owner = _refundee;

/// @audit bridgeOut() called prior to this assignment
835:         deposit.hTokens = addressArray;

/// @audit bridgeOut() called prior to this assignment
838:         deposit.tokens = addressArray;

/// @audit bridgeOut() called prior to this assignment
841:         deposit.amounts = uintArray;

/// @audit bridgeOut() called prior to this assignment
844:         deposit.deposits = uintArray;

/// @audit bridgeOut() called prior to this assignment
846:         deposit.status = STATUS_SUCCESS;

/// @audit bridgeOutMultiple() called prior to this assignment
882:         deposit.owner = _refundee;

/// @audit bridgeOutMultiple() called prior to this assignment
883:         deposit.hTokens = _hTokens;

/// @audit bridgeOutMultiple() called prior to this assignment
884:         deposit.tokens = _tokens;

/// @audit bridgeOutMultiple() called prior to this assignment
885:         deposit.amounts = _amounts;

/// @audit bridgeOutMultiple() called prior to this assignment
886:         deposit.deposits = _deposits;

/// @audit bridgeOutMultiple() called prior to this assignment
887:         deposit.status = STATUS_SUCCESS;

GitHub: 832, 835, 838, 841, 844, 846, 882, 883, 884, 885, 886, 887

File: src/BranchPort.sol

/// @audit withdraw() called prior to this assignment
205:         getPortStrategyTokenDebt[_strategy][_token] = portStrategyTokenDebt - amountToWithdraw;

/// @audit withdraw() called prior to this assignment
207:         getStrategyTokenDebt[_token] = strategyTokenDebt - amountToWithdraw;

GitHub: 205, 207

File: src/CoreRootRouter.sol

/// @audit null() called prior to this assignment
88:          hTokenFactoryAddress = _hTokenFactory;

GitHub: 88

File: src/RootBridgeAgent.sol

/// @audit bridgeToRoot() called prior to this assignment
254:         settlementReference.status = STATUS_SUCCESS;

/// @audit bridgeToRoot() called prior to this assignment
343:         delete getSettlement[_settlementNonce];

/// @audit bridgeToRoot() called prior to this assignment
680:             settlementReference.status = STATUS_SUCCESS;

/// @audit bridgeToRoot() called prior to this assignment
1014:        settlement.owner = _refundee;

/// @audit bridgeToRoot() called prior to this assignment
1015:        settlement.recipient = _recipient;

/// @audit bridgeToRoot() called prior to this assignment
1018:        settlement.hTokens = addressArray;

/// @audit bridgeToRoot() called prior to this assignment
1021:        settlement.tokens = addressArray;

/// @audit bridgeToRoot() called prior to this assignment
1024:        settlement.amounts = uintArray;

/// @audit bridgeToRoot() called prior to this assignment
1027:        settlement.deposits = uintArray;

/// @audit bridgeToRoot() called prior to this assignment
1029:        settlement.dstChainId = _dstChainId;

/// @audit bridgeToRoot() called prior to this assignment
1030:        settlement.status = STATUS_SUCCESS;

/// @audit bridgeToRoot() called prior to this assignment
1106:        settlement.owner = _refundee;

/// @audit bridgeToRoot() called prior to this assignment
1107:        settlement.recipient = _recipient;

/// @audit bridgeToRoot() called prior to this assignment
1108:        settlement.hTokens = hTokens;

/// @audit bridgeToRoot() called prior to this assignment
1109:        settlement.tokens = tokens;

/// @audit bridgeToRoot() called prior to this assignment
1110:        settlement.amounts = _amounts;

/// @audit bridgeToRoot() called prior to this assignment
1111:        settlement.deposits = _deposits;

/// @audit bridgeToRoot() called prior to this assignment
1112:        settlement.dstChainId = _dstChainId;

/// @audit bridgeToRoot() called prior to this assignment
1113:        settlement.status = STATUS_SUCCESS;

GitHub: 254, 343, 680, 1014, 1015, 1018, 1021, 1024, 1027, 1029, 1030, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113

File: src/RootPort.sol

/// @audit syncBranchBridgeAgent() called prior to this assignment
162:         getBridgeAgentManager[_coreRootBridgeAgent] = owner();

/// @audit syncBranchBridgeAgent() called prior to this assignment
465:         isChainId[_chainId] = true;

/// @audit syncBranchBridgeAgent() called prior to this assignment
467:         isGlobalAddress[newGlobalToken] = true;

/// @audit syncBranchBridgeAgent() called prior to this assignment
469:         getGlobalTokenFromLocal[_newLocalBranchWrappedNativeTokenAddress][_chainId] = newGlobalToken;

/// @audit syncBranchBridgeAgent() called prior to this assignment
471:         getLocalTokenFromGlobal[newGlobalToken][_chainId] = _newLocalBranchWrappedNativeTokenAddress;

/// @audit syncBranchBridgeAgent() called prior to this assignment
473          getLocalTokenFromUnderlying[_newUnderlyingBranchWrappedNativeTokenAddress][_chainId] =
474:             _newLocalBranchWrappedNativeTokenAddress;

/// @audit syncBranchBridgeAgent() called prior to this assignment
476          getUnderlyingTokenFromLocal[_newLocalBranchWrappedNativeTokenAddress][_chainId] =
477:             _newUnderlyingBranchWrappedNativeTokenAddress;

GitHub: 162, 465, 467, 469, 471, 473, 476

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit push() called prior to this assignment
74:          localCoreRouterAddress = _coreRouter;

GitHub: 74

[L‑19] Missing checks for address(0x0) in the constructor/initializer

There are 7 instances of this issue:

File: src/CoreBranchRouter.sol

31:          hTokenFactoryAddress = _hTokenFactoryAddress;

GitHub: 31

File: src/CoreRootRouter.sol

73:          rootPortAddress = _rootPortAddress;

86:          bridgeAgentAddress = payable(_bridgeAgentAddress);

88:          hTokenFactoryAddress = _hTokenFactory;

GitHub: 73, 86, 88

File: src/VirtualAccount.sol

36:          userAddress = _userAddress;

37:          localPortAddress = _localPortAddress;

GitHub: 36, 37

File: src/factories/RootBridgeAgentFactory.sol

35:          lzEndpointAddress = _lzEndpointAddress;

GitHub: 35

[L‑20] Missing checks for address(0x0) when updating address state variables

There is one instance of this issue:

File: src/BranchPort.sol

419:         coreBranchRouterAddress = _coreBranchRouter;

GitHub: 419

[L‑21] State variables not capped at reasonable values

Consider adding minimum/maximum value checks to ensure that the state variables below can never be used to excessively harm users, including via griefing

There are 2 instances of this issue:

File: src/BranchPort.sol

389:         strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;

408:         strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;

GitHub: 389, 408

[L‑22] Tokens may be minted to address(0x0)

Neither the listed functions, nor _mint() prevent minting to address(0x0)

There are 2 instances of this issue:

File: src/token/ERC20hTokenBranch.sol

29       function mint(address account, uint256 amount) external override onlyOwner returns (bool) {
30           _mint(account, amount);
31           return true;
32:      }

GitHub: 29

File: src/token/ERC20hTokenRoot.sol

57       function mint(address to, uint256 amount, uint256 chainId) external onlyOwner returns (bool) {
58           getTokenBalance[chainId] += amount;
59           _mint(to, amount);
60           return true;
61:      }

GitHub: 57

[L‑23] Initialization can be front-run

The initialize() functions below are not called by another contract atomically after the contract is deployed, so it's possible for a malicious user to call initialize() which, if it's noticed in time, would require the project to re-deploy the contract in order to properly initialize. Consider creating a factory contract, which will new and initialize() each contract atomically.

There are 8 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

60:      function initialize(address _localBridgeAgentAddress) external onlyOwner {

GitHub: 60

File: src/BranchPort.sol

122:     function initialize(address _coreBranchRouter, address _bridgeAgentFactory) external virtual onlyOwner {

GitHub: 122

File: src/CoreRootRouter.sol

83:      function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

GitHub: 83

File: src/MulticallRootRouter.sol

109:     function initialize(address _bridgeAgentAddress) external onlyOwner {

GitHub: 109

File: src/RootPort.sol

129:     function initialize(address _bridgeAgentFactory, address _coreRootRouter) external onlyOwner {

GitHub: 129

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

56:      function initialize(address _coreRootBridgeAgent) external override onlyOwner {

GitHub: 56

File: src/factories/ERC20hTokenBranchFactory.sol

60:      function initialize(address _wrappedNativeTokenAddress, address _coreRouter) external onlyOwner {

GitHub: 60

File: src/factories/ERC20hTokenRootFactory.sol

49:      function initialize(address _coreRouter) external onlyOwner {

GitHub: 49

Non-critical Issues

[N‑01] public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There are 6 instances of this issue:

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)
588           public
589           override
590:          requiresEndpoint(_endpoint, _srcAddress)

GitHub: 578, 587

File: src/RootBridgeAgent.sol

423:      function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public {

434       function lzReceiveNonBlocking(
435           address _endpoint,
436           uint16 _srcChainId,
437           bytes calldata _srcAddress,
438           bytes calldata _payload
439:      ) public override requiresEndpoint(_endpoint, _srcChainId, _srcAddress) {

GitHub: 423, 434

File: src/VirtualAccount.sol

85:       function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {

GitHub: 85

File: src/token/ERC20hTokenBranch.sol

35:       function burn(uint256 amount) public override onlyOwner {

GitHub: 35

[N‑02] Event is not properly indexed

Index event fields make the field more quickly accessible to off-chain tools that parse events. This is especially useful when it comes to filtering based on an address. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Where applicable, each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three applicable fields, all of the applicable fields should be indexed.

There is one instance of this issue:

File: src/interfaces/IRootPort.sol

380:      event VirtualAccountCreated(address indexed user, address account);

GitHub: 380

[N‑03] Duplicated require()/revert() checks should be refactored to a modifier or function

The compiler will inline the function, which will avoid JUMP instructions usually associated with functions

There are 12 instances of this issue:

see instances
File: src/BranchBridgeAgent.sol

441:          if (deposit.owner != msg.sender) revert NotDepositOwner();

624:              if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();

GitHub: 441, 624

File: src/BranchPort.sol

560:          if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();

GitHub: 560

File: src/CoreRootRouter.sol

366:          revert();

GitHub: 366

File: src/MulticallRootRouter.sol

204:          revert();

GitHub: 204

File: src/RootBridgeAgent.sol

670:              if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable();

910:          if (callee == address(0)) revert UnrecognizedBridgeAgent();

GitHub: 670, 910

File: src/RootPort.sol

264:          if (_localAddress == address(0)) revert InvalidLocalAddress();

299:          if (!isGlobalAddress[_hToken]) revert UnrecognizedToken();

544:          if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter();

545:          if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();

GitHub: 264, 299, 544, 545

File: src/VirtualAccount.sol

103:              if (!success) revert CallFailed();

GitHub: 103

[N‑04] Common functions should be refactored to a common base contract

The functions below have the same implementation as is seen in other files. The functions should be refactored into functions of a common base contract

There is one instance of this issue:

File: src/RootPort.sol

/// @audit seen in src/BranchPort.sol
166       function renounceOwnership() public payable override onlyOwner {
167           revert("Cannot renounce ownership");
168:      }

GitHub: 166

[N‑05] Import declarations should import specific identifiers, rather than the whole file

Using import declarations of the form import {<identifier_name>} from "some/file.sol" avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation (but does not save any gas)

There is one instance of this issue:

File: src/interfaces/ILayerZeroEndpoint.sol

5:    import "./ILayerZeroUserApplicationConfig.sol";

GitHub: 5

[N‑06] Events that mark critical parameter changes should contain both the old and the new value

This should especially be done if the new value is not required to be different from the old value

There are 6 instances of this issue:

File: src/BranchPort.sol

/// @audit updatePortStrategy()
410:          emit PortStrategyUpdated(_portStrategy, _token, _dailyManagementLimit);

/// @audit setCoreBranchRouter()
423:          emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);

GitHub: 410, 423

File: src/RootPort.sol

/// @audit setAddresses()
255:          emit LocalTokenAdded(_underlyingAddress, _localAddress, _globalAddress, _srcChainId);

/// @audit setLocalAddress()
269:          emit GlobalTokenAdded(_localAddress, _globalAddress, _srcChainId);

/// @audit setCoreRootRouter()
517:          emit CoreRootSet(_coreRootRouter, _coreRootBridgeAgent);

/// @audit setCoreBranchRouter()
535:          emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent, _dstChainId);

GitHub: 255, 269, 517, 535

[N‑07] Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.

There are 25 instances of this issue:

see instances
File: src/BranchBridgeAgent.sol

/// @audit seen in src/ArbitrumBranchPort.sol 
56:       uint16 public immutable localChainId;

GitHub: 56

File: src/CoreRootRouter.sol

/// @audit seen in src/BranchBridgeAgent.sol 
47:       uint256 public immutable rootChainId;

/// @audit seen in src/ArbitrumBranchPort.sol 
51:       address public immutable rootPortAddress;

GitHub: 47, 51

File: src/MulticallRootRouter.sol

/// @audit seen in src/BranchBridgeAgent.sol 
65:       uint256 public immutable localChainId;

/// @audit seen in src/BranchBridgeAgent.sol 
68:       address public immutable localPortAddress;

GitHub: 65, 68

File: src/RootBridgeAgent.sol

/// @audit seen in src/MulticallRootRouter.sol 
40:       uint16 public immutable localChainId;

/// @audit seen in src/BranchBridgeAgent.sol 
46:       address public immutable localRouterAddress;

/// @audit seen in src/MulticallRootRouter.sol 
49:       address public immutable localPortAddress;

/// @audit seen in src/BranchBridgeAgent.sol 
52:       address public immutable lzEndpointAddress;

/// @audit seen in src/BranchBridgeAgent.sol 
55:       address public immutable bridgeAgentExecutorAddress;

GitHub: 40, 46, 49, 52, 55

File: src/RootPort.sol

/// @audit seen in src/RootBridgeAgent.sol 
34:       uint256 public immutable localChainId;

GitHub: 34

File: src/VirtualAccount.sol

/// @audit seen in src/RootBridgeAgent.sol 
24:       address public immutable override localPortAddress;

GitHub: 24

File: src/factories/BranchBridgeAgentFactory.sol

/// @audit seen in src/RootPort.sol 
21:       uint16 public immutable localChainId;

/// @audit seen in src/CoreRootRouter.sol 
24:       uint16 public immutable rootChainId;

/// @audit seen in src/VirtualAccount.sol 
33:       address public immutable localPortAddress;

/// @audit seen in src/RootBridgeAgent.sol 
36:       address public immutable lzEndpointAddress;

GitHub: 21, 24, 33, 36

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit seen in src/factories/BranchBridgeAgentFactory.sol 
14:       uint24 public immutable localChainId;

/// @audit seen in src/factories/BranchBridgeAgentFactory.sol 
17:       address public immutable localPortAddress;

GitHub: 14, 17

File: src/factories/ERC20hTokenRootFactory.sol

/// @audit seen in src/factories/ERC20hTokenBranchFactory.sol 
14:       uint16 public immutable localChainId;

/// @audit seen in src/CoreRootRouter.sol 
17:       address public immutable rootPortAddress;

GitHub: 14, 17

File: src/factories/RootBridgeAgentFactory.sol

/// @audit seen in src/factories/BranchBridgeAgentFactory.sol 
13:       uint16 public immutable rootChainId;

/// @audit seen in src/factories/ERC20hTokenRootFactory.sol 
16:       address public immutable rootPortAddress;

/// @audit seen in src/factories/BranchBridgeAgentFactory.sol 
19:       address public immutable lzEndpointAddress;

GitHub: 13, 16, 19

File: src/token/ERC20hTokenRoot.sol

/// @audit seen in src/factories/ERC20hTokenRootFactory.sol 
14:       uint16 public immutable override localChainId;

/// @audit seen in src/RootBridgeAgent.sol 
17:       address public immutable override factoryAddress;

GitHub: 14, 17

[N‑08] Inconsistent spacing in comments

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file

There are 145 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

113:          //Sends message to Root Bridge Agent

GitHub: 113

File: src/ArbitrumBranchPort.sol

49:       ///@inheritdoc IArbitrumBranchPort

72:       ///@inheritdoc IArbitrumBranchPort

126:          //Store Underlying Tokens

131:          //Burn hTokens if any are being used

GitHub: 49, 72, 126, 131

File: src/ArbitrumCoreBranchRouter.sol

50:       ///@inheritdoc CoreBranchRouter

52:           //Encode Data, no need to create local token since we are already in the global environment

64:           //Send Cross-Chain request (System Response/Request)

117:          //Send Cross-Chain request

125:      ///@inheritdoc CoreBranchRouter

GitHub: 50, 52, 64, 117, 125

File: src/BaseBranchRouter.sol

94:           //Transfer tokens to this contract.

97:           //Perform call to bridge agent.

109:          //Transfer tokens to this contract.

112:          //Perform call to bridge agent.

GitHub: 94, 97, 109, 112

File: src/BranchBridgeAgent.sol

187:          //Encode Data for cross-chain call.

190:          //Perform Call

201:          //Encode Data for cross-chain call.

204:          //Perform Call

215:          //Cache Deposit Nonce

218:          //Encode Data for cross-chain call.

223:          //Create Deposit and Send Cross-Chain request

226:          //Perform Call

237:          //Cache Deposit Nonce

240:          //Encode Data for cross-chain call.

252:          //Create Deposit and Send Cross-Chain request

257:          //Perform Call

268:          //Encode Data for cross-chain call.

271:          //Perform Signed Call without deposit

283:          //Cache Deposit Nonce

286:          //Encode Data for cross-chain call.

298:          //Create Deposit and Send Cross-Chain request

301:          //Perform Call

334:          //Perform Call

353:          //Check if deposit belongs to message sender

356:          //Encode Data for cross-chain call.

361:                  //Pack new Data

385:                  //Pack new Data

426:          //Encode Data for cross-chain call.

429:          //Update State and Perform Call

592:          //Save Action Flag

603:              //Check if tx has already been executed

606:              //Try to execute the remote request

607:              //Flag 0 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoSettlement(localRouterAddress, _payload)

623:              //Check if tx has already been executed

626:              //Try to execute the remote request

627:              //Flag 1 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement(recipient, localRouterAddress, _payload)

645:              //Check if tx has already been executed

648:              //Try to execute remote request

662:              //DEPOSIT FLAG: 3 (Retrieve Settlement)

667:              //Get nonce

670:              //Check if settlement is in retrieve mode

674:                  //Set settlement to retrieve mode, if not already set.

676:                  //Trigger fallback/Retry failed fallback

680:              //DEPOSIT FLAG: 4 (Fallback)

682:              //Get nonce

694:              //Unrecognized Function Selector

713:          //Update tx state as executed

716:          //Try to execute the remote request

733:          //Update tx state as executed

736:          //Try to execute the remote request

739:          //Update tx state if execution failed

741:              //Read the fallback flag and perform the fallback call if necessary. If not, allow for retrying deposit.

769:          //Sends message to LayerZero messaging layer

786:          //Sends message to LayerZero messaging layer

937:          //Verify Endpoint

941:          //Verify Remote Caller

GitHub: 187, 190, 201, 204, 215, 218, 223, 226, 237, 240, 252, 257, 268, 271, 283, 286, 298, 301, 334, 353, 356, 361, 385, 426, 429, 592, 603, 606, 607, 623, 626, 627, 645, 648, 662, 667, 670, 674, 676, 680, 682, 694, 713, 716, 733, 736, 739, 741, 769, 786, 937, 941

File: src/CoreBranchRouter.sol

63:           //Get Token Info

66:           //Create Token

71:           //Encode Data

77:           //Send Cross-Chain request (System Response/Request)

174:          //Create Token

183:          //Send Cross-Chain request

231:          //Send Cross-Chain request

GitHub: 63, 66, 71, 77, 174, 183, 231

File: src/CoreRootRouter.sol

138:          //Add new global token to branch chain

173:          //Add new global token to branch chain

192:          //Encode CallData

198:          //Add new global token to branch chain

225:          //Add new global token to branch chain

256:          //Add new global token to branch chain

286:          //Add new global token to branch chain

436:          //Add new global token to branch chain

465:          //Create a new global token

GitHub: 138, 173, 192, 198, 225, 256, 286, 436, 465

File: src/MulticallRootRouter.sol

202:      ///@inheritdoc IRootRouter

207:      ///@inheritdoc IRootRouter

523:          //Move output hTokens from Root to Branch and call 'clearToken'.

565:          //Move output hTokens from Root to Branch and call 'clearTokens'.

GitHub: 202, 207, 523, 565

File: src/RootBridgeAgent.sol

167:          //Encode Data for call.

170:          //Perform Call to clear hToken balance on destination branch chain.

197:          //Perform Call.

275:          //Get settlement storage reference

291:          //Encode Data for cross-chain call.

294:          //Retrieve Deposit

491:              //Parse Deposit Nonce

494:              //Check if tx has already been executed

543:              //Check if tx has already been executed

576:              //DEPOSIT FLAG: 5 (Call with Deposit + msg.sender)

581:              //Check if tx has already been executed

679:              //Update Settlement Staus

682:              //Retry settlement call with new params and gas

700:              //Parse deposit nonce

703:              //Check if deposit is in retrieve mode

707:                  //Set settlement to retrieve mode, if not already set.

711:                  //Trigger fallback/Retry failed fallback

717:              //DEPOSIT FLAG: 9 (Fallback)

750:          //Update tx state as executed

753:          //Try to execute the remote request

775:          //Update tx state as executed

778:          //Try to execute the remote request

781:          //Update tx state if execution failed

783:              //Read the fallback flag.

822:              //Sends message to Layerzero Enpoint

834:              //Send Gas to Local Branch Bridge Agent

836:              //Execute locally

878:              //Pack new Data

892:              //Pack new Data

914:              //Sends message to Layerzero Enpoint

926:              //Send Gas to Local Branch Bridge Agent

928:              //Execute locally

939:          //Sends message to LayerZero messaging layer

986:          //Update State to reflect bridgeOut

1063:         //Update Settlement Nonce

GitHub: 167, 170, 197, 275, 291, 294, 491, 494, 543, 576, 581, 679, 682, 700, 703, 707, 711, 717, 750, 753, 775, 778, 781, 783, 822, 834, 836, 878, 892, 914, 926, 928, 939, 986, 1063

File: src/RootBridgeAgentExecutor.sol

87:           // Read Deposit Params

96:           // Bridge In Assets

99:           // Check if there is additional calldata in the payload

133:          // Check if there is additional calldata in the payload

184:          // Check if there is additional calldata in the payload

218:          // Check if there is additional calldata in the payload

272:          // Parse Parameters

282:              // Cache offset

285:              // Parse Params

337:          // Save Deposit Multiple Params

GitHub: 87, 96, 99, 133, 184, 218, 272, 282, 285, 337

File: src/interfaces/BridgeAgentStructs.sol

9:        uint256 gasLimit; // gas allocated for the cross-chain call.

81:       uint32 settlementNonce; // Settlement nonce.

82:       address recipient; // Recipient of the settlement.

83:       address hToken; // Input Local hTokens Address.

84:       address token; // Input Native / underlying Token Address.

85:       uint256 amount; // Amount of Local hTokens deposited for interaction.

86:       uint256 deposit; // Amount of native tokens deposited for interaction.

90:       uint8 numberOfAssets; // Number of assets to deposit.

91:       address recipient; // Recipient of the settlement.

92:       uint32 settlementNonce; // Settlement nonce.

93:       address[] hTokens; // Input Local hTokens Addresses.

94:       address[] tokens; // Input Native / underlying Token Addresses.

95:       uint256[] amounts; // Amount of Local hTokens deposited for interaction.

96:       uint256[] deposits; // Amount of native tokens deposited for interaction.

GitHub: 9, 81, 82, 83, 84, 85, 86, 90, 91, 92, 93, 94, 95, 96

[N‑09] Lines are too long

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. The solidity style guide recommends a maximumum line length of 120 characters, so the lines below should be split when they reach that length.

There are 32 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

60:           address _globalToken = IRootPort(_rootPortAddress).getLocalTokenFromUnderlying(_underlyingAddress, localChainId);

GitHub: 60

File: src/BranchBridgeAgent.sol

112:       * @param _lzEndpointAddress Local Layerzero Endpoint Address where cross-chain requests are sent to the Root Chain Router.

548:                              PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * currentIterationOffset

607:              //Flag 0 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoSettlement(localRouterAddress, _payload)

627:              //Flag 1 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement(recipient, localRouterAddress, _payload)

649:              // Flag 2 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlementMultiple(recipient, localRouterAddress, _payload)

763:       *   @param _gParams LayerZero gas information. (_gasLimit,_remoteBranchExecutionGas,_nativeTokenRecipientOnDstChain)

GitHub: 112, 548, 607, 627, 649, 763

File: src/BranchPort.sol

462:       * @notice Internal function to return the minimum amount of reserves of a given Strategy Token the Port should hold.

GitHub: 462

File: src/MulticallRootRouter.sol

484:       *   @notice Function to perform a set of actions on the omnichain environment without using the user's Virtual Acccount.

603:      /// @notice Verifies the caller is the Bridge Agent Executor. Internal function used in modifier to reduce contract bytesize.

GitHub: 484, 603

File: src/RootBridgeAgent.sol

61:       /// @notice Chain -> Branch Bridge Agent Address. For N chains, each Root Bridge Agent Address has M =< N Branch Bridge Agent Address.

64:       /// @notice Message Path for each connected Branch Bridge Agent as bytes for Layzer Zero interaction = localAddress + destinationAddress abi.encodePacked()

84:       /// @notice If true, bridge agent has already served a request with this nonce from  a given chain. Chain -> Nonce -> Bool

457:              // Flag 0 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSystemRequest(_localRouterAddress, _payload, _srcChainId)

480:              // Flag 1 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoDeposit(localRouterAddress, payload, _srcChainId)

503:              // Flag 2 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDeposit(localRouterAddress, _payload, _srcChainId)

526:              // Flag 3 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDepositMultiple(localRouterAddress, _payload, _srcChainId)

560:              // Flag 4 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedNoDeposit(address(userAccount), localRouterAddress, data, _srcChainId

598:              // Flag 5 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDeposit(address(userAccount), localRouterAddress, data, _srcChainId)

638:              // Flag 6 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDepositMultiple(address(userAccount), localRouterAddress, data, _srcChainId)

GitHub: 61, 64, 84, 457, 480, 503, 526, 560, 598, 638

File: src/VirtualAccount.sol

159:      /// @notice Modifier that verifies msg sender is the approved to use the virtual account. Either the owner or an approved router.

GitHub: 159

File: src/interfaces/IBranchBridgeAgent.sol

36:    *              (See https://github.com/LayerZero-Labs/solidity-examples/blob/8e62ebc886407aafc89dbd2a778e61b7c0a25ca0/contracts/lzApp/NonblockingLzApp.sol)

GitHub: 36

File: src/interfaces/IBranchPort.sol

166:       * @notice Reverts the toggle on the given bridge agent factory. If it's active, it will de-activate it and vice-versa.

GitHub: 166

File: src/interfaces/ILayerZeroEndpoint.sol

12:       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address

14:       // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

GitHub: 12, 14

File: src/interfaces/IRootBridgeAgent.sol

34:    *              (See https://github.com/LayerZero-Labs/solidity-examples/blob/8e62ebc886407aafc89dbd2a778e61b7c0a25ca0/contracts/lzApp/NonblockingLzApp.sol)

178:       *   @param _refundee the effective owner of the settlement this address receives excess gas deposited on source chain

201:       *   @param _refundee the effective owner of the settlement this address receives excess gas deposited on source chain

GitHub: 34, 178, 201

File: src/interfaces/IRootRouter.sol

36:        *   @notice Function responsible of executing a crosschain request which contains cross-chain deposit information attached.

47:        *   @notice Function responsible of executing a crosschain request which contains cross-chain deposit information for multiple assets attached.

66:        * @notice Function responsible of executing a crosschain request which contains cross-chain deposit information and msg.sender attached.

80:        * @notice Function responsible of executing a crosschain request which contains cross-chain deposit information for multiple assets and msg.sender attached.

GitHub: 36, 47, 66, 80

[N‑10] Long functions should be refactored into multiple, smaller, functions

There are 13 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit 77 lines (69 in the body)
343       function retryDeposit(
344           bool _isSigned,
345           uint32 _depositNonce,
346           bytes calldata _params,
347           GasParams calldata _gParams,
348           bool _hasFallbackToggled
349:      ) external payable override lock {

/// @audit 78 lines (71 in the body)
494       function clearTokens(bytes calldata _sParams, address _recipient)
495           external
496           override
497           requiresAgentExecutor
498:          returns (SettlementMultipleParams memory)

/// @audit 115 lines (109 in the body)
587       function lzReceiveNonBlocking(address _endpoint, bytes calldata _srcAddress, bytes calldata _payload)
588           public
589           override
590:          requiresEndpoint(_endpoint, _srcAddress)

GitHub: 343, 494, 587

File: src/CoreBranchRouter.sol

/// @audit 64 lines (62 in the body)
86:       function executeNoSettlement(bytes calldata _params) external payable virtual override requiresAgentExecutor {

GitHub: 86

File: src/MulticallRootRouter.sol

/// @audit 64 lines (62 in the body)
137:      function execute(bytes calldata encodedData, uint16) external payable override lock requiresExecutor {

/// @audit 78 lines (70 in the body)
223       function executeSigned(bytes calldata encodedData, address userAccount, uint16)
224           external
225           payable
226           override
227           lock
228:          requiresExecutor

/// @audit 78 lines (70 in the body)
312       function executeSignedDepositSingle(bytes calldata encodedData, DepositParams calldata, address userAccount, uint16)
313           external
314           payable
315           override
316           requiresExecutor
317:          lock

/// @audit 77 lines (70 in the body)
401       function executeSignedDepositMultiple(
402           bytes calldata encodedData,
403           DepositMultipleParams calldata,
404           address userAccount,
405           uint16
406:      ) external payable override requiresExecutor lock {

GitHub: 137, 223, 312, 401

File: src/RootBridgeAgent.sol

/// @audit 304 lines (297 in the body)
434       function lzReceiveNonBlocking(
435           address _endpoint,
436           uint16 _srcChainId,
437           bytes calldata _srcAddress,
438           bytes calldata _payload
439:      ) public override requiresEndpoint(_endpoint, _srcChainId, _srcAddress) {

/// @audit 76 lines (61 in the body)
856       function _performRetrySettlementCall(
857           bool _hasFallbackToggled,
858           address[] memory _hTokens,
859           address[] memory _tokens,
860           uint256[] memory _amounts,
861           uint256[] memory _deposits,
862           bytes memory _params,
863           uint32 _settlementNonce,
864           address payable _refundee,
865           address _recipient,
866           uint16 _dstChainId,
867           GasParams memory _gParams,
868:          uint256 _value

/// @audit 66 lines (54 in the body)
966       function _createSettlement(
967           uint32 _settlementNonce,
968           address payable _refundee,
969           address _recipient,
970           uint16 _dstChainId,
971           bytes memory _params,
972           address _globalAddress,
973           uint256 _amount,
974           uint256 _deposit,
975           bool _hasFallbackToggled
976:      ) internal returns (bytes memory _payload) {

/// @audit 70 lines (58 in the body)
1045      function _createSettlementMultiple(
1046          uint32 _settlementNonce,
1047          address payable _refundee,
1048          address _recipient,
1049          uint16 _dstChainId,
1050          address[] memory _globalAddresses,
1051          uint256[] memory _amounts,
1052          uint256[] memory _deposits,
1053          bytes memory _params,
1054          bool _hasFallbackToggled
1055:     ) internal returns (bytes memory _payload) {

GitHub: 434, 856, 966, 1045

File: src/RootBridgeAgentExecutor.sol

/// @audit 81 lines (76 in the body)
268       function _bridgeInMultiple(address _recipient, bytes calldata _dParams, uint16 _srcChainId)
269           internal
270:          returns (DepositMultipleParams memory dParams)

GitHub: 268

[N‑11] Inconsistent method of specifying a floating pragma

Some files use >=, some use ^. The instances below are examples of the method that has the fewest instances for a specific version. Note that using >= without also specifying <= will lead to failures to compile, or external project incompatability, when the major version changes and there are breaking-changes, so ^ should be preferred regardless of the instance counts

There are 3 instances of this issue:

File: src/interfaces/ILayerZeroEndpoint.sol

3:    pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroReceiver.sol

3:    pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

3:    pragma solidity >=0.5.0;

GitHub: 3

[N‑12] Using >/>= without specifying an upper bound is unsafe

There will be breaking changes in future versions of solidity, and at that point your code will no longer be compatable. While you may have the specific version to use in a configuration file, others that include your source files may not.

There are 3 instances of this issue:

File: src/interfaces/ILayerZeroEndpoint.sol

3:    pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroReceiver.sol

3:    pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

3:    pragma solidity >=0.5.0;

GitHub: 3

[N‑13] Typos

There are 37 instances of this issue:

see instances
File: src/BranchBridgeAgent.sol

/// @audit Enpoint
929:      /// @notice Modifier verifies the caller is the Layerzero Enpoint or Local Branch Bridge Agent.

GitHub: 929

File: src/BranchBridgeAgentExecutor.sol

/// @audit reamininig
91:               // Send reamininig native / gas token to recipient

/// @audit reamininig
125:              // Send reamininig native / gas token to recipient

GitHub: 91, 125

File: src/BranchPort.sol

/// @audit overrriden
134:      /// @notice Function being overrriden to prevent mistakenly renouncing ownership.

/// @audit startegy
177:          // Withdraw tokens from startegy

/// @audit startegy
209:          // Withdraw tokens from startegy

GitHub: 134, 177, 209

File: src/MulticallRootRouter.sol

/// @audit Acccount
484:       *   @notice Function to perform a set of actions on the omnichain environment without using the user's Virtual Acccount.

/// @audit adn
501:       *  @param refundee settlement owner adn excess gas receiver.

/// @audit adn
537:       *  @param refundee settlement owner adn excess gas receiver.

GitHub: 484, 501, 537

File: src/RootBridgeAgent.sol

/// @audit Layzer
64:       /// @notice Message Path for each connected Branch Bridge Agent as bytes for Layzer Zero interaction = localAddress + destinationAddress abi.encodePacked()

/// @audit setttlement
300:          // Get setttlement storage reference

/// @audit Staus
679:              //Update Settlement Staus

/// @audit Enpoint
822:              //Sends message to Layerzero Enpoint

/// @audit mulitple
890:              // Check if it's mulitple asset settlement

/// @audit Enpoint
914:              //Sends message to Layerzero Enpoint

/// @audit Enpoint
934:       * @notice Internal function performs call to Layerzero Enpoint Contract for cross-chain messaging.

/// @audit Setttlement
1013:         // Update Setttlement

/// @audit Setttlement
1105:         // Update Setttlement

/// @audit Enpoint
1203:     /// @notice Modifier verifies the caller is the Layerzero Enpoint or Local Branch Bridge Agent.

GitHub: 64, 300, 679, 822, 890, 914, 934, 1013, 1105, 1203

File: src/RootPort.sol

/// @audit overriden
165:      /// @notice Function being overriden to prevent mistakenly renouncing ownership.

GitHub: 165

File: src/factories/RootBridgeAgentFactory.sol

/// @audit Enpoint
18:       /// @notice Local Layerzero Enpoint Address

GitHub: 18

File: src/interfaces/BridgeAgentConstants.sol

/// @audit Redeeem
19:       // Settlement / Deposit Redeeem Status

GitHub: 19

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit Enpoint
130:       * @notice Internal function performs call to Layerzero Enpoint Contract for cross-chain messaging.

/// @audit enconded
145:       *   @param params enconded parameters to execute on the root chain router.

/// @audit enconded
157:       *   @param params enconded parameters to execute on the root chain router.

/// @audit enconded
173:       *   @param params enconded parameters to execute on the root chain router.

/// @audit enconded
189:       *   @param params enconded parameters to execute on the root chain router.

/// @audit enconded
201:       *   @param params enconded parameters to execute on the root chain router.

/// @audit enconded
220:       *   @param params enconded parameters to execute on the root chain router.

/// @audit addresse
299:       *     @param hToken  local hToken addresse to clear balance for.

/// @audit addresse
300:       *     @param token  native / underlying token addresse to clear balance for.

GitHub: 130, 145, 157, 173, 189, 201, 220, 299, 300

File: src/interfaces/IBranchPort.sol

/// @audit Ulyses
8:     * @notice Ulyses `Port` implementation for Branch Chain deployment. This contract

/// @audit thfe
172:       * @notice Reverts thfe toggle on the given bridge agent  If it's active, it will de-activate it and vice-versa.

GitHub: 8, 172

File: src/interfaces/IBranchRouter.sol

/// @audit enconded
41:        *   @param params RLP enconded parameters to execute on the root chain.

GitHub: 41

File: src/interfaces/IRootBridgeAgent.sol

/// @audit singned
50:    *       0x04 | Call to Root Router without Deposit + singned message.

/// @audit singned
51:    *       0x05 | Call to Root Router with Deposit + singned message.

/// @audit foricng
245:       * @notice Function that allows retrieval of failed Settlement's foricng fallback to be triggered.

GitHub: 50, 51, 245

[N‑14] File is missing NatSpec

There are 3 instances of this issue:

File: src/interfaces/BridgeAgentStructs.sol

GitHub: various

File: src/interfaces/IMulticall2.sol

GitHub: various

File: src/token/ERC20hTokenBranch.sol

GitHub: various

[N‑15] Function definition modifier order does not follow Solidity style guide

See this link for an explanation of the correct ordering

There are 2 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit      function lzReceiveNonBlocking()          public          override          requiresEndpoint()      {          //Save Action Flag          bytes1 flag = _payload[0] & 0x7F;            // Save settlement nonce          uint32 nonce;            // DEPOSIT FLAG: 0 ()          if () {              // Get Settlement Nonce              nonce = uint32());                //Check if tx has already been executed              if () revert AlreadyExecutedTransaction();                //Try to execute the remote request              //Flag 0 - BranchBridgeAgentExecutor().executeNoSettlement()              _execute()              );                // DEPOSIT FLAG: 1 ()          } else if () {              // Parse recipient              address payable recipient = payable())));                // Parse Settlement Nonce              nonce = uint32());                //Check if tx has already been executed              if () revert AlreadyExecutedTransaction();                //Try to execute the remote request              //Flag 1 - BranchBridgeAgentExecutor().executeWithSettlement()              _execute()              );                // DEPOSIT FLAG: 2 ()          } else if () {              // Parse recipient              address payable recipient = payable())));                // Parse deposit nonce              nonce = uint32());                //Check if tx has already been executed              if () revert AlreadyExecutedTransaction();                //Try to execute remote request              // Flag 2 - BranchBridgeAgentExecutor().executeWithSettlementMultiple()              _execute()              );                //DEPOSIT FLAG: 3 ()          } else if () {              // Parse recipient              address payable recipient = payable())));                //Get nonce              nonce = uint32());                //Check if settlement is in retrieve mode              if () {                  revert AlreadyExecutedTransaction();              } else {                  //Set settlement to retrieve mode, if not already set.                  if () executionState[nonce] = STATUS_RETRIEVE;                  //Trigger fallback/Retry failed fallback                  _performFallbackCall();              }                //DEPOSIT FLAG: 4 ()          } else if () {              //Get nonce              nonce = uint32());                // Reopen Deposit for redemption              getDeposit[nonce].status = STATUS_FAILED;                // Emit Fallback Event              emit LogFallback();                // Return to prevent unnecessary logic/emits              return;                //Unrecognized Function Selector          } else {              revert UnknownFlag();          }            // Emit Execution Event          emit LogExecute();      }  : override before constant/payable 
587       function lzReceiveNonBlocking(address _endpoint, bytes calldata _srcAddress, bytes calldata _payload)
588           public
589           override
590:          requiresEndpoint(_endpoint, _srcAddress)

GitHub: 587

File: src/RootBridgeAgent.sol

/// @audit      function redeemSettlement() 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 () revert SettlementRedeemUnavailable();          if ()) revert SettlementRedeemUnavailable();            // Check if Settlement Owner is msg.sender or msg.sender is the virtual account of the settlement owner.          if () {              if ().getUserAccount())) {                  revert NotSettlementOwner();              }          }            // Clear Global hTokens To Recipient on Root Chain cancelling Settlement to Branch          for () {              // Save to memory              address _hToken = settlement.hTokens[i];                // Check if asset              if ()) {                  // Save to memory                  uint24 _dstChainId = settlement.dstChainId;                    // Move hTokens from Branch to Root + Mint Sufficient hTokens to match new port deposit                  IPort().bridgeToRoot().getGlobalTokenFromLocal(),                      settlement.amounts[i],                      settlement.deposits[i],                      _dstChainId                  );              }                unchecked {                  ++i;              }          }            // Delete Settlement          delete getSettlement[_settlementNonce];      }  : override before virtual 
299:      function redeemSettlement(uint32 _settlementNonce) external override lock {

GitHub: 299

[N‑16] Assembly blocks should have extensive comments

Assembly blocks take a lot more time to audit than normal Solidity code, and often have gotchas and side-effects that the Solidity versions of the same code do not. Consider adding more comments explaining what is being done in every step of the assembly code, and describe why assembly is being used instead of Solidity.

There is one instance of this issue:

File: src/VirtualAccount.sol

149           assembly {
150               size := extcodesize(addr)
151:          }

GitHub: 149

[N‑17] Visibility should be set explicitly rather than defaulting to internal

There are 3 instances of this issue:

File: src/BranchPort.sol

83        mapping(address strategy => mapping(address token => uint256 dailyLimitRemaining)) public
84:           strategyDailyLimitRemaining;

GitHub: 83

File: src/RootPort.sol

96        mapping(address chainId => mapping(uint256 underlyingAddress => address localAddress)) public
97:           getLocalTokenFromUnderlying;

100       mapping(address chainId => mapping(uint256 localAddress => address underlyingAddress)) public
101:          getUnderlyingTokenFromLocal;

GitHub: 96, 100

[N‑18] Function ordering does not follow the Solidity style guide

According to the Solidity style guide, functions should be laid out in the following order :constructor(), receive(), fallback(), external, public, internal, private, but the cases below do not follow this pattern

There are 11 instances of this issue:

File: src/ArbitrumCoreBranchRouter.sol

/// @audit _receiveAddBridgeAgent() came earlier
126:      function executeNoSettlement(bytes calldata _data) external payable override requiresAgentExecutor {

GitHub: 126

File: src/BranchBridgeAgent.sol

/// @audit _execute() came earlier
765:      function _performCall(address payable _refundee, bytes memory _payload, GasParams calldata _gParams)

GitHub: 765

File: src/BranchPort.sol

/// @audit renounceOwnership() came earlier
144:      function manage(address _token, uint256 _amount) external override requiresPortStrategy(_token) {

/// @audit withdraw() came earlier
237       function bridgeIn(address _recipient, address _localAddress, uint256 _amount)
238           external
239           override
240:          requiresBridgeAgent

GitHub: 144, 237

File: src/RootBridgeAgent.sol

/// @audit bridgeIn() came earlier
387       function bridgeInMultiple(address _recipient, DepositMultipleParams calldata _dParams, uint256 _srcChainId)
388           external
389           override
390:          requiresAgentExecutor

/// @audit _execute() came earlier
808       function _performCall(
809           uint16 _dstChainId,
810           address payable _refundee,
811           bytes memory _payload,
812:          GasParams calldata _gParams

/// @audit _updateStateOnBridgeOut() came earlier
1170:     function approveBranchBridgeAgent(uint256 _branchChainId) external override requiresManager {

GitHub: 387, 808, 1170

File: src/RootPort.sol

/// @audit renounceOwnership() came earlier
175       function getLocalToken(address _localAddress, uint256 _srcChainId, uint256 _dstChainId)
176           external
177           view
178           override
179:          returns (address)

/// @audit _getLocalToken() came earlier
200       function getUnderlyingTokenFromGlobal(address _globalAddress, uint256 _srcChainId)
201           external
202           view
203           override
204:          returns (address)

/// @audit addVirtualAccount() came earlier
369       function toggleVirtualAccountApproved(VirtualAccount _userAccount, address _router)
370           external
371           override
372:          requiresBridgeAgent

GitHub: 175, 200, 369

File: src/VirtualAccount.sol

/// @audit payableCall() came earlier
119:      function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {

GitHub: 119

[N‑19] Contract does not follow the Solidity style guide's suggested layout ordering

The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering

There are 15 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

/// @audit function _transferAndApproveMultipleTokens came earlier
206       modifier requiresAgentExecutor() {
207           if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();
208           _;
209:      }

GitHub: 206

File: src/BranchBridgeAgent.sol

/// @audit function _clearToken came earlier
922       modifier lock() {
923           require(_unlocked == 1);
924           _unlocked = 2;
925           _;
926           _unlocked = 1;
927:      }

/// @audit function _requiresEndpoint came earlier
947       modifier requiresRouter() {
948           if (msg.sender != localRouterAddress) revert UnrecognizedRouter();
949           _;
950:      }

GitHub: 922, 947

File: src/BranchPort.sol

/// @audit function _bridgeOut came earlier
541       modifier requiresCoreRouter() {
542           if (msg.sender != coreBranchRouterAddress) revert UnrecognizedCore();
543           _;
544:      }

GitHub: 541

File: src/CoreRootRouter.sol

/// @audit function _syncBranchBridgeAgent came earlier
511       modifier requiresExecutor() {
512           if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();
513           _;
514:      }

GitHub: 511

File: src/MulticallRootRouter.sol

/// @audit function _decode came earlier
590       modifier lock() {
591           require(_unlocked == 1);
592           _unlocked = 2;
593           _;
594           _unlocked = 1;
595:      }

GitHub: 590

File: src/RootBridgeAgent.sol

/// @audit function syncBranchBridgeAgent came earlier
1190      modifier lock() {
1191          require(_unlocked == 1);
1192          _unlocked = 2;
1193          _;
1194          _unlocked = 1;
1195:     }

GitHub: 1190

File: src/RootPort.sol

/// @audit function syncNewCoreBranchRouter came earlier
557       modifier requiresBridgeAgentFactory() {
558           if (!isBridgeAgentFactory[msg.sender]) revert UnrecognizedBridgeAgentFactory();
559           _;
560:      }

GitHub: 557

File: src/VirtualAccount.sol

/// @audit function isContract came earlier
160       modifier requiresApprovedCaller() {
161           if (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) {
162               if (msg.sender != userAddress) {
163                   revert UnauthorizedCaller();
164               }
165           }
166           _;
167:      }

GitHub: 160

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit function createToken came earlier
112       modifier requiresCoreRouter() {
113           if (msg.sender != localCoreRouterAddress) revert UnrecognizedCoreRouter();
114           _;
115:      }

GitHub: 112

File: src/factories/ERC20hTokenRootFactory.sol

/// @audit function createToken came earlier
96        modifier requiresCoreRouterOrPort() {
97            if (msg.sender != coreRootRouterAddress) {
98                if (msg.sender != rootPortAddress) {
99                    revert UnrecognizedCoreRouterOrPort();
100               }
101           }
102           _;
103:      }

GitHub: 96

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit function lzReceiveNonBlocking came earlier
332:      event LogExecute(uint256 indexed nonce);

GitHub: 332

File: src/interfaces/IBranchPort.sol

/// @audit function setCoreBranchRouter came earlier
220:      event DebtCreated(address indexed _strategy, address indexed _token, uint256 _amount);

GitHub: 220

File: src/interfaces/IRootBridgeAgent.sol

/// @audit function syncBranchBridgeAgent came earlier
337:      event LogExecute(uint256 indexed depositNonce, uint256 indexed srcChainId);

GitHub: 337

File: src/interfaces/IRootPort.sol

/// @audit function syncNewCoreBranchRouter came earlier
371:      event BridgeAgentFactoryAdded(address indexed bridgeAgentFactory);

GitHub: 371

[N‑20] Add inline comments for unnamed variables

function foo(address x, address) -> function foo(address x, address /* y */)

There are 18 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

88        /// @dev This functionality should be accessed from Root environment
89:       function retrySettlement(uint32, bytes calldata, GasParams[2] calldata, bool) external payable override lock {}

GitHub: 88

File: src/BranchBridgeAgent.sol

578:      function lzReceive(uint16, bytes calldata _srcAddress, uint64, bytes calldata _payload) public override {

GitHub: 578

File: src/CoreRootRouter.sol

332:      function execute(bytes calldata _encodedData, uint16) external payable override requiresExecutor {

350:      function executeDepositSingle(bytes memory, DepositParams memory, uint16)

360:      function executeDepositMultiple(bytes calldata, DepositMultipleParams memory, uint16)

370:      function executeSigned(bytes memory, address, uint16) external payable override requiresExecutor {

375:      function executeSignedDepositSingle(bytes memory, DepositParams memory, address, uint16)

385:      function executeSignedDepositMultiple(bytes memory, DepositMultipleParams memory, address, uint16)

GitHub: 332, 350, 360, 370, 375, 385

File: src/MulticallRootRouter.sol

122       /// @dev This function will revert when called.
123:      function executeResponse(bytes memory, uint16) external payable override {

137:      function execute(bytes calldata encodedData, uint16) external payable override lock requiresExecutor {

203:      function executeDepositSingle(bytes calldata, DepositParams calldata, uint16) external payable override {

209:      function executeDepositMultiple(bytes calldata, DepositMultipleParams calldata, uint16) external payable {

223:      function executeSigned(bytes calldata encodedData, address userAccount, uint16)

312:      function executeSignedDepositSingle(bytes calldata encodedData, DepositParams calldata, address userAccount, uint16)

GitHub: 122, 137, 203, 209, 223, 312

File: src/RootBridgeAgent.sol

423:      function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public {

GitHub: 423

File: src/VirtualAccount.sol

119:      function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {

124:      function onERC1155Received(address, address, uint256, uint256, bytes calldata)

134:      function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)

GitHub: 119, 124, 134

[N‑21] Invalid NatSpec comment style

NatSpec must begin with ///, or use /* ... */ syntax

There are 67 instances of this issue:

File: src/interfaces/ILayerZeroEndpoint.sol

8:        // @notice send a LayerZero message to the specified address at a LayerZero endpoint.

9:        // @param _dstChainId - the destination chain identifier

10:       // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains

11:       // @param _payload - a custom bytes payload to send to the destination contract

12:       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address

13:       // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction

14:       // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

24:       // @notice used by the messaging library to publish verified payload

25:       // @param _srcChainId - the source chain identifier

26:       // @param _srcAddress - the source contract (as bytes) at the source chain

27:       // @param _dstAddress - the address on destination chain

28:       // @param _nonce - the unbound message ordering nonce

29:       // @param _gasLimit - the gas limit for external contract execution

30:       // @param _payload - verified payload to send to the destination contract

40:       // @notice get the inboundNonce of a receiver from a source chain which could be EVM or non-EVM chain

41:       // @param _srcChainId - the source chain identifier

42:       // @param _srcAddress - the source chain contract address

45:       // @notice get the outboundNonce from this source chain which, consequently, is always an EVM

46:       // @param _srcAddress - the source chain contract address

49:       // @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery

50:       // @param _dstChainId - the destination chain identifier

51:       // @param _userApplication - the user app address on this EVM chain

52:       // @param _payload - the custom message to send over LayerZero

53:       // @param _payInZRO - if false, user app pays the protocol fee in native token

54:       // @param _adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain

63:       // @notice get this Endpoint's immutable source identifier

66:       // @notice the interface to retry failed message on this Endpoint destination

67:       // @param _srcChainId - the source chain identifier

68:       // @param _srcAddress - the source chain contract address

69:       // @param _payload - the payload to be retried

72:       // @notice query if any STORED payload (message blocking) at the endpoint.

73:       // @param _srcChainId - the source chain identifier

74:       // @param _srcAddress - the source chain contract address

77:       // @notice query if the _libraryAddress is valid for sending msgs.

78:       // @param _userApplication - the user app address on this EVM chain

81:       // @notice query if the _libraryAddress is valid for receiving msgs.

82:       // @param _userApplication - the user app address on this EVM chain

85:       // @notice query if the non-reentrancy guard for send() is on

86:       // @return true if the guard is on. false otherwise

89:       // @notice query if the non-reentrancy guard for receive() is on

90:       // @return true if the guard is on. false otherwise

93:       // @notice get the configuration of the LayerZero messaging library of the specified version

94:       // @param _version - messaging library version

95:       // @param _chainId - the chainId for the pending config change

96:       // @param _userApplication - the contract address of the user application

97:       // @param _configType - type of configuration. every messaging library has its own convention.

103:      // @notice get the send() LayerZero messaging library version

104:      // @param _userApplication - the contract address of the user application

107:      // @notice get the lzReceive() LayerZero messaging library version

108:      // @param _userApplication - the contract address of the user application

GitHub: 8, 9, 10, 11, 12, 13, 14, 24, 25, 26, 27, 28, 29, 30, 40, 41, 42, 45, 46, 49, 50, 51, 52, 53, 54, 63, 66, 67, 68, 69, 72, 73, 74, 77, 78, 81, 82, 85, 86, 89, 90, 93, 94, 95, 96, 97, 103, 104, 107, 108

File: src/interfaces/ILayerZeroReceiver.sol

6:        // @notice LayerZero endpoint will invoke this function to deliver the message on the destination

7:        // @param _srcChainId - the source endpoint identifier

8:        // @param _srcAddress - the source sending contract address from the source chain

9:        // @param _nonce - the ordered message nonce

10:       // @param _payload - the signed payload is the UA bytes has encoded to be sent

GitHub: 6, 7, 8, 9, 10

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

6:        // @notice set the configuration of the LayerZero messaging library of the specified version

7:        // @param _version - messaging library version

8:        // @param _chainId - the chainId for the pending config change

9:        // @param _configType - type of configuration. every messaging library has its own convention.

10:       // @param _config - configuration in the bytes. can encode arbitrary content.

13:       // @notice set the send() LayerZero messaging library version to _version

14:       // @param _version - new messaging library version

17:       // @notice set the lzReceive() LayerZero messaging library version to _version

18:       // @param _version - new messaging library version

21:       // @notice Only when the UA needs to resume the message flow in blocking mode and clear the stored payload

22:       // @param _srcChainId - the chainId of the source chain

23:       // @param _srcAddress - the contract address of the source contract at the source chain

GitHub: 6, 7, 8, 9, 10, 13, 14, 17, 18, 21, 22, 23

[N‑22] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

There is one instance of this issue:

File: Various Files

[N‑23] Large or complicated code bases should implement invariant tests

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement invariant fuzzing tests. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

There is one instance of this issue:

File: Various Files

[N‑24] Consider adding formal verification proofs

Consider using formal verification to mathematically prove that your code does what is intended, and does not have any edge cases with unexpected behavior. The solidity compiler itself has this functionality built in

There is one instance of this issue:

File: Various Files

[N‑25] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, for readability

Well-organized data structures make code reviews easier, which may lead to fewer bugs. Consider combining related mappings into mappings to structs, so it's clear what data is related

There are 3 instances of this issue:

File: src/BranchPort.sol

74        mapping(address strategy => mapping(address token => uint256 debt)) public getPortStrategyTokenDebt;
75    
76        /// @notice Mapping returns the last time a given Port Strategy managed a given Strategy Token.
77        mapping(address strategy => mapping(address token => uint256 lastManaged)) public lastManaged;
78    
79        /// @notice Mapping returns the time limit a given Port Strategy must wait before managing a Strategy Token.
80        mapping(address strategy => mapping(address token => uint256 dailyLimitAmount)) public strategyDailyLimitAmount;
81    
82        /// @notice Mapping returns the amount of a Strategy Token a given Port Strategy can manage.
83        mapping(address strategy => mapping(address token => uint256 dailyLimitRemaining)) public
84:           strategyDailyLimitRemaining;

GitHub: 74

File: src/RootBridgeAgent.sol

62        mapping(uint256 chainId => address branchBridgeAgent) public getBranchBridgeAgent;
63    
64        /// @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;
66    
67        /// @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;

GitHub: 62

File: src/RootPort.sol

87        mapping(address token => bool isGlobalToken) public isGlobalAddress;
88    
89        /// @notice ChainId -> Local Address -> Global Address
90        mapping(address chainId => mapping(uint256 localAddress => address globalAddress)) public getGlobalTokenFromLocal;
91    
92        /// @notice ChainId -> Global Address -> Local Address
93        mapping(address chainId => mapping(uint256 globalAddress => address localAddress)) public getLocalTokenFromGlobal;
94    
95        /// @notice ChainId -> Underlying Address -> Local Address
96        mapping(address chainId => mapping(uint256 underlyingAddress => address localAddress)) public
97            getLocalTokenFromUnderlying;
98    
99        /// @notice Mapping from Local Address to Underlying Address.
100       mapping(address chainId => mapping(uint256 localAddress => address underlyingAddress)) public
101:          getUnderlyingTokenFromLocal;

GitHub: 87

[N‑26] Custom errors should be used rather than revert()/require()

Custom errors are available from solidity version 0.8.4. Custom errors are more easily processed in try-catch blocks, and are easier to re-use and maintain.

There are 54 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

39:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

GitHub: 39

File: src/BaseBranchRouter.sol

61:           require(_localBridgeAgentAddress != address(0), "Bridge Agent address cannot be 0");

213:          require(_unlocked == 1);

GitHub: 61, 213

File: src/BranchBridgeAgent.sol

125:          require(_rootBridgeAgentAddress != address(0), "Root Bridge Agent Address cannot be the zero address.");

126           require(
127               _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
128               "Layerzero Endpoint Address cannot be the zero address."
129:          );

130:          require(_localRouterAddress != address(0), "Local Router Address cannot be the zero address.");

131:          require(_localPortAddress != address(0), "Local Port Address cannot be the zero address.");

923:          require(_unlocked == 1);

GitHub: 125, 126, 130, 131, 923

File: src/BranchPort.sol

109:          require(_owner != address(0), "Owner is zero address");

123:          require(coreBranchRouterAddress == address(0), "Contract already initialized");

124:          require(!isBridgeAgentFactory[_bridgeAgentFactory], "Contract already initialized");

126:          require(_coreBranchRouter != address(0), "CoreBranchRouter is zero address");

127:          require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address");

181:          require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed");

213           require(
214               ERC20(_token).balanceOf(address(this)) - currBalance == amountToWithdraw, "Port Strategy Withdraw Failed"
215:          );

332:          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");

333:          require(_newCoreRouter != address(0), "New CoreRouter address is zero");

567:          require(_unlocked == 1);

GitHub: 109, 123, 124, 126, 127, 181, 213, 332, 333, 567

File: src/CoreRootRouter.sol

84:           require(_setup, "Contract is already initialized");

278:          require(msg.sender == rootPortAddress, "Only root port can call");

GitHub: 84, 278

File: src/MulticallRootRouter.sol

93:           require(_localPortAddress != address(0), "Local Port Address cannot be 0");

94:           require(_multicallAddress != address(0), "Multicall Address cannot be 0");

110:          require(_bridgeAgentAddress != address(0), "Bridge Agent Address cannot be 0");

591:          require(_unlocked == 1);

GitHub: 93, 94, 110, 591

File: src/RootBridgeAgent.sol

111:          require(_lzEndpointAddress != address(0), "Layerzero Enpoint Address cannot be zero address");

112:          require(_localPortAddress != address(0), "Port Address cannot be zero address");

113:          require(_localRouterAddress != address(0), "Router Address cannot be zero address");

1191:         require(_unlocked == 1);

GitHub: 111, 112, 113, 1191

File: src/RootPort.sol

130:          require(_bridgeAgentFactory != address(0), "Bridge Agent Factory cannot be 0 address.");

131:          require(_coreRootRouter != address(0), "Core Root Router cannot be 0 address.");

132:          require(_setup, "Setup ended.");

152:          require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0 address.");

153:          require(_coreLocalBranchBridgeAgent != address(0), "Core Local Branch Bridge Agent cannot be 0 address.");

154:          require(_localBranchPortAddress != address(0), "Local Branch Port Address cannot be 0 address.");

155:          require(isBridgeAgent[_coreRootBridgeAgent], "Core Bridge Agent doesn't exist.");

156:          require(_setupCore, "Core Setup ended.");

GitHub: 130, 131, 132, 152, 153, 154, 155, 156

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

57:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent Address cannot be 0");

84            require(
85                msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
86:           );

87            require(
88                _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
89                "Root Bridge Agent Factory Address does not match."
90:           );

GitHub: 57, 84, 87

File: src/factories/BranchBridgeAgentFactory.sol

61:           require(_rootBridgeAgentFactoryAddress != address(0), "Root Bridge Agent Factory Address cannot be 0");

62            require(
63                _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
64                "Layerzero Endpoint Address cannot be the zero address."
65:           );

66:           require(_localCoreBranchRouterAddress != address(0), "Core Branch Router Address cannot be 0");

67:           require(_localPortAddress != address(0), "Port Address cannot be 0");

68:           require(_owner != address(0), "Owner cannot be 0");

88:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0");

120           require(
121               msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
122:          );

123           require(
124               _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
125               "Root Bridge Agent Factory Address does not match."
126:          );

GitHub: 61, 62, 66, 67, 68, 88, 120, 123

File: src/factories/ERC20hTokenBranchFactory.sol

43:           require(_localPortAddress != address(0), "Port address cannot be 0");

61:           require(_coreRouter != address(0), "CoreRouter address cannot be 0");

GitHub: 43, 61

File: src/factories/ERC20hTokenRootFactory.sol

35:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

50:           require(_coreRouter != address(0), "CoreRouter address cannot be 0");

GitHub: 35, 50

File: src/factories/RootBridgeAgentFactory.sol

32:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

GitHub: 32

File: src/token/ERC20hTokenRoot.sol

39:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

40:           require(_factoryAddress != address(0), "Factory Address cannot be 0");

GitHub: 39, 40

[N‑27] Use abi.encodeCall() instead of abi.encodeWithSignature()/abi.encodeWithSelector()

abi.encodeCall() has compiler type safety, whereas the other two functions do not

There are 12 instances of this issue:

File: src/BranchBridgeAgent.sol

582:             abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcAddress, _payload)

654:                 abi.encodeWithSelector(

632:                 abi.encodeWithSelector(

610:                 abi.encodeWithSelector(

GitHub: 582, 654, 632, 610

File: src/RootBridgeAgent.sol

427:             abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcChainId, _srcAddress, _payload)

643:                 abi.encodeWithSelector(

603:                 abi.encodeWithSelector(

563:                 abi.encodeWithSelector(

529:                 abi.encodeWithSelector(

506:                 abi.encodeWithSelector(

483:                 abi.encodeWithSelector(

460:                 abi.encodeWithSelector(

GitHub: 427, 643, 603, 563, 529, 506, 483, 460

[N‑28] Style guide: Non-external/public variable names should begin with an underscore

According to the Solidity Style Guide, non-external/public variable names should begin with an underscore

There are 23 instances of this issue:

File: src/BranchBridgeAgent.sol

64:      bytes private rootBridgeAgentPath;

GitHub: 64

File: src/BranchPort.sol

97:      uint256 internal constant DIVISIONER = 1e4;

98:      uint256 internal constant MIN_RESERVE_RATIO = 3e3;

GitHub: 97, 98

File: src/interfaces/BridgeAgentConstants.sol

13:      uint8 internal constant STATUS_READY = 0;

15:      uint8 internal constant STATUS_DONE = 1;

17:      uint8 internal constant STATUS_RETRIEVE = 2;

21:      uint8 internal constant STATUS_FAILED = 1;

23:      uint8 internal constant STATUS_SUCCESS = 0;

27:      uint256 internal constant PARAMS_START = 1;

29:      uint256 internal constant PARAMS_START_SIGNED = 21;

31:      uint256 internal constant PARAMS_TKN_START = 5;

33:      uint256 internal constant PARAMS_TKN_START_SIGNED = 25;

35:      uint256 internal constant PARAMS_ENTRY_SIZE = 32;

37:      uint256 internal constant PARAMS_ADDRESS_SIZE = 20;

39:      uint256 internal constant PARAMS_TKN_SET_SIZE = 109;

41:      uint256 internal constant PARAMS_TKN_SET_SIZE_MULTIPLE = 128;

43:      uint256 internal constant ADDRESS_END_OFFSET = 12;

45:      uint256 internal constant PARAMS_AMT_OFFSET = 64;

47:      uint256 internal constant PARAMS_DEPOSIT_OFFSET = 96;

49:      uint256 internal constant PARAMS_END_OFFSET = 6;

51:      uint256 internal constant PARAMS_END_SIGNED_OFFSET = 26;

53:      uint256 internal constant PARAMS_SETTLEMENT_OFFSET = 129;

57:      uint256 internal constant MAX_TOKENS_LENGTH = 255;

GitHub: 13, 15, 17, 21, 23, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 57

[N‑29] Variable names for immutables should use CONSTANT_CASE

For immutable variable names, each word should use all capital letters, with underscores separating each word (CONSTANT_CASE)

There are 39 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

22:      uint16 public immutable localChainId;

26:      address public immutable rootPortAddress;

GitHub: 22, 26

File: src/BranchBridgeAgent.sol

53:      uint16 public immutable rootChainId;

56:      uint16 public immutable localChainId;

60:      address public immutable rootBridgeAgentAddress;

67:      address public immutable lzEndpointAddress;

70:      address public immutable localRouterAddress;

74:      address public immutable localPortAddress;

77:      address public immutable bridgeAgentExecutorAddress;

GitHub: 53, 56, 60, 67, 70, 74, 77

File: src/CoreBranchRouter.sol

20:      address public immutable hTokenFactoryAddress;

GitHub: 20

File: src/CoreRootRouter.sol

47:      uint256 public immutable rootChainId;

51:      address public immutable rootPortAddress;

GitHub: 47, 51

File: src/MulticallRootRouter.sol

65:      uint256 public immutable localChainId;

68:      address public immutable localPortAddress;

71:      address public immutable multicallAddress;

GitHub: 65, 68, 71

File: src/RootBridgeAgent.sol

40:      uint16 public immutable localChainId;

43:      address public immutable factoryAddress;

46:      address public immutable localRouterAddress;

49:      address public immutable localPortAddress;

52:      address public immutable lzEndpointAddress;

55:      address public immutable bridgeAgentExecutorAddress;

GitHub: 40, 43, 46, 49, 52, 55

File: src/RootPort.sol

34:      uint256 public immutable localChainId;

GitHub: 34

File: src/VirtualAccount.sol

21:      address public immutable override userAddress;

24:      address public immutable override localPortAddress;

GitHub: 21, 24

File: src/factories/BranchBridgeAgentFactory.sol

21:      uint16 public immutable localChainId;

24:      uint16 public immutable rootChainId;

27:      address public immutable rootBridgeAgentFactoryAddress;

30:      address public immutable localCoreBranchRouterAddress;

33:      address public immutable localPortAddress;

36:      address public immutable lzEndpointAddress;

GitHub: 21, 24, 27, 30, 33, 36

File: src/factories/ERC20hTokenBranchFactory.sol

14:      uint24 public immutable localChainId;

17:      address public immutable localPortAddress;

GitHub: 14, 17

File: src/factories/ERC20hTokenRootFactory.sol

14:      uint16 public immutable localChainId;

17:      address public immutable rootPortAddress;

GitHub: 14, 17

File: src/factories/RootBridgeAgentFactory.sol

13:      uint16 public immutable rootChainId;

16:      address public immutable rootPortAddress;

19:      address public immutable lzEndpointAddress;

GitHub: 13, 16, 19

File: src/token/ERC20hTokenRoot.sol

14:      uint16 public immutable override localChainId;

17:      address public immutable override factoryAddress;

GitHub: 14, 17

[N‑30] Array indicies should be referenced via enums rather than via numeric literals

There are 66 instances of this issue:

see instances
File: src/ArbitrumCoreBranchRouter.sol

127:         if (_data[0] == 0x02) {

146:         } else if (_data[0] == 0x03) {

152:         } else if (_data[0] == 0x04) {

157:         } else if (_data[0] == 0x05) {

162:         } else if (_data[0] == 0x06) {

GitHub: 127, 146, 152, 157, 162

File: src/BranchBridgeAgent.sol

376:                     deposit.hTokens[0],

377:                     deposit.tokens[0],

378:                     deposit.amounts[0],

379:                     deposit.deposits[0],

366:                     deposit.hTokens[0],

367:                     deposit.tokens[0],

368:                     deposit.amounts[0],

369:                     deposit.deposits[0],

471:         bytes memory params = abi.encode(_settlementNonce, msg.sender, _params, _gParams[1]);

477:         _performCall(payable(msg.sender), payload, _gParams[0]);

501:         uint8 numOfAssets = uint8(bytes1(_sParams[0]));

593:         bytes1 flag = _payload[0] & 0x7F;

651:                 _payload[0] == 0x82,

629:                 _payload[0] == 0x81,

834:         addressArray[0] = _hToken;

837:         addressArray[0] = _token;

840:         uintArray[0] = _amount;

843:         uintArray[0] = _deposit;

GitHub: 376, 377, 378, 379, 366, 367, 368, 369, 471, 477, 501, 593, 651, 629, 834, 837, 840, 843

File: src/CoreBranchRouter.sol

48:          bytes memory params = abi.encode(msg.sender, _globalAddress, _dstChainId, [_gParams[1], _gParams[2]]);

54:          IBridgeAgent(localBridgeAgentAddress).callOut{value: msg.value}(payable(msg.sender), payload, _gParams[0]);

88:          if (_params[0] == 0x01) {

100:         } else if (_params[0] == 0x02) {

115:         } else if (_params[0] == 0x03) {

121:         } else if (_params[0] == 0x04) {

127:         } else if (_params[0] == 0x05) {

133:         } else if (_params[0] == 0x06) {

140:         } else if (_params[0] == 0x07) {

GitHub: 48, 48, 54, 88, 100, 115, 121, 127, 133, 140

File: src/CoreRootRouter.sol

132:             _gParams[1]

140:             payable(_refundee), _refundee, _dstChainId, payload, _gParams[0]

304:         bytes1 funcId = _encodedData[0];

334:         bytes1 funcId = _encodedData[0];

339:                 abi.decode(_encodedData[1:], (address, address, uint16, GasParams[2]));

430:             _gParams[1]

438:             payable(_refundee), _refundee, _dstChainId, payload, _gParams[0]

GitHub: 132, 140, 304, 334, 339, 430, 438

File: src/MulticallRootRouter.sol

139:         bytes1 funcId = encodedData[0];

231:         bytes1 funcId = encodedData[0];

320:         bytes1 funcId = encodedData[0];

408:         bytes1 funcId = encodedData[0];

GitHub: 139, 231, 320, 408

File: src/RootBridgeAgent.sol

444:         if (_payload[0] == 0x00) {

467:         } else if (_payload[0] == 0x01) {

490:         } else if (_payload[0] == 0x02) {

513:         } else if (_payload[0] == 0x03) {

539:         } else if (_payload[0] == 0x04) {

577:         } else if (_payload[0] & 0x7F == 0x05) {

617:         } else if (_payload[0] & 0x7F == 0x06) {

657:         } else if (_payload[0] & 0x7F == 0x07) {

699:         } else if (_payload[0] == 0x08) {

718:         } else if (_payload[0] == 0x09) {

684:                 _payload[0] == 0x87,

640:                 _payload[0] == 0x86,

600:                 _payload[0] == 0x85,

883:                 _hTokens[0],

884:                 _tokens[0],

885:                 _amounts[0],

886:                 _deposits[0],

1017:        addressArray[0] = localAddress;

1020:        addressArray[0] = underlyingAddress;

1023:        uintArray[0] = _amount;

1026:        uintArray[0] = _deposit;

GitHub: 444, 467, 490, 513, 539, 577, 617, 657, 699, 718, 684, 640, 600, 883, 884, 885, 886, 1017, 1020, 1023, 1026

File: src/RootBridgeAgentExecutor.sol

273:         uint8 numOfAssets = uint8(bytes1(_dParams[0]));

GitHub: 273

[N‑31] Consider using named mappings

Consider moving to solidity version 0.8.18 or later, and using named mappings to make it easier to understand the purpose of each mapping

There are 40 instances of this issue:

File: src/BranchBridgeAgent.sol

87:      mapping(uint256 depositNonce => Deposit depositInfo) public getDeposit;

94:      mapping(uint256 settlementNonce => uint256 state) public executionState;

GitHub: 87, 94

File: src/BranchPort.sol

32:      mapping(address bridgeAgent => bool isActiveBridgeAgent) public isBridgeAgent;

42:      mapping(address bridgeAgentFactory => bool isActiveBridgeAgentFactory) public isBridgeAgentFactory;

52:      mapping(address token => bool allowsStrategies) public isStrategyToken;

58:      mapping(address token => uint256 debt) public getStrategyTokenDebt;

61:      mapping(address token => uint256 minimumReserveRatio) public getMinimumTokenReserveRatio;

68:      mapping(address strategy => mapping(address token => bool isActiveStrategy)) public isPortStrategy;

74:      mapping(address strategy => mapping(address token => uint256 debt)) public getPortStrategyTokenDebt;

77:      mapping(address strategy => mapping(address token => uint256 lastManaged)) public lastManaged;

80:      mapping(address strategy => mapping(address token => uint256 dailyLimitAmount)) public strategyDailyLimitAmount;

83:      mapping(address strategy => mapping(address token => uint256 dailyLimitRemaining)) public

GitHub: 32, 42, 52, 58, 61, 68, 68, 74, 74, 77, 77, 80, 80, 83, 83

File: src/RootBridgeAgent.sol

62:      mapping(uint256 chainId => address branchBridgeAgent) public getBranchBridgeAgent;

65:      mapping(uint256 chainId => bytes branchBridgeAgentPath) public getBranchBridgeAgentPath;

68:      mapping(uint256 chainId => bool allowed) public isBranchBridgeAgentAllowed;

78:      mapping(uint256 nonce => Settlement settlementInfo) public getSettlement;

85:      mapping(uint256 chainId => mapping(uint256 nonce => uint256 state)) public executionState;

GitHub: 62, 65, 68, 78, 85, 85

File: src/RootPort.sol

50:      mapping(address user => VirtualAccount account) public getUserAccount;

54:      mapping(VirtualAccount acount => mapping(address router => bool allowed)) public isRouterApproved;

61:      mapping(uint256 chainId => bool isActive) public isChainId;

64:      mapping(address bridgeAgent => bool isActive) public isBridgeAgent;

70:      mapping(address bridgeAgent => address bridgeAgentManager) public getBridgeAgentManager;

77:      mapping(address bridgeAgentFactory => bool isActive) public isBridgeAgentFactory;

87:      mapping(address token => bool isGlobalToken) public isGlobalAddress;

90:      mapping(address chainId => mapping(uint256 localAddress => address globalAddress)) public getGlobalTokenFromLocal;

93:      mapping(address chainId => mapping(uint256 globalAddress => address localAddress)) public getLocalTokenFromGlobal;

96:      mapping(address chainId => mapping(uint256 underlyingAddress => address localAddress)) public

100:     mapping(address chainId => mapping(uint256 localAddress => address underlyingAddress)) public

GitHub: 50, 54, 54, 61, 64, 70, 77, 87, 90, 90, 93, 93, 96, 96, 100, 100

File: src/token/ERC20hTokenRoot.sol

20:      mapping(uint256 chainId => uint256 balance) public override getTokenBalance;

GitHub: 20

[N‑32] Use of override is unnecessary

Starting with Solidity version 0.8.8, using the override keyword when the function solely overrides an interface function, and the function doesn't exist in multiple base contracts, is unnecessary.

There are 117 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

89:      function retrySettlement(uint32, bytes calldata, GasParams[2] calldata, bool) external payable override lock {}

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 {

GitHub: 89, 99, 112, 125

File: src/ArbitrumBranchPort.sol

50       function depositToPort(address _depositor, address _recipient, address _underlyingAddress, uint256 _deposit)
51           external
52           override
53           lock
54           requiresBridgeAgent
55:      {

73       function withdrawFromPort(address _depositor, address _recipient, address _globalAddress, uint256 _amount)
74           external
75           override
76           lock
77           requiresBridgeAgent
78:      {

107:     function _bridgeIn(address _recipient, address _localAddress, uint256 _amount) internal override {

119      function _bridgeOut(
120          address _depositor,
121          address _localAddress,
122          address _underlyingAddress,
123          uint256 _amount,
124          uint256 _deposit
125:     ) internal override {

GitHub: 50, 73, 107, 119

File: src/ArbitrumCoreBranchRouter.sol

51:      function addLocalToken(address _underlyingAddress, GasParams calldata) external payable override {

85       function _receiveAddBridgeAgent(
86           address _newBranchRouter,
87           address _branchBridgeAgentFactory,
88           address _rootBridgeAgent,
89           address _rootBridgeAgentFactory,
90           address _refundee,
91           GasParams memory _gParams
92:      ) internal override {

126:     function executeNoSettlement(bytes calldata _data) external payable override requiresAgentExecutor {

GitHub: 51, 85, 126

File: src/BaseBranchRouter.sol

74:      function getDepositEntry(uint32 _depositNonce) external view override returns (Deposit memory) {

83:      function callOut(bytes calldata _params, GasParams calldata _gParams) external payable override lock {

88       function callOutAndBridge(bytes calldata _params, DepositInput calldata _dParams, GasParams calldata _gParams)
89           external
90           payable
91           override
92           lock
93:      {

104      function callOutAndBridgeMultiple(
105          bytes calldata _params,
106          DepositMultipleInput calldata _dParams,
107          GasParams calldata _gParams
108:     ) external payable override lock {

123:     function executeNoSettlement(bytes calldata) external payable virtual override requiresAgentExecutor {

128      function executeSettlement(bytes calldata, SettlementParams memory)
129          external
130          payable
131          virtual
132          override
133          requiresAgentExecutor
134:     {

139      function executeSettlementMultiple(bytes calldata, SettlementMultipleParams memory)
140          external
141          payable
142          virtual
143          override
144          requiresAgentExecutor
145:     {

GitHub: 74, 83, 88, 104, 123, 128, 139

File: src/BranchBridgeAgent.sol

156:     function getDepositEntry(uint32 _depositNonce) external view override returns (Deposit memory) {

180      function callOutSystem(address payable _refundee, bytes calldata _params, GasParams calldata _gParams)
181          external
182          payable
183          override
184          lock
185          requiresRouter
186:     {

195      function callOut(address payable _refundee, bytes calldata _params, GasParams calldata _gParams)
196          external
197          payable
198          override
199          lock
200:     {

209      function callOutAndBridge(
210          address payable _refundee,
211          bytes calldata _params,
212          DepositInput memory _dParams,
213          GasParams calldata _gParams
214:     ) external payable override lock {

231      function callOutAndBridgeMultiple(
232          address payable _refundee,
233          bytes calldata _params,
234          DepositMultipleInput memory _dParams,
235          GasParams calldata _gParams
236:     ) external payable override lock {

262      function callOutSigned(address payable _refundee, bytes calldata _params, GasParams calldata _gParams)
263          external
264          payable
265          override
266          lock
267:     {

276      function callOutSignedAndBridge(
277          address payable _refundee,
278          bytes calldata _params,
279          DepositInput memory _dParams,
280          GasParams calldata _gParams,
281          bool _hasFallbackToggled
282:     ) external payable override lock {

306      function callOutSignedAndBridgeMultiple(
307          address payable _refundee,
308          bytes calldata _params,
309          DepositMultipleInput memory _dParams,
310          GasParams calldata _gParams,
311          bool _hasFallbackToggled
312:     ) external payable override lock {

343      function retryDeposit(
344          bool _isSigned,
345          uint32 _depositNonce,
346          bytes calldata _params,
347          GasParams calldata _gParams,
348          bool _hasFallbackToggled
349:     ) external payable override lock {

422:     function retrieveDeposit(uint32 _depositNonce, GasParams calldata _gParams) external payable override lock {

434:     function redeemDeposit(uint32 _depositNonce) external override lock {

464      function retrySettlement(
465          uint32 _settlementNonce,
466          bytes calldata _params,
467          GasParams[2] calldata _gParams,
468          bool _hasFallbackToggled
469:     ) external payable virtual override lock {

485      function clearToken(address _recipient, address _hToken, address _token, uint256 _amount, uint256 _deposit)
486          external
487          override
488          requiresAgentExecutor
489:     {

494      function clearTokens(bytes calldata _sParams, address _recipient)
495          external
496          override
497          requiresAgentExecutor
498          returns (SettlementMultipleParams memory)
499:     {

578:     function lzReceive(uint16, bytes calldata _srcAddress, uint64, bytes calldata _payload) public override {

587      function lzReceiveNonBlocking(address _endpoint, bytes calldata _srcAddress, bytes calldata _payload)
588          public
589          override
590          requiresEndpoint(_endpoint, _srcAddress)
591:     {

GitHub: 156, 180, 195, 209, 231, 262, 276, 306, 343, 422, 434, 464, 485, 494, 578, 587

File: src/BranchPort.sol

135:     function renounceOwnership() public payable override onlyOwner {

144:     function manage(address _token, uint256 _amount) external override requiresPortStrategy(_token) {

167:     function replenishReserves(address _token, uint256 _amount) external override lock {

188:     function replenishReserves(address _strategy, address _token) external override lock {

226      function withdraw(address _recipient, address _underlyingAddress, uint256 _deposit)
227          public
228          virtual
229          override
230          lock
231          requiresBridgeAgent
232:     {

237      function bridgeIn(address _recipient, address _localAddress, uint256 _amount)
238          external
239          override
240          requiresBridgeAgent
241:     {

246      function bridgeInMultiple(
247          address _recipient,
248          address[] memory _localAddresses,
249          address[] memory _underlyingAddresses,
250          uint256[] memory _amounts,
251          uint256[] memory _deposits
252:     ) external override requiresBridgeAgent {

277      function bridgeOut(
278          address _depositor,
279          address _localAddress,
280          address _underlyingAddress,
281          uint256 _amount,
282          uint256 _deposit
283:     ) external override lock requiresBridgeAgent {

288      function bridgeOutMultiple(
289          address _depositor,
290          address[] memory _localAddresses,
291          address[] memory _underlyingAddresses,
292          uint256[] memory _amounts,
293          uint256[] memory _deposits
294:     ) external override lock requiresBridgeAgent {

319:     function addBridgeAgent(address _bridgeAgent) external override requiresBridgeAgentFactory {

331:     function setCoreRouter(address _newCoreRouter) external override requiresCoreRouter {

338:     function addBridgeAgentFactory(address _newBridgeAgentFactory) external override requiresCoreRouter {

348:     function toggleBridgeAgentFactory(address _newBridgeAgentFactory) external override requiresCoreRouter {

355:     function toggleBridgeAgent(address _bridgeAgent) external override requiresCoreRouter {

362:     function addStrategyToken(address _token, uint256 _minimumReservesRatio) external override requiresCoreRouter {

375:     function toggleStrategyToken(address _token) external override requiresCoreRouter {

382      function addPortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
383          external
384          override
385          requiresCoreRouter
386:     {

396:     function togglePortStrategy(address _portStrategy, address _token) external override requiresCoreRouter {

403      function updatePortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
404          external
405          override
406          requiresCoreRouter
407:     {

414      function setCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent)
415          external
416          override
417          requiresCoreRouter
418:     {

GitHub: 135, 144, 167, 188, 226, 237, 246, 277, 288, 319, 331, 338, 348, 355, 362, 375, 382, 396, 403, 414

File: src/CoreBranchRouter.sol

86:      function executeNoSettlement(bytes calldata _params) external payable virtual override requiresAgentExecutor {

GitHub: 86

File: src/CoreRootRouter.sol

297      function executeResponse(bytes calldata _encodedData, uint16 _srcChainId)
298          external
299          payable
300          override
301          requiresExecutor
302:     {

332:     function execute(bytes calldata _encodedData, uint16) external payable override requiresExecutor {

350      function executeDepositSingle(bytes memory, DepositParams memory, uint16)
351          external
352          payable
353          override
354          requiresExecutor
355:     {

360      function executeDepositMultiple(bytes calldata, DepositMultipleParams memory, uint16)
361          external
362          payable
363          override
364          requiresExecutor
365:     {

370:     function executeSigned(bytes memory, address, uint16) external payable override requiresExecutor {

375      function executeSignedDepositSingle(bytes memory, DepositParams memory, address, uint16)
376          external
377          payable
378          override
379          requiresExecutor
380:     {

385      function executeSignedDepositMultiple(bytes memory, DepositMultipleParams memory, address, uint16)
386          external
387          payable
388          override
389          requiresExecutor
390:     {

GitHub: 297, 332, 350, 360, 370, 375, 385

File: src/MulticallRootRouter.sol

123:     function executeResponse(bytes memory, uint16) external payable override {

137:     function execute(bytes calldata encodedData, uint16) external payable override lock requiresExecutor {

203:     function executeDepositSingle(bytes calldata, DepositParams calldata, uint16) external payable override {

223      function executeSigned(bytes calldata encodedData, address userAccount, uint16)
224          external
225          payable
226          override
227          lock
228          requiresExecutor
229:     {

312      function executeSignedDepositSingle(bytes calldata encodedData, DepositParams calldata, address userAccount, uint16)
313          external
314          payable
315          override
316          requiresExecutor
317          lock
318:     {

401      function executeSignedDepositMultiple(
402          bytes calldata encodedData,
403          DepositMultipleParams calldata,
404          address userAccount,
405          uint16
406:     ) external payable override requiresExecutor lock {

GitHub: 123, 137, 203, 223, 312, 401

File: src/MulticallRootRouterLibZip.sol

37:      function _decode(bytes calldata data) internal pure override returns (bytes memory) {

GitHub: 37

File: src/RootBridgeAgent.sol

135:     function getSettlementEntry(uint32 _settlementNonce) external view override returns (Settlement memory) {

160      function callOut(
161          address payable _refundee,
162          address _recipient,
163          uint16 _dstChainId,
164          bytes calldata _params,
165          GasParams calldata _gParams
166:     ) external payable override lock requiresRouter {

175      function callOutAndBridge(
176          address payable _refundee,
177          address _recipient,
178          uint16 _dstChainId,
179          bytes calldata _params,
180          SettlementInput calldata _sParams,
181          GasParams calldata _gParams,
182          bool _hasFallbackToggled
183:     ) external payable override lock requiresRouter {

202      function callOutAndBridgeMultiple(
203          address payable _refundee,
204          address _recipient,
205          uint16 _dstChainId,
206          bytes calldata _params,
207          SettlementMultipleInput calldata _sParams,
208          GasParams calldata _gParams,
209          bool _hasFallbackToggled
210:     ) external payable override lock requiresRouter {

233      function retrySettlement(
234          uint32 _settlementNonce,
235          address _recipient,
236          bytes calldata _params,
237          GasParams calldata _gParams,
238          bool _hasFallbackToggled
239:     ) external payable override lock {

299:     function redeemSettlement(uint32 _settlementNonce) external override lock {

351      function bridgeIn(address _recipient, DepositParams memory _dParams, uint256 _srcChainId)
352          public
353          override
354          requiresAgentExecutor
355:     {

387      function bridgeInMultiple(address _recipient, DepositMultipleParams calldata _dParams, uint256 _srcChainId)
388          external
389          override
390          requiresAgentExecutor
391:     {

434      function lzReceiveNonBlocking(
435          address _endpoint,
436          uint16 _srcChainId,
437          bytes calldata _srcAddress,
438          bytes calldata _payload
439:     ) public override requiresEndpoint(_endpoint, _srcChainId, _srcAddress) {

1170:    function approveBranchBridgeAgent(uint256 _branchChainId) external override requiresManager {

1176     function syncBranchBridgeAgent(address _newBranchBridgeAgent, uint256 _branchChainId)
1177         external
1178         override
1179         requiresPort
1180:    {

GitHub: 135, 160, 175, 202, 233, 299, 351, 387, 434, 1170, 1176

File: src/RootPort.sol

166:     function renounceOwnership() public payable override onlyOwner {

175      function getLocalToken(address _localAddress, uint256 _srcChainId, uint256 _dstChainId)
176          external
177          view
178          override
179          returns (address)
180:     {

200      function getUnderlyingTokenFromGlobal(address _globalAddress, uint256 _srcChainId)
201          external
202          view
203          override
204          returns (address)
205:     {

211:     function isGlobalToken(address _globalAddress, uint256 _srcChainId) external view override returns (bool) {

216:     function isLocalToken(address _localAddress, uint256 _srcChainId) external view override returns (bool) {

230:     function isUnderlyingToken(address _underlyingToken, uint256 _srcChainId) external view override returns (bool) {

239      function setAddresses(
240          address _globalAddress,
241          address _localAddress,
242          address _underlyingAddress,
243          uint256 _srcChainId
244:     ) external override requiresCoreRootRouter {

259      function setLocalAddress(address _globalAddress, address _localAddress, uint256 _srcChainId)
260          external
261          override
262          requiresCoreRootRouter
263:     {

277      function bridgeToRoot(address _recipient, address _hToken, uint256 _amount, uint256 _deposit, uint256 _srcChainId)
278          external
279          override
280          requiresBridgeAgent
281:     {

294      function bridgeToRootFromLocalBranch(address _from, address _hToken, uint256 _amount)
295          external
296          override
297          requiresLocalBranchPort
298:     {

304      function bridgeToLocalBranchFromRoot(address _to, address _hToken, uint256 _amount)
305          external
306          override
307          requiresLocalBranchPort
308:     {

315      function burn(address _from, address _hToken, uint256 _amount, uint256 _srcChainId)
316          external
317          override
318          requiresBridgeAgent
319:     {

325      function burnFromLocalBranch(address _from, address _hToken, uint256 _amount)
326          external
327          override
328          requiresLocalBranchPort
329:     {

336      function mintToLocalBranch(address _to, address _hToken, uint256 _amount)
337          external
338          override
339          requiresLocalBranchPort
340:     {

350:     function fetchVirtualAccount(address _user) external override returns (VirtualAccount account) {

369      function toggleVirtualAccountApproved(VirtualAccount _userAccount, address _router)
370          external
371          override
372          requiresBridgeAgent
373:     {

382:     function addBridgeAgent(address _manager, address _bridgeAgent) external override requiresBridgeAgentFactory {

393      function syncBranchBridgeAgentWithRoot(
394          address _newBranchBridgeAgent,
395          address _rootBridgeAgent,
396          uint256 _branchChainId
397:     ) external override requiresCoreRootRouter {

414:     function toggleBridgeAgent(address _bridgeAgent) external override onlyOwner {

421:     function addBridgeAgentFactory(address _bridgeAgentFactory) external override onlyOwner {

431:     function toggleBridgeAgentFactory(address _bridgeAgentFactory) external override onlyOwner {

438      function addNewChain(
439          address _coreBranchBridgeAgentAddress,
440          uint256 _chainId,
441          string memory _wrappedGasTokenName,
442          string memory _wrappedGasTokenSymbol,
443          uint8 _wrappedGasTokenDecimals,
444          address _newLocalBranchWrappedNativeTokenAddress,
445          address _newUnderlyingBranchWrappedNativeTokenAddress
446:     ) external override onlyOwner {

483:     function addEcosystemToken(address _ecoTokenGlobalAddress) external override onlyOwner {

509:     function setCoreRootRouter(address _coreRootRouter, address _coreRootBridgeAgent) external override onlyOwner {

521      function setCoreBranchRouter(
522          address _refundee,
523          address _coreBranchRouter,
524          address _coreBranchBridgeAgent,
525          uint16 _dstChainId,
526          GasParams calldata _gParams
527:     ) external payable override onlyOwner {

539      function syncNewCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent, uint16 _dstChainId)
540          external
541          override
542          onlyOwner
543:     {

GitHub: 166, 175, 200, 211, 216, 230, 239, 259, 277, 294, 304, 315, 325, 336, 350, 369, 382, 393, 414, 421, 431, 438, 483, 509, 521, 539

File: src/VirtualAccount.sol

51:      function withdrawNative(uint256 _amount) external override requiresApprovedCaller {

56:      function withdrawERC20(address _token, uint256 _amount) external override requiresApprovedCaller {

61:      function withdrawERC721(address _token, uint256 _tokenId) external override requiresApprovedCaller {

66:      function call(Call[] calldata calls) external override requiresApprovedCaller returns (bytes[] memory returnData) {

119:     function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {

124      function onERC1155Received(address, address, uint256, uint256, bytes calldata)
125          external
126          pure
127          override
128          returns (bytes4)
129:     {

134      function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
135          external
136          pure
137          override
138          returns (bytes4)
139:     {

GitHub: 51, 56, 61, 66, 119, 124, 134

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

56:      function initialize(address _coreRootBridgeAgent) external override onlyOwner {

79       function createBridgeAgent(
80           address _newBranchRouterAddress,
81           address _rootBridgeAgentAddress,
82           address _rootBridgeAgentFactoryAddress
83:      ) external virtual override returns (address newBridgeAgent) {

GitHub: 56, 79

File: src/token/ERC20hTokenBranch.sol

29:      function mint(address account, uint256 amount) external override onlyOwner returns (bool) {

35:      function burn(uint256 amount) public override onlyOwner {

GitHub: 29, 35

[N‑33] Consider using descriptive constants when passing zero as a function argument

Passing zero as a function argument can sometimes result in a security issue (e.g. passing zero as the slippage parameter). Consider using a constant variable with a descriptive name, so it's clear that the argument is intentionally being used, and for the right reasons.

There are 4 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

105:         IRootBridgeAgent(_rootBridgeAgentAddress).lzReceive(rootChainId, "", 0, _calldata);

114          IRootBridgeAgent(rootBridgeAgentAddress).lzReceive(
115              rootChainId, "", 0, abi.encodePacked(bytes1(0x09), _settlementNonce)
116:         );

GitHub: 105, 114

File: src/RootBridgeAgent.sol

837:             IBranchBridgeAgent(callee).lzReceive(0, "", 0, _payload);

929:             IBranchBridgeAgent(callee).lzReceive(0, "", 0, payload);

GitHub: 837, 929

[N‑34] Variables need not be initialized to zero

The default value for variables is zero, so initializing them to zero is superfluous.

There are 17 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

192:         for (uint256 i = 0; i < _hTokens.length;) {

GitHub: 192

File: src/BranchBridgeAgent.sol

447:         for (uint256 i = 0; i < deposit.tokens.length;) {

513:         for (uint256 i = 0; i < numOfAssets;) {

GitHub: 447, 513

File: src/BranchPort.sol

257:         for (uint256 i = 0; i < length;) {

305:         for (uint256 i = 0; i < length;) {

GitHub: 257, 305

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;) {

GitHub: 278, 367, 455, 557

File: src/RootBridgeAgent.sol

318:         for (uint256 i = 0; i < settlement.hTokens.length;) {

399:         for (uint256 i = 0; i < length;) {

1070:        for (uint256 i = 0; i < hTokens.length;) {

GitHub: 318, 399, 1070

File: src/RootBridgeAgentExecutor.sol

281:         for (uint256 i = 0; i < uint256(uint8(numOfAssets));) {

GitHub: 281

File: src/VirtualAccount.sol

70:          for (uint256 i = 0; i < length;) {

90:          for (uint256 i = 0; i < length;) {

GitHub: 70, 90

File: src/interfaces/BridgeAgentConstants.sol

13:      uint8 internal constant STATUS_READY = 0;

23:      uint8 internal constant STATUS_SUCCESS = 0;

GitHub: 13, 23

[N‑35] require()/revert() statements should have descriptive reason strings

There are 13 instances of this issue:

File: src/BaseBranchRouter.sol

213:         require(_unlocked == 1);

GitHub: 213

File: src/BranchBridgeAgent.sol

923:         require(_unlocked == 1);

GitHub: 923

File: src/BranchPort.sol

567:         require(_unlocked == 1);

GitHub: 567

File: src/CoreRootRouter.sol

356:         revert();

366:         revert();

371:         revert();

381:         revert();

391:         revert();

GitHub: 356, 366, 371, 381, 391

File: src/MulticallRootRouter.sol

124:         revert();

204:         revert();

210:         revert();

591:         require(_unlocked == 1);

GitHub: 124, 204, 210, 591

File: src/RootBridgeAgent.sol

1191:        require(_unlocked == 1);

GitHub: 1191

[N‑36] Contracts/libraries should each be defined in separate files

This helps to make tracking changes across commits easier, among other reasons. The instances below are the second+ contract/library within each file

There are 4 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

31   contract ArbitrumBranchBridgeAgent is BranchBridgeAgent {
32       /*///////////////////////////////////////////////////////////////
33                               CONSTRUCTOR
34       //////////////////////////////////////////////////////////////*/
35   
36       /**
37        * @notice Constructor for Arbitrum Branch Bridge Agent.
38        *  @param _localChainId Local Chain Layer Zero Id.
39        *  @param _rootBridgeAgentAddress Root Bridge Agent Address.
40        *  @param _localRouterAddress Local Core Branch Router Address.
41        *  @param _localPortAddress Local Branch Port Address.
42:       */

GitHub: 31

File: src/BranchBridgeAgent.sol

45:  contract BranchBridgeAgent is IBranchBridgeAgent, BridgeAgentConstants {

GitHub: 45

File: src/BranchBridgeAgentExecutor.sol

29:  contract BranchBridgeAgentExecutor is Ownable, BridgeAgentConstants {

GitHub: 29

File: src/RootBridgeAgentExecutor.sol

27   contract RootBridgeAgentExecutor is Ownable, BridgeAgentConstants {
28       /*///////////////////////////////////////////////////////////////
29                                   CONSTRUCTOR
30       //////////////////////////////////////////////////////////////*/
31       /**
32        * @notice Constructor for Root Bridge Agent Executor.
33        * @param _rootBridgeAgent the owner of the contract in charge of calling the different execution functions.
34:       */

GitHub: 27

[N‑37] Contract uses both require()/revert() as well as custom errors

Consider using just one method in a single file

There are 10 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

14:  contract ArbitrumBranchPort is BranchPort, IArbitrumBranchPort {

GitHub: 14

File: src/BaseBranchRouter.sol

25:  contract BaseBranchRouter is IBranchRouter, Ownable {

GitHub: 25

File: src/BranchBridgeAgent.sol

45:  contract BranchBridgeAgent is IBranchBridgeAgent, BridgeAgentConstants {

GitHub: 45

File: src/BranchPort.sol

17:  contract BranchPort is Ownable, IBranchPort {

GitHub: 17

File: src/CoreRootRouter.sol

38   contract CoreRootRouter is IRootRouter, Ownable {
39       /*///////////////////////////////////////////////////////////////
40                       CORE ROOT ROUTER STATE
41       //////////////////////////////////////////////////////////////*/
42   
43:      /// @notice Boolean to indicate if the contract is in set up mode.

GitHub: 38

File: src/MulticallRootRouter.sol

57:  contract MulticallRootRouter is IRootRouter, Ownable {

GitHub: 57

File: src/RootBridgeAgent.sol

32:  contract RootBridgeAgent is IRootBridgeAgent, BridgeAgentConstants {

GitHub: 32

File: src/RootPort.sol

16:  contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/factories/ERC20hTokenBranchFactory.sol

12   contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

12   contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

[N‑38] Contract doesn't handle all NFT types

There are two primary standards for NFTs - ERC-721 and ERC-1155. Supporting one but not the other excludes half of the available market.

There are 2 instances of this issue:

File: src/VirtualAccount.sol

17   contract VirtualAccount is IVirtualAccount, ERC1155Receiver {
18       using SafeTransferLib for address;
19   
20       /// @inheritdoc IVirtualAccount
21       address public immutable override userAddress;
22   
23       /// @inheritdoc IVirtualAccount
24       address public immutable override localPortAddress;
25   
26       /*//////////////////////////////////////////////////////////////
27                               CONSTRUCTOR
28       //////////////////////////////////////////////////////////////*/
29   
30       /**
31        * @notice Constructor for Virtual Account.
32        * @param _userAddress Address of the user account.
33        * @param _localPortAddress Address of the root port contract.
34        */
35       constructor(address _userAddress, address _localPortAddress) {
36           userAddress = _userAddress;
37           localPortAddress = _localPortAddress;
38       }
39   
40       /*//////////////////////////////////////////////////////////////
41                               FALLBACK FUNCTIONS
42       //////////////////////////////////////////////////////////////*/
43   
44       receive() external payable {}
45   
46       /*//////////////////////////////////////////////////////////////
47                               EXTERNAL FUNCTIONS
48       //////////////////////////////////////////////////////////////*/
49   
50       /// @inheritdoc IVirtualAccount
51       function withdrawNative(uint256 _amount) external override requiresApprovedCaller {
52           msg.sender.safeTransferETH(_amount);
53       }
54   
55       /// @inheritdoc IVirtualAccount
56       function withdrawERC20(address _token, uint256 _amount) external override requiresApprovedCaller {
57           _token.safeTransfer(msg.sender, _amount);
58       }
59   
60       /// @inheritdoc IVirtualAccount
61       function withdrawERC721(address _token, uint256 _tokenId) external override requiresApprovedCaller {
62           ERC721(_token).transferFrom(address(this), msg.sender, _tokenId);
63       }
64   
65       /// @inheritdoc IVirtualAccount
66       function call(Call[] calldata calls) external override requiresApprovedCaller returns (bytes[] memory returnData) {
67           uint256 length = calls.length;
68           returnData = new bytes[](length);
69   
70           for (uint256 i = 0; i < length;) {
71               bool success;
72               Call calldata _call = calls[i];
73   
74               if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData);
75   
76               if (!success) revert CallFailed();
77   
78               unchecked {
79                   ++i;
80               }
81           }
82       }
83   
84       /// @inheritdoc IVirtualAccount
85       function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {
86           uint256 valAccumulator;
87           uint256 length = calls.length;
88           returnData = new bytes[](length);
89           PayableCall calldata _call;
90           for (uint256 i = 0; i < length;) {
91               _call = calls[i];
92               uint256 val = _call.value;
93               // Humanity will be a Type V Kardashev Civilization before this overflows - andreas
94               // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
95               unchecked {
96                   valAccumulator += val;
97               }
98   
99               bool success;
100  
101              if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);
102  
103              if (!success) revert CallFailed();
104  
105              unchecked {
106                  ++i;
107              }
108          }
109  
110          // Finally, make sure the msg.value = SUM(call[0...i].value)
111          if (msg.value != valAccumulator) revert CallFailed();
112      }
113  
114      /*//////////////////////////////////////////////////////////////
115                              EXTERNAL HOOKS
116      //////////////////////////////////////////////////////////////*/
117  
118      /// @inheritdoc IERC721Receiver
119      function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
120          return this.onERC721Received.selector;
121      }
122  
123      /// @inheritdoc IERC1155Receiver
124      function onERC1155Received(address, address, uint256, uint256, bytes calldata)
125          external
126          pure
127          override
128          returns (bytes4)
129      {
130          return this.onERC1155Received.selector;
131      }
132  
133      /// @inheritdoc IERC1155Receiver
134      function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
135          external
136          pure
137          override
138          returns (bytes4)
139      {
140          return this.onERC1155BatchReceived.selector;
141      }
142  
143      /*//////////////////////////////////////////////////////////////
144                              INTERNAL HELPERS
145      //////////////////////////////////////////////////////////////*/
146  
147      function isContract(address addr) internal view returns (bool) {
148          uint256 size;
149          assembly {
150              size := extcodesize(addr)
151          }
152          return size > 0;
153      }
154  
155      /*///////////////////////////////////////////////////////////////
156                                  MODIFIERS
157      //////////////////////////////////////////////////////////////*/
158  
159      /// @notice Modifier that verifies msg sender is the approved to use the virtual account. Either the owner or an approved router.
160      modifier requiresApprovedCaller() {
161          if (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) {
162              if (msg.sender != userAddress) {
163                  revert UnauthorizedCaller();
164              }
165          }
166          _;
167      }
168: }

GitHub: 17

File: src/interfaces/IVirtualAccount.sol

27   interface IVirtualAccount is IERC721Receiver {
28       /**
29        * @notice Returns the address of the user that owns the VirtualAccount.
30        * @return The address of the user that owns the VirtualAccount.
31        */
32       function userAddress() external view returns (address);
33   
34       /**
35        * @notice Returns the address of the local port.
36        * @return The address of the local port.
37        */
38       function localPortAddress() external view returns (address);
39   
40       /**
41        * @notice Withdraws native tokens from the VirtualAccount.
42        * @param _amount The amount of tokens to withdraw.
43        */
44       function withdrawNative(uint256 _amount) external;
45   
46       /**
47        * @notice Withdraws ERC20 tokens from the VirtualAccount.
48        * @param _token The address of the ERC20 token to withdraw.
49        * @param _amount The amount of tokens to withdraw.
50        */
51       function withdrawERC20(address _token, uint256 _amount) external;
52   
53       /**
54        * @notice Withdraws ERC721 tokens from the VirtualAccount.
55        * @param _token The address of the ERC721 token to withdraw.
56        * @param _tokenId The id of the token to withdraw.
57        */
58       function withdrawERC721(address _token, uint256 _tokenId) external;
59   
60       /**
61        * @notice Aggregate calls ensuring each call is successful. Inspired by `Multicall2` contract.
62        * @param callInput The call to make.
63        * @return The return data of the call.
64        */
65       function call(Call[] calldata callInput) external returns (bytes[] memory);
66   
67       /**
68        * @notice Aggregate calls with a msg value ensuring each call is successful. Inspired by `Multicall3` contract.
69        * @param calls The calls to make.
70        * @return The return data of the calls.
71        * @dev Reverts if msg.value is less than the sum of the call values.
72        */
73       function payableCall(PayableCall[] calldata calls) external payable returns (bytes[] memory);
74   
75       /*///////////////////////////////////////////////////////////////
76                                   ERRORS
77       //////////////////////////////////////////////////////////////*/
78   
79       error CallFailed();
80   
81       error UnauthorizedCaller();
82:  }

GitHub: 27

[N‑39] Style guide: Non-external/public function names should begin with an underscore

According to the Solidity Style Guide, non-external/public function names should begin with an underscore

There are 2 instances of this issue:

File: src/RootPort.sol

359:     function addVirtualAccount(address _user) internal returns (VirtualAccount newAccount) {

GitHub: 359

File: src/VirtualAccount.sol

147:     function isContract(address addr) internal view returns (bool) {

GitHub: 147

[N‑40] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 125 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

115:             rootChainId, "", 0, abi.encodePacked(bytes1(0x09), _settlementNonce)

GitHub: 115

File: src/ArbitrumCoreBranchRouter.sol

62:          bytes memory payload = abi.encodePacked(bytes1(0x02), params);

115:         bytes memory payload = abi.encodePacked(bytes1(0x04), data);

127:         if (_data[0] == 0x02) {

146:         } else if (_data[0] == 0x03) {

152:         } else if (_data[0] == 0x04) {

157:         } else if (_data[0] == 0x05) {

162:         } else if (_data[0] == 0x06) {

GitHub: 62, 115, 127, 146, 152, 157, 162

File: src/BranchBridgeAgent.sol

188:         bytes memory payload = abi.encodePacked(bytes1(0x00), depositNonce++, _params);

202:         bytes memory payload = abi.encodePacked(bytes1(0x01), depositNonce++, _params);

220:             bytes1(0x02), _depositNonce, _dParams.hToken, _dParams.token, _dParams.amount, _dParams.deposit, _params

242:             bytes1(0x03),

269:         bytes memory payload = abi.encodePacked(bytes1(0x04), msg.sender, depositNonce++, _params);

288:             _hasFallbackToggled ? bytes1(0x85) : bytes1(0x05),

318:             _hasFallbackToggled ? bytes1(0x86) : bytes1(0x06),

399:                     bytes1(0x03),

387:                     _hasFallbackToggled ? bytes1(0x86) : bytes1(0x06),

374:                     bytes1(0x02),

363:                     _hasFallbackToggled ? bytes1(0x85) : bytes1(0x05),

427:         bytes memory payload = abi.encodePacked(bytes1(0x08), msg.sender, _depositNonce);

474:         bytes memory payload = abi.encodePacked(_hasFallbackToggled ? bytes1(0x87) : bytes1(0x07), params);

581:             150,

593:         bytes1 flag = _payload[0] & 0x7F;

599:         if (flag == 0x00) {

616:         } else if (flag == 0x01) {

638:         } else if (flag == 0x02) {

663:         } else if (flag == 0x03) {

681:         } else if (flag == 0x04) {

643:             nonce = uint32(bytes4(_payload[22:26]));

651:                 _payload[0] == 0x82,

629:                 _payload[0] == 0x81,

790:             abi.encodePacked(bytes1(0x09), _settlementNonce),

942:         if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

943:         if (rootBridgeAgentAddress != address(uint160(bytes20(_srcAddress[20:])))) revert LayerZeroUnauthorizedCaller();

GitHub: 188, 202, 220, 242, 269, 288, 288, 318, 318, 399, 387, 387, 374, 363, 363, 427, 474, 474, 581, 593, 599, 616, 638, 663, 681, 643, 643, 651, 629, 790, 942, 943

File: src/BranchBridgeAgentExecutor.sol

75:              hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),

76:              token: address(uint160(bytes20(_payload[45:65]))),

77:              amount: uint256(bytes32(_payload[65:97])),

78:              deposit: uint256(bytes32(_payload[97:PARAMS_SETTLEMENT_OFFSET]))

GitHub: 75, 76, 76, 77, 77, 78

File: src/BranchPort.sol

299:         if (length > 255) revert InvalidInputArrays();

GitHub: 299

File: src/CoreBranchRouter.sol

51:          bytes memory payload = abi.encodePacked(bytes1(0x01), params);

43:      function addGlobalToken(address _globalAddress, uint256 _dstChainId, GasParams[3] calldata _gParams)

75:          bytes memory payload = abi.encodePacked(bytes1(0x02), params);

88:          if (_params[0] == 0x01) {

100:         } else if (_params[0] == 0x02) {

115:         } else if (_params[0] == 0x03) {

121:         } else if (_params[0] == 0x04) {

127:         } else if (_params[0] == 0x05) {

133:         } else if (_params[0] == 0x06) {

140:         } else if (_params[0] == 0x07) {

181:         bytes memory payload = abi.encodePacked(bytes1(0x03), params);

229:         bytes memory payload = abi.encodePacked(bytes1(0x04), params);

GitHub: 51, 43, 75, 88, 100, 115, 121, 127, 133, 140, 181, 229

File: src/CoreRootRouter.sol

136:         bytes memory payload = abi.encodePacked(bytes1(0x02), params);

171:         bytes memory payload = abi.encodePacked(bytes1(0x03), params);

196:         bytes memory payload = abi.encodePacked(bytes1(0x04), params);

223:         bytes memory payload = abi.encodePacked(bytes1(0x05), params);

254:         bytes memory payload = abi.encodePacked(bytes1(0x06), params);

284:         bytes memory payload = abi.encodePacked(bytes1(0x07), params);

307:         if (funcId == 0x02) {

314:         } else if (funcId == 0x03) {

320:         } else if (funcId == 0x04) {

337:         if (funcId == 0x01) {

434:         bytes memory payload = abi.encodePacked(bytes1(0x01), params);

GitHub: 136, 171, 196, 223, 254, 284, 307, 314, 320, 337, 434

File: src/MulticallRootRouter.sol

142:         if (funcId == 0x01) {

150:         } else if (funcId == 0x02) {

174:         } else if (funcId == 0x03) {

234:         if (funcId == 0x01) {

242:         } else if (funcId == 0x02) {

265:         } else if (funcId == 0x03) {

323:         if (funcId == 0x01) {

331:         } else if (funcId == 0x02) {

354:         } else if (funcId == 0x03) {

411:         if (funcId == 0x01) {

419:         } else if (funcId == 0x02) {

442:         } else if (funcId == 0x03) {

GitHub: 142, 150, 174, 234, 242, 265, 323, 331, 354, 411, 419, 442

File: src/RootBridgeAgent.sol

168:         bytes memory payload = abi.encodePacked(bytes1(0x00), _recipient, settlementNonce++, _params);

292:         bytes memory payload = abi.encodePacked(bytes1(0x03), settlementOwner, _settlementNonce);

426:             150,

444:         if (_payload[0] == 0x00) {

467:         } else if (_payload[0] == 0x01) {

490:         } else if (_payload[0] == 0x02) {

513:         } else if (_payload[0] == 0x03) {

539:         } else if (_payload[0] == 0x04) {

577:         } else if (_payload[0] & 0x7F == 0x05) {

617:         } else if (_payload[0] & 0x7F == 0x06) {

657:         } else if (_payload[0] & 0x7F == 0x07) {

699:         } else if (_payload[0] == 0x08) {

718:         } else if (_payload[0] == 0x09) {

684:                 _payload[0] == 0x87,

640:                 _payload[0] == 0x86,

600:                 _payload[0] == 0x85,

515:             nonce = uint32(bytes4(_payload[2:6]));

894:                 _hasFallbackToggled ? bytes1(0x82) : bytes1(0x02),

880:                 _hasFallbackToggled ? bytes1(0x81) : bytes1(0x01),

943:             abi.encodePacked(bytes1(0x04), _depositNonce),

993:             _hasFallbackToggled ? bytes1(0x81) : bytes1(0x01),

1090:            _hasFallbackToggled ? bytes1(0x02) & 0x0F : bytes1(0x02),

1210:            if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

GitHub: 168, 292, 426, 444, 467, 490, 513, 539, 577, 577, 617, 617, 657, 657, 699, 718, 684, 640, 600, 515, 894, 894, 880, 880, 943, 993, 993, 1090, 1090, 1090, 1210

File: src/RootBridgeAgentExecutor.sol

91:              token: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),

92:              amount: uint256(bytes32(_payload[45:77])),

93:              deposit: uint256(bytes32(_payload[77:PARAMS_TKN_SET_SIZE]))

175:             hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),

176:             token: address(uint160(bytes20(_payload[45:65]))),

177:             amount: uint256(bytes32(_payload[65:97])),

178:             deposit: uint256(bytes32(_payload[97:PARAMS_SETTLEMENT_OFFSET]))

274:         uint32 nonce = uint32(bytes4(_dParams[PARAMS_START:5]));

GitHub: 91, 92, 92, 93, 175, 176, 176, 177, 177, 178, 274

File: src/interfaces/ICoreBranchRouter.sol

38:      function addGlobalToken(address _globalAddress, uint256 _dstChainId, GasParams[3] calldata _gasParams)

GitHub: 38

[N‑41] Events are missing sender information

When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender the events of these types of action will make events much more useful to end users, especially when msg.sender is not tx.origin.

There are 27 instances of this issue:

File: src/BranchBridgeAgent.sol

689:             emit LogFallback(nonce);

700:         emit LogExecute(nonce);

GitHub: 689, 700

File: src/BranchPort.sol

218:         emit DebtRepaid(_strategy, _token, amountToWithdraw);

344:         emit BridgeAgentFactoryAdded(_newBridgeAgentFactory);

351:         emit BridgeAgentFactoryToggled(_newBridgeAgentFactory);

358:         emit BridgeAgentToggled(_bridgeAgent);

371:         emit StrategyTokenAdded(_token, _minimumReservesRatio);

378:         emit StrategyTokenToggled(_token);

392:         emit PortStrategyAdded(_portStrategy, _token, _dailyManagementLimit);

399:         emit PortStrategyToggled(_portStrategy, _token);

410:         emit PortStrategyUpdated(_portStrategy, _token, _dailyManagementLimit);

423:         emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);

GitHub: 218, 344, 351, 358, 371, 378, 392, 399, 410, 423

File: src/RootBridgeAgent.sol

726:             emit LogFallback(nonce, _srcChainId);

736:         emit LogExecute(nonce, _srcChainId);

GitHub: 726, 736

File: src/RootPort.sol

255:         emit LocalTokenAdded(_underlyingAddress, _localAddress, _globalAddress, _srcChainId);

269:         emit GlobalTokenAdded(_localAddress, _globalAddress, _srcChainId);

365:         emit VirtualAccountCreated(_user, address(newAccount));

389:         emit BridgeAgentAdded(_bridgeAgent, _manager);

406:         emit BridgeAgentSynced(_newBranchBridgeAgent, _rootBridgeAgent, _branchChainId);

417:         emit BridgeAgentToggled(_bridgeAgent);

427:         emit BridgeAgentFactoryAdded(_bridgeAgentFactory);

434:         emit BridgeAgentFactoryToggled(_bridgeAgentFactory);

479:         emit NewChainAdded(_chainId);

505:         emit EcosystemTokenAdded(_ecoTokenGlobalAddress);

517:         emit CoreRootSet(_coreRootRouter, _coreRootBridgeAgent);

535:         emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent, _dstChainId);

549:         emit CoreBranchSynced(_coreBranchRouter, _coreBranchBridgeAgent, _dstChainId);

GitHub: 255, 269, 365, 389, 406, 417, 427, 434, 479, 505, 517, 535, 549

[N‑42] Consider adding a block/deny-list

Doing so will significantly increase centralization, but will help to prevent hackers from using stolen tokens

There are 12 instances of this issue:

see instances
File: src/ArbitrumCoreBranchRouter.sol

37   contract ArbitrumCoreBranchRouter is CoreBranchRouter {
38       /*///////////////////////////////////////////////////////////////
39                                CONSTRUCTOR
40       //////////////////////////////////////////////////////////////*/
41       /**
42        * @notice Constructor for Arbitrum Core Branch Router.
43:       */

GitHub: 37

File: src/BaseBranchRouter.sol

25:  contract BaseBranchRouter is IBranchRouter, Ownable {

GitHub: 25

File: src/BranchPort.sol

17:  contract BranchPort is Ownable, IBranchPort {

GitHub: 17

File: src/CoreBranchRouter.sol

18   contract CoreBranchRouter is ICoreBranchRouter, BaseBranchRouter {
19:      /// @notice hToken Factory Address.

GitHub: 18

File: src/CoreRootRouter.sol

38   contract CoreRootRouter is IRootRouter, Ownable {
39       /*///////////////////////////////////////////////////////////////
40                       CORE ROOT ROUTER STATE
41       //////////////////////////////////////////////////////////////*/
42   
43:      /// @notice Boolean to indicate if the contract is in set up mode.

GitHub: 38

File: src/RootBridgeAgent.sol

32:  contract RootBridgeAgent is IRootBridgeAgent, BridgeAgentConstants {

GitHub: 32

File: src/RootPort.sol

16:  contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/VirtualAccount.sol

17:  contract VirtualAccount is IVirtualAccount, ERC1155Receiver {

GitHub: 17

File: src/factories/ERC20hTokenBranchFactory.sol

12   contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

12   contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/token/ERC20hTokenBranch.sol

12:  contract ERC20hTokenBranch is ERC20, Ownable, IERC20hTokenBranch {

GitHub: 12

File: src/token/ERC20hTokenRoot.sol

12   contract ERC20hTokenRoot is ERC20, Ownable, IERC20hTokenRoot {
13:      /// @inheritdoc IERC20hTokenRoot

GitHub: 12

[N‑43] Consider using SafeTransferLib.safeTransferETH() or Address.sendValue() for clearer semantic meaning

These Functions indicate their purpose with their name more clearly than using low-level calls.

There are 3 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

103:         _rootBridgeAgentAddress.call{value: msg.value}("");

GitHub: 103

File: src/RootBridgeAgent.sol

835:             callee.call{value: msg.value}("");

927:             callee.call{value: _value}("");

GitHub: 835, 927

[N‑44] Unused import

The identifier is imported but never used within the file

There are 15 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

/// @audit IBranchBridgeAgent
6:   import {GasParams, IBranchBridgeAgent} from "./interfaces/IBranchBridgeAgent.sol";

GitHub: 6

File: src/BaseBranchRouter.sol

/// @audit DepositParams
16:      DepositParams,

/// @audit DepositMultipleParams
18:      DepositMultipleParams,

GitHub: 16, 18

File: src/BranchBridgeAgent.sol

/// @audit ILayerZeroReceiver
15:      ILayerZeroReceiver,

GitHub: 15

File: src/CoreBranchRouter.sol

/// @audit IBranchRouter
9:   import {IBranchRouter} from "./interfaces/IBranchRouter.sol";

GitHub: 9

File: src/RootBridgeAgent.sol

/// @audit ILayerZeroReceiver
18:      ILayerZeroReceiver,

GitHub: 18

File: src/RootBridgeAgentExecutor.sol

/// @audit IRootBridgeAgent
7:   import {IRootBridgeAgent} from "./interfaces/IRootBridgeAgent.sol";

GitHub: 7

File: src/VirtualAccount.sol

/// @audit IERC1155Receiver
10:  import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";

/// @audit IERC721Receiver
11:  import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

GitHub: 10, 11

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

/// @audit ArbitrumBranchBridgeAgent
6:   import {ArbitrumBranchBridgeAgent, DeployArbitrumBranchBridgeAgent} from "../ArbitrumBranchBridgeAgent.sol";

GitHub: 6

File: src/factories/ERC20hTokenRootFactory.sol

/// @audit ERC20
6:   import {ERC20} from "solmate/tokens/ERC20.sol";

GitHub: 6

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit DepositParams
11:      DepositParams,

/// @audit DepositMultipleParams
12:      DepositMultipleParams,

/// @audit SettlementParams
13:      SettlementParams,

GitHub: 11, 12, 13

File: src/interfaces/IRootBridgeAgent.sol

/// @audit SettlementParams
13:      SettlementParams

GitHub: 13

[N‑45] Array is push()ed but not pop()ed

Array entries are added but are never removed. Consider whether this should be the case, or whether there should be a maximum, or whether old entries should be removed. Cases where there are specific potential problems will be flagged separately under a different issue.

There are 12 instances of this issue:

File: src/BranchPort.sol

131:         bridgeAgentFactories.push(_bridgeAgentFactory);

323:         bridgeAgents.push(_bridgeAgent);

342:         bridgeAgentFactories.push(_newBridgeAgentFactory);

367:         strategyTokens.push(_token);

388:         portStrategies.push(_portStrategy);

421:         bridgeAgents.push(_coreBranchBridgeAgent);

GitHub: 131, 323, 342, 367, 388, 421

File: src/RootPort.sol

136:         bridgeAgentFactories.push(_bridgeAgentFactory);

385:         bridgeAgents.push(_bridgeAgent);

424:         bridgeAgentFactories.push(_bridgeAgentFactory);

GitHub: 136, 385, 424

File: src/factories/ERC20hTokenBranchFactory.sol

72:          hTokens.push(newToken);

104:         hTokens.push(newToken);

GitHub: 72, 104

File: src/factories/ERC20hTokenRootFactory.sol

89:          hTokens.push(newToken);

GitHub: 89

[N‑46] Unused struct definition

Note that there may be cases where a struct superficially appears to be used, but this is only because there are multiple definitions of the struct in different files. In such cases, the struct definition should be moved into a separate file. The instances below are the unused definitions.

There is one instance of this issue:

File: src/interfaces/IMulticall2.sol

15       struct Result {
16           bool success;
17           bytes returnData;
18:      }

GitHub: 15

[N‑47] Unused error definition

Note that there may be cases where an error superficially appears to be used, but this is only because there are multiple definitions of the error in different files. In such cases, the error definition should be moved into a separate file. The instances below are the unused definitions.

There are 7 instances of this issue:

File: src/interfaces/IBranchBridgeAgent.sol

348:     error InsufficientGas();

GitHub: 348

File: src/interfaces/IRootBridgeAgent.sol

345:     error GasErrorOrRepeatedTx();

349:     error NotDao();

358:     error UnrecognizedLocalBridgeAgent();

372:     error InsufficientGasForFees();

376:     error CallerIsNotPool();

377:     error AmountsAreZero();

GitHub: 345, 349, 358, 372, 376, 377

[N‑48] NatSpec: Contract declarations should have NatSpec descriptions

e.g. @dev or @notice, and it must appear above the contract definition braces in order to be identified by the compiler as NatSpec

There are 4 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

10:  library DeployArbitrumBranchBridgeAgent {

GitHub: 10

File: src/interfaces/ILayerZeroEndpoint.sol

7    interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
8        // @notice send a LayerZero message to the specified address at a LayerZero endpoint.
9        // @param _dstChainId - the destination chain identifier
10       // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
11       // @param _payload - a custom bytes payload to send to the destination contract
12       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
13       // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
14:      // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

GitHub: 7

File: src/interfaces/ILayerZeroReceiver.sol

5    interface ILayerZeroReceiver {
6        // @notice LayerZero endpoint will invoke this function to deliver the message on the destination
7        // @param _srcChainId - the source endpoint identifier
8        // @param _srcAddress - the source sending contract address from the source chain
9        // @param _nonce - the ordered message nonce
10:      // @param _payload - the signed payload is the UA bytes has encoded to be sent

GitHub: 5

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

5    interface ILayerZeroUserApplicationConfig {
6        // @notice set the configuration of the LayerZero messaging library of the specified version
7        // @param _version - messaging library version
8        // @param _chainId - the chainId for the pending config change
9        // @param _configType - type of configuration. every messaging library has its own convention.
10:      // @param _config - configuration in the bytes. can encode arbitrary content.

GitHub: 5

[N‑49] NatSpec: Contract declarations should have @notice tags

@notice is used to explain to end users what the contract does, and the compiler interprets /// or /** comments as this tag if one wasn't explicitly provided

There are 4 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

10:  library DeployArbitrumBranchBridgeAgent {

GitHub: 10

File: src/interfaces/ILayerZeroEndpoint.sol

7    interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
8        // @notice send a LayerZero message to the specified address at a LayerZero endpoint.
9        // @param _dstChainId - the destination chain identifier
10       // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
11       // @param _payload - a custom bytes payload to send to the destination contract
12       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
13       // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
14:      // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

GitHub: 7

File: src/interfaces/ILayerZeroReceiver.sol

5    interface ILayerZeroReceiver {
6        // @notice LayerZero endpoint will invoke this function to deliver the message on the destination
7        // @param _srcChainId - the source endpoint identifier
8        // @param _srcAddress - the source sending contract address from the source chain
9        // @param _nonce - the ordered message nonce
10:      // @param _payload - the signed payload is the UA bytes has encoded to be sent

GitHub: 5

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

5    interface ILayerZeroUserApplicationConfig {
6        // @notice set the configuration of the LayerZero messaging library of the specified version
7        // @param _version - messaging library version
8        // @param _chainId - the chainId for the pending config change
9        // @param _configType - type of configuration. every messaging library has its own convention.
10:      // @param _config - configuration in the bytes. can encode arbitrary content.

GitHub: 5

[N‑50] NatSpec: Error declarations should have NatSpec descriptions

There are 90 instances of this issue:

see instances
File: src/CoreRootRouter.sol

520:     error InvalidChainId();

522:     error UnauthorizedChainId();

524:     error UnauthorizedCallerNotManager();

526:     error TokenAlreadyAdded();

528:     error UnrecognizedGlobalToken();

530:     error UnrecognizedBridgeAgentFactory();

GitHub: 520, 522, 524, 526, 528, 530

File: src/interfaces/IArbitrumBranchPort.sol

45:      error UnknownGlobalToken();

46:      error UnknownUnderlyingToken();

GitHub: 45, 46

File: src/interfaces/IBranchBridgeAgent.sol

339:     error UnknownFlag();

340:     error ExecutionFailure();

342:     error LayerZeroUnauthorizedCaller();

343:     error LayerZeroUnauthorizedEndpoint();

345:     error AlreadyExecutedTransaction();

347:     error InvalidInput();

348:     error InsufficientGas();

350:     error NotDepositOwner();

351:     error DepositRetryUnavailableUseCallout();

352:     error DepositRedeemUnavailable();

354:     error UnrecognizedRouter();

355:     error UnrecognizedBridgeAgentExecutor();

GitHub: 339, 340, 342, 343, 345, 347, 348, 350, 351, 352, 354, 355

File: src/interfaces/IBranchPort.sol

245:     error AlreadyAddedBridgeAgent();

246:     error AlreadyAddedBridgeAgentFactory();

247:     error InvalidMinimumReservesRatio();

248:     error InvalidInputArrays();

249:     error InsufficientReserves();

250:     error UnrecognizedCore();

251:     error UnrecognizedBridgeAgent();

252:     error UnrecognizedBridgeAgentFactory();

253:     error UnrecognizedPortStrategy();

254:     error UnrecognizedStrategyToken();

255:     error NotEnoughDebtToRepay();

GitHub: 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255

File: src/interfaces/IBranchRouter.sol

113:     error UnrecognizedFunctionId();

115:     error UnrecognizedBridgeAgentExecutor();

GitHub: 113, 115

File: src/interfaces/ICoreBranchRouter.sol

53:      error UnrecognizedBridgeAgent();

54:      error UnrecognizedBridgeAgentFactory();

GitHub: 53, 54

File: src/interfaces/IERC20hTokenBranchFactory.sol

32:      error UnrecognizedCoreRouter();

34:      error UnrecognizedPort();

GitHub: 32, 34

File: src/interfaces/IERC20hTokenRoot.sol

57:      error UnrecognizedPort();

GitHub: 57

File: src/interfaces/IERC20hTokenRootFactory.sol

32:      error UnrecognizedCoreRouterOrPort();

GitHub: 32

File: src/interfaces/IPortStrategy.sol

29:      error UnrecognizedPort();

GitHub: 29

File: src/interfaces/IRootBridgeAgent.sol

344:     error ExecutionFailure();

345:     error GasErrorOrRepeatedTx();

346:     error AlreadyExecutedTransaction();

347:     error UnknownFlag();

349:     error NotDao();

351:     error LayerZeroUnauthorizedEndpoint();

352:     error LayerZeroUnauthorizedCaller();

354:     error AlreadyAddedBridgeAgent();

355:     error UnrecognizedExecutor();

356:     error UnrecognizedPort();

357:     error UnrecognizedBridgeAgent();

358:     error UnrecognizedLocalBridgeAgent();

359:     error UnrecognizedBridgeAgentManager();

360:     error UnrecognizedRouter();

362:     error UnrecognizedUnderlyingAddress();

363:     error UnrecognizedLocalAddress();

365:     error SettlementRetryUnavailable();

366:     error SettlementRetryUnavailableUseCallout();

367:     error SettlementRedeemUnavailable();

368:     error SettlementRetrieveUnavailable();

369:     error NotSettlementOwner();

371:     error InsufficientBalanceForSettlement();

372:     error InsufficientGasForFees();

373:     error InvalidInputParams();

374:     error InvalidInputParamsLength();

376:     error CallerIsNotPool();

377:     error AmountsAreZero();

GitHub: 344, 345, 346, 347, 349, 351, 352, 354, 355, 356, 357, 358, 359, 360, 362, 363, 365, 366, 367, 368, 369, 371, 372, 373, 374, 376, 377

File: src/interfaces/IRootPort.sol

399:     error InvalidGlobalAddress();

400:     error InvalidLocalAddress();

401:     error InvalidUnderlyingAddress();

403:     error InvalidUserAddress();

405:     error InvalidCoreRootRouter();

406:     error InvalidCoreRootBridgeAgent();

407:     error InvalidCoreBranchRouter();

408:     error InvalidCoreBrancBridgeAgent();

410:     error UnrecognizedBridgeAgentFactory();

411:     error UnrecognizedBridgeAgent();

413:     error UnrecognizedToken();

414:     error UnableToMint();

416:     error AlreadyAddedChain();

417:     error AlreadyAddedEcosystemToken();

419:     error AlreadyAddedBridgeAgent();

420:     error AlreadyAddedBridgeAgentFactory();

421:     error BridgeAgentNotAllowed();

422:     error UnrecognizedCoreRootRouter();

423:     error UnrecognizedLocalBranchPort();

GitHub: 399, 400, 401, 403, 405, 406, 407, 408, 410, 411, 413, 414, 416, 417, 419, 420, 421, 422, 423

File: src/interfaces/IRootRouter.sol

97:      error UnrecognizedFunctionId();

98:      error UnrecognizedBridgeAgentExecutor();

GitHub: 97, 98

File: src/interfaces/IVirtualAccount.sol

79:      error CallFailed();

81:      error UnauthorizedCaller();

GitHub: 79, 81

[N‑51] NatSpec: Event declarations should have NatSpec descriptions

There are 28 instances of this issue:

File: src/interfaces/IBranchBridgeAgent.sol

332:     event LogExecute(uint256 indexed nonce);

333:     event LogFallback(uint256 indexed nonce);

GitHub: 332, 333

File: src/interfaces/IBranchPort.sol

220:     event DebtCreated(address indexed _strategy, address indexed _token, uint256 _amount);

221:     event DebtRepaid(address indexed _strategy, address indexed _token, uint256 _amount);

223:     event StrategyTokenAdded(address indexed _token, uint256 indexed _minimumReservesRatio);

224:     event StrategyTokenToggled(address indexed _token);

226      event PortStrategyAdded(
227          address indexed _portStrategy, address indexed _token, uint256 indexed _dailyManagementLimit
228:     );

229:     event PortStrategyToggled(address indexed _portStrategy, address indexed _token);

230      event PortStrategyUpdated(
231          address indexed _portStrategy, address indexed _token, uint256 indexed _dailyManagementLimit
232:     );

234:     event BridgeAgentFactoryAdded(address indexed _bridgeAgentFactory);

235:     event BridgeAgentFactoryToggled(address indexed _bridgeAgentFactory);

237:     event BridgeAgentToggled(address indexed _bridgeAgent);

239:     event CoreBranchSet(address indexed _coreBranchRouter, address indexed _coreBranchBridgeAgent);

GitHub: 220, 221, 223, 224, 226, 229, 230, 234, 235, 237, 239

File: src/interfaces/IRootBridgeAgent.sol

337:     event LogExecute(uint256 indexed depositNonce, uint256 indexed srcChainId);

338:     event LogFallback(uint256 indexed settlementNonce, uint256 indexed dstChainId);

GitHub: 337, 338

File: src/interfaces/IRootPort.sol

371:     event BridgeAgentFactoryAdded(address indexed bridgeAgentFactory);

372:     event BridgeAgentFactoryToggled(address indexed bridgeAgentFactory);

374:     event BridgeAgentAdded(address indexed bridgeAgent, address indexed manager);

375:     event BridgeAgentToggled(address indexed bridgeAgent);

376:     event BridgeAgentSynced(address indexed bridgeAgent, address indexed rootBridgeAgent, uint256 indexed srcChainId);

378:     event NewChainAdded(uint256 indexed chainId);

380:     event VirtualAccountCreated(address indexed user, address account);

382      event LocalTokenAdded(
383          address indexed underlyingAddress, address indexed localAddress, address indexed globalAddress, uint256 chainId
384:     );

385:     event GlobalTokenAdded(address indexed localAddress, address indexed globalAddress, uint256 indexed chainId);

386:     event EcosystemTokenAdded(address indexed ecoTokenGlobalAddress);

387:     event CoreRootSet(address indexed coreRootRouter, address indexed coreRootBridgeAgent);

388      event CoreBranchSet(
389          address indexed coreBranchRouter, address indexed coreBranchBridgeAgent, uint16 indexed dstChainId
390:     );

391      event CoreBranchSynced(
392          address indexed coreBranchRouter, address indexed coreBranchBridgeAgent, uint16 indexed dstChainId
393:     );

GitHub: 371, 372, 374, 375, 376, 378, 380, 382, 385, 386, 387, 388, 391

[N‑52] NatSpec: State variable declarations should have NatSpec descriptions

e.g. @notice for public state variables, and @dev for non-public ones

There are 22 instances of this issue:

File: src/BranchPort.sol

97:      uint256 internal constant DIVISIONER = 1e4;

98:      uint256 internal constant MIN_RESERVE_RATIO = 3e3;

GitHub: 97, 98

File: src/interfaces/BridgeAgentConstants.sol

13:      uint8 internal constant STATUS_READY = 0;

15:      uint8 internal constant STATUS_DONE = 1;

17:      uint8 internal constant STATUS_RETRIEVE = 2;

21:      uint8 internal constant STATUS_FAILED = 1;

23:      uint8 internal constant STATUS_SUCCESS = 0;

27:      uint256 internal constant PARAMS_START = 1;

29:      uint256 internal constant PARAMS_START_SIGNED = 21;

31:      uint256 internal constant PARAMS_TKN_START = 5;

33:      uint256 internal constant PARAMS_TKN_START_SIGNED = 25;

35:      uint256 internal constant PARAMS_ENTRY_SIZE = 32;

37:      uint256 internal constant PARAMS_ADDRESS_SIZE = 20;

39:      uint256 internal constant PARAMS_TKN_SET_SIZE = 109;

41:      uint256 internal constant PARAMS_TKN_SET_SIZE_MULTIPLE = 128;

43:      uint256 internal constant ADDRESS_END_OFFSET = 12;

45:      uint256 internal constant PARAMS_AMT_OFFSET = 64;

47:      uint256 internal constant PARAMS_DEPOSIT_OFFSET = 96;

49:      uint256 internal constant PARAMS_END_OFFSET = 6;

51:      uint256 internal constant PARAMS_END_SIGNED_OFFSET = 26;

53:      uint256 internal constant PARAMS_SETTLEMENT_OFFSET = 129;

57:      uint256 internal constant MAX_TOKENS_LENGTH = 255;

GitHub: 13, 15, 17, 21, 23, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 57

[N‑53] NatSpec: Function declarations should have NatSpec descriptions

There are 37 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

11       function deploy(uint16 _localChainId, address _daoAddress, address _localRouterAddress, address _localPortAddress)
12           external
13           returns (ArbitrumBranchBridgeAgent)
14:      {

GitHub: 11

File: src/BaseBranchRouter.sol

48:      constructor() {

GitHub: 48

File: src/BranchBridgeAgent.sol

24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27           address _rootBridgeAgentAddress,
28           address _lzEndpointAddress,
29           address _localRouterAddress,
30           address _localPortAddress
31:      ) external returns (BranchBridgeAgent) {

149:     receive() external payable {}

GitHub: 24, 149

File: src/BranchBridgeAgentExecutor.sol

16:      function deploy() external returns (address) {

GitHub: 16

File: src/CoreRootRouter.sol

83:      function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

GitHub: 83

File: src/MulticallRootRouterLibZip.sol

29       constructor(uint256 _localChainId, address _localPortAddress, address _multicallAddress)
30           MulticallRootRouter(_localChainId, _localPortAddress, _multicallAddress)
31:      {}

GitHub: 29

File: src/RootBridgeAgent.sol

128:     receive() external payable {}

GitHub: 128

File: src/RootBridgeAgentExecutor.sol

14:      function deploy(address _rootBridgeAgent) external returns (address) {

GitHub: 14

File: src/RootPort.sol

304      function bridgeToLocalBranchFromRoot(address _to, address _hToken, uint256 _amount)
305          external
306          override
307          requiresLocalBranchPort
308:     {

GitHub: 304

File: src/VirtualAccount.sol

44:      receive() external payable {}

GitHub: 44

File: src/interfaces/ILayerZeroEndpoint.sol

15       function send(
16           uint16 _dstChainId,
17           bytes calldata _destination,
18           bytes calldata _payload,
19           address payable _refundAddress,
20           address _zroPaymentAddress,
21           bytes calldata _adapterParams
22:      ) external payable;

31       function receivePayload(
32           uint16 _srcChainId,
33           bytes calldata _srcAddress,
34           address _dstAddress,
35           uint64 _nonce,
36           uint256 _gasLimit,
37           bytes calldata _payload
38:      ) external;

43:      function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64);

47:      function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64);

55       function estimateFees(
56           uint16 _dstChainId,
57           address _userApplication,
58           bytes calldata _payload,
59           bool _payInZRO,
60           bytes calldata _adapterParam
61:      ) external view returns (uint256 nativeFee, uint256 zroFee);

64:      function getChainId() external view returns (uint16);

70:      function retryPayload(uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload) external;

75:      function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool);

79:      function getSendLibraryAddress(address _userApplication) external view returns (address);

83:      function getReceiveLibraryAddress(address _userApplication) external view returns (address);

87:      function isSendingPayload() external view returns (bool);

91:      function isReceivingPayload() external view returns (bool);

98       function getConfig(uint16 _version, uint16 _chainId, address _userApplication, uint256 _configType)
99           external
100          view
101:         returns (bytes memory);

105:     function getSendVersion(address _userApplication) external view returns (uint16);

109:     function getReceiveVersion(address _userApplication) external view returns (uint16);

GitHub: 15, 31, 43, 47, 55, 64, 70, 75, 79, 83, 87, 91, 98, 105, 109

File: src/interfaces/ILayerZeroReceiver.sol

11:      function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes calldata _payload) external;

GitHub: 11

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

11:      function setConfig(uint16 _version, uint16 _chainId, uint256 _configType, bytes calldata _config) external;

15:      function setSendVersion(uint16 _version) external;

19:      function setReceiveVersion(uint16 _version) external;

24:      function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external;

GitHub: 11, 15, 19, 24

File: src/interfaces/IMulticall2.sol

20:      function aggregate(Call[] memory calls) external returns (uint256 blockNumber, bytes[] memory returnData);

GitHub: 20

File: src/interfaces/IRootBridgeAgentFactory.sol

16:      function createBridgeAgent(address newRootRouterAddress) external returns (address newBridgeAgent);

GitHub: 16

File: src/interfaces/IRootPort.sol

11:      function bridgeAgentAddress() external view returns (address);

12:      function hTokenFactoryAddress() external view returns (address);

13       function setCoreBranch(
14           address _refundee,
15           address _coreBranchRouter,
16           address _coreBranchBridgeAgent,
17           uint16 _dstChainId,
18           GasParams calldata _gParams
19:      ) external payable;

GitHub: 11, 12, 13

File: src/token/ERC20hTokenBranch.sol

13       constructor(
14           string memory chainName,
15           string memory chainSymbol,
16           string memory _name,
17           string memory _symbol,
18           uint8 _decimals,
19           address _owner
20:      ) ERC20(string(string.concat(chainName, _name)), string(string.concat(chainSymbol, _symbol)), _decimals) {

GitHub: 13

[N‑54] NatSpec: Function declarations should have @notice tags

@notice is used to explain to end users what the function does, and the compiler interprets /// or /** comments as this tag if one wasn't explicitly provided

There are 37 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

11       function deploy(uint16 _localChainId, address _daoAddress, address _localRouterAddress, address _localPortAddress)
12           external
13           returns (ArbitrumBranchBridgeAgent)
14:      {

GitHub: 11

File: src/BaseBranchRouter.sol

48:      constructor() {

GitHub: 48

File: src/BranchBridgeAgent.sol

24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27           address _rootBridgeAgentAddress,
28           address _lzEndpointAddress,
29           address _localRouterAddress,
30           address _localPortAddress
31:      ) external returns (BranchBridgeAgent) {

149:     receive() external payable {}

GitHub: 24, 149

File: src/BranchBridgeAgentExecutor.sol

16:      function deploy() external returns (address) {

GitHub: 16

File: src/CoreRootRouter.sol

83:      function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

GitHub: 83

File: src/MulticallRootRouterLibZip.sol

29       constructor(uint256 _localChainId, address _localPortAddress, address _multicallAddress)
30           MulticallRootRouter(_localChainId, _localPortAddress, _multicallAddress)
31:      {}

GitHub: 29

File: src/RootBridgeAgent.sol

128:     receive() external payable {}

GitHub: 128

File: src/RootBridgeAgentExecutor.sol

14:      function deploy(address _rootBridgeAgent) external returns (address) {

GitHub: 14

File: src/RootPort.sol

304      function bridgeToLocalBranchFromRoot(address _to, address _hToken, uint256 _amount)
305          external
306          override
307          requiresLocalBranchPort
308:     {

GitHub: 304

File: src/VirtualAccount.sol

44:      receive() external payable {}

GitHub: 44

File: src/interfaces/ILayerZeroEndpoint.sol

15       function send(
16           uint16 _dstChainId,
17           bytes calldata _destination,
18           bytes calldata _payload,
19           address payable _refundAddress,
20           address _zroPaymentAddress,
21           bytes calldata _adapterParams
22:      ) external payable;

31       function receivePayload(
32           uint16 _srcChainId,
33           bytes calldata _srcAddress,
34           address _dstAddress,
35           uint64 _nonce,
36           uint256 _gasLimit,
37           bytes calldata _payload
38:      ) external;

43:      function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64);

47:      function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64);

55       function estimateFees(
56           uint16 _dstChainId,
57           address _userApplication,
58           bytes calldata _payload,
59           bool _payInZRO,
60           bytes calldata _adapterParam
61:      ) external view returns (uint256 nativeFee, uint256 zroFee);

64:      function getChainId() external view returns (uint16);

70:      function retryPayload(uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload) external;

75:      function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool);

79:      function getSendLibraryAddress(address _userApplication) external view returns (address);

83:      function getReceiveLibraryAddress(address _userApplication) external view returns (address);

87:      function isSendingPayload() external view returns (bool);

91:      function isReceivingPayload() external view returns (bool);

98       function getConfig(uint16 _version, uint16 _chainId, address _userApplication, uint256 _configType)
99           external
100          view
101:         returns (bytes memory);

105:     function getSendVersion(address _userApplication) external view returns (uint16);

109:     function getReceiveVersion(address _userApplication) external view returns (uint16);

GitHub: 15, 31, 43, 47, 55, 64, 70, 75, 79, 83, 87, 91, 98, 105, 109

File: src/interfaces/ILayerZeroReceiver.sol

11:      function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes calldata _payload) external;

GitHub: 11

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

11:      function setConfig(uint16 _version, uint16 _chainId, uint256 _configType, bytes calldata _config) external;

15:      function setSendVersion(uint16 _version) external;

19:      function setReceiveVersion(uint16 _version) external;

24:      function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external;

GitHub: 11, 15, 19, 24

File: src/interfaces/IMulticall2.sol

20:      function aggregate(Call[] memory calls) external returns (uint256 blockNumber, bytes[] memory returnData);

GitHub: 20

File: src/interfaces/IRootBridgeAgentFactory.sol

16:      function createBridgeAgent(address newRootRouterAddress) external returns (address newBridgeAgent);

GitHub: 16

File: src/interfaces/IRootPort.sol

11:      function bridgeAgentAddress() external view returns (address);

12:      function hTokenFactoryAddress() external view returns (address);

13       function setCoreBranch(
14           address _refundee,
15           address _coreBranchRouter,
16           address _coreBranchBridgeAgent,
17           uint16 _dstChainId,
18           GasParams calldata _gParams
19:      ) external payable;

GitHub: 11, 12, 13

File: src/token/ERC20hTokenBranch.sol

13       constructor(
14           string memory chainName,
15           string memory chainSymbol,
16           string memory _name,
17           string memory _symbol,
18           uint8 _decimals,
19           address _owner
20:      ) ERC20(string(string.concat(chainName, _name)), string(string.concat(chainSymbol, _symbol)), _decimals) {

GitHub: 13

[N‑55] Constants in comparisons should appear on the left side

Doing so will prevent typo bugs

There are 62 instances of this issue:

see instances
File: src/ArbitrumCoreBranchRouter.sol

127:         if (_data[0] == 0x02) {

146:         } else if (_data[0] == 0x03) {

152:         } else if (_data[0] == 0x04) {

157:         } else if (_data[0] == 0x05) {

162:         } else if (_data[0] == 0x06) {

GitHub: 127, 146, 152, 157, 162

File: src/BaseBranchRouter.sol

213:         require(_unlocked == 1);

GitHub: 213

File: src/BranchBridgeAgent.sol

359:         if (uint8(deposit.hTokens.length) == 1) {

412:         if (payload.length == 0) revert DepositRetryUnavailableUseCallout();

599:         if (flag == 0x00) {

616:         } else if (flag == 0x01) {

638:         } else if (flag == 0x02) {

663:         } else if (flag == 0x03) {

681:         } else if (flag == 0x04) {

651:                 _payload[0] == 0x82,

629:                 _payload[0] == 0x81,

923:         require(_unlocked == 1);

942:         if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

GitHub: 359, 412, 599, 616, 638, 663, 681, 651, 629, 923, 942

File: src/BranchPort.sol

487:         if (block.timestamp - lastManaged[msg.sender][_token] >= 1 days) {

567:         require(_unlocked == 1);

GitHub: 487, 567

File: src/CoreBranchRouter.sol

88:          if (_params[0] == 0x01) {

100:         } else if (_params[0] == 0x02) {

115:         } else if (_params[0] == 0x03) {

121:         } else if (_params[0] == 0x04) {

127:         } else if (_params[0] == 0x05) {

133:         } else if (_params[0] == 0x06) {

140:         } else if (_params[0] == 0x07) {

GitHub: 88, 100, 115, 121, 127, 133, 140

File: src/CoreRootRouter.sol

307:         if (funcId == 0x02) {

314:         } else if (funcId == 0x03) {

320:         } else if (funcId == 0x04) {

337:         if (funcId == 0x01) {

GitHub: 307, 314, 320, 337

File: src/MulticallRootRouter.sol

142:         if (funcId == 0x01) {

150:         } else if (funcId == 0x02) {

174:         } else if (funcId == 0x03) {

234:         if (funcId == 0x01) {

242:         } else if (funcId == 0x02) {

265:         } else if (funcId == 0x03) {

323:         if (funcId == 0x01) {

331:         } else if (funcId == 0x02) {

354:         } else if (funcId == 0x03) {

411:         if (funcId == 0x01) {

419:         } else if (funcId == 0x02) {

442:         } else if (funcId == 0x03) {

591:         require(_unlocked == 1);

GitHub: 142, 150, 174, 234, 242, 265, 323, 331, 354, 411, 419, 442, 591

File: src/RootBridgeAgent.sol

444:         if (_payload[0] == 0x00) {

467:         } else if (_payload[0] == 0x01) {

490:         } else if (_payload[0] == 0x02) {

513:         } else if (_payload[0] == 0x03) {

539:         } else if (_payload[0] == 0x04) {

577:         } else if (_payload[0] & 0x7F == 0x05) {

617:         } else if (_payload[0] & 0x7F == 0x06) {

657:         } else if (_payload[0] & 0x7F == 0x07) {

699:         } else if (_payload[0] == 0x08) {

718:         } else if (_payload[0] == 0x09) {

684:                 _payload[0] == 0x87,

640:                 _payload[0] == 0x86,

600:                 _payload[0] == 0x85,

871:         if (_hTokens.length == 0) revert SettlementRetryUnavailableUseCallout();

877:         if (_hTokens.length == 1) {

1141:        if (_amount == 0 && _deposit == 0) revert InvalidInputParams();

1191:        require(_unlocked == 1);

1210:            if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

GitHub: 444, 467, 490, 513, 539, 577, 617, 657, 699, 718, 684, 640, 600, 871, 877, 1141, 1141, 1191, 1210

[N‑56] Custom error has no error details

Consider adding parameters to the error to indicate which user or values caused the failure

There are 90 instances of this issue:

see instances
File: src/CoreRootRouter.sol

520:     error InvalidChainId();

522:     error UnauthorizedChainId();

524:     error UnauthorizedCallerNotManager();

526:     error TokenAlreadyAdded();

528:     error UnrecognizedGlobalToken();

530:     error UnrecognizedBridgeAgentFactory();

GitHub: 520, 522, 524, 526, 528, 530

File: src/interfaces/IArbitrumBranchPort.sol

45:      error UnknownGlobalToken();

46:      error UnknownUnderlyingToken();

GitHub: 45, 46

File: src/interfaces/IBranchBridgeAgent.sol

339:     error UnknownFlag();

340:     error ExecutionFailure();

342:     error LayerZeroUnauthorizedCaller();

343:     error LayerZeroUnauthorizedEndpoint();

345:     error AlreadyExecutedTransaction();

347:     error InvalidInput();

348:     error InsufficientGas();

350:     error NotDepositOwner();

351:     error DepositRetryUnavailableUseCallout();

352:     error DepositRedeemUnavailable();

354:     error UnrecognizedRouter();

355:     error UnrecognizedBridgeAgentExecutor();

GitHub: 339, 340, 342, 343, 345, 347, 348, 350, 351, 352, 354, 355

File: src/interfaces/IBranchPort.sol

245:     error AlreadyAddedBridgeAgent();

246:     error AlreadyAddedBridgeAgentFactory();

247:     error InvalidMinimumReservesRatio();

248:     error InvalidInputArrays();

249:     error InsufficientReserves();

250:     error UnrecognizedCore();

251:     error UnrecognizedBridgeAgent();

252:     error UnrecognizedBridgeAgentFactory();

253:     error UnrecognizedPortStrategy();

254:     error UnrecognizedStrategyToken();

255:     error NotEnoughDebtToRepay();

GitHub: 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255

File: src/interfaces/IBranchRouter.sol

113:     error UnrecognizedFunctionId();

115:     error UnrecognizedBridgeAgentExecutor();

GitHub: 113, 115

File: src/interfaces/ICoreBranchRouter.sol

53:      error UnrecognizedBridgeAgent();

54:      error UnrecognizedBridgeAgentFactory();

GitHub: 53, 54

File: src/interfaces/IERC20hTokenBranchFactory.sol

32:      error UnrecognizedCoreRouter();

34:      error UnrecognizedPort();

GitHub: 32, 34

File: src/interfaces/IERC20hTokenRoot.sol

57:      error UnrecognizedPort();

GitHub: 57

File: src/interfaces/IERC20hTokenRootFactory.sol

32:      error UnrecognizedCoreRouterOrPort();

GitHub: 32

File: src/interfaces/IPortStrategy.sol

29:      error UnrecognizedPort();

GitHub: 29

File: src/interfaces/IRootBridgeAgent.sol

344:     error ExecutionFailure();

345:     error GasErrorOrRepeatedTx();

346:     error AlreadyExecutedTransaction();

347:     error UnknownFlag();

349:     error NotDao();

351:     error LayerZeroUnauthorizedEndpoint();

352:     error LayerZeroUnauthorizedCaller();

354:     error AlreadyAddedBridgeAgent();

355:     error UnrecognizedExecutor();

356:     error UnrecognizedPort();

357:     error UnrecognizedBridgeAgent();

358:     error UnrecognizedLocalBridgeAgent();

359:     error UnrecognizedBridgeAgentManager();

360:     error UnrecognizedRouter();

362:     error UnrecognizedUnderlyingAddress();

363:     error UnrecognizedLocalAddress();

365:     error SettlementRetryUnavailable();

366:     error SettlementRetryUnavailableUseCallout();

367:     error SettlementRedeemUnavailable();

368:     error SettlementRetrieveUnavailable();

369:     error NotSettlementOwner();

371:     error InsufficientBalanceForSettlement();

372:     error InsufficientGasForFees();

373:     error InvalidInputParams();

374:     error InvalidInputParamsLength();

376:     error CallerIsNotPool();

377:     error AmountsAreZero();

GitHub: 344, 345, 346, 347, 349, 351, 352, 354, 355, 356, 357, 358, 359, 360, 362, 363, 365, 366, 367, 368, 369, 371, 372, 373, 374, 376, 377

File: src/interfaces/IRootPort.sol

399:     error InvalidGlobalAddress();

400:     error InvalidLocalAddress();

401:     error InvalidUnderlyingAddress();

403:     error InvalidUserAddress();

405:     error InvalidCoreRootRouter();

406:     error InvalidCoreRootBridgeAgent();

407:     error InvalidCoreBranchRouter();

408:     error InvalidCoreBrancBridgeAgent();

410:     error UnrecognizedBridgeAgentFactory();

411:     error UnrecognizedBridgeAgent();

413:     error UnrecognizedToken();

414:     error UnableToMint();

416:     error AlreadyAddedChain();

417:     error AlreadyAddedEcosystemToken();

419:     error AlreadyAddedBridgeAgent();

420:     error AlreadyAddedBridgeAgentFactory();

421:     error BridgeAgentNotAllowed();

422:     error UnrecognizedCoreRootRouter();

423:     error UnrecognizedLocalBranchPort();

GitHub: 399, 400, 401, 403, 405, 406, 407, 408, 410, 411, 413, 414, 416, 417, 419, 420, 421, 422, 423

File: src/interfaces/IRootRouter.sol

97:      error UnrecognizedFunctionId();

98:      error UnrecognizedBridgeAgentExecutor();

GitHub: 97, 98

File: src/interfaces/IVirtualAccount.sol

79:      error CallFailed();

81:      error UnauthorizedCaller();

GitHub: 79, 81

[N‑57] NatSpec: Contract declarations should have @author tags

There are 9 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

10:  library DeployArbitrumBranchBridgeAgent {

GitHub: 10

File: src/BranchBridgeAgent.sol

23:  library DeployBranchBridgeAgent {

GitHub: 23

File: src/BranchBridgeAgentExecutor.sol

15:  library DeployBranchBridgeAgentExecutor {

GitHub: 15

File: src/RootBridgeAgentExecutor.sol

13:  library DeployRootBridgeAgentExecutor {

GitHub: 13

File: src/VirtualAccount.sol

17:  contract VirtualAccount is IVirtualAccount, ERC1155Receiver {

GitHub: 17

File: src/interfaces/ILayerZeroEndpoint.sol

7    interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
8        // @notice send a LayerZero message to the specified address at a LayerZero endpoint.
9        // @param _dstChainId - the destination chain identifier
10       // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
11       // @param _payload - a custom bytes payload to send to the destination contract
12       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
13       // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
14:      // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

GitHub: 7

File: src/interfaces/ILayerZeroReceiver.sol

5    interface ILayerZeroReceiver {
6        // @notice LayerZero endpoint will invoke this function to deliver the message on the destination
7        // @param _srcChainId - the source endpoint identifier
8        // @param _srcAddress - the source sending contract address from the source chain
9        // @param _nonce - the ordered message nonce
10:      // @param _payload - the signed payload is the UA bytes has encoded to be sent

GitHub: 5

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

5    interface ILayerZeroUserApplicationConfig {
6        // @notice set the configuration of the LayerZero messaging library of the specified version
7        // @param _version - messaging library version
8        // @param _chainId - the chainId for the pending config change
9        // @param _configType - type of configuration. every messaging library has its own convention.
10:      // @param _config - configuration in the bytes. can encode arbitrary content.

GitHub: 5

File: src/interfaces/IRootPort.sol

10:  interface ICoreRootRouter {

GitHub: 10

[N‑58] NatSpec: Contract declarations should have @dev tags

@dev is used to explain extra details to developers

There are 34 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

10:  library DeployArbitrumBranchBridgeAgent {

31   contract ArbitrumBranchBridgeAgent is BranchBridgeAgent {
32       /*///////////////////////////////////////////////////////////////
33                               CONSTRUCTOR
34       //////////////////////////////////////////////////////////////*/
35   
36       /**
37        * @notice Constructor for Arbitrum Branch Bridge Agent.
38        *  @param _localChainId Local Chain Layer Zero Id.
39        *  @param _rootBridgeAgentAddress Root Bridge Agent Address.
40        *  @param _localRouterAddress Local Core Branch Router Address.
41        *  @param _localPortAddress Local Branch Port Address.
42:       */

GitHub: 10, 31

File: src/ArbitrumBranchPort.sol

14:  contract ArbitrumBranchPort is BranchPort, IArbitrumBranchPort {

GitHub: 14

File: src/BaseBranchRouter.sol

25:  contract BaseBranchRouter is IBranchRouter, Ownable {

GitHub: 25

File: src/BranchBridgeAgent.sol

23:  library DeployBranchBridgeAgent {

45:  contract BranchBridgeAgent is IBranchBridgeAgent, BridgeAgentConstants {

GitHub: 23, 45

File: src/BranchBridgeAgentExecutor.sol

15:  library DeployBranchBridgeAgentExecutor {

GitHub: 15

File: src/BranchPort.sol

17:  contract BranchPort is Ownable, IBranchPort {

GitHub: 17

File: src/CoreBranchRouter.sol

18   contract CoreBranchRouter is ICoreBranchRouter, BaseBranchRouter {
19:      /// @notice hToken Factory Address.

GitHub: 18

File: src/RootBridgeAgent.sol

32:  contract RootBridgeAgent is IRootBridgeAgent, BridgeAgentConstants {

GitHub: 32

File: src/RootBridgeAgentExecutor.sol

13:  library DeployRootBridgeAgentExecutor {

GitHub: 13

File: src/RootPort.sol

16:  contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/VirtualAccount.sol

17:  contract VirtualAccount is IVirtualAccount, ERC1155Receiver {

GitHub: 17

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

17   contract ArbitrumBranchBridgeAgentFactory is BranchBridgeAgentFactory {
18       /*///////////////////////////////////////////////////////////////
19                                CONSTRUCTOR
20       //////////////////////////////////////////////////////////////*/
21   
22       /**
23        * @notice Constructor for Bridge Agent Factory Contract.
24        *  @param _rootChainId Root Chain Layer Zero Id.
25        *  @param _rootBridgeAgentFactoryAddress Root Bridge Agent Factory Address.
26        *  @param _localCoreBranchRouterAddress Local Core Branch Router Address.
27        *  @param _localPortAddress Local Branch Port Address.
28        *  @param _owner Owner of the contract.
29:       */

GitHub: 17

File: src/factories/BranchBridgeAgentFactory.sol

19   contract BranchBridgeAgentFactory is Ownable, IBranchBridgeAgentFactory {
20:      /// @notice Local Chain Id.

GitHub: 19

File: src/factories/ERC20hTokenBranchFactory.sol

12   contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

12   contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/RootBridgeAgentFactory.sol

11   contract RootBridgeAgentFactory is IRootBridgeAgentFactory {
12:      /// @notice Root Chain Id

GitHub: 11

File: src/interfaces/IArbitrumBranchPort.sol

16   interface IArbitrumBranchPort is IBranchPort {
17       /*///////////////////////////////////////////////////////////////
18                           EXTERNAL FUNCTIONS
19       //////////////////////////////////////////////////////////////*/
20   
21       /**
22        * @notice Function to deposit underlying/native token amount into Port in exchange for Local hToken.
23        *     @param _depositor underlying/native token depositor.
24        *     @param _recipient hToken receiver.
25        *     @param _underlyingAddress underlying/native token address.
26        *     @param _amount amount of tokens.
27:       */

GitHub: 16

File: src/interfaces/IBranchBridgeAgentFactory.sol

12   interface IBranchBridgeAgentFactory {
13       /*///////////////////////////////////////////////////////////////
14                           BRIDGE AGENT FUNCTIONS
15       //////////////////////////////////////////////////////////////*/
16   
17       /**
18        * @notice Creates a new Branch Bridge Agent.
19        * @param newRootRouterAddress New Root Router Address.
20        * @param rootBridgeAgentAddress Root Bridge Agent Address.
21        * @param _rootBridgeAgentFactoryAddress Root Bridge Agent Factory Address.
22        * @return newBridgeAgent New Bridge Agent Address.
23:       */

GitHub: 12

File: src/interfaces/IBranchPort.sol

14   interface IBranchPort {
15       /*///////////////////////////////////////////////////////////////
16                               VIEW FUNCTIONS
17       //////////////////////////////////////////////////////////////*/
18       /**
19        * @notice Returns true if the address is a Bridge Agent.
20        *   @param _bridgeAgent Bridge Agent address.
21        *   @return bool.
22:       */

GitHub: 14

File: src/interfaces/IBranchRouter.sol

21   interface IBranchRouter {
22       /*///////////////////////////////////////////////////////////////
23                               VIEW / STATE
24       //////////////////////////////////////////////////////////////*/
25   
26:      /// @notice External function to return the Branch Chain's Local Port Address.

GitHub: 21

File: src/interfaces/IERC20hTokenBranch.sol

11   interface IERC20hTokenBranch {
12       /*///////////////////////////////////////////////////////////////
13                           ERC20 LOGIC
14       //////////////////////////////////////////////////////////////*/
15   
16       /**
17        * @notice Function to mint tokens in the Branch Chain.
18        * @param account Address of the account to receive the tokens.
19        * @param amount Amount of tokens to be minted.
20        * @return Boolean indicating if the operation was successful.
21:       */

GitHub: 11

File: src/interfaces/ILayerZeroEndpoint.sol

7    interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
8        // @notice send a LayerZero message to the specified address at a LayerZero endpoint.
9        // @param _dstChainId - the destination chain identifier
10       // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
11       // @param _payload - a custom bytes payload to send to the destination contract
12       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
13       // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
14:      // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

GitHub: 7

File: src/interfaces/ILayerZeroReceiver.sol

5    interface ILayerZeroReceiver {
6        // @notice LayerZero endpoint will invoke this function to deliver the message on the destination
7        // @param _srcChainId - the source endpoint identifier
8        // @param _srcAddress - the source sending contract address from the source chain
9        // @param _nonce - the ordered message nonce
10:      // @param _payload - the signed payload is the UA bytes has encoded to be sent

GitHub: 5

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

5    interface ILayerZeroUserApplicationConfig {
6        // @notice set the configuration of the LayerZero messaging library of the specified version
7        // @param _version - messaging library version
8        // @param _chainId - the chainId for the pending config change
9        // @param _configType - type of configuration. every messaging library has its own convention.
10:      // @param _config - configuration in the bytes. can encode arbitrary content.

GitHub: 5

File: src/interfaces/IMulticall2.sol

9:   interface IMulticall2 {

GitHub: 9

File: src/interfaces/IPortStrategy.sol

12   interface IPortStrategy {
13       /*///////////////////////////////////////////////////////////////
14                             TOKEN MANAGEMENT
15       //////////////////////////////////////////////////////////////*/
16   
17       /**
18        * @notice Function to withdraw underlying/native token amount back into Branch Port.
19        *   @param _recipient hToken receiver.
20        *   @param _token native token address.
21        *   @param _amount amount of tokens.
22:       */

GitHub: 12

File: src/interfaces/IRootBridgeAgentFactory.sol

11   interface IRootBridgeAgentFactory {
12       /*///////////////////////////////////////////////////////////////
13                           BRIDGE AGENT FUNCTIONS
14       //////////////////////////////////////////////////////////////*/
15:  

GitHub: 11

File: src/interfaces/IRootPort.sol

10:  interface ICoreRootRouter {

31   interface IRootPort {
32       /*///////////////////////////////////////////////////////////////
33                           VIEW FUNCTIONS
34       //////////////////////////////////////////////////////////////*/
35       /**
36        * @notice View Function returns True if the chain Id has been added to the system.
37        *  @param _chainId The Layer Zero chainId of the chain.
38        * @return bool True if the chain Id has been added to the system.
39:       */

GitHub: 10, 31

File: src/interfaces/IRootRouter.sol

14   interface IRootRouter {
15       /*///////////////////////////////////////////////////////////////
16                           LAYERZERO FUNCTIONS
17       ///////////////////////////////////////////////////////////////*/
18   
19       /**
20        *     @notice Function to execute Branch Bridge Agent system initiated requests with no asset deposit.
21        *     @param params data received from messaging layer.
22        *     @param srcChainId chain where the request originated from.
23        *
24:       */

GitHub: 14

File: src/token/ERC20hTokenBranch.sol

12:  contract ERC20hTokenBranch is ERC20, Ownable, IERC20hTokenBranch {

GitHub: 12

File: src/token/ERC20hTokenRoot.sol

12   contract ERC20hTokenRoot is ERC20, Ownable, IERC20hTokenRoot {
13:      /// @inheritdoc IERC20hTokenRoot

GitHub: 12

[N‑59] NatSpec: Contract declarations should have @title tags

There are 4 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

10:  library DeployArbitrumBranchBridgeAgent {

GitHub: 10

File: src/interfaces/ILayerZeroEndpoint.sol

7    interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
8        // @notice send a LayerZero message to the specified address at a LayerZero endpoint.
9        // @param _dstChainId - the destination chain identifier
10       // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
11       // @param _payload - a custom bytes payload to send to the destination contract
12       // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
13       // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
14:      // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination

GitHub: 7

File: src/interfaces/ILayerZeroReceiver.sol

5    interface ILayerZeroReceiver {
6        // @notice LayerZero endpoint will invoke this function to deliver the message on the destination
7        // @param _srcChainId - the source endpoint identifier
8        // @param _srcAddress - the source sending contract address from the source chain
9        // @param _nonce - the ordered message nonce
10:      // @param _payload - the signed payload is the UA bytes has encoded to be sent

GitHub: 5

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

5    interface ILayerZeroUserApplicationConfig {
6        // @notice set the configuration of the LayerZero messaging library of the specified version
7        // @param _version - messaging library version
8        // @param _chainId - the chainId for the pending config change
9        // @param _configType - type of configuration. every messaging library has its own convention.
10:      // @param _config - configuration in the bytes. can encode arbitrary content.

GitHub: 5

[N‑60] Empty function body

Consider adding a comment about why the function body is empty

There is one instance of this issue:

File: src/MulticallRootRouterLibZip.sol

29       constructor(uint256 _localChainId, address _localPortAddress, address _multicallAddress)
30           MulticallRootRouter(_localChainId, _localPortAddress, _multicallAddress)
31:      {}

GitHub: 29

[N‑61] Use bytes.concat() on bytes instead of abi.encodePacked() for clearer semantic meaning

Starting with version 0.8.4, Solidity has the bytes.concat() function, which allows one to concatenate a list of bytes/strings, without extra padding. Using this function rather than abi.encodePacked() makes the intended operation more clear, leading to less reviewer confusion.

There are 14 instances of this issue:

File: src/ArbitrumCoreBranchRouter.sol

62:          bytes memory payload = abi.encodePacked(bytes1(0x02), params);

115:         bytes memory payload = abi.encodePacked(bytes1(0x04), data);

GitHub: 62, 115

File: src/BranchBridgeAgent.sol

474:         bytes memory payload = abi.encodePacked(_hasFallbackToggled ? bytes1(0x87) : bytes1(0x07), params);

GitHub: 474

File: src/CoreBranchRouter.sol

51:          bytes memory payload = abi.encodePacked(bytes1(0x01), params);

75:          bytes memory payload = abi.encodePacked(bytes1(0x02), params);

181:         bytes memory payload = abi.encodePacked(bytes1(0x03), params);

229:         bytes memory payload = abi.encodePacked(bytes1(0x04), params);

GitHub: 51, 75, 181, 229

File: src/CoreRootRouter.sol

136:         bytes memory payload = abi.encodePacked(bytes1(0x02), params);

171:         bytes memory payload = abi.encodePacked(bytes1(0x03), params);

196:         bytes memory payload = abi.encodePacked(bytes1(0x04), params);

223:         bytes memory payload = abi.encodePacked(bytes1(0x05), params);

254:         bytes memory payload = abi.encodePacked(bytes1(0x06), params);

284:         bytes memory payload = abi.encodePacked(bytes1(0x07), params);

434:         bytes memory payload = abi.encodePacked(bytes1(0x01), params);

GitHub: 136, 171, 196, 223, 254, 284, 434

[N‑62] if-statement can be converted to a ternary

The code can be made more compact while also increasing readability by converting the following if-statements to ternaries (e.g. foo += (x > y) ? a : b)

There are 2 instances of this issue:

File: src/BranchBridgeAgent.sol

384              if (_isSigned) {
385                  //Pack new Data
386                  payload = abi.encodePacked(
387                      _hasFallbackToggled ? bytes1(0x86) : bytes1(0x06),
388                      msg.sender,
389                      uint8(deposit.hTokens.length),
390                      _depositNonce,
391                      deposit.hTokens,
392                      deposit.tokens,
393                      deposit.amounts,
394                      deposit.deposits,
395                      _params
396                  );
397              } else {
398                  payload = abi.encodePacked(
399                      bytes1(0x03),
400                      uint8(deposit.hTokens.length),
401                      _depositNonce,
402                      deposit.hTokens,
403                      deposit.tokens,
404                      deposit.amounts,
405                      deposit.deposits,
406                      _params
407                  );
408:             }

360              if (_isSigned) {
361                  //Pack new Data
362                  payload = abi.encodePacked(
363                      _hasFallbackToggled ? bytes1(0x85) : bytes1(0x05),
364                      msg.sender,
365                      _depositNonce,
366                      deposit.hTokens[0],
367                      deposit.tokens[0],
368                      deposit.amounts[0],
369                      deposit.deposits[0],
370                      _params
371                  );
372              } else {
373                  payload = abi.encodePacked(
374                      bytes1(0x02),
375                      _depositNonce,
376                      deposit.hTokens[0],
377                      deposit.tokens[0],
378                      deposit.amounts[0],
379                      deposit.deposits[0],
380                      _params
381                  );
382:             }

GitHub: 384, 360

[N‑63] Overflows in unchecked blocks

While integers with a large number of bits are unlikely to overflow on human time scales, it is not strictly correct to use an unchecked block around them, because eventually they will overflow, and unchecked blocks are meant for cases where it's mathematically impossible for an operation to trigger an overflow (e.g. a prior require() statement prevents the overflow case)

There are 15 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

196:                 ++i;

GitHub: 196

File: src/BranchBridgeAgent.sol

451:                 ++i;

564:                 ++i;

GitHub: 451, 564

File: src/BranchPort.sol

271:                 ++i;

309:                 i++;

GitHub: 271, 309

File: src/MulticallRootRouter.sol

282:                     ++i;

371:                     ++i;

459:                     ++i;

561:                 ++i;

GitHub: 282, 371, 459, 561

File: src/RootBridgeAgent.sol

338:                 ++i;

413:                 ++i;

1084:                ++i;

GitHub: 338, 413, 1084

File: src/RootBridgeAgentExecutor.sol

333:                 ++i;

GitHub: 333

File: src/VirtualAccount.sol

79:                  ++i;

106:                 ++i;

GitHub: 79, 106

[N‑64] Consider splitting long calculations

The longer a string of operations is, the harder it is to understand it. Consider splitting the full calculation into more steps, with more descriptive temporary variable names, and add extensive comments.

There are 8 instances of this issue:

File: src/BranchBridgeAgent.sol

518              _hTokens[i] = address(
519                  uint160(
520                      bytes20(
521                          bytes32(
522                              _sParams[
523                                  PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET:
524                                      PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset)
525                              ]
526                          )
527                      )
528                  )
529:             );

531              _tokens[i] = address(
532                  uint160(
533                      bytes20(
534                          bytes32(
535                              _sParams[
536                                  PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (i + numOfAssets) + ADDRESS_END_OFFSET:
537                                      PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (currentIterationOffset + numOfAssets)
538                              ]
539                          )
540                      )
541                  )
542:             );

544              _amounts[i] = uint256(
545                  bytes32(
546                      _sParams[
547                          PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * i:
548                              PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * currentIterationOffset
549                      ]
550                  )
551:             );

553              _deposits[i] = uint256(
554                  bytes32(
555                      _sParams[
556                          PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * i:
557                              PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets
558                                  + PARAMS_ENTRY_SIZE * currentIterationOffset
559                      ]
560                  )
561:             );

GitHub: 518, 531, 544, 553

File: src/RootBridgeAgentExecutor.sol

286              hTokens[i] = address(
287                  uint160(
288                      bytes20(
289                          bytes32(
290                              _dParams[
291                                  PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET:
292                                      PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset)
293                              ]
294                          )
295                      )
296                  )
297:             );

299              tokens[i] = address(
300                  uint160(
301                      bytes20(
302                          bytes32(
303                              _dParams[
304                                  PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (i + numOfAssets) + ADDRESS_END_OFFSET:
305                                      PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (currentIterationOffset + numOfAssets)
306                              ]
307                          )
308                      )
309                  )
310:             );

312              amounts[i] = uint256(
313                  bytes32(
314                      _dParams[
315                          PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + (PARAMS_ENTRY_SIZE * i):
316                              PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets
317                                  + (PARAMS_ENTRY_SIZE * currentIterationOffset)
318                      ]
319                  )
320:             );

322              deposits[i] = uint256(
323                  bytes32(
324                      _dParams[
325                          PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets + (PARAMS_ENTRY_SIZE * i):
326                              PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets
327                                  + PARAMS_ENTRY_SIZE * currentIterationOffset
328                      ]
329                  )
330:             );

GitHub: 286, 299, 312, 322

[N‑65] Contract should expose an interface

The contracts should expose an interface so that other projects can more easily integrate with it, without having to develop their own non-standard variants.

There are 47 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

69:      function depositToPort(address underlyingAddress, uint256 amount) external payable lock {

79:      function withdrawFromPort(address localAddress, uint256 amount) external payable lock {

GitHub: 69, 79

File: src/BaseBranchRouter.sol

60:      function initialize(address _localBridgeAgentAddress) external onlyOwner {

GitHub: 60

File: src/BranchBridgeAgent.sol

149:     receive() external payable {}

161      function getFeeEstimate(uint256 _gasLimit, uint256 _remoteBranchExecutionGas, bytes calldata _payload)
162          external
163          view
164          returns (uint256 _fee)
165:     {

GitHub: 149, 161

File: src/BranchBridgeAgentExecutor.sol

53:      function executeNoSettlement(address _router, bytes calldata _payload) external payable onlyOwner {

66       function executeWithSettlement(address _recipient, address _router, bytes calldata _payload)
67           external
68           payable
69           onlyOwner
70:      {

104      function executeWithSettlementMultiple(address _recipient, address _router, bytes calldata _payload)
105          external
106          payable
107          onlyOwner
108:     {

GitHub: 53, 66, 104

File: src/BranchPort.sol

122:     function initialize(address _coreBranchRouter, address _bridgeAgentFactory) external virtual onlyOwner {

GitHub: 122

File: src/CoreBranchRouter.sol

43       function addGlobalToken(address _globalAddress, uint256 _dstChainId, GasParams[3] calldata _gParams)
44           external
45           payable
46:      {

62:      function addLocalToken(address _underlyingAddress, GasParams calldata _gParams) external payable virtual {

GitHub: 43, 62

File: src/CoreRootRouter.sol

83:      function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

103      function addBranchToBridgeAgent(
104          address _rootBridgeAgent,
105          address _branchBridgeAgentFactory,
106          address _newBranchRouter,
107          address _refundee,
108          uint16 _dstChainId,
109          GasParams[2] calldata _gParams
110:     ) external payable {

156      function toggleBranchBridgeAgentFactory(
157          address _rootBridgeAgentFactory,
158          address _branchBridgeAgentFactory,
159          address _refundee,
160          uint16 _dstChainId,
161          GasParams calldata _gParams
162:     ) external payable onlyOwner {

186      function removeBranchBridgeAgent(
187          address _branchBridgeAgent,
188          address _refundee,
189          uint16 _dstChainId,
190          GasParams calldata _gParams
191:     ) external payable onlyOwner {

212      function manageStrategyToken(
213          address _underlyingToken,
214          uint256 _minimumReservesRatio,
215          address _refundee,
216          uint16 _dstChainId,
217          GasParams calldata _gParams
218:     ) external payable onlyOwner {

241      function managePortStrategy(
242          address _portStrategy,
243          address _underlyingToken,
244          uint256 _dailyManagementLimit,
245          bool _isUpdateDailyLimit,
246          address _refundee,
247          uint16 _dstChainId,
248          GasParams calldata _gParams
249:     ) external payable onlyOwner {

270      function setCoreBranch(
271          address _refundee,
272          address _coreBranchRouter,
273          address _coreBranchBridgeAgent,
274          uint16 _dstChainId,
275          GasParams calldata _gParams
276:     ) external payable {

GitHub: 83, 103, 156, 186, 212, 241, 270

File: src/MulticallRootRouter.sol

109:     function initialize(address _bridgeAgentAddress) external onlyOwner {

209:     function executeDepositMultiple(bytes calldata, DepositMultipleParams calldata, uint16) external payable {

GitHub: 109, 209

File: src/RootBridgeAgent.sol

128:     receive() external payable {}

140      function getFeeEstimate(
141          uint256 _gasLimit,
142          uint256 _remoteBranchExecutionGas,
143          bytes calldata _payload,
144          uint16 _dstChainId
145:     ) external view returns (uint256 _fee) {

274:     function retrieveSettlement(uint32 _settlementNonce, GasParams calldata _gParams) external payable lock {

423:     function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public {

GitHub: 128, 140, 274, 423

File: src/RootBridgeAgentExecutor.sol

50       function executeSystemRequest(address _router, bytes calldata _payload, uint16 _srcChainId)
51           external
52           payable
53           onlyOwner
54:      {

66       function executeNoDeposit(address _router, bytes calldata _payload, uint16 _srcChainId)
67           external
68           payable
69           onlyOwner
70:      {

82       function executeWithDeposit(address _router, bytes calldata _payload, uint16 _srcChainId)
83           external
84           payable
85           onlyOwner
86:      {

115      function executeWithDepositMultiple(address _router, bytes calldata _payload, uint16 _srcChainId)
116          external
117          payable
118          onlyOwner
119:     {

150      function executeSignedNoDeposit(address _account, address _router, bytes calldata _payload, uint16 _srcChainId)
151          external
152          payable
153          onlyOwner
154:     {

167      function executeSignedWithDeposit(address _account, address _router, bytes calldata _payload, uint16 _srcChainId)
168          external
169          payable
170          onlyOwner
171:     {

201      function executeSignedWithDepositMultiple(
202          address _account,
203          address _router,
204          bytes calldata _payload,
205          uint16 _srcChainId
206:     ) external payable onlyOwner {

GitHub: 50, 66, 82, 115, 150, 167, 201

File: src/RootPort.sol

129:     function initialize(address _bridgeAgentFactory, address _coreRootRouter) external onlyOwner {

147      function initializeCore(
148          address _coreRootBridgeAgent,
149          address _coreLocalBranchBridgeAgent,
150          address _localBranchPortAddress
151:     ) external onlyOwner {

221      function isLocalToken(address _localAddress, uint256 _srcChainId, uint256 _dstChainId)
222          external
223          view
224          returns (bool)
225:     {

GitHub: 129, 147, 221

File: src/VirtualAccount.sol

44:      receive() external payable {}

85:      function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {

GitHub: 44, 85

File: src/factories/BranchBridgeAgentFactory.sol

87:      function initialize(address _coreRootBridgeAgent) external virtual onlyOwner {

115      function createBridgeAgent(
116          address _newBranchRouterAddress,
117          address _rootBridgeAgentAddress,
118          address _rootBridgeAgentFactoryAddress
119:     ) external virtual returns (address newBridgeAgent) {

GitHub: 87, 115

File: src/factories/ERC20hTokenBranchFactory.sol

60:      function initialize(address _wrappedNativeTokenAddress, address _coreRouter) external onlyOwner {

87:      function getHTokens() external view returns (ERC20hTokenBranch[] memory) {

96       function createToken(string memory _name, string memory _symbol, uint8 _decimals, bool _addPrefix)
97           external
98           requiresCoreRouter
99           returns (ERC20hTokenBranch newToken)
100:     {

GitHub: 60, 87, 96

File: src/factories/ERC20hTokenRootFactory.sol

49:      function initialize(address _coreRouter) external onlyOwner {

63:      function getHTokens() external view returns (ERC20hTokenRoot[] memory) {

76       function createToken(string memory _name, string memory _symbol, uint8 _decimals)
77           external
78           requiresCoreRouterOrPort
79           returns (ERC20hTokenRoot newToken)
80:      {

GitHub: 49, 63, 76

File: src/factories/RootBridgeAgentFactory.sol

48:      function createBridgeAgent(address _newRootRouterAddress) external returns (address newBridgeAgent) {

GitHub: 48

File: src/token/ERC20hTokenRoot.sol

57:      function mint(address to, uint256 amount, uint256 chainId) external onlyOwner returns (bool) {

69:      function burn(address from, uint256 amount, uint256 chainId) external onlyOwner {

GitHub: 57, 69

[N‑66] else-block not required

One level of nesting can be removed by not having an else block when the if-block returns, and if (foo) { return 1; } else { return 2; } becomes if (foo) { return 1; } return 2;. A following else if can become if

There are 2 instances of this issue:

File: src/BranchBridgeAgent.sol

671              if (executionState[nonce] == STATUS_DONE) {
672                  revert AlreadyExecutedTransaction();
673              } else {
674                  //Set settlement to retrieve mode, if not already set.
675                  if (executionState[nonce] == STATUS_READY) executionState[nonce] = STATUS_RETRIEVE;
676                  //Trigger fallback/Retry failed fallback
677                  _performFallbackCall(recipient, nonce);
678:             }

GitHub: 671

File: src/RootBridgeAgent.sol

704              if (executionState[_srcChainId][nonce] == STATUS_DONE) {
705                  revert AlreadyExecutedTransaction();
706              } else {
707                  //Set settlement to retrieve mode, if not already set.
708                  if (executionState[_srcChainId][nonce] == STATUS_READY) {
709                      executionState[_srcChainId][nonce] = STATUS_RETRIEVE;
710                  }
711                  //Trigger fallback/Retry failed fallback
712                  _performFallbackCall(
713                      payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))), nonce, _srcChainId
714                  );
715:             }

GitHub: 704

[N‑67] Missing event and or timelock for critical parameter change

Events help non-contract tools to track changes, and timelocks prevent users from being surprised by changes

There is one instance of this issue:

File: src/BranchPort.sol

331      function setCoreRouter(address _newCoreRouter) external override requiresCoreRouter {
332          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");
333          require(_newCoreRouter != address(0), "New CoreRouter address is zero");
334          coreBranchRouterAddress = _newCoreRouter;
335:     }

GitHub: 331

[N‑68] Setters should prevent re-setting of the same value

This especially problematic when the setter also emits the same value, which may be confusing to offline parsers

There are 11 instances of this issue:

File: src/BranchPort.sol

331      function setCoreRouter(address _newCoreRouter) external override requiresCoreRouter {
332          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");
333          require(_newCoreRouter != address(0), "New CoreRouter address is zero");
334          coreBranchRouterAddress = _newCoreRouter;
335:     }

362      function addStrategyToken(address _token, uint256 _minimumReservesRatio) external override requiresCoreRouter {
363          if (_minimumReservesRatio >= DIVISIONER || _minimumReservesRatio < MIN_RESERVE_RATIO) {
364              revert InvalidMinimumReservesRatio();
365          }
366  
367          strategyTokens.push(_token);
368          getMinimumTokenReserveRatio[_token] = _minimumReservesRatio;
369          isStrategyToken[_token] = true;
370  
371          emit StrategyTokenAdded(_token, _minimumReservesRatio);
372:     }

382      function addPortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
383          external
384          override
385          requiresCoreRouter
386      {
387          if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();
388          portStrategies.push(_portStrategy);
389          strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;
390          isPortStrategy[_portStrategy][_token] = true;
391  
392          emit PortStrategyAdded(_portStrategy, _token, _dailyManagementLimit);
393:     }

403      function updatePortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
404          external
405          override
406          requiresCoreRouter
407      {
408          strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;
409  
410          emit PortStrategyUpdated(_portStrategy, _token, _dailyManagementLimit);
411:     }

414      function setCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent)
415          external
416          override
417          requiresCoreRouter
418      {
419          coreBranchRouterAddress = _coreBranchRouter;
420          isBridgeAgent[_coreBranchBridgeAgent] = true;
421          bridgeAgents.push(_coreBranchBridgeAgent);
422  
423          emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);
424:     }

GitHub: 331, 362, 382, 403, 414

File: src/RootPort.sol

239      function setAddresses(
240          address _globalAddress,
241          address _localAddress,
242          address _underlyingAddress,
243          uint256 _srcChainId
244      ) external override requiresCoreRootRouter {
245          if (_globalAddress == address(0)) revert InvalidGlobalAddress();
246          if (_localAddress == address(0)) revert InvalidLocalAddress();
247          if (_underlyingAddress == address(0)) revert InvalidUnderlyingAddress();
248  
249          isGlobalAddress[_globalAddress] = true;
250          getGlobalTokenFromLocal[_localAddress][_srcChainId] = _globalAddress;
251          getLocalTokenFromGlobal[_globalAddress][_srcChainId] = _localAddress;
252          getLocalTokenFromUnderlying[_underlyingAddress][_srcChainId] = _localAddress;
253          getUnderlyingTokenFromLocal[_localAddress][_srcChainId] = _underlyingAddress;
254  
255          emit LocalTokenAdded(_underlyingAddress, _localAddress, _globalAddress, _srcChainId);
256:     }

259      function setLocalAddress(address _globalAddress, address _localAddress, uint256 _srcChainId)
260          external
261          override
262          requiresCoreRootRouter
263      {
264          if (_localAddress == address(0)) revert InvalidLocalAddress();
265  
266          getGlobalTokenFromLocal[_localAddress][_srcChainId] = _globalAddress;
267          getLocalTokenFromGlobal[_globalAddress][_srcChainId] = _localAddress;
268  
269          emit GlobalTokenAdded(_localAddress, _globalAddress, _srcChainId);
270:     }

382      function addBridgeAgent(address _manager, address _bridgeAgent) external override requiresBridgeAgentFactory {
383          if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent();
384  
385          bridgeAgents.push(_bridgeAgent);
386          getBridgeAgentManager[_bridgeAgent] = _manager;
387          isBridgeAgent[_bridgeAgent] = true;
388  
389          emit BridgeAgentAdded(_bridgeAgent, _manager);
390:     }

438      function addNewChain(
439          address _coreBranchBridgeAgentAddress,
440          uint256 _chainId,
441          string memory _wrappedGasTokenName,
442          string memory _wrappedGasTokenSymbol,
443          uint8 _wrappedGasTokenDecimals,
444          address _newLocalBranchWrappedNativeTokenAddress,
445          address _newUnderlyingBranchWrappedNativeTokenAddress
446      ) external override onlyOwner {
447          // Check if chain already added
448          if (isChainId[_chainId]) revert AlreadyAddedChain();
449  
450          // Create new global token for new chain's wrapped native token
451          address newGlobalToken = address(
452              IERC20hTokenRootFactory(ICoreRootRouter(coreRootRouterAddress).hTokenFactoryAddress()).createToken(
453                  _wrappedGasTokenName, _wrappedGasTokenSymbol, _wrappedGasTokenDecimals
454              )
455          );
456  
457          // Sync new branch bridge agent with root core bridge agent
458          IBridgeAgent(ICoreRootRouter(coreRootRouterAddress).bridgeAgentAddress()).syncBranchBridgeAgent(
459              _coreBranchBridgeAgentAddress, _chainId
460          );
461  
462          // Update State
463  
464          // 1. Add new chain to chainId mapping
465          isChainId[_chainId] = true;
466          // 2. Add new chain to global address mapping
467          isGlobalAddress[newGlobalToken] = true;
468          // 3. Add new branch local token to global token address mapping
469          getGlobalTokenFromLocal[_newLocalBranchWrappedNativeTokenAddress][_chainId] = newGlobalToken;
470          // 4. Add new global token to branch local token address mapping
471          getLocalTokenFromGlobal[newGlobalToken][_chainId] = _newLocalBranchWrappedNativeTokenAddress;
472          // 5. Add new branch underlying token to branch local token address mapping
473          getLocalTokenFromUnderlying[_newUnderlyingBranchWrappedNativeTokenAddress][_chainId] =
474              _newLocalBranchWrappedNativeTokenAddress;
475          // 6. Add new branch local token to branch underlying token address mapping
476          getUnderlyingTokenFromLocal[_newLocalBranchWrappedNativeTokenAddress][_chainId] =
477              _newUnderlyingBranchWrappedNativeTokenAddress;
478  
479          emit NewChainAdded(_chainId);
480:     }

483      function addEcosystemToken(address _ecoTokenGlobalAddress) external override onlyOwner {
484          // Check if token already added
485          if (isGlobalAddress[_ecoTokenGlobalAddress]) revert AlreadyAddedEcosystemToken();
486  
487          // Check if token is already a underlying token in current chain
488          if (getUnderlyingTokenFromLocal[_ecoTokenGlobalAddress][localChainId] != address(0)) {
489              revert AlreadyAddedEcosystemToken();
490          }
491  
492          // Check if token is already a local branch token in current chain
493          if (getLocalTokenFromUnderlying[_ecoTokenGlobalAddress][localChainId] != address(0)) {
494              revert AlreadyAddedEcosystemToken();
495          }
496  
497          // Update State
498          // 1. Add new global token to global address mapping
499          isGlobalAddress[_ecoTokenGlobalAddress] = true;
500          // 2. Add new branch local token address to global token mapping
501          getGlobalTokenFromLocal[_ecoTokenGlobalAddress][localChainId] = _ecoTokenGlobalAddress;
502          // 3. Add new global token to branch local token address mapping
503          getLocalTokenFromGlobal[_ecoTokenGlobalAddress][localChainId] = _ecoTokenGlobalAddress;
504  
505          emit EcosystemTokenAdded(_ecoTokenGlobalAddress);
506:     }

509      function setCoreRootRouter(address _coreRootRouter, address _coreRootBridgeAgent) external override onlyOwner {
510          if (_coreRootRouter == address(0)) revert InvalidCoreRootRouter();
511          if (_coreRootBridgeAgent == address(0)) revert InvalidCoreRootBridgeAgent();
512  
513          coreRootRouterAddress = _coreRootRouter;
514          coreRootBridgeAgentAddress = _coreRootBridgeAgent;
515          getBridgeAgentManager[_coreRootBridgeAgent] = owner();
516  
517          emit CoreRootSet(_coreRootRouter, _coreRootBridgeAgent);
518:     }

GitHub: 239, 259, 382, 438, 483, 509

[N‑69] Polymorphic functions make security audits more time-consuming and error-prone

The instances below point to one of two functions with the same name. Consider naming each function differently, in order to make code navigation and analysis easier.

There are 6 instances of this issue:

File: src/BranchBridgeAgent.sol

730      function _execute(bool _hasFallbackToggled, uint32 _settlementNonce, address _refundee, bytes memory _calldata)
731          private
732:     {

GitHub: 730

File: src/BranchPort.sol

188:     function replenishReserves(address _strategy, address _token) external override lock {

GitHub: 188

File: src/RootBridgeAgent.sol

768      function _execute(
769          bool _hasFallbackToggled,
770          uint32 _depositNonce,
771          address _refundee,
772          bytes memory _calldata,
773          uint16 _srcChainId
774:     ) private {

GitHub: 768

File: src/RootPort.sol

221      function isLocalToken(address _localAddress, uint256 _srcChainId, uint256 _dstChainId)
222          external
223          view
224          returns (bool)
225:     {

GitHub: 221

File: src/interfaces/IBranchPort.sol

72:      function replenishReserves(address _strategy, address _token) external;

GitHub: 72

File: src/interfaces/IRootPort.sol

144      function isLocalToken(address _localAddress, uint256 _srcChainId, uint256 _dstChainId)
145          external
146          view
147:         returns (bool);

GitHub: 144

[N‑70] Imports could be organized more systematically

The contract's interface should be imported first, followed by each of the interfaces it uses, followed by all other files. The examples below do not follow this layout.

There are 4 instances of this issue:

File: src/BranchBridgeAgent.sol

9    import {
10       Deposit,
11       DepositInput,
12       DepositMultipleInput,
13       GasParams,
14       IBranchBridgeAgent,
15       ILayerZeroReceiver,
16       SettlementMultipleParams
17:  } from "./interfaces/IBranchBridgeAgent.sol";

GitHub: 9

File: src/BranchBridgeAgentExecutor.sol

12:  import {SettlementParams, SettlementMultipleParams} from "./interfaces/IBranchBridgeAgent.sol";

GitHub: 12

File: src/RootBridgeAgent.sol

14   import {
15       GasParams,
16       DepositParams,
17       DepositMultipleParams,
18       ILayerZeroReceiver,
19       IRootBridgeAgent,
20       Settlement,
21       SettlementInput,
22       SettlementMultipleInput
23:  } from "./interfaces/IRootBridgeAgent.sol";

GitHub: 14

File: src/VirtualAccount.sol

10:  import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";

GitHub: 10

[N‑71] Use the latest solidity (prior to 0.8.20 if on L2s) for deployment

When deploying contracts, you should use the latest released version of Solidity. Apart from exceptional cases, only the latest version receives security fixes.

https://docs.soliditylang.org/en/v0.8.20/

Since deployed contracts should not use floating pragmas, I've flagged all instances where a version prior to 0.8.19 is allowed by the version pragma

There are 23 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/ArbitrumBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/ArbitrumCoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BaseBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/CoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/CoreRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouterLibZip.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/VirtualAccount.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/BranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenBranchFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenRootFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/RootBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/BridgeAgentConstants.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenBranch.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenRoot.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

[N‑72] Consider moving msg.sender checks to a common authorization modifier

There are 7 instances of this issue:

File: src/CoreRootRouter.sol

112          if (msg.sender != IPort(rootPortAddress).getBridgeAgentManager(_rootBridgeAgent)) {
113              revert UnauthorizedCallerNotManager();
114:         }

278:         require(msg.sender == rootPortAddress, "Only root port can call");

GitHub: 112, 278

File: src/RootBridgeAgent.sol

248              if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementReference.owner))) {
249                  revert NotSettlementOwner();
250:             }

286              if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner))) {
287                  revert NotSettlementOwner();
288:             }

312              if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner))) {
313                  revert NotSettlementOwner();
314:             }

GitHub: 248, 286, 312

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

84           require(
85               msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
86:          );

GitHub: 84

File: src/factories/BranchBridgeAgentFactory.sol

120          require(
121              msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
122:         );

GitHub: 120

[N‑73] Non-library/interface files should use fixed compiler versions, not floating ones

Note that some file names may indicate an interface, but actually contain abstract contracts

There are 23 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/ArbitrumBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/ArbitrumCoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BaseBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/CoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/CoreRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouterLibZip.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/VirtualAccount.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/BranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenBranchFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenRootFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/RootBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/BridgeAgentConstants.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenBranch.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenRoot.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

[N‑74] Consider adding emergency-stop functionality

Adding a way to quickly halt protocol functionality in an emergency, rather than having to pause individual contracts one-by-one, will make in-progress hack mitigation faster and much less stressful.

There are 13 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

25:  contract BaseBranchRouter is IBranchRouter, Ownable {

GitHub: 25

File: src/BranchBridgeAgentExecutor.sol

29:  contract BranchBridgeAgentExecutor is Ownable, BridgeAgentConstants {

GitHub: 29

File: src/BranchPort.sol

17:  contract BranchPort is Ownable, IBranchPort {

GitHub: 17

File: src/CoreRootRouter.sol

38   contract CoreRootRouter is IRootRouter, Ownable {
39       /*///////////////////////////////////////////////////////////////
40                       CORE ROOT ROUTER STATE
41       //////////////////////////////////////////////////////////////*/
42   
43:      /// @notice Boolean to indicate if the contract is in set up mode.

GitHub: 38

File: src/MulticallRootRouter.sol

57:  contract MulticallRootRouter is IRootRouter, Ownable {

GitHub: 57

File: src/RootBridgeAgentExecutor.sol

27   contract RootBridgeAgentExecutor is Ownable, BridgeAgentConstants {
28       /*///////////////////////////////////////////////////////////////
29                                   CONSTRUCTOR
30       //////////////////////////////////////////////////////////////*/
31       /**
32        * @notice Constructor for Root Bridge Agent Executor.
33        * @param _rootBridgeAgent the owner of the contract in charge of calling the different execution functions.
34:       */

GitHub: 27

File: src/RootPort.sol

16:  contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

17   contract ArbitrumBranchBridgeAgentFactory is BranchBridgeAgentFactory {
18       /*///////////////////////////////////////////////////////////////
19                                CONSTRUCTOR
20       //////////////////////////////////////////////////////////////*/
21   
22       /**
23        * @notice Constructor for Bridge Agent Factory Contract.
24        *  @param _rootChainId Root Chain Layer Zero Id.
25        *  @param _rootBridgeAgentFactoryAddress Root Bridge Agent Factory Address.
26        *  @param _localCoreBranchRouterAddress Local Core Branch Router Address.
27        *  @param _localPortAddress Local Branch Port Address.
28        *  @param _owner Owner of the contract.
29:       */

GitHub: 17

File: src/factories/BranchBridgeAgentFactory.sol

19   contract BranchBridgeAgentFactory is Ownable, IBranchBridgeAgentFactory {
20:      /// @notice Local Chain Id.

GitHub: 19

File: src/factories/ERC20hTokenBranchFactory.sol

12   contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

12   contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/token/ERC20hTokenBranch.sol

12:  contract ERC20hTokenBranch is ERC20, Ownable, IERC20hTokenBranch {

GitHub: 12

File: src/token/ERC20hTokenRoot.sol

12   contract ERC20hTokenRoot is ERC20, Ownable, IERC20hTokenRoot {
13:      /// @inheritdoc IERC20hTokenRoot

GitHub: 12

[N‑75] NatSpec: Function @return is missing

There are 46 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

/// @audit Missing '@return '
11       function deploy(uint16 _localChainId, address _daoAddress, address _localRouterAddress, address _localPortAddress)
12           external
13:          returns (ArbitrumBranchBridgeAgent)

GitHub: 11

File: src/BranchBridgeAgent.sol

/// @audit Missing '@return '
24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27           address _rootBridgeAgentAddress,
28           address _lzEndpointAddress,
29           address _localRouterAddress,
30           address _localPortAddress
31:      ) external returns (BranchBridgeAgent) {

GitHub: 24

File: src/BranchBridgeAgentExecutor.sol

/// @audit Missing '@return '
16:      function deploy() external returns (address) {

GitHub: 16

File: src/MulticallRootRouter.sol

/// @audit Missing '@return blockNumber'
/// @audit Missing '@return returnData'
483      /**
484       *   @notice Function to perform a set of actions on the omnichain environment without using the user's Virtual Acccount.
485       *   @param calls to be executed.
486       *
487       */
488      function _multicall(IMulticall.Call[] memory calls)
489          internal
490:         returns (uint256 blockNumber, bytes[] memory returnData)

/// @audit Missing '@return '
581:     function _decode(bytes calldata data) internal pure virtual returns (bytes memory) {

GitHub: 483, 483, 581

File: src/MulticallRootRouterLibZip.sol

/// @audit Missing '@return '
37:      function _decode(bytes calldata data) internal pure override returns (bytes memory) {

GitHub: 37

File: src/RootBridgeAgent.sol

/// @audit Missing '@return _payload'
966      function _createSettlement(
967          uint32 _settlementNonce,
968          address payable _refundee,
969          address _recipient,
970          uint16 _dstChainId,
971          bytes memory _params,
972          address _globalAddress,
973          uint256 _amount,
974          uint256 _deposit,
975          bool _hasFallbackToggled
976:     ) internal returns (bytes memory _payload) {

/// @audit Missing '@return _payload'
1045     function _createSettlementMultiple(
1046         uint32 _settlementNonce,
1047         address payable _refundee,
1048         address _recipient,
1049         uint16 _dstChainId,
1050         address[] memory _globalAddresses,
1051         uint256[] memory _amounts,
1052         uint256[] memory _deposits,
1053         bytes memory _params,
1054         bool _hasFallbackToggled
1055:    ) internal returns (bytes memory _payload) {

GitHub: 966, 1045

File: src/RootBridgeAgentExecutor.sol

/// @audit Missing '@return '
14:      function deploy(address _rootBridgeAgent) external returns (address) {

/// @audit Missing '@return dParams'
260       *         4. N * 32 bytes for the amount of underlying tokens to be bridged in.
261       *     5. Each of the 4 token related arrays are of length N and start at the following indexes:
262       *         1. PARAMS_TKN_START [hToken address has no offset from token information start].
263       *         2. PARAMS_TKN_START + (PARAMS_ADDRESS_SIZE * N)
264       *         3. PARAMS_TKN_START + (PARAMS_AMT_OFFSET * N)
265       *         4. PARAMS_TKN_START + (PARAMS_DEPOSIT_OFFSET * N)
266       *
267       */
268      function _bridgeInMultiple(address _recipient, bytes calldata _dParams, uint16 _srcChainId)
269          internal
270:         returns (DepositMultipleParams memory dParams)

GitHub: 14, 260

File: src/RootPort.sol

/// @audit Missing '@return '
184      /**
185       * @notice View Function returns Local Token's Local Address on another chain.
186       * @param _localAddress The address of the token in the local chain.
187       * @param _srcChainId The chainId of the chain where the token is deployed.
188       * @param _dstChainId The chainId of the chain for which the token address is requested.
189       */
190      function _getLocalToken(address _localAddress, uint256 _srcChainId, uint256 _dstChainId)
191          internal
192          view
193:         returns (address)

/// @audit Missing '@return newAccount'
355      /**
356       * @notice Creates a new virtual account for a user.
357       * @param _user address of the user to associate a virtual account with.
358       */
359:     function addVirtualAccount(address _user) internal returns (VirtualAccount newAccount) {

GitHub: 184, 355

File: src/VirtualAccount.sol

/// @audit Missing '@return '
147:     function isContract(address addr) internal view returns (bool) {

GitHub: 147

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

/// @audit Missing '@return newBridgeAgent'
74       /**
75        * @notice Creates a new bridge agent for a branch chain.
76        * @param _newBranchRouterAddress Address of the new branch router.
77        * @param _rootBridgeAgentAddress Address of the root bridge agent to connect to.
78        */
79       function createBridgeAgent(
80           address _newBranchRouterAddress,
81           address _rootBridgeAgentAddress,
82           address _rootBridgeAgentFactoryAddress
83:      ) external virtual override returns (address newBridgeAgent) {

GitHub: 74

File: src/factories/BranchBridgeAgentFactory.sol

/// @audit Missing '@return newBridgeAgent'
110      /**
111       * @notice Creates a new bridge agent for a new branch chain.
112       * @param _newBranchRouterAddress Address of the new branch router.
113       * @param _rootBridgeAgentAddress Address of the root bridge agent to connect to.
114       */
115      function createBridgeAgent(
116          address _newBranchRouterAddress,
117          address _rootBridgeAgentAddress,
118          address _rootBridgeAgentFactoryAddress
119:     ) external virtual returns (address newBridgeAgent) {

GitHub: 110

File: src/factories/ERC20hTokenRootFactory.sol

/// @audit Missing '@return newToken'
70       /**
71        * @notice Function to create a new hToken.
72        * @param _name Name of the Token.
73        * @param _symbol Symbol of the Token.
74        * @param _decimals Decimals of the Token.
75        */
76       function createToken(string memory _name, string memory _symbol, uint8 _decimals)
77           external
78           requiresCoreRouterOrPort
79:          returns (ERC20hTokenRoot newToken)

GitHub: 70

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit Missing '@return '
104      /**
105       * @notice External function that returns a given deposit entry.
106       *    @param depositNonce Identifier for user deposit.
107       *
108       */
109:     function getDepositEntry(uint32 depositNonce) external view returns (Deposit memory);

/// @audit Missing '@return '
307      /**
308       * @notice Function to request balance clearance from a Port to a given address.
309       *     @param sParams encode packed multiple settlement info.
310       *
311       */
312      function clearTokens(bytes calldata sParams, address recipient)
313          external
314:         returns (SettlementMultipleParams memory);

GitHub: 104, 307

File: src/interfaces/IBranchRouter.sol

/// @audit Missing '@return '
26       /// @notice External function to return the Branch Chain's Local Port Address.
27:      function localPortAddress() external view returns (address);

/// @audit Missing '@return '
29       /// @notice Address for local Branch Bridge Agent who processes requests and interacts with local port.
30:      function localBridgeAgentAddress() external view returns (address);

/// @audit Missing '@return '
32       /// @notice Local Bridge Agent Executor Address.
33:      function bridgeAgentExecutorAddress() external view returns (address);

/// @audit Missing '@return '
74       /**
75        * @notice External function that returns a given deposit entry.
76        *     @param depositNonce Identifier for user deposit.
77        *
78        */
79:      function getDepositEntry(uint32 depositNonce) external view returns (Deposit memory);

GitHub: 26, 29, 32, 74

File: src/interfaces/IERC20hTokenBranchFactory.sol

/// @audit Missing '@return newToken'
17       /**
18        * @notice Function to create a new Branch hToken.
19        * @param _name Name of the Token.
20        * @param _symbol Symbol of the Token.
21        * @param _decimals Decimals of the Token.
22        * @param _addPrefix Boolean to add or not the chain prefix to the token name and symbol.
23        */
24       function createToken(string memory _name, string memory _symbol, uint8 _decimals, bool _addPrefix)
25           external
26:          returns (ERC20hTokenBranch newToken);

GitHub: 17

File: src/interfaces/IERC20hTokenRootFactory.sol

/// @audit Missing '@return newToken'
18       /**
19        * @notice Function to create a new hToken.
20        * @param _name Name of the Token.
21        * @param _symbol Symbol of the Token.
22        * @param _decimals Decimals of the Token.
23        */
24       function createToken(string memory _name, string memory _symbol, uint8 _decimals)
25           external
26:          returns (ERC20hTokenRoot newToken);

GitHub: 18

File: src/interfaces/ILayerZeroEndpoint.sol

/// @audit Missing '@return '
43:      function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64);

/// @audit Missing '@return '
47:      function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64);

/// @audit Missing '@return nativeFee'
/// @audit Missing '@return zroFee'
55       function estimateFees(
56           uint16 _dstChainId,
57           address _userApplication,
58           bytes calldata _payload,
59           bool _payInZRO,
60           bytes calldata _adapterParam
61:      ) external view returns (uint256 nativeFee, uint256 zroFee);

/// @audit Missing '@return '
64:      function getChainId() external view returns (uint16);

/// @audit Missing '@return '
75:      function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool);

/// @audit Missing '@return '
79:      function getSendLibraryAddress(address _userApplication) external view returns (address);

/// @audit Missing '@return '
83:      function getReceiveLibraryAddress(address _userApplication) external view returns (address);

/// @audit Missing '@return '
87:      function isSendingPayload() external view returns (bool);

/// @audit Missing '@return '
91:      function isReceivingPayload() external view returns (bool);

/// @audit Missing '@return '
98       function getConfig(uint16 _version, uint16 _chainId, address _userApplication, uint256 _configType)
99           external
100          view
101:         returns (bytes memory);

/// @audit Missing '@return '
105:     function getSendVersion(address _userApplication) external view returns (uint16);

/// @audit Missing '@return '
109:     function getReceiveVersion(address _userApplication) external view returns (uint16);

GitHub: 43, 47, 55, 55, 64, 75, 79, 83, 87, 91, 98, 105, 109

File: src/interfaces/IMulticall2.sol

/// @audit Missing '@return blockNumber'
/// @audit Missing '@return returnData'
20:      function aggregate(Call[] memory calls) external returns (uint256 blockNumber, bytes[] memory returnData);

GitHub: 20, 20

File: src/interfaces/IRootBridgeAgent.sol

/// @audit Missing '@return '
106      /**
107       * @notice External function that returns a given settlement entry.
108       *   @param _settlementNonce Identifier for token settlement.
109       *
110       */
111:     function getSettlementEntry(uint32 _settlementNonce) external view returns (Settlement memory);

GitHub: 106

File: src/interfaces/IRootBridgeAgentFactory.sol

/// @audit Missing '@return newBridgeAgent'
16:      function createBridgeAgent(address newRootRouterAddress) external returns (address newBridgeAgent);

GitHub: 16

File: src/interfaces/IRootPort.sol

/// @audit Missing '@return '
11:      function bridgeAgentAddress() external view returns (address);

/// @audit Missing '@return '
12:      function hTokenFactoryAddress() external view returns (address);

/// @audit Missing '@return account'
257      /**
258       * @notice Gets the virtual account given a user address.
259       * @param _user address of the user to get the virtual account for.
260       */
261:     function fetchVirtualAccount(address _user) external returns (VirtualAccount account);

GitHub: 11, 12, 257

File: src/token/ERC20hTokenRoot.sol

/// @audit Missing '@return '
51       /**
52        * @notice Mints new tokens and updates the total supply for the given chain.
53        * @param to Address to mint tokens to.
54        * @param amount Amount of tokens to mint.
55        * @param chainId Chain Id of the chain to mint tokens to.
56        */
57:      function mint(address to, uint256 amount, uint256 chainId) external onlyOwner returns (bool) {

GitHub: 51

[N‑76] NatSpec: Function @param is missing

There are 111 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

/// @audit Missing '@param _localChainId'
/// @audit Missing '@param _daoAddress'
/// @audit Missing '@param _localRouterAddress'
/// @audit Missing '@param _localPortAddress'
11:      function deploy(uint16 _localChainId, address _daoAddress, address _localRouterAddress, address _localPortAddress)

/// @audit Missing '@param _endpoint'
123      /// @notice Verifies the caller is the Root Bridge Agent.
124      /// @dev Internal function used in modifier to reduce contract bytesize.
125:     function _requiresEndpoint(address _endpoint, bytes calldata) internal view override {

GitHub: 11, 11, 11, 11, 123

File: src/BranchBridgeAgent.sol

/// @audit Missing '@param _rootChainId'
24       function deploy(
25:          uint16 _rootChainId,

/// @audit Missing '@param _localChainId'
24       function deploy(
25           uint16 _rootChainId,
26:          uint16 _localChainId,

/// @audit Missing '@param _rootBridgeAgentAddress'
24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27:          address _rootBridgeAgentAddress,

/// @audit Missing '@param _lzEndpointAddress'
24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27           address _rootBridgeAgentAddress,
28:          address _lzEndpointAddress,

/// @audit Missing '@param _localRouterAddress'
24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27           address _rootBridgeAgentAddress,
28           address _lzEndpointAddress,
29:          address _localRouterAddress,

/// @audit Missing '@param _localPortAddress'
24       function deploy(
25           uint16 _rootChainId,
26           uint16 _localChainId,
27           address _rootBridgeAgentAddress,
28           address _lzEndpointAddress,
29           address _localRouterAddress,
30:          address _localPortAddress

/// @audit Missing '@param _endpoint'
/// @audit Missing '@param _srcAddress'
935      /// @notice Internal function for caller verification. To be overwritten in `ArbitrumBranchBridgeAgent'.
936:     function _requiresEndpoint(address _endpoint, bytes calldata _srcAddress) internal view virtual {

GitHub: 24, 24, 24, 24, 24, 24, 935, 935

File: src/BranchPort.sol

/// @audit Missing '@param _strategyTokenDebt'
430      /**
431       * @notice Returns amount of Strategy Tokens
432       *   @param _token Address of a given Strategy Token.
433       *   @return uint256 excess reserves
434       */
435:     function _excessReserves(uint256 _strategyTokenDebt, address _token) internal view returns (uint256) {

/// @audit Missing '@param _strategyTokenDebt'
/// @audit Missing '@param currBalance'
444      /**
445       * @notice Returns amount of Strategy Tokens needed to reach minimum reserves
446       *   @param _token Address of a given Strategy Token.
447       *   @return uint256 excess reserves
448       */
449:     function _reservesLacking(uint256 _strategyTokenDebt, address _token, uint256 currBalance)

GitHub: 430, 444, 444

File: src/CoreBranchRouter.sol

/// @audit Missing '@param _decimals'
160       *  @param _symbol token symbol.
161       *  @param _refundee the address of the excess gas receiver.
162       *  @param _gParams Gas parameters for remote execution.
163       *  @dev FUNC ID: 1
164       *  @dev all hTokens have 18 decimals.
165       */
166      function _receiveAddGlobalToken(
167          address _globalAddress,
168          string memory _name,
169          string memory _symbol,
170:         uint8 _decimals,

GitHub: 160

File: src/CoreRootRouter.sol

/// @audit Missing '@param _bridgeAgentAddress'
/// @audit Missing '@param _hTokenFactory'
83:      function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

/// @audit Missing '@param _rootBridgeAgent'
95       /**
96        * @notice Add a new Chain (Branch Bridge Agent and respective Router) to a Root Bridge Agent.
97        * @param _branchBridgeAgentFactory Address of the branch Bridge Agent Factory.
98        * @param _newBranchRouter Address of the new branch router.
99        * @param _refundee Address of the excess gas receiver.
100       * @param _dstChainId Chain Id of the branch chain where the new Bridge Agent will be deployed.
101       * @param _gParams Gas parameters for remote execution.
102       */
103      function addBranchToBridgeAgent(
104:         address _rootBridgeAgent,

GitHub: 83, 83, 95

File: src/MulticallRootRouter.sol

/// @audit Missing '@param gasParams'
505       *  @param depositOut Amount of output hTokens to deposit.
506       *  @param dstChainId Chain Id of the destination chain.
507       */
508      function _approveAndCallOut(
509          address refundee,
510          address recipient,
511          address outputToken,
512          uint256 amountOut,
513          uint256 depositOut,
514          uint16 dstChainId,
515:         GasParams memory gasParams

/// @audit Missing '@param dstChainId'
540       *  @param amountsOut Total amount of tokens to send.
541       *  @param depositsOut Amounts of tokens to withdraw from the destination port.
542       *
543       */
544      function _approveMultipleAndCallOut(
545          address refundee,
546          address recipient,
547          address[] memory outputTokens,
548          uint256[] memory amountsOut,
549          uint256[] memory depositsOut,
550:         uint16 dstChainId,

/// @audit Missing '@param gasParams'
541       *  @param depositsOut Amounts of tokens to withdraw from the destination port.
542       *
543       */
544      function _approveMultipleAndCallOut(
545          address refundee,
546          address recipient,
547          address[] memory outputTokens,
548          uint256[] memory amountsOut,
549          uint256[] memory depositsOut,
550          uint16 dstChainId,
551:         GasParams memory gasParams

/// @audit Missing '@param data'
581:     function _decode(bytes calldata data) internal pure virtual returns (bytes memory) {

GitHub: 505, 540, 541, 581

File: src/MulticallRootRouterLibZip.sol

/// @audit Missing '@param _localChainId'
/// @audit Missing '@param _localPortAddress'
/// @audit Missing '@param _multicallAddress'
29:      constructor(uint256 _localChainId, address _localPortAddress, address _multicallAddress)

/// @audit Missing '@param data'
37:      function _decode(bytes calldata data) internal pure override returns (bytes memory) {

GitHub: 29, 29, 29, 37

File: src/RootBridgeAgent.sol

/// @audit Missing '@param _value'
858          address[] memory _hTokens,
859          address[] memory _tokens,
860          uint256[] memory _amounts,
861          uint256[] memory _deposits,
862          bytes memory _params,
863          uint32 _settlementNonce,
864          address payable _refundee,
865          address _recipient,
866          uint16 _dstChainId,
867          GasParams memory _gParams,
868:         uint256 _value

/// @audit Missing '@param _refundee'
933      /**
934       * @notice Internal function performs call to Layerzero Enpoint Contract for cross-chain messaging.
935       *   @param _depositNonce branch deposit nonce.
936       *   @param _dstChainId Chain ID of destination chain.
937       */
938:     function _performFallbackCall(address payable _refundee, uint32 _depositNonce, uint16 _dstChainId) internal {

GitHub: 858, 933

File: src/RootBridgeAgentExecutor.sol

/// @audit Missing '@param _rootBridgeAgent'
14:      function deploy(address _rootBridgeAgent) external returns (address) {

/// @audit Missing '@param _recipient'
237      /**
238       * @notice Internal function to move assets from branch chain to root omnichain environment.
239       *   @param _dParams Cross-Chain Deposit of Multiple Tokens Params.
240       *   @param _srcChainId chain to bridge from.
241       *
242       */
243:     function _bridgeIn(address _recipient, DepositParams memory _dParams, uint16 _srcChainId) internal {

/// @audit Missing '@param _recipient'
258       *         2. N * 32 bytes for the underlying token address.
259       *         3. N * 32 bytes for the amount of hTokens to be bridged in.
260       *         4. N * 32 bytes for the amount of underlying tokens to be bridged in.
261       *     5. Each of the 4 token related arrays are of length N and start at the following indexes:
262       *         1. PARAMS_TKN_START [hToken address has no offset from token information start].
263       *         2. PARAMS_TKN_START + (PARAMS_ADDRESS_SIZE * N)
264       *         3. PARAMS_TKN_START + (PARAMS_AMT_OFFSET * N)
265       *         4. PARAMS_TKN_START + (PARAMS_DEPOSIT_OFFSET * N)
266       *
267       */
268:     function _bridgeInMultiple(address _recipient, bytes calldata _dParams, uint16 _srcChainId)

GitHub: 14, 237, 258

File: src/RootPort.sol

/// @audit Missing '@param _to'
/// @audit Missing '@param _hToken'
/// @audit Missing '@param _amount'
304:     function bridgeToLocalBranchFromRoot(address _to, address _hToken, uint256 _amount)

GitHub: 304, 304, 304

File: src/VirtualAccount.sol

/// @audit Missing '@param addr'
147:     function isContract(address addr) internal view returns (bool) {

GitHub: 147

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

/// @audit Missing '@param _rootBridgeAgentFactoryAddress'
74       /**
75        * @notice Creates a new bridge agent for a branch chain.
76        * @param _newBranchRouterAddress Address of the new branch router.
77        * @param _rootBridgeAgentAddress Address of the root bridge agent to connect to.
78        */
79       function createBridgeAgent(
80           address _newBranchRouterAddress,
81           address _rootBridgeAgentAddress,
82:          address _rootBridgeAgentFactoryAddress

GitHub: 74

File: src/factories/BranchBridgeAgentFactory.sol

/// @audit Missing '@param _rootBridgeAgentFactoryAddress'
110      /**
111       * @notice Creates a new bridge agent for a new branch chain.
112       * @param _newBranchRouterAddress Address of the new branch router.
113       * @param _rootBridgeAgentAddress Address of the root bridge agent to connect to.
114       */
115      function createBridgeAgent(
116          address _newBranchRouterAddress,
117          address _rootBridgeAgentAddress,
118:         address _rootBridgeAgentFactoryAddress

GitHub: 110

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit Missing '@param recipient'
307      /**
308       * @notice Function to request balance clearance from a Port to a given address.
309       *     @param sParams encode packed multiple settlement info.
310       *
311       */
312:     function clearTokens(bytes calldata sParams, address recipient)

GitHub: 307

File: src/interfaces/IBranchPort.sol

/// @audit Missing '@param _underlyingAddresses'
96       /**
97        * @notice Setter function to increase local hToken supply.
98        *   @param _recipient hToken receiver.
99        *   @param _localAddresses token addresses.
100       *   @param _amounts amount of tokens.
101       *
102       */
103      function bridgeInMultiple(
104          address _recipient,
105          address[] memory _localAddresses,
106:         address[] memory _underlyingAddresses,

/// @audit Missing '@param _deposits'
98        *   @param _recipient hToken receiver.
99        *   @param _localAddresses token addresses.
100       *   @param _amounts amount of tokens.
101       *
102       */
103      function bridgeInMultiple(
104          address _recipient,
105          address[] memory _localAddresses,
106          address[] memory _underlyingAddresses,
107          uint256[] memory _amounts,
108:         uint256[] memory _deposits

/// @audit Missing '@param _depositor'
111      /**
112       * @notice Setter function to decrease local hToken supply.
113       *   @param _localAddress token address.
114       *   @param _amount amount of tokens.
115       *   @param _deposit amount of underlying tokens.
116       *
117       */
118      function bridgeOut(
119:         address _depositor,

/// @audit Missing '@param _underlyingAddress'
111      /**
112       * @notice Setter function to decrease local hToken supply.
113       *   @param _localAddress token address.
114       *   @param _amount amount of tokens.
115       *   @param _deposit amount of underlying tokens.
116       *
117       */
118      function bridgeOut(
119          address _depositor,
120          address _localAddress,
121:         address _underlyingAddress,

/// @audit Missing '@param _minimumReservesRatio'
177      /**
178       * @notice Adds a new strategy token.
179       * @param _token address of the token to add to the Strategy Tokens
180       */
181:     function addStrategyToken(address _token, uint256 _minimumReservesRatio) external;

/// @audit Missing '@param _token'
/// @audit Missing '@param _dailyManagementLimit'
189      /**
190       * @notice Adds a new Port strategy to the given port
191       * @param _portStrategy address of the bridge agent factory to add to the Port
192       */
193:     function addPortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit) external;

/// @audit Missing '@param _token'
195      /**
196       * @notice Reverts the toggle on the given port strategy. If it's active, it will de-activate it and vice-versa.
197       * @param _portStrategy address of the bridge agent factory to add to the Port
198       */
199:     function togglePortStrategy(address _portStrategy, address _token) external;

GitHub: 96, 98, 111, 111, 177, 189, 189, 195

File: src/interfaces/ILayerZeroEndpoint.sol

/// @audit Missing '@param _dstChainId'
15       function send(
16:          uint16 _dstChainId,

/// @audit Missing '@param _destination'
15       function send(
16           uint16 _dstChainId,
17:          bytes calldata _destination,

/// @audit Missing '@param _payload'
15       function send(
16           uint16 _dstChainId,
17           bytes calldata _destination,
18:          bytes calldata _payload,

/// @audit Missing '@param _refundAddress'
15       function send(
16           uint16 _dstChainId,
17           bytes calldata _destination,
18           bytes calldata _payload,
19:          address payable _refundAddress,

/// @audit Missing '@param _zroPaymentAddress'
15       function send(
16           uint16 _dstChainId,
17           bytes calldata _destination,
18           bytes calldata _payload,
19           address payable _refundAddress,
20:          address _zroPaymentAddress,

/// @audit Missing '@param _adapterParams'
15       function send(
16           uint16 _dstChainId,
17           bytes calldata _destination,
18           bytes calldata _payload,
19           address payable _refundAddress,
20           address _zroPaymentAddress,
21:          bytes calldata _adapterParams

/// @audit Missing '@param _srcChainId'
31       function receivePayload(
32:          uint16 _srcChainId,

/// @audit Missing '@param _srcAddress'
31       function receivePayload(
32           uint16 _srcChainId,
33:          bytes calldata _srcAddress,

/// @audit Missing '@param _dstAddress'
31       function receivePayload(
32           uint16 _srcChainId,
33           bytes calldata _srcAddress,
34:          address _dstAddress,

/// @audit Missing '@param _nonce'
31       function receivePayload(
32           uint16 _srcChainId,
33           bytes calldata _srcAddress,
34           address _dstAddress,
35:          uint64 _nonce,

/// @audit Missing '@param _gasLimit'
31       function receivePayload(
32           uint16 _srcChainId,
33           bytes calldata _srcAddress,
34           address _dstAddress,
35           uint64 _nonce,
36:          uint256 _gasLimit,

/// @audit Missing '@param _payload'
31       function receivePayload(
32           uint16 _srcChainId,
33           bytes calldata _srcAddress,
34           address _dstAddress,
35           uint64 _nonce,
36           uint256 _gasLimit,
37:          bytes calldata _payload

/// @audit Missing '@param _srcChainId'
/// @audit Missing '@param _srcAddress'
43:      function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64);

/// @audit Missing '@param _dstChainId'
/// @audit Missing '@param _srcAddress'
47:      function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64);

/// @audit Missing '@param _dstChainId'
55       function estimateFees(
56:          uint16 _dstChainId,

/// @audit Missing '@param _userApplication'
55       function estimateFees(
56           uint16 _dstChainId,
57:          address _userApplication,

/// @audit Missing '@param _payload'
55       function estimateFees(
56           uint16 _dstChainId,
57           address _userApplication,
58:          bytes calldata _payload,

/// @audit Missing '@param _payInZRO'
55       function estimateFees(
56           uint16 _dstChainId,
57           address _userApplication,
58           bytes calldata _payload,
59:          bool _payInZRO,

/// @audit Missing '@param _adapterParam'
55       function estimateFees(
56           uint16 _dstChainId,
57           address _userApplication,
58           bytes calldata _payload,
59           bool _payInZRO,
60:          bytes calldata _adapterParam

/// @audit Missing '@param _srcChainId'
/// @audit Missing '@param _srcAddress'
/// @audit Missing '@param _payload'
70:      function retryPayload(uint16 _srcChainId, bytes calldata _srcAddress, bytes calldata _payload) external;

/// @audit Missing '@param _srcChainId'
/// @audit Missing '@param _srcAddress'
75:      function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool);

/// @audit Missing '@param _userApplication'
79:      function getSendLibraryAddress(address _userApplication) external view returns (address);

/// @audit Missing '@param _userApplication'
83:      function getReceiveLibraryAddress(address _userApplication) external view returns (address);

/// @audit Missing '@param _version'
/// @audit Missing '@param _chainId'
/// @audit Missing '@param _userApplication'
/// @audit Missing '@param _configType'
98:      function getConfig(uint16 _version, uint16 _chainId, address _userApplication, uint256 _configType)

/// @audit Missing '@param _userApplication'
105:     function getSendVersion(address _userApplication) external view returns (uint16);

/// @audit Missing '@param _userApplication'
109:     function getReceiveVersion(address _userApplication) external view returns (uint16);

GitHub: 15, 15, 15, 15, 15, 15, 31, 31, 31, 31, 31, 31, 43, 43, 47, 47, 55, 55, 55, 55, 55, 70, 70, 70, 75, 75, 79, 83, 98, 98, 98, 98, 105, 109

File: src/interfaces/ILayerZeroReceiver.sol

/// @audit Missing '@param _srcChainId'
/// @audit Missing '@param _srcAddress'
/// @audit Missing '@param _nonce'
/// @audit Missing '@param _payload'
11:      function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes calldata _payload) external;

GitHub: 11, 11, 11, 11

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

/// @audit Missing '@param _version'
/// @audit Missing '@param _chainId'
/// @audit Missing '@param _configType'
/// @audit Missing '@param _config'
11:      function setConfig(uint16 _version, uint16 _chainId, uint256 _configType, bytes calldata _config) external;

/// @audit Missing '@param _version'
15:      function setSendVersion(uint16 _version) external;

/// @audit Missing '@param _version'
19:      function setReceiveVersion(uint16 _version) external;

/// @audit Missing '@param _srcChainId'
/// @audit Missing '@param _srcAddress'
24:      function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external;

GitHub: 11, 11, 11, 11, 15, 19, 24, 24

File: src/interfaces/IMulticall2.sol

/// @audit Missing '@param calls'
20:      function aggregate(Call[] memory calls) external returns (uint256 blockNumber, bytes[] memory returnData);

GitHub: 20

File: src/interfaces/IRootBridgeAgent.sol

/// @audit Missing '@param _refundee'
160      /**
161       * @notice External function performs call to LayerZero Endpoint Contract for cross-chain messaging.
162       *   @param _recipient address to receive any outstanding gas on the destination chain.
163       *   @param _dstChainId Chain to bridge to.
164       *   @param _params Calldata for function call.
165       *   @param _gParams Gas Parameters for cross-chain message.
166       *   @dev Internal function performs call to LayerZero Endpoint Contract for cross-chain messaging.
167       */
168      function callOut(
169:         address payable _refundee,

GitHub: 160

File: src/interfaces/IRootBridgeAgentFactory.sol

/// @audit Missing '@param newRootRouterAddress'
16:      function createBridgeAgent(address newRootRouterAddress) external returns (address newBridgeAgent);

GitHub: 16

File: src/interfaces/IRootPort.sol

/// @audit Missing '@param _refundee'
13       function setCoreBranch(
14:          address _refundee,

/// @audit Missing '@param _coreBranchRouter'
13       function setCoreBranch(
14           address _refundee,
15:          address _coreBranchRouter,

/// @audit Missing '@param _coreBranchBridgeAgent'
13       function setCoreBranch(
14           address _refundee,
15           address _coreBranchRouter,
16:          address _coreBranchBridgeAgent,

/// @audit Missing '@param _dstChainId'
13       function setCoreBranch(
14           address _refundee,
15           address _coreBranchRouter,
16           address _coreBranchBridgeAgent,
17:          uint16 _dstChainId,

/// @audit Missing '@param _gParams'
13       function setCoreBranch(
14           address _refundee,
15           address _coreBranchRouter,
16           address _coreBranchBridgeAgent,
17           uint16 _dstChainId,
18:          GasParams calldata _gParams

/// @audit Missing '@param _underlyingAddress'
232      /**
233       * @notice Setter function to update a Global hToken's Local hToken Address.
234       *   @param _globalAddress new hToken address to update.
235       *   @param _localAddress new underlying/native token address to set.
236       *
237       */
238      function setAddresses(
239          address _globalAddress,
240          address _localAddress,
241:         address _underlyingAddress,

/// @audit Missing '@param _srcChainId'
232      /**
233       * @notice Setter function to update a Global hToken's Local hToken Address.
234       *   @param _globalAddress new hToken address to update.
235       *   @param _localAddress new underlying/native token address to set.
236       *
237       */
238      function setAddresses(
239          address _globalAddress,
240          address _localAddress,
241          address _underlyingAddress,
242:         uint256 _srcChainId

/// @audit Missing '@param _srcChainId'
245      /**
246       * @notice Setter function to update a Global hToken's Local hToken Address.
247       *   @param _globalAddress new hToken address to update.
248       *   @param _localAddress new underlying/native token address to set.
249       *
250       */
251:     function setLocalAddress(address _globalAddress, address _localAddress, uint256 _srcChainId) external;

GitHub: 13, 13, 13, 13, 13, 232, 232, 245

File: src/token/ERC20hTokenBranch.sol

/// @audit Missing '@param chainName'
13       constructor(
14:          string memory chainName,

/// @audit Missing '@param chainSymbol'
13       constructor(
14           string memory chainName,
15:          string memory chainSymbol,

/// @audit Missing '@param _name'
13       constructor(
14           string memory chainName,
15           string memory chainSymbol,
16:          string memory _name,

/// @audit Missing '@param _symbol'
13       constructor(
14           string memory chainName,
15           string memory chainSymbol,
16           string memory _name,
17:          string memory _symbol,

/// @audit Missing '@param _decimals'
13       constructor(
14           string memory chainName,
15           string memory chainSymbol,
16           string memory _name,
17           string memory _symbol,
18:          uint8 _decimals,

/// @audit Missing '@param _owner'
13       constructor(
14           string memory chainName,
15           string memory chainSymbol,
16           string memory _name,
17           string memory _symbol,
18           uint8 _decimals,
19:          address _owner

GitHub: 13, 13, 13, 13, 13, 13

[N‑77] Consider bounding input array length

The functions below take in an unbounded array, and make function calls for entries in the array. While the function will revert if it eventually runs out of gas, it may be a nicer user experience to require() that the length of the array is below some reasonable maximum, so that the user doesn't have to use up a full transaction's gas only to see that the transaction reverts.

There are 7 instances of this issue:

File: src/BaseBranchRouter.sol

192          for (uint256 i = 0; i < _hTokens.length;) {
193              _transferAndApproveToken(_hTokens[i], _tokens[i], _amounts[i], _deposits[i]);
194  
195              unchecked {
196                  ++i;
197              }
198:         }

GitHub: 192

File: src/BranchPort.sol

257          for (uint256 i = 0; i < length;) {
258              // Check if hTokens are being bridged in
259              if (_amounts[i] - _deposits[i] > 0) {
260                  unchecked {
261                      _bridgeIn(_recipient, _localAddresses[i], _amounts[i] - _deposits[i]);
262                  }
263              }
264  
265              // Check if underlying tokens are being cleared
266              if (_deposits[i] > 0) {
267                  withdraw(_recipient, _underlyingAddresses[i], _deposits[i]);
268              }
269  
270              unchecked {
271                  ++i;
272              }
273:         }

305          for (uint256 i = 0; i < length;) {
306              _bridgeOut(_depositor, _localAddresses[i], _underlyingAddresses[i], _amounts[i], _deposits[i]);
307  
308              unchecked {
309                  i++;
310              }
311:         }

GitHub: 257, 305

File: src/MulticallRootRouter.sol

557          for (uint256 i = 0; i < outputTokens.length;) {
558              // Approve Root Port to spend output hTokens.
559              outputTokens[i].safeApprove(_bridgeAgentAddress, amountsOut[i]);
560              unchecked {
561                  ++i;
562              }
563:         }

GitHub: 557

File: src/RootBridgeAgent.sol

1070         for (uint256 i = 0; i < hTokens.length;) {
1071             // Populate Addresses for Settlement
1072             hTokens[i] = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddresses[i], _dstChainId);
1073             tokens[i] = IPort(localPortAddress).getUnderlyingTokenFromLocal(hTokens[i], _dstChainId);
1074 
1075             // Avoid stack too deep
1076             uint16 destChainId = _dstChainId;
1077 
1078             // Update State to reflect bridgeOut
1079             _updateStateOnBridgeOut(
1080                 msg.sender, _globalAddresses[i], hTokens[i], tokens[i], _amounts[i], _deposits[i], destChainId
1081             );
1082 
1083             unchecked {
1084                 ++i;
1085             }
1086:        }

GitHub: 1070

File: src/VirtualAccount.sol

70           for (uint256 i = 0; i < length;) {
71               bool success;
72               Call calldata _call = calls[i];
73   
74               if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData);
75   
76               if (!success) revert CallFailed();
77   
78               unchecked {
79                   ++i;
80               }
81:          }

90           for (uint256 i = 0; i < length;) {
91               _call = calls[i];
92               uint256 val = _call.value;
93               // Humanity will be a Type V Kardashev Civilization before this overflows - andreas
94               // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
95               unchecked {
96                   valAccumulator += val;
97               }
98   
99               bool success;
100  
101              if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);
102  
103              if (!success) revert CallFailed();
104  
105              unchecked {
106                  ++i;
107              }
108:         }

GitHub: 70, 90

[N‑78] Named imports of parent contracts are missing

There is one instance of this issue:

File: src/interfaces/ILayerZeroEndpoint.sol

/// @audit ILayerZeroUserApplicationConfig
7:   interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {

GitHub: 7

[N‑79] Complex casting

Consider whether the number of casts is really necessary, or whether using a different type would be more appropriate. Alternatively, add comments to explain in detail why the casts are necessary, and any implicit reasons why the cast does not introduce an overflow.

There are 59 instances of this issue:

File: src/BranchBridgeAgent.sol

500          // Parse Tokens Length
501:         uint8 numOfAssets = uint8(bytes1(_sParams[0]));

503          // Parse Nonce
504:         uint32 nonce = uint32(bytes4(_sParams[PARAMS_START:PARAMS_TKN_START]));

517              // Parse Params
518              _hTokens[i] = address(
519                  uint160(
520                      bytes20(
521                          bytes32(
522                              _sParams[
523                                  PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET:
524                                      PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset)
525                              ]
526                          )
527                      )
528                  )
529:             );

530  
531              _tokens[i] = address(
532                  uint160(
533                      bytes20(
534                          bytes32(
535                              _sParams[
536                                  PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (i + numOfAssets) + ADDRESS_END_OFFSET:
537                                      PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (currentIterationOffset + numOfAssets)
538                              ]
539                          )
540                      )
541                  )
542:             );

543  
544              _amounts[i] = uint256(
545                  bytes32(
546                      _sParams[
547                          PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * i:
548                              PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * currentIterationOffset
549                      ]
550                  )
551:             );

552  
553              _deposits[i] = uint256(
554                  bytes32(
555                      _sParams[
556                          PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets + PARAMS_ENTRY_SIZE * i:
557                              PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets
558                                  + PARAMS_ENTRY_SIZE * currentIterationOffset
559                      ]
560                  )
561:             );

682              //Get nonce
683:             nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

664              // Parse recipient
665:             address payable recipient = payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))));

667              //Get nonce
668:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

639              // Parse recipient
640:             address payable recipient = payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))));

642              // Parse deposit nonce
643:             nonce = uint32(bytes4(_payload[22:26]));

617              // Parse recipient
618:             address payable recipient = payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))));

620              // Parse Settlement Nonce
621:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

600              // Get Settlement Nonce
601:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

942          if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();
943:         if (rootBridgeAgentAddress != address(uint160(bytes20(_srcAddress[20:])))) revert LayerZeroUnauthorizedCaller();

GitHub: 500, 503, 517, 530, 543, 552, 682, 664, 667, 639, 642, 617, 620, 600, 942

File: src/BranchBridgeAgentExecutor.sol

72           SettlementParams memory sParams = SettlementParams({
73:              settlementNonce: uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])),

74               recipient: _recipient,
75:              hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),

75               hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),
76:              token: address(uint160(bytes20(_payload[45:65]))),

76               token: address(uint160(bytes20(_payload[45:65]))),
77:              amount: uint256(bytes32(_payload[65:97])),

77               amount: uint256(bytes32(_payload[65:97])),
78:              deposit: uint256(bytes32(_payload[97:PARAMS_SETTLEMENT_OFFSET]))

109          // Parse Values
110:         uint256 assetsOffset = uint8(bytes1(_payload[PARAMS_START_SIGNED])) * PARAMS_TKN_SET_SIZE_MULTIPLE;

GitHub: 72, 74, 75, 76, 77, 109

File: src/RootBridgeAgent.sol

719              // Parse nonce
720:             nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

700              //Parse deposit nonce
701:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

712                  _performFallbackCall(
713:                     payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))), nonce, _srcChainId

618              // Parse deposit nonce
619:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED + PARAMS_START:PARAMS_START_SIGNED + PARAMS_TKN_START]));

627              VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(
628:                 address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))

641                  nonce,
642:                 address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))),

578              // Parse deposit nonce
579:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

587              VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(
588:                 address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))

601                  nonce,
602:                 address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))),

540              // Parse deposit nonce
541:             nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

549              VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(
550:                 address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))

514              // Parse deposit nonce
515:             nonce = uint32(bytes4(_payload[2:6]));

491              //Parse Deposit Nonce
492:             nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

468              // Parse Deposit Nonce
469:             nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

445              // Parse deposit nonce
446:             nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

1211 
1212:            if (getBranchBridgeAgent[_srcChain] != address(uint160(bytes20(_srcAddress[PARAMS_ADDRESS_SIZE:])))) {

GitHub: 719, 700, 712, 618, 627, 641, 578, 587, 601, 540, 549, 514, 491, 468, 445, 1211

File: src/RootBridgeAgentExecutor.sol

88           DepositParams memory dParams = DepositParams({
89:              depositNonce: uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START])),

89               depositNonce: uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START])),
90:              hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START:PARAMS_TKN_START_SIGNED]))),

90               hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START:PARAMS_TKN_START_SIGNED]))),
91:              token: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),

91               token: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),
92:              amount: uint256(bytes32(_payload[45:77])),

92               amount: uint256(bytes32(_payload[45:77])),
93:              deposit: uint256(bytes32(_payload[77:PARAMS_TKN_SET_SIZE]))

124                  PARAMS_START:
125:                     PARAMS_END_OFFSET + uint256(uint8(bytes1(_payload[PARAMS_START]))) * PARAMS_TKN_SET_SIZE_MULTIPLE

129  
130:         uint256 numOfAssets = uint8(bytes1(_payload[PARAMS_START]));

173          DepositParams memory dParams = DepositParams({
174:             depositNonce: uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])),

174              depositNonce: uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED])),
175:             hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),

175              hToken: address(uint160(bytes20(_payload[PARAMS_TKN_START_SIGNED:45]))),
176:             token: address(uint160(bytes20(_payload[45:65]))),

176              token: address(uint160(bytes20(_payload[45:65]))),
177:             amount: uint256(bytes32(_payload[65:97])),

177              amount: uint256(bytes32(_payload[65:97])),
178:             deposit: uint256(bytes32(_payload[97:PARAMS_SETTLEMENT_OFFSET]))

212                      PARAMS_END_SIGNED_OFFSET
213:                         + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE

221                  > PARAMS_END_SIGNED_OFFSET
222:                     + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE

227                      PARAMS_END_SIGNED_OFFSET
228:                         + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE:

272          // Parse Parameters
273:         uint8 numOfAssets = uint8(bytes1(_dParams[0]));

273          uint8 numOfAssets = uint8(bytes1(_dParams[0]));
274:         uint32 nonce = uint32(bytes4(_dParams[PARAMS_START:5]));

285              // Parse Params
286              hTokens[i] = address(
287                  uint160(
288                      bytes20(
289                          bytes32(
290                              _dParams[
291                                  PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET:
292                                      PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset)
293                              ]
294                          )
295                      )
296                  )
297:             );

298  
299              tokens[i] = address(
300                  uint160(
301                      bytes20(
302                          bytes32(
303                              _dParams[
304                                  PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (i + numOfAssets) + ADDRESS_END_OFFSET:
305                                      PARAMS_TKN_START + PARAMS_ENTRY_SIZE * (currentIterationOffset + numOfAssets)
306                              ]
307                          )
308                      )
309                  )
310:             );

311  
312              amounts[i] = uint256(
313                  bytes32(
314                      _dParams[
315                          PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets + (PARAMS_ENTRY_SIZE * i):
316                              PARAMS_TKN_START + PARAMS_AMT_OFFSET * numOfAssets
317                                  + (PARAMS_ENTRY_SIZE * currentIterationOffset)
318                      ]
319                  )
320:             );

321  
322              deposits[i] = uint256(
323                  bytes32(
324                      _dParams[
325                          PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets + (PARAMS_ENTRY_SIZE * i):
326                              PARAMS_TKN_START + PARAMS_DEPOSIT_OFFSET * numOfAssets
327                                  + PARAMS_ENTRY_SIZE * currentIterationOffset
328                      ]
329                  )
330:             );

280  
281:         for (uint256 i = 0; i < uint256(uint8(numOfAssets));) {

GitHub: 88, 89, 90, 91, 92, 124, 129, 173, 174, 175, 176, 177, 212, 221, 227, 272, 273, 285, 298, 311, 321, 280

[N‑80] Events may be emitted out of order due to reentrancy

Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls

There are 7 instances of this issue:

File: src/BranchPort.sol

/// @audit withdraw() prior to emission
184:         emit DebtRepaid(msg.sender, _token, _amount);

/// @audit withdraw() prior to emission
218:         emit DebtRepaid(_strategy, _token, amountToWithdraw);

GitHub: 184, 218

File: src/RootBridgeAgent.sol

/// @audit fetchVirtualAccount() prior to emission
736:         emit LogExecute(nonce, _srcChainId);

GitHub: 736

File: src/RootPort.sol

/// @audit syncBranchBridgeAgent() prior to emission
406:         emit BridgeAgentSynced(_newBranchBridgeAgent, _rootBridgeAgent, _branchChainId);

/// @audit syncBranchBridgeAgent() prior to emission
479:         emit NewChainAdded(_chainId);

/// @audit syncBranchBridgeAgent() prior to emission
535:         emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent, _dstChainId);

/// @audit syncBranchBridgeAgent() prior to emission
549:         emit CoreBranchSynced(_coreBranchRouter, _coreBranchBridgeAgent, _dstChainId);

GitHub: 406, 479, 535, 549

[N‑81] Non-assembly method available

assembly{ id := chainid() } => uint256 id = block.chainid, assembly { size := extcodesize() } => uint256 size = address().code.length, assembly { hash := extcodehash() } => bytes32 hash = address().codehash There are some automated tools that will flag a project as having higher complexity if there is inline-assembly, so it's best to avoid using it where it's not necessary

There is one instance of this issue:

File: src/VirtualAccount.sol

149          assembly {
150              size := extcodesize(addr)
151:         }

GitHub: 149

[N‑82] Missing checks constructor/initializer assignments

Consider whether reasonable bounds checks for variables would be useful

There are 9 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

41:          localChainId = _localChainId;

GitHub: 41

File: src/CoreRootRouter.sol

72:          rootChainId = _rootChainId;

GitHub: 72

File: src/MulticallRootRouter.sol

96:          localChainId = _localChainId;

GitHub: 96

File: src/RootBridgeAgent.sol

116:         localChainId = _localChainId;

GitHub: 116

File: src/RootPort.sol

112:         localChainId = _localChainId;

GitHub: 112

File: src/factories/ERC20hTokenBranchFactory.sol

46:          localChainId = _localChainId;

GitHub: 46

File: src/factories/ERC20hTokenRootFactory.sol

36:          localChainId = _localChainId;

GitHub: 36

File: src/factories/RootBridgeAgentFactory.sol

34:          rootChainId = _rootChainId;

GitHub: 34

File: src/token/ERC20hTokenRoot.sol

42:          localChainId = _localChainId;

GitHub: 42

Gas Optimizations

[G‑01] Enable IR-based code generation

By using --via-ir or {"viaIR": true}, the compiler is able to use more advanced multi-function optimizations, for extra gas savings.

There is one instance of this issue:

File: Various Files

[G‑02] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 5 instances of this issue:

File: src/BranchPort.sol

58        mapping(address token => uint256 debt) public getStrategyTokenDebt;
59    
60        /// @notice Mapping returns the minimum ratio of a given Strategy Token the Port should hold.
61        mapping(address token => uint256 minimumReserveRatio) public getMinimumTokenReserveRatio;
62    
63        /*///////////////////////////////////////////////////////////////
64                            PORT STRATEGIES STATE
65        //////////////////////////////////////////////////////////////*/
66    
67        /// @notice Mapping returns true if Port Strategy is allowed to manage a given Strategy Token.
68:       mapping(address strategy => mapping(address token => bool isActiveStrategy)) public isPortStrategy;

74        mapping(address strategy => mapping(address token => uint256 debt)) public getPortStrategyTokenDebt;
75    
76        /// @notice Mapping returns the last time a given Port Strategy managed a given Strategy Token.
77        mapping(address strategy => mapping(address token => uint256 lastManaged)) public lastManaged;
78    
79        /// @notice Mapping returns the time limit a given Port Strategy must wait before managing a Strategy Token.
80        mapping(address strategy => mapping(address token => uint256 dailyLimitAmount)) public strategyDailyLimitAmount;
81    
82        /// @notice Mapping returns the amount of a Strategy Token a given Port Strategy can manage.
83        mapping(address strategy => mapping(address token => uint256 dailyLimitRemaining)) public
84:           strategyDailyLimitRemaining;

GitHub: 58, 74

File: src/RootBridgeAgent.sol

62        mapping(uint256 chainId => address branchBridgeAgent) public getBranchBridgeAgent;
63    
64        /// @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;
66    
67        /// @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;

GitHub: 62

File: src/RootPort.sol

70        mapping(address bridgeAgent => address bridgeAgentManager) public getBridgeAgentManager;
71    
72        /*///////////////////////////////////////////////////////////////
73                        BRIDGE AGENT FACTORIES
74        //////////////////////////////////////////////////////////////*/
75    
76        /// @notice Mapping from Underlying Address to isUnderlying (bool).
77:       mapping(address bridgeAgentFactory => bool isActive) public isBridgeAgentFactory;

87        mapping(address token => bool isGlobalToken) public isGlobalAddress;
88    
89        /// @notice ChainId -> Local Address -> Global Address
90        mapping(address chainId => mapping(uint256 localAddress => address globalAddress)) public getGlobalTokenFromLocal;
91    
92        /// @notice ChainId -> Global Address -> Local Address
93        mapping(address chainId => mapping(uint256 globalAddress => address localAddress)) public getLocalTokenFromGlobal;
94    
95        /// @notice ChainId -> Underlying Address -> Local Address
96        mapping(address chainId => mapping(uint256 underlyingAddress => address localAddress)) public
97            getLocalTokenFromUnderlying;
98    
99        /// @notice Mapping from Local Address to Underlying Address.
100       mapping(address chainId => mapping(uint256 localAddress => address underlyingAddress)) public
101:          getUnderlyingTokenFromLocal;

GitHub: 70, 87

[G‑03] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values.

There are 3 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit rootBridgeAgentPath (constructor)
142:          rootBridgeAgentPath = abi.encodePacked(_rootBridgeAgentAddress, address(this));

GitHub: 142

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit chainName (constructor)
44:           chainName = string.concat(_chainName, " Ulysses");

/// @audit chainSymbol (constructor)
45:           chainSymbol = string.concat(_chainSymbol, "-u");

GitHub: 44, 45

[G‑04] Avoid contract existence checks by using low-level calls

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

There are 52 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

/// @audit getLocalTokenFromUnderlying()
60:           address _globalToken = IRootPort(_rootPortAddress).getLocalTokenFromUnderlying(_underlyingAddress, localChainId);

/// @audit getUnderlyingTokenFromLocal()
87:               IRootPort(_rootPortAddress).getUnderlyingTokenFromLocal(_globalAddress, localChainId);

GitHub: 60, 87

File: src/ArbitrumCoreBranchRouter.sol

/// @audit name()
56:               string.concat("Arbitrum Ulysses ", ERC20(_underlyingAddress).name()),

/// @audit symbol()
57:               string.concat("arb-u", ERC20(_underlyingAddress).symbol()),

/// @audit decimals()
58:               ERC20(_underlyingAddress).decimals()

/// @audit createBridgeAgent()
102:          address newBridgeAgent = IBridgeAgentFactory(_branchBridgeAgentFactory).createBridgeAgent(

GitHub: 56, 57, 58, 102

File: src/BaseBranchRouter.sol

/// @audit localPortAddress()
63:           localPortAddress = IBridgeAgent(_localBridgeAgentAddress).localPortAddress();

/// @audit bridgeAgentExecutorAddress()
64:           bridgeAgentExecutorAddress = IBridgeAgent(_localBridgeAgentAddress).bridgeAgentExecutorAddress();

GitHub: 63, 64

File: src/BranchBridgeAgent.sol

/// @audit estimateFees()
166:          (_fee,) = ILayerZeroEndpoint(lzEndpointAddress).estimateFees(

GitHub: 166

File: src/BranchBridgeAgentExecutor.sol

/// @audit clearTokens()
114:          SettlementMultipleParams memory sParams = BranchBridgeAgent(payable(msg.sender)).clearTokens(

GitHub: 114

File: src/BranchPort.sol

/// @audit balanceOf()
175:          uint256 currBalance = ERC20(_token).balanceOf(address(this));

/// @audit balanceOf()
181:          require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed");

/// @audit balanceOf()
193:          uint256 currBalance = ERC20(_token).balanceOf(address(this));

/// @audit balanceOf()
214:              ERC20(_token).balanceOf(address(this)) - currBalance == amountToWithdraw, "Port Strategy Withdraw Failed"

/// @audit balanceOf()
436:          uint256 currBalance = ERC20(_token).balanceOf(address(this));

GitHub: 175, 181, 193, 214, 436

File: src/CoreBranchRouter.sol

/// @audit decimals()
64:           uint8 decimals = ERC20(_underlyingAddress).decimals();

/// @audit createToken()
67:           ERC20hToken newToken = ITokenFactory(hTokenFactoryAddress).createToken(

/// @audit name()
/// @audit symbol()
68:               ERC20(_underlyingAddress).name(), ERC20(_underlyingAddress).symbol(), decimals, true

/// @audit createToken()
175:          ERC20hToken newToken = ITokenFactory(hTokenFactoryAddress).createToken(_name, _symbol, _decimals, false);

/// @audit createBridgeAgent()
216:          address newBridgeAgent = IBridgeAgentFactory(_branchBridgeAgentFactory).createBridgeAgent(

GitHub: 64, 67, 68, 68, 175, 216

File: src/CoreRootRouter.sol

/// @audit bridgeAgentExecutorAddress()
87:           bridgeAgentExecutorAddress = IBridgeAgent(_bridgeAgentAddress).bridgeAgentExecutorAddress();

/// @audit getBridgeAgentManager()
112:          if (msg.sender != IPort(rootPortAddress).getBridgeAgentManager(_rootBridgeAgent)) {

/// @audit getBranchBridgeAgent()
120:          if (IBridgeAgent(_rootBridgeAgent).getBranchBridgeAgent(_dstChainId) != address(0)) revert InvalidChainId();

/// @audit factoryAddress()
130:              IBridgeAgent(_rootBridgeAgent).factoryAddress(),

/// @audit name()
426:              ERC20(_globalAddress).name(),

/// @audit symbol()
427:              ERC20(_globalAddress).symbol(),

/// @audit decimals()
428:              ERC20(_globalAddress).decimals(),

/// @audit createToken()
466:          address newToken = address(IFactory(hTokenFactoryAddress).createToken(_name, _symbol, _decimals));

GitHub: 87, 112, 120, 130, 426, 427, 428, 466

File: src/MulticallRootRouter.sol

/// @audit bridgeAgentExecutorAddress()
113:          bridgeAgentExecutorAddress = IBridgeAgent(_bridgeAgentAddress).bridgeAgentExecutorAddress();

/// @audit aggregate()
493:          (blockNumber, returnData) = IMulticall(multicallAddress).aggregate(calls);

GitHub: 113, 493

File: src/RootBridgeAgent.sol

/// @audit estimateFees()
146:          (_fee,) = ILayerZeroEndpoint(lzEndpointAddress).estimateFees(

/// @audit getUserAccount()
248:              if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementReference.owner))) {

/// @audit getUserAccount()
286:              if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner))) {

/// @audit getUserAccount()
312:              if (msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner))) {

/// @audit getLocalTokenFromUnderlying()
371:              if (IPort(_localPortAddress).getLocalTokenFromUnderlying(_dParams.token, _srcChainId) != _dParams.hToken) {

/// @audit excessivelySafeCall()
424:          (bool success,) = address(this).excessivelySafeCall(

/// @audit fetchVirtualAccount()
549:              VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(

/// @audit fetchVirtualAccount()
587:              VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(

/// @audit fetchVirtualAccount()
627:              VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(

/// @audit getUserAccount()
674:                  if (owner != address(IPort(localPortAddress).getUserAccount(settlementReference.owner))) {

/// @audit getLocalTokenFromGlobal()
981:          address localAddress = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddress, _dstChainId);

/// @audit getUnderlyingTokenFromLocal()
984:          address underlyingAddress = IPort(localPortAddress).getUnderlyingTokenFromLocal(localAddress, _dstChainId);

/// @audit getLocalTokenFromGlobal()
1072:             hTokens[i] = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddresses[i], _dstChainId);

/// @audit getUnderlyingTokenFromLocal()
1073:             tokens[i] = IPort(localPortAddress).getUnderlyingTokenFromLocal(hTokens[i], _dstChainId);

/// @audit getTokenBalance()
1158:             if (IERC20hTokenRoot(_globalAddress).getTokenBalance(_dstChainId) < _deposit) {

/// @audit getBridgeAgentManager()
1233:         if (msg.sender != IPort(localPortAddress).getBridgeAgentManager(address(this))) {

GitHub: 146, 248, 286, 312, 371, 424, 549, 587, 627, 674, 981, 984, 1072, 1073, 1158, 1233

File: src/RootPort.sol

/// @audit getBranchBridgeAgent()
398:          if (IBridgeAgent(_rootBridgeAgent).getBranchBridgeAgent(_branchChainId) != address(0)) {

/// @audit createToken()
452:              IERC20hTokenRootFactory(ICoreRootRouter(coreRootRouterAddress).hTokenFactoryAddress()).createToken(

GitHub: 398, 452

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit name()
66:               ERC20(_wrappedNativeTokenAddress).name(),

/// @audit symbol()
67:               ERC20(_wrappedNativeTokenAddress).symbol(),

/// @audit decimals()
68:               ERC20(_wrappedNativeTokenAddress).decimals(),

GitHub: 66, 67, 68

[G‑05] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There is one instance of this issue:

File: src/RootPort.sol

/// @audit coreRootRouterAddress on line 452
458:          IBridgeAgent(ICoreRootRouter(coreRootRouterAddress).bridgeAgentAddress()).syncBranchBridgeAgent(

GitHub: 458

[G‑06] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 14 instances of this issue:

see instances
File: src/ArbitrumCoreBranchRouter.sol

85        function _receiveAddBridgeAgent(
86            address _newBranchRouter,
87            address _branchBridgeAgentFactory,
88            address _rootBridgeAgent,
89            address _rootBridgeAgentFactory,
90            address _refundee,
91:           GasParams memory _gParams

GitHub: 85

File: src/BaseBranchRouter.sol

186       function _transferAndApproveMultipleTokens(
187           address[] memory _hTokens,
188           address[] memory _tokens,
189           uint256[] memory _amounts,
190:          uint256[] memory _deposits

GitHub: 186

File: src/BranchPort.sol

435:      function _excessReserves(uint256 _strategyTokenDebt, address _token) internal view returns (uint256) {

449       function _reservesLacking(uint256 _strategyTokenDebt, address _token, uint256 currBalance)
450           internal
451           view
452:          returns (uint256)

485:      function _checkTimeLimit(address _token, uint256 _amount) internal {

GitHub: 435, 449, 485

File: src/CoreBranchRouter.sol

166       function _receiveAddGlobalToken(
167           address _globalAddress,
168           string memory _name,
169           string memory _symbol,
170           uint8 _decimals,
171           address _refundee,
172:          GasParams memory _gParams

GitHub: 166

File: src/CoreRootRouter.sol

406       function _addGlobalToken(
407           address _refundee,
408           address _globalAddress,
409           uint16 _dstChainId,
410:          GasParams[2] memory _gParams

452       function _addLocalToken(
453           address _underlyingAddress,
454           address _localAddress,
455           string memory _name,
456           string memory _symbol,
457           uint8 _decimals,
458:          uint16 _srcChainId

481:      function _setLocalToken(address _globalAddress, address _localAddress, uint16 _dstChainId) internal {

500:      function _syncBranchBridgeAgent(address _newBranchBridgeAgent, address _rootBridgeAgent, uint256 _srcChainId)

GitHub: 406, 452, 481, 500

File: src/MulticallRootRouter.sol

604       function _requiresExecutor() internal view {
605:          if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();

GitHub: 604

File: src/RootBridgeAgent.sol

966       function _createSettlement(
967           uint32 _settlementNonce,
968           address payable _refundee,
969           address _recipient,
970           uint16 _dstChainId,
971           bytes memory _params,
972           address _globalAddress,
973           uint256 _amount,
974           uint256 _deposit,
975           bool _hasFallbackToggled
976:      ) internal returns (bytes memory _payload) {

1045      function _createSettlementMultiple(
1046          uint32 _settlementNonce,
1047          address payable _refundee,
1048          address _recipient,
1049          uint16 _dstChainId,
1050          address[] memory _globalAddresses,
1051          uint256[] memory _amounts,
1052          uint256[] memory _deposits,
1053          bytes memory _params,
1054          bool _hasFallbackToggled
1055:     ) internal returns (bytes memory _payload) {

GitHub: 966, 1045

File: src/RootPort.sol

359:      function addVirtualAccount(address _user) internal returns (VirtualAccount newAccount) {

GitHub: 359

[G‑07] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 8 instances of this issue:

File: src/BaseBranchRouter.sol

192:          for (uint256 i = 0; i < _hTokens.length;) {

GitHub: 192

File: src/BranchBridgeAgent.sol

447:          for (uint256 i = 0; i < deposit.tokens.length;) {

GitHub: 447

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;) {

GitHub: 278, 367, 455, 557

File: src/RootBridgeAgent.sol

318:          for (uint256 i = 0; i < settlement.hTokens.length;) {

1070:         for (uint256 i = 0; i < hTokens.length;) {

GitHub: 318, 1070

[G‑08] require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

There are 22 instances of this issue:

File: src/BranchBridgeAgent.sol

125:          require(_rootBridgeAgentAddress != address(0), "Root Bridge Agent Address cannot be the zero address.");

126           require(
127               _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
128               "Layerzero Endpoint Address cannot be the zero address."
129:          );

130:          require(_localRouterAddress != address(0), "Local Router Address cannot be the zero address.");

131:          require(_localPortAddress != address(0), "Local Port Address cannot be the zero address.");

GitHub: 125, 126, 130, 131

File: src/BranchPort.sol

127:          require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address");

GitHub: 127

File: src/RootBridgeAgent.sol

111:          require(_lzEndpointAddress != address(0), "Layerzero Enpoint Address cannot be zero address");

112:          require(_localPortAddress != address(0), "Port Address cannot be zero address");

113:          require(_localRouterAddress != address(0), "Router Address cannot be zero address");

GitHub: 111, 112, 113

File: src/RootPort.sol

130:          require(_bridgeAgentFactory != address(0), "Bridge Agent Factory cannot be 0 address.");

131:          require(_coreRootRouter != address(0), "Core Root Router cannot be 0 address.");

152:          require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0 address.");

153:          require(_coreLocalBranchBridgeAgent != address(0), "Core Local Branch Bridge Agent cannot be 0 address.");

154:          require(_localBranchPortAddress != address(0), "Local Branch Port Address cannot be 0 address.");

GitHub: 130, 131, 152, 153, 154

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

57:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent Address cannot be 0");

84            require(
85                msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
86:           );

87            require(
88                _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
89                "Root Bridge Agent Factory Address does not match."
90:           );

GitHub: 57, 84, 87

File: src/factories/BranchBridgeAgentFactory.sol

61:           require(_rootBridgeAgentFactoryAddress != address(0), "Root Bridge Agent Factory Address cannot be 0");

62            require(
63                _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
64                "Layerzero Endpoint Address cannot be the zero address."
65:           );

66:           require(_localCoreBranchRouterAddress != address(0), "Core Branch Router Address cannot be 0");

88:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0");

120           require(
121               msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
122:          );

123           require(
124               _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
125               "Root Bridge Agent Factory Address does not match."
126:          );

GitHub: 61, 62, 66, 88, 120, 123

[G‑09] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 24 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

/// @audit depositToPort(), withdrawFromPort()
31:   contract ArbitrumBranchBridgeAgent is BranchBridgeAgent {

GitHub: 31

File: src/BranchBridgeAgentExecutor.sol

/// @audit executeNoSettlement(), executeWithSettlement(), executeWithSettlementMultiple()
29:   contract BranchBridgeAgentExecutor is Ownable, BridgeAgentConstants {

GitHub: 29

File: src/CoreBranchRouter.sol

/// @audit addGlobalToken(), addLocalToken()
18:   contract CoreBranchRouter is ICoreBranchRouter, BaseBranchRouter {

GitHub: 18

File: src/CoreRootRouter.sol

/// @audit initialize(), addBranchToBridgeAgent(), toggleBranchBridgeAgentFactory(), removeBranchBridgeAgent(), manageStrategyToken(), managePortStrategy(), setCoreBranch()
38:   contract CoreRootRouter is IRootRouter, Ownable {

GitHub: 38

File: src/MulticallRootRouter.sol

/// @audit initialize(), executeDepositMultiple()
57:   contract MulticallRootRouter is IRootRouter, Ownable {

GitHub: 57

File: src/RootBridgeAgent.sol

/// @audit getFeeEstimate(), retrieveSettlement(), lzReceive()
32:   contract RootBridgeAgent is IRootBridgeAgent, BridgeAgentConstants {

GitHub: 32

File: src/RootBridgeAgentExecutor.sol

/// @audit executeSystemRequest(), executeNoDeposit(), executeWithDeposit(), executeWithDepositMultiple(), executeSignedNoDeposit(), executeSignedWithDeposit(), executeSignedWithDepositMultiple()
27:   contract RootBridgeAgentExecutor is Ownable, BridgeAgentConstants {

GitHub: 27

File: src/RootPort.sol

/// @audit initialize(), initializeCore(), isLocalToken()
16:   contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/factories/BranchBridgeAgentFactory.sol

/// @audit initialize(), createBridgeAgent()
19:   contract BranchBridgeAgentFactory is Ownable, IBranchBridgeAgentFactory {

GitHub: 19

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit initialize(), getHTokens(), createToken()
12:   contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

/// @audit initialize(), getHTokens(), createToken()
12:   contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {

GitHub: 12

File: src/interfaces/IArbitrumBranchPort.sol

/// @audit depositToPort(), withdrawFromPort()
16:   interface IArbitrumBranchPort is IBranchPort {

GitHub: 16

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit localPortAddress(), bridgeAgentExecutorAddress(), getDepositEntry(), getFeeEstimate(), callOutSystem(), callOut(), callOutAndBridge(), callOutAndBridgeMultiple(), callOutSigned(), callOutSignedAndBridge(), callOutSignedAndBridgeMultiple(), retryDeposit(), retrieveDeposit(), redeemDeposit(), retrySettlement(), clearToken(), clearTokens(), lzReceiveNonBlocking()
87:   interface IBranchBridgeAgent is ILayerZeroReceiver {

GitHub: 87

File: src/interfaces/IBranchPort.sol

/// @audit isBridgeAgent(), isStrategyToken(), isPortStrategy(), isBridgeAgentFactory(), manage(), replenishReserves(), replenishReserves(), withdraw(), bridgeIn(), bridgeInMultiple(), bridgeOut(), bridgeOutMultiple(), addBridgeAgent(), setCoreRouter(), addBridgeAgentFactory(), toggleBridgeAgentFactory(), toggleBridgeAgent(), addStrategyToken(), toggleStrategyToken(), addPortStrategy(), togglePortStrategy(), updatePortStrategy(), setCoreBranchRouter()
14:   interface IBranchPort {

GitHub: 14

File: src/interfaces/IBranchRouter.sol

/// @audit localPortAddress(), localBridgeAgentAddress(), bridgeAgentExecutorAddress(), callOut(), callOutAndBridge(), callOutAndBridgeMultiple(), getDepositEntry(), executeNoSettlement(), executeSettlement(), executeSettlementMultiple()
21:   interface IBranchRouter {

GitHub: 21

File: src/interfaces/ICoreBranchRouter.sol

/// @audit addGlobalToken(), addLocalToken()
27:   interface ICoreBranchRouter {

GitHub: 27

File: src/interfaces/IERC20hTokenRoot.sol

/// @audit localChainId(), factoryAddress(), getTokenBalance(), burn()
12:   interface IERC20hTokenRoot {

GitHub: 12

File: src/interfaces/ILayerZeroEndpoint.sol

/// @audit send(), receivePayload(), getInboundNonce(), getOutboundNonce(), estimateFees(), getChainId(), retryPayload(), hasStoredPayload(), getSendLibraryAddress(), getReceiveLibraryAddress(), isSendingPayload(), isReceivingPayload(), getConfig(), getSendVersion(), getReceiveVersion()
7:    interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {

GitHub: 7

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

/// @audit setConfig(), setSendVersion(), setReceiveVersion(), forceResumeReceive()
5:    interface ILayerZeroUserApplicationConfig {

GitHub: 5

File: src/interfaces/IRootBridgeAgent.sol

/// @audit settlementNonce(), getSettlementEntry(), bridgeAgentExecutorAddress(), factoryAddress(), getBranchBridgeAgent(), isBranchBridgeAgentAllowed(), getFeeEstimate(), callOut(), callOutAndBridge(), callOutAndBridgeMultiple(), retrySettlement(), retrieveSettlement(), redeemSettlement(), bridgeIn(), bridgeInMultiple(), lzReceiveNonBlocking(), approveBranchBridgeAgent(), syncBranchBridgeAgent()
94:   interface IRootBridgeAgent is ILayerZeroReceiver {

GitHub: 94

File: src/interfaces/IRootPort.sol

/// @audit bridgeAgentAddress(), hTokenFactoryAddress(), setCoreBranch()
10:   interface ICoreRootRouter {

/// @audit isChainId(), getBridgeAgentManager(), isBridgeAgentFactory(), isGlobalAddress(), getGlobalTokenFromLocal(), getLocalTokenFromGlobal(), getLocalTokenFromUnderlying(), getLocalToken(), getUnderlyingTokenFromLocal(), getUnderlyingTokenFromGlobal(), isGlobalToken(), isLocalToken(), isLocalToken(), isUnderlyingToken(), getUserAccount(), isRouterApproved(), bridgeToRoot(), bridgeToRootFromLocalBranch(), bridgeToLocalBranchFromRoot(), mintToLocalBranch(), burnFromLocalBranch(), setAddresses(), setLocalAddress(), fetchVirtualAccount(), toggleVirtualAccountApproved(), syncBranchBridgeAgentWithRoot(), addBridgeAgent(), toggleBridgeAgent(), addBridgeAgentFactory(), toggleBridgeAgentFactory(), addNewChain(), addEcosystemToken(), setCoreRootRouter(), setCoreBranchRouter(), syncNewCoreBranchRouter()
31:   interface IRootPort {

GitHub: 10, 31

File: src/interfaces/IRootRouter.sol

/// @audit executeResponse(), execute(), executeDepositSingle(), executeDepositMultiple(), executeSigned(), executeSignedDepositSingle(), executeSignedDepositMultiple()
14:   interface IRootRouter {

GitHub: 14

File: src/interfaces/IVirtualAccount.sol

/// @audit userAddress(), localPortAddress(), withdrawNative(), withdrawERC20(), withdrawERC721(), call(), payableCall()
27:   interface IVirtualAccount is IERC721Receiver {

GitHub: 27

[G‑10] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There is one instance of this issue:

File: src/BranchPort.sol

309:                  i++;

GitHub: 309

[G‑11] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

There are 21 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit uint32 depositNonce
140:          depositNonce = 1;

/// @audit uint32 nonce
601:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

/// @audit uint32 nonce
621:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

/// @audit uint32 nonce
643:              nonce = uint32(bytes4(_payload[22:26]));

/// @audit uint32 nonce
668:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

/// @audit uint32 nonce
683:              nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

/// @audit uint32 depositNonce
821:          depositNonce = _depositNonce + 1;

/// @audit uint32 depositNonce
875:          depositNonce = _depositNonce + 1;

GitHub: 140, 601, 621, 643, 668, 683, 821, 875

File: src/RootBridgeAgent.sol

/// @audit uint32 settlementNonce
121:          settlementNonce = 1;

/// @audit uint32 nonce
446:              nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

/// @audit uint32 nonce
469:              nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

/// @audit uint32 nonce
492:              nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

/// @audit uint32 nonce
515:              nonce = uint32(bytes4(_payload[2:6]));

/// @audit uint32 nonce
541:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

/// @audit uint32 nonce
579:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

/// @audit uint32 nonce
619:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED + PARAMS_START:PARAMS_START_SIGNED + PARAMS_TKN_START]));

/// @audit uint32 nonce
701:              nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

/// @audit uint32 nonce
720:              nonce = uint32(bytes4(_payload[PARAMS_START:PARAMS_TKN_START]));

/// @audit uint32 settlementNonce
978:          settlementNonce = _settlementNonce + 1;

/// @audit uint32 settlementNonce
1064:         settlementNonce = _settlementNonce + 1;

GitHub: 121, 446, 469, 492, 515, 541, 579, 619, 701, 720, 978, 1064

File: src/factories/ERC20hTokenBranchFactory.sol

/// @audit uint24 localChainId
46:           localChainId = _localChainId;

GitHub: 46

[G‑12] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There are 12 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

22:       uint16 public immutable localChainId;

GitHub: 22

File: src/BranchBridgeAgent.sol

53:       uint16 public immutable rootChainId;

56:       uint16 public immutable localChainId;

GitHub: 53, 56

File: src/CoreRootRouter.sol

47:       uint256 public immutable rootChainId;

GitHub: 47

File: src/MulticallRootRouter.sol

65:       uint256 public immutable localChainId;

GitHub: 65

File: src/RootBridgeAgent.sol

40:       uint16 public immutable localChainId;

GitHub: 40

File: src/RootPort.sol

34:       uint256 public immutable localChainId;

GitHub: 34

File: src/factories/BranchBridgeAgentFactory.sol

21:       uint16 public immutable localChainId;

24:       uint16 public immutable rootChainId;

GitHub: 21, 24

File: src/factories/ERC20hTokenBranchFactory.sol

14:       uint24 public immutable localChainId;

GitHub: 14

File: src/factories/ERC20hTokenRootFactory.sol

14:       uint16 public immutable localChainId;

GitHub: 14

File: src/factories/RootBridgeAgentFactory.sol

13:       uint16 public immutable rootChainId;

GitHub: 13

[G‑13] Inverting the condition of an if-else-statement wastes gas

Flipping the true and false blocks instead saves 3 gas

There is one instance of this issue:

File: src/CoreBranchRouter.sol

317           if (!IPort(_localPortAddress).isPortStrategy(_portStrategy, _underlyingToken)) {
318               // If Port Strategy is not active, add new Port Strategy.
319               IPort(_localPortAddress).addPortStrategy(_portStrategy, _underlyingToken, _dailyManagementLimit);
320           } else if (_isUpdateDailyLimit) {
321               // Or update daily limit.
322               IPort(_localPortAddress).updatePortStrategy(_portStrategy, _underlyingToken, _dailyManagementLimit);
323           } else {
324               // Or Toggle Port Strategy.
325               IPort(_localPortAddress).togglePortStrategy(_portStrategy, _underlyingToken);
326:          }

GitHub: 317

[G‑14] require() or revert() statements that check input arguments should be at the top of the function

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.

There are 11 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit expensive op on line 127
130:          require(_localRouterAddress != address(0), "Local Router Address cannot be the zero address.");

/// @audit expensive op on line 127
131:          require(_localPortAddress != address(0), "Local Port Address cannot be the zero address.");

GitHub: 130, 131

File: src/BranchPort.sol

/// @audit expensive op on line 124
126:          require(_coreBranchRouter != address(0), "CoreBranchRouter is zero address");

/// @audit expensive op on line 124
127:          require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address");

/// @audit expensive op on line 178
181:          require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed");

/// @audit expensive op on line 332
333:          require(_newCoreRouter != address(0), "New CoreRouter address is zero");

GitHub: 126, 127, 181, 333

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

/// @audit expensive op on line 85
87            require(
88                _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
89                "Root Bridge Agent Factory Address does not match."
90:           );

GitHub: 87

File: src/factories/BranchBridgeAgentFactory.sol

/// @audit expensive op on line 63
66:           require(_localCoreBranchRouterAddress != address(0), "Core Branch Router Address cannot be 0");

/// @audit expensive op on line 63
67:           require(_localPortAddress != address(0), "Port Address cannot be 0");

/// @audit expensive op on line 63
68:           require(_owner != address(0), "Owner cannot be 0");

/// @audit expensive op on line 121
123           require(
124               _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
125               "Root Bridge Agent Factory Address does not match."
126:          );

GitHub: 66, 67, 68, 123

[G‑15] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 49 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

39:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

GitHub: 39

File: src/BaseBranchRouter.sol

61:           require(_localBridgeAgentAddress != address(0), "Bridge Agent address cannot be 0");

GitHub: 61

File: src/BranchBridgeAgent.sol

125:          require(_rootBridgeAgentAddress != address(0), "Root Bridge Agent Address cannot be the zero address.");

126           require(
127               _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
128               "Layerzero Endpoint Address cannot be the zero address."
129:          );

130:          require(_localRouterAddress != address(0), "Local Router Address cannot be the zero address.");

131:          require(_localPortAddress != address(0), "Local Port Address cannot be the zero address.");

GitHub: 125, 126, 130, 131

File: src/BranchPort.sol

109:          require(_owner != address(0), "Owner is zero address");

123:          require(coreBranchRouterAddress == address(0), "Contract already initialized");

124:          require(!isBridgeAgentFactory[_bridgeAgentFactory], "Contract already initialized");

126:          require(_coreBranchRouter != address(0), "CoreBranchRouter is zero address");

127:          require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address");

181:          require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed");

213           require(
214               ERC20(_token).balanceOf(address(this)) - currBalance == amountToWithdraw, "Port Strategy Withdraw Failed"
215:          );

332:          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");

333:          require(_newCoreRouter != address(0), "New CoreRouter address is zero");

GitHub: 109, 123, 124, 126, 127, 181, 213, 332, 333

File: src/CoreRootRouter.sol

84:           require(_setup, "Contract is already initialized");

278:          require(msg.sender == rootPortAddress, "Only root port can call");

GitHub: 84, 278

File: src/MulticallRootRouter.sol

93:           require(_localPortAddress != address(0), "Local Port Address cannot be 0");

94:           require(_multicallAddress != address(0), "Multicall Address cannot be 0");

110:          require(_bridgeAgentAddress != address(0), "Bridge Agent Address cannot be 0");

GitHub: 93, 94, 110

File: src/RootBridgeAgent.sol

111:          require(_lzEndpointAddress != address(0), "Layerzero Enpoint Address cannot be zero address");

112:          require(_localPortAddress != address(0), "Port Address cannot be zero address");

113:          require(_localRouterAddress != address(0), "Router Address cannot be zero address");

GitHub: 111, 112, 113

File: src/RootPort.sol

130:          require(_bridgeAgentFactory != address(0), "Bridge Agent Factory cannot be 0 address.");

131:          require(_coreRootRouter != address(0), "Core Root Router cannot be 0 address.");

132:          require(_setup, "Setup ended.");

152:          require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0 address.");

153:          require(_coreLocalBranchBridgeAgent != address(0), "Core Local Branch Bridge Agent cannot be 0 address.");

154:          require(_localBranchPortAddress != address(0), "Local Branch Port Address cannot be 0 address.");

155:          require(isBridgeAgent[_coreRootBridgeAgent], "Core Bridge Agent doesn't exist.");

156:          require(_setupCore, "Core Setup ended.");

GitHub: 130, 131, 132, 152, 153, 154, 155, 156

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

57:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent Address cannot be 0");

84            require(
85                msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
86:           );

87            require(
88                _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
89                "Root Bridge Agent Factory Address does not match."
90:           );

GitHub: 57, 84, 87

File: src/factories/BranchBridgeAgentFactory.sol

61:           require(_rootBridgeAgentFactoryAddress != address(0), "Root Bridge Agent Factory Address cannot be 0");

62            require(
63                _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
64                "Layerzero Endpoint Address cannot be the zero address."
65:           );

66:           require(_localCoreBranchRouterAddress != address(0), "Core Branch Router Address cannot be 0");

67:           require(_localPortAddress != address(0), "Port Address cannot be 0");

68:           require(_owner != address(0), "Owner cannot be 0");

88:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0");

120           require(
121               msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
122:          );

123           require(
124               _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
125               "Root Bridge Agent Factory Address does not match."
126:          );

GitHub: 61, 62, 66, 67, 68, 88, 120, 123

File: src/factories/ERC20hTokenBranchFactory.sol

43:           require(_localPortAddress != address(0), "Port address cannot be 0");

61:           require(_coreRouter != address(0), "CoreRouter address cannot be 0");

GitHub: 43, 61

File: src/factories/ERC20hTokenRootFactory.sol

35:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

50:           require(_coreRouter != address(0), "CoreRouter address cannot be 0");

GitHub: 35, 50

File: src/factories/RootBridgeAgentFactory.sol

32:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

GitHub: 32

File: src/token/ERC20hTokenRoot.sol

39:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

40:           require(_factoryAddress != address(0), "Factory Address cannot be 0");

GitHub: 39, 40

[G‑16] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 21 instances of this issue:

see instances
File: src/BaseBranchRouter.sol

60:       function initialize(address _localBridgeAgentAddress) external onlyOwner {

GitHub: 60

File: src/BranchPort.sol

122:      function initialize(address _coreBranchRouter, address _bridgeAgentFactory) external virtual onlyOwner {

GitHub: 122

File: src/CoreRootRouter.sol

83:       function initialize(address _bridgeAgentAddress, address _hTokenFactory) external onlyOwner {

GitHub: 83

File: src/MulticallRootRouter.sol

109:      function initialize(address _bridgeAgentAddress) external onlyOwner {

GitHub: 109

File: src/RootPort.sol

129:      function initialize(address _bridgeAgentFactory, address _coreRootRouter) external onlyOwner {

147       function initializeCore(
148           address _coreRootBridgeAgent,
149           address _coreLocalBranchBridgeAgent,
150           address _localBranchPortAddress
151:      ) external onlyOwner {

414:      function toggleBridgeAgent(address _bridgeAgent) external override onlyOwner {

421:      function addBridgeAgentFactory(address _bridgeAgentFactory) external override onlyOwner {

431:      function toggleBridgeAgentFactory(address _bridgeAgentFactory) external override onlyOwner {

438       function addNewChain(
439           address _coreBranchBridgeAgentAddress,
440           uint256 _chainId,
441           string memory _wrappedGasTokenName,
442           string memory _wrappedGasTokenSymbol,
443           uint8 _wrappedGasTokenDecimals,
444           address _newLocalBranchWrappedNativeTokenAddress,
445           address _newUnderlyingBranchWrappedNativeTokenAddress
446:      ) external override onlyOwner {

483:      function addEcosystemToken(address _ecoTokenGlobalAddress) external override onlyOwner {

509:      function setCoreRootRouter(address _coreRootRouter, address _coreRootBridgeAgent) external override onlyOwner {

539       function syncNewCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent, uint16 _dstChainId)
540           external
541           override
542:          onlyOwner

GitHub: 129, 147, 414, 421, 431, 438, 483, 509, 539

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

56:       function initialize(address _coreRootBridgeAgent) external override onlyOwner {

GitHub: 56

File: src/factories/BranchBridgeAgentFactory.sol

87:       function initialize(address _coreRootBridgeAgent) external virtual onlyOwner {

GitHub: 87

File: src/factories/ERC20hTokenBranchFactory.sol

60:       function initialize(address _wrappedNativeTokenAddress, address _coreRouter) external onlyOwner {

GitHub: 60

File: src/factories/ERC20hTokenRootFactory.sol

49:       function initialize(address _coreRouter) external onlyOwner {

GitHub: 49

File: src/token/ERC20hTokenBranch.sol

29:       function mint(address account, uint256 amount) external override onlyOwner returns (bool) {

35:       function burn(uint256 amount) public override onlyOwner {

GitHub: 29, 35

File: src/token/ERC20hTokenRoot.sol

57:       function mint(address to, uint256 amount, uint256 chainId) external onlyOwner returns (bool) {

69:       function burn(address from, uint256 amount, uint256 chainId) external onlyOwner {

GitHub: 57, 69

[G‑17] Constructors can be marked payable

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

There are 22 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

43        constructor(
44            uint16 _localChainId,
45            address _rootBridgeAgentAddress,
46            address _localRouterAddress,
47            address _localPortAddress
48        )
49            BranchBridgeAgent(
50                _localChainId,
51                _localChainId,
52                _rootBridgeAgentAddress,
53                address(0),
54                _localRouterAddress,
55                _localPortAddress
56:           )

GitHub: 43

File: src/ArbitrumBranchPort.sol

38:       constructor(uint16 _localChainId, address _rootPortAddress, address _owner) BranchPort(_owner) {

GitHub: 38

File: src/ArbitrumCoreBranchRouter.sol

44:       constructor() CoreBranchRouter(address(0)) {}

GitHub: 44

File: src/BaseBranchRouter.sol

48        constructor() {
49:           _initializeOwner(msg.sender);

GitHub: 48

File: src/BranchBridgeAgent.sol

117       constructor(
118           uint16 _rootChainId,
119           uint16 _localChainId,
120           address _rootBridgeAgentAddress,
121           address _lzEndpointAddress,
122           address _localRouterAddress,
123:          address _localPortAddress

GitHub: 117

File: src/BranchBridgeAgentExecutor.sol

39        constructor() {
40:           _initializeOwner(msg.sender);

GitHub: 39

File: src/BranchPort.sol

108:      constructor(address _owner) {

GitHub: 108

File: src/CoreBranchRouter.sol

30:       constructor(address _hTokenFactoryAddress) BaseBranchRouter() {

GitHub: 30

File: src/CoreRootRouter.sol

71:       constructor(uint256 _rootChainId, address _rootPortAddress) {

GitHub: 71

File: src/MulticallRootRouter.sol

92:       constructor(uint256 _localChainId, address _localPortAddress, address _multicallAddress) {

GitHub: 92

File: src/MulticallRootRouterLibZip.sol

29        constructor(uint256 _localChainId, address _localPortAddress, address _multicallAddress)
30:           MulticallRootRouter(_localChainId, _localPortAddress, _multicallAddress)

GitHub: 29

File: src/RootBridgeAgent.sol

105       constructor(
106           uint16 _localChainId,
107           address _lzEndpointAddress,
108           address _localPortAddress,
109:          address _localRouterAddress

GitHub: 105

File: src/RootBridgeAgentExecutor.sol

35:       constructor(address _rootBridgeAgent) {

GitHub: 35

File: src/RootPort.sol

111:      constructor(uint256 _localChainId) {

GitHub: 111

File: src/VirtualAccount.sol

35:       constructor(address _userAddress, address _localPortAddress) {

GitHub: 35

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

30        constructor(
31            uint16 _rootChainId,
32            address _rootBridgeAgentFactoryAddress,
33            address _localCoreBranchRouterAddress,
34            address _localPortAddress,
35            address _owner
36        )
37            BranchBridgeAgentFactory(
38                _rootChainId,
39                _rootChainId,
40                _rootBridgeAgentFactoryAddress,
41                address(0),
42                _localCoreBranchRouterAddress,
43                _localPortAddress,
44                _owner
45:           )

GitHub: 30

File: src/factories/BranchBridgeAgentFactory.sol

52        constructor(
53            uint16 _localChainId,
54            uint16 _rootChainId,
55            address _rootBridgeAgentFactoryAddress,
56            address _lzEndpointAddress,
57            address _localCoreBranchRouterAddress,
58            address _localPortAddress,
59:           address _owner

GitHub: 52

File: src/factories/ERC20hTokenBranchFactory.sol

42:       constructor(uint16 _localChainId, address _localPortAddress, string memory _chainName, string memory _chainSymbol) {

GitHub: 42

File: src/factories/ERC20hTokenRootFactory.sol

34:       constructor(uint16 _localChainId, address _rootPortAddress) {

GitHub: 34

File: src/factories/RootBridgeAgentFactory.sol

31:       constructor(uint16 _rootChainId, address _lzEndpointAddress, address _rootPortAddress) {

GitHub: 31

File: src/token/ERC20hTokenBranch.sol

13        constructor(
14            string memory chainName,
15            string memory chainSymbol,
16            string memory _name,
17            string memory _symbol,
18            uint8 _decimals,
19            address _owner
20:       ) ERC20(string(string.concat(chainName, _name)), string(string.concat(chainSymbol, _symbol)), _decimals) {

GitHub: 13

File: src/token/ERC20hTokenRoot.sol

31        constructor(
32            uint16 _localChainId,
33            address _factoryAddress,
34            address _rootPortAddress,
35            string memory _name,
36            string memory _symbol,
37            uint8 _decimals
38:       ) ERC20(string(string.concat(_name)), string(string.concat(_symbol)), _decimals) {

GitHub: 31

[G‑18] Reduce gas usage by moving to Solidity 0.8.19 or later

See this link for the full details

There are 44 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/ArbitrumBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/ArbitrumCoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BaseBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/CoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/CoreRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouterLibZip.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/VirtualAccount.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/BranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenBranchFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenRootFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/RootBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/BridgeAgentConstants.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/BridgeAgentStructs.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IArbitrumBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IBranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IBranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/ICoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenBranch.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenBranchFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenRoot.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenRootFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/ILayerZeroEndpoint.sol

3:   pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroReceiver.sol

3:   pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

3:   pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/IMulticall2.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IPortStrategy.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IRootBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IRootBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IRootPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IVirtualAccount.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenBranch.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenRoot.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

[G‑19] >= costs less gas than >

The compiler uses opcodes GT and ISZERO for solidity code that uses >, but only requires LT for >=, which saves 3 gas. If < is being used, the condition can be inverted.

There are 33 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

127:         if (_deposit > 0) {

132:         if (_amount - _deposit > 0) {

GitHub: 127, 132

File: src/BaseBranchRouter.sol

165:         if (_amount - _deposit > 0) {

173:         if (_deposit > 0) {

GitHub: 165, 173

File: src/BranchBridgeAgent.sol

383:         } else if (uint8(deposit.hTokens.length) > 1) {

869:         if (_hTokens.length > MAX_TOKENS_LENGTH) revert InvalidInput();

906:         if (_amount - _deposit > 0) {

912:         if (_deposit > 0) {

GitHub: 383, 869, 906, 912

File: src/BranchBridgeAgentExecutor.sol

87:          if (_payload.length > PARAMS_SETTLEMENT_OFFSET) {

119:         if (_payload.length > settlementEndOffset) {

GitHub: 87, 119

File: src/BranchPort.sol

149:         if (_amount > _excessReserves(_strategyTokenDebt, _token)) revert InsufficientReserves();

259:             if (_amounts[i] - _deposits[i] > 0) {

266:             if (_deposits[i] > 0) {

299:         if (length > 255) revert InvalidInputArrays();

525:         if (_hTokenAmount > 0) {

531:         if (_deposit > 0) {

GitHub: 149, 259, 266, 299, 525, 531

File: src/RootBridgeAgent.sol

357:         if (_dParams.amount < _dParams.deposit) revert InvalidInputParams();

363:         if (_dParams.amount > 0) {

370:         if (_dParams.deposit > 0) {

396:         if (length > MAX_TOKENS_LENGTH) revert InvalidInputParams();

891:         } else if (_hTokens.length > 1) {

1057:        if (_globalAddresses.length > MAX_TOKENS_LENGTH) revert InvalidInputParamsLength();

1142:        if (_amount < _deposit) revert InvalidInputParams();

1146:        if (_underlyingAddress == address(0)) if (_deposit > 0) revert UnrecognizedUnderlyingAddress();

1149:        if (_amount - _deposit > 0) {

1156:        if (_deposit > 0) {

1158:            if (IERC20hTokenRoot(_globalAddress).getTokenBalance(_dstChainId) < _deposit) {

GitHub: 357, 363, 370, 396, 891, 1057, 1142, 1146, 1149, 1156, 1158

File: src/RootBridgeAgentExecutor.sol

100:         if (_payload.length > PARAMS_TKN_SET_SIZE) {

134:         if (length > PARAMS_END_OFFSET + (numOfAssets * PARAMS_TKN_SET_SIZE_MULTIPLE)) {

185:         if (_payload.length > PARAMS_SETTLEMENT_OFFSET) {

220              _payload.length
221                  > PARAMS_END_SIGNED_OFFSET
222:                     + uint256(uint8(bytes1(_payload[PARAMS_START_SIGNED]))) * PARAMS_TKN_SET_SIZE_MULTIPLE

GitHub: 100, 134, 185, 220

File: src/RootPort.sol

284:         if (_amount - _deposit > 0) {

290:         if (_deposit > 0) if (!ERC20hTokenRoot(_hToken).mint(_recipient, _deposit, _srcChainId)) revert UnableToMint();

GitHub: 284, 290

[G‑20] Nesting if-statements is cheaper than using &&

Nesting if-statements avoids the stack operations of setting up and using an extra jumpdest, and saves 6 gas

There is one instance of this issue:

File: src/RootBridgeAgent.sol

1141:        if (_amount == 0 && _deposit == 0) revert InvalidInputParams();

GitHub: 1141

[G‑21] Use assembly to emit events, in order to save gas

Using the scratch space for event arguments (two words or fewer) will save gas over needing Solidity's full abi memory expansion used for emitting normally.

There are 19 instances of this issue:

File: src/BranchBridgeAgent.sol

689:             emit LogFallback(nonce);

700:         emit LogExecute(nonce);

GitHub: 689, 700

File: src/BranchPort.sol

344:         emit BridgeAgentFactoryAdded(_newBridgeAgentFactory);

351:         emit BridgeAgentFactoryToggled(_newBridgeAgentFactory);

358:         emit BridgeAgentToggled(_bridgeAgent);

371:         emit StrategyTokenAdded(_token, _minimumReservesRatio);

378:         emit StrategyTokenToggled(_token);

399:         emit PortStrategyToggled(_portStrategy, _token);

423:         emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);

GitHub: 344, 351, 358, 371, 378, 399, 423

File: src/RootBridgeAgent.sol

726:             emit LogFallback(nonce, _srcChainId);

736:         emit LogExecute(nonce, _srcChainId);

GitHub: 726, 736

File: src/RootPort.sol

365:         emit VirtualAccountCreated(_user, address(newAccount));

389:         emit BridgeAgentAdded(_bridgeAgent, _manager);

417:         emit BridgeAgentToggled(_bridgeAgent);

427:         emit BridgeAgentFactoryAdded(_bridgeAgentFactory);

434:         emit BridgeAgentFactoryToggled(_bridgeAgentFactory);

479:         emit NewChainAdded(_chainId);

505:         emit EcosystemTokenAdded(_ecoTokenGlobalAddress);

517:         emit CoreRootSet(_coreRootRouter, _coreRootBridgeAgent);

GitHub: 365, 389, 417, 427, 434, 479, 505, 517

[G‑22] Use += for mappings

Using += for mappings saves 40 gas due to not having to recalculate the mapping's value's hash

There are 5 instances of this issue:

File: src/BranchPort.sol

349:         isBridgeAgentFactory[_newBridgeAgentFactory] = !isBridgeAgentFactory[_newBridgeAgentFactory];

356:         isBridgeAgent[_bridgeAgent] = !isBridgeAgent[_bridgeAgent];

376:         isStrategyToken[_token] = !isStrategyToken[_token];

GitHub: 349, 356, 376

File: src/RootPort.sol

415:         isBridgeAgent[_bridgeAgent] = !isBridgeAgent[_bridgeAgent];

432:         isBridgeAgentFactory[_bridgeAgentFactory] = !isBridgeAgentFactory[_bridgeAgentFactory];

GitHub: 415, 432

[G‑23] Using bools for storage incurs overhead

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(0) and uint256(1) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD

There are 13 instances of this issue:

File: src/BranchPort.sol

32:      mapping(address bridgeAgent => bool isActiveBridgeAgent) public isBridgeAgent;

42:      mapping(address bridgeAgentFactory => bool isActiveBridgeAgentFactory) public isBridgeAgentFactory;

52:      mapping(address token => bool allowsStrategies) public isStrategyToken;

68:      mapping(address strategy => mapping(address token => bool isActiveStrategy)) public isPortStrategy;

GitHub: 32, 42, 52, 68

File: src/CoreRootRouter.sol

44:      bool internal _setup;

GitHub: 44

File: src/RootBridgeAgent.sol

68:      mapping(uint256 chainId => bool allowed) public isBranchBridgeAgentAllowed;

GitHub: 68

File: src/RootPort.sol

24:      bool internal _setup;

27:      bool internal _setupCore;

54:      mapping(VirtualAccount acount => mapping(address router => bool allowed)) public isRouterApproved;

61:      mapping(uint256 chainId => bool isActive) public isChainId;

64:      mapping(address bridgeAgent => bool isActive) public isBridgeAgent;

77:      mapping(address bridgeAgentFactory => bool isActive) public isBridgeAgentFactory;

87:      mapping(address token => bool isGlobalToken) public isGlobalAddress;

GitHub: 24, 27, 54, 61, 64, 77, 87

[G‑24] Use uint256(1)/uint256(2) instead of true/false to save gas for changes

Avoids a Gsset (20000 gas) when changing from false to true, after having been true in the past

There are 13 instances of this issue:

File: src/BranchPort.sol

32:      mapping(address bridgeAgent => bool isActiveBridgeAgent) public isBridgeAgent;

42:      mapping(address bridgeAgentFactory => bool isActiveBridgeAgentFactory) public isBridgeAgentFactory;

52:      mapping(address token => bool allowsStrategies) public isStrategyToken;

68:      mapping(address strategy => mapping(address token => bool isActiveStrategy)) public isPortStrategy;

GitHub: 32, 42, 52, 68

File: src/CoreRootRouter.sol

44:      bool internal _setup;

GitHub: 44

File: src/RootBridgeAgent.sol

68:      mapping(uint256 chainId => bool allowed) public isBranchBridgeAgentAllowed;

GitHub: 68

File: src/RootPort.sol

24:      bool internal _setup;

27:      bool internal _setupCore;

54:      mapping(VirtualAccount acount => mapping(address router => bool allowed)) public isRouterApproved;

61:      mapping(uint256 chainId => bool isActive) public isChainId;

64:      mapping(address bridgeAgent => bool isActive) public isBridgeAgent;

77:      mapping(address bridgeAgentFactory => bool isActive) public isBridgeAgentFactory;

87:      mapping(address token => bool isGlobalToken) public isGlobalAddress;

GitHub: 24, 27, 54, 61, 64, 77, 87

[G‑25] Simple checks for zero can be done using assembly to save gas

There are 25 instances of this issue:

File: src/ArbitrumBranchPort.sol

63:          if (_globalToken == address(0)) revert UnknownGlobalToken();

90:          if (_underlyingAddress == address(0)) revert UnknownUnderlyingToken();

GitHub: 63, 90

File: src/BranchBridgeAgent.sol

412:         if (payload.length == 0) revert DepositRetryUnavailableUseCallout();

440:         if (deposit.owner == address(0)) revert DepositRedeemUnavailable();

GitHub: 412, 440

File: src/RootBridgeAgent.sol

244:         if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable();

282:         if (settlementOwner == address(0)) revert SettlementRetrieveUnavailable();

308:         if (settlementOwner == address(0)) revert SettlementRedeemUnavailable();

670:             if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable();

818:         if (callee == address(0)) revert UnrecognizedBridgeAgent();

871:         if (_hTokens.length == 0) revert SettlementRetryUnavailableUseCallout();

910:         if (callee == address(0)) revert UnrecognizedBridgeAgent();

1145:        if (_localAddress == address(0)) revert UnrecognizedLocalAddress();

1146:        if (_underlyingAddress == address(0)) if (_deposit > 0) revert UnrecognizedUnderlyingAddress();

GitHub: 244, 282, 308, 670, 818, 871, 910, 1145, 1146

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();

264:         if (_localAddress == address(0)) revert InvalidLocalAddress();

352:         if (address(account) == address(0)) account = addVirtualAccount(_user);

360:         if (_user == address(0)) revert InvalidUserAddress();

510:         if (_coreRootRouter == address(0)) revert InvalidCoreRootRouter();

511:         if (_coreRootBridgeAgent == address(0)) revert InvalidCoreRootBridgeAgent();

528:         if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter();

529:         if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();

544:         if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter();

545:         if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();

GitHub: 245, 246, 247, 264, 352, 360, 510, 511, 528, 529, 544, 545

[G‑26] Avoid updating storage when the value hasn't changed

If the old value is equal to the new value, not re-storing the value will avoid a Gsreset (2900 gas), potentially at the expense of a Gcoldsload (2100 gas) or a Gwarmaccess (100 gas)

There are 11 instances of this issue:

File: src/BranchPort.sol

331      function setCoreRouter(address _newCoreRouter) external override requiresCoreRouter {
332          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");
333          require(_newCoreRouter != address(0), "New CoreRouter address is zero");
334          coreBranchRouterAddress = _newCoreRouter;
335:     }

362      function addStrategyToken(address _token, uint256 _minimumReservesRatio) external override requiresCoreRouter {
363          if (_minimumReservesRatio >= DIVISIONER || _minimumReservesRatio < MIN_RESERVE_RATIO) {
364              revert InvalidMinimumReservesRatio();
365          }
366  
367          strategyTokens.push(_token);
368          getMinimumTokenReserveRatio[_token] = _minimumReservesRatio;
369          isStrategyToken[_token] = true;
370  
371          emit StrategyTokenAdded(_token, _minimumReservesRatio);
372:     }

382      function addPortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
383          external
384          override
385          requiresCoreRouter
386      {
387          if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();
388          portStrategies.push(_portStrategy);
389          strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;
390          isPortStrategy[_portStrategy][_token] = true;
391  
392          emit PortStrategyAdded(_portStrategy, _token, _dailyManagementLimit);
393:     }

403      function updatePortStrategy(address _portStrategy, address _token, uint256 _dailyManagementLimit)
404          external
405          override
406          requiresCoreRouter
407      {
408          strategyDailyLimitAmount[_portStrategy][_token] = _dailyManagementLimit;
409  
410          emit PortStrategyUpdated(_portStrategy, _token, _dailyManagementLimit);
411:     }

414      function setCoreBranchRouter(address _coreBranchRouter, address _coreBranchBridgeAgent)
415          external
416          override
417          requiresCoreRouter
418      {
419          coreBranchRouterAddress = _coreBranchRouter;
420          isBridgeAgent[_coreBranchBridgeAgent] = true;
421          bridgeAgents.push(_coreBranchBridgeAgent);
422  
423          emit CoreBranchSet(_coreBranchRouter, _coreBranchBridgeAgent);
424:     }

GitHub: 331, 362, 382, 403, 414

File: src/RootPort.sol

239      function setAddresses(
240          address _globalAddress,
241          address _localAddress,
242          address _underlyingAddress,
243          uint256 _srcChainId
244      ) external override requiresCoreRootRouter {
245          if (_globalAddress == address(0)) revert InvalidGlobalAddress();
246          if (_localAddress == address(0)) revert InvalidLocalAddress();
247          if (_underlyingAddress == address(0)) revert InvalidUnderlyingAddress();
248  
249          isGlobalAddress[_globalAddress] = true;
250          getGlobalTokenFromLocal[_localAddress][_srcChainId] = _globalAddress;
251          getLocalTokenFromGlobal[_globalAddress][_srcChainId] = _localAddress;
252          getLocalTokenFromUnderlying[_underlyingAddress][_srcChainId] = _localAddress;
253          getUnderlyingTokenFromLocal[_localAddress][_srcChainId] = _underlyingAddress;
254  
255          emit LocalTokenAdded(_underlyingAddress, _localAddress, _globalAddress, _srcChainId);
256:     }

259      function setLocalAddress(address _globalAddress, address _localAddress, uint256 _srcChainId)
260          external
261          override
262          requiresCoreRootRouter
263      {
264          if (_localAddress == address(0)) revert InvalidLocalAddress();
265  
266          getGlobalTokenFromLocal[_localAddress][_srcChainId] = _globalAddress;
267          getLocalTokenFromGlobal[_globalAddress][_srcChainId] = _localAddress;
268  
269          emit GlobalTokenAdded(_localAddress, _globalAddress, _srcChainId);
270:     }

382      function addBridgeAgent(address _manager, address _bridgeAgent) external override requiresBridgeAgentFactory {
383          if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent();
384  
385          bridgeAgents.push(_bridgeAgent);
386          getBridgeAgentManager[_bridgeAgent] = _manager;
387          isBridgeAgent[_bridgeAgent] = true;
388  
389          emit BridgeAgentAdded(_bridgeAgent, _manager);
390:     }

438      function addNewChain(
439          address _coreBranchBridgeAgentAddress,
440          uint256 _chainId,
441          string memory _wrappedGasTokenName,
442          string memory _wrappedGasTokenSymbol,
443          uint8 _wrappedGasTokenDecimals,
444          address _newLocalBranchWrappedNativeTokenAddress,
445          address _newUnderlyingBranchWrappedNativeTokenAddress
446      ) external override onlyOwner {
447          // Check if chain already added
448          if (isChainId[_chainId]) revert AlreadyAddedChain();
449  
450          // Create new global token for new chain's wrapped native token
451          address newGlobalToken = address(
452              IERC20hTokenRootFactory(ICoreRootRouter(coreRootRouterAddress).hTokenFactoryAddress()).createToken(
453                  _wrappedGasTokenName, _wrappedGasTokenSymbol, _wrappedGasTokenDecimals
454              )
455          );
456  
457          // Sync new branch bridge agent with root core bridge agent
458          IBridgeAgent(ICoreRootRouter(coreRootRouterAddress).bridgeAgentAddress()).syncBranchBridgeAgent(
459              _coreBranchBridgeAgentAddress, _chainId
460          );
461  
462          // Update State
463  
464          // 1. Add new chain to chainId mapping
465          isChainId[_chainId] = true;
466          // 2. Add new chain to global address mapping
467          isGlobalAddress[newGlobalToken] = true;
468          // 3. Add new branch local token to global token address mapping
469          getGlobalTokenFromLocal[_newLocalBranchWrappedNativeTokenAddress][_chainId] = newGlobalToken;
470          // 4. Add new global token to branch local token address mapping
471          getLocalTokenFromGlobal[newGlobalToken][_chainId] = _newLocalBranchWrappedNativeTokenAddress;
472          // 5. Add new branch underlying token to branch local token address mapping
473          getLocalTokenFromUnderlying[_newUnderlyingBranchWrappedNativeTokenAddress][_chainId] =
474              _newLocalBranchWrappedNativeTokenAddress;
475          // 6. Add new branch local token to branch underlying token address mapping
476          getUnderlyingTokenFromLocal[_newLocalBranchWrappedNativeTokenAddress][_chainId] =
477              _newUnderlyingBranchWrappedNativeTokenAddress;
478  
479          emit NewChainAdded(_chainId);
480:     }

483      function addEcosystemToken(address _ecoTokenGlobalAddress) external override onlyOwner {
484          // Check if token already added
485          if (isGlobalAddress[_ecoTokenGlobalAddress]) revert AlreadyAddedEcosystemToken();
486  
487          // Check if token is already a underlying token in current chain
488          if (getUnderlyingTokenFromLocal[_ecoTokenGlobalAddress][localChainId] != address(0)) {
489              revert AlreadyAddedEcosystemToken();
490          }
491  
492          // Check if token is already a local branch token in current chain
493          if (getLocalTokenFromUnderlying[_ecoTokenGlobalAddress][localChainId] != address(0)) {
494              revert AlreadyAddedEcosystemToken();
495          }
496  
497          // Update State
498          // 1. Add new global token to global address mapping
499          isGlobalAddress[_ecoTokenGlobalAddress] = true;
500          // 2. Add new branch local token address to global token mapping
501          getGlobalTokenFromLocal[_ecoTokenGlobalAddress][localChainId] = _ecoTokenGlobalAddress;
502          // 3. Add new global token to branch local token address mapping
503          getLocalTokenFromGlobal[_ecoTokenGlobalAddress][localChainId] = _ecoTokenGlobalAddress;
504  
505          emit EcosystemTokenAdded(_ecoTokenGlobalAddress);
506:     }

509      function setCoreRootRouter(address _coreRootRouter, address _coreRootBridgeAgent) external override onlyOwner {
510          if (_coreRootRouter == address(0)) revert InvalidCoreRootRouter();
511          if (_coreRootBridgeAgent == address(0)) revert InvalidCoreRootBridgeAgent();
512  
513          coreRootRouterAddress = _coreRootRouter;
514          coreRootBridgeAgentAddress = _coreRootBridgeAgent;
515          getBridgeAgentManager[_coreRootBridgeAgent] = owner();
516  
517          emit CoreRootSet(_coreRootRouter, _coreRootBridgeAgent);
518:     }

GitHub: 239, 259, 382, 438, 483, 509

[G‑27] unchecked {} can be used on the division of two uints in order to save gas

The division cannot overflow, since both the numerator and the denominator are non-negative

There is one instance of this issue:

File: src/BranchPort.sol

473:         return ((_currBalance + _strategyTokenDebt) * getMinimumTokenReserveRatio[_token]) / DIVISIONER;

GitHub: 473

[G‑28] Using this to access functions results in an external call, wasting gas

External calls have an overhead of 100 gas, which can be avoided by not referencing the function using this. Contracts are allowed to override their parents' functions and change the visibility from external to public, so make this change if it's required in order to call the function internally.

There are 2 instances of this issue:

File: src/BranchBridgeAgent.sol

579:         address(this).excessivelySafeCall(

GitHub: 579

File: src/RootBridgeAgent.sol

424:         (bool success,) = address(this).excessivelySafeCall(

GitHub: 424

[G‑29] Use assembly for small keccak256 hashes, in order to save gas

If the arguments to the encode call can fit into the scratch space (two words or fewer), then it's more efficient to use assembly to generate the hash (80 gas): keccak256(abi.encodePacked(x, y)) -> assembly {mstore(0x00, a); mstore(0x20, b); let hash := keccak256(0x00, 0x40); }

There is one instance of this issue:

File: src/RootPort.sol

362:         newAccount = new VirtualAccount{salt: keccak256(abi.encode(_user))}(_user, address(this));

GitHub: 362

[G‑30] Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to copy read each index of the calldata to memory. Each copy costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obviates the need for copies of words of the struct/array not being read. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracts to use calldata arguments instead.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved

There are 23 instances of this issue:

see instances
File: src/BranchBridgeAgent.sol

/// @audit callOutAndBridge(_dParams)
212:         DepositInput memory _dParams,

/// @audit callOutAndBridgeMultiple(_dParams)
234:         DepositMultipleInput memory _dParams,

/// @audit callOutSignedAndBridge(_dParams)
279:         DepositInput memory _dParams,

/// @audit callOutSignedAndBridgeMultiple(_dParams)
309:         DepositMultipleInput memory _dParams,

GitHub: 212, 234, 279, 309

File: src/BranchPort.sol

/// @audit bridgeInMultiple(_amounts)
250:         uint256[] memory _amounts,

/// @audit bridgeInMultiple(_deposits)
251:         uint256[] memory _deposits

/// @audit bridgeOutMultiple(_amounts)
292:         uint256[] memory _amounts,

/// @audit bridgeOutMultiple(_deposits)
293:         uint256[] memory _deposits

GitHub: 250, 251, 292, 293

File: src/RootBridgeAgent.sol

/// @audit bridgeIn(_dParams)
351:     function bridgeIn(address _recipient, DepositParams memory _dParams, uint256 _srcChainId)

GitHub: 351

File: src/interfaces/IBranchBridgeAgent.sol

/// @audit callOutAndBridge(depositParams)
166:         DepositInput memory depositParams,

/// @audit callOutAndBridgeMultiple(depositParams)
182:         DepositMultipleInput memory depositParams,

/// @audit callOutSignedAndBridge(depositParams)
211:         DepositInput memory depositParams,

/// @audit callOutSignedAndBridgeMultiple(depositParams)
230:         DepositMultipleInput memory depositParams,

GitHub: 166, 182, 211, 230

File: src/interfaces/IBranchPort.sol

/// @audit bridgeInMultiple(_amounts)
107:         uint256[] memory _amounts,

/// @audit bridgeInMultiple(_deposits)
108:         uint256[] memory _deposits

/// @audit bridgeOutMultiple(_amounts)
139:         uint256[] memory _amounts,

/// @audit bridgeOutMultiple(_deposits)
140:         uint256[] memory _deposits

GitHub: 107, 108, 139, 140

File: src/interfaces/IMulticall2.sol

/// @audit aggregate(calls)
20:      function aggregate(Call[] memory calls) external returns (uint256 blockNumber, bytes[] memory returnData);

GitHub: 20

File: src/interfaces/IRootBridgeAgent.sol

/// @audit bridgeIn(_dParams)
271:     function bridgeIn(address _recipient, DepositParams memory _dParams, uint256 _srcChainId) external;

GitHub: 271

File: src/interfaces/IRootRouter.sol

/// @audit executeDepositSingle(dParams)
42:      function executeDepositSingle(bytes memory params, DepositParams memory dParams, uint16 srcChainId)

/// @audit executeDepositMultiple(dParams)
53:      function executeDepositMultiple(bytes memory params, DepositMultipleParams memory dParams, uint16 srcChainId)

/// @audit executeSignedDepositSingle(dParams)
74:          DepositParams memory dParams,

/// @audit executeSignedDepositMultiple(dParams)
88:          DepositMultipleParams memory dParams,

GitHub: 42, 53, 74, 88

[G‑31] Avoid transferring amounts of zero in order to save gas

Skipping the external call when nothing will be transferred, will save at least 100 gas

There are 10 instances of this issue:

File: src/ArbitrumBranchPort.sol

66:          _underlyingAddress.safeTransferFrom(_depositor, address(this), _deposit);

94:          _underlyingAddress.safeTransfer(_recipient, _amount);

GitHub: 66, 94

File: src/BaseBranchRouter.sol

167:                 _hToken.safeTransferFrom(msg.sender, address(this), _amount - _deposit);

GitHub: 167

File: src/BranchPort.sol

233:         _underlyingAddress.safeTransfer(_recipient, _deposit);

GitHub: 233

File: src/RootBridgeAgent.sol

1151:                _globalAddress.safeTransferFrom(_depositor, localPortAddress, _amount - _deposit);

GitHub: 1151

File: src/RootPort.sol

286:                 _hToken.safeTransfer(_recipient, _amount - _deposit);

301:         _hToken.safeTransferFrom(_from, address(this), _amount);

311:         _hToken.safeTransfer(_to, _amount);

GitHub: 286, 301, 311

File: src/VirtualAccount.sol

57:          _token.safeTransfer(msg.sender, _amount);

62:          ERC721(_token).transferFrom(address(this), msg.sender, _tokenId);

GitHub: 57, 62

[G‑32] Avoid fetching a low-level call's return data by using assembly

Even if you don't assign the call's second return value, it still gets copied to memory. Use assembly instead to prevent this and save 159 gas:

(bool success,) = payable(receiver).call{gas: gas, value: value}(""); -> bool success; assembly { success := call(gas, receiver, value, 0, 0, 0, 0) }

There are 9 instances of this issue:

File: src/ArbitrumBranchBridgeAgent.sol

103:         _rootBridgeAgentAddress.call{value: msg.value}("");

GitHub: 103

File: src/BranchBridgeAgent.sol

717:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

737:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

GitHub: 717, 737

File: src/RootBridgeAgent.sol

754:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

779:         (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);

835:             callee.call{value: msg.value}("");

927:             callee.call{value: _value}("");

GitHub: 754, 779, 835, 927

File: src/VirtualAccount.sol

74:              if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData);

101:             if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);

GitHub: 74, 101

Disputed Issues

The issues below may be reported by other bots/wardens, but can be penalized/ignored since either the rule or the specified instances are invalid

[D‑01] Unchecked return value of low-level call()/delegatecall()

The function being called may revert, which will be indicated by the return value to call()/delegatecall(). If the return value is not checked, the code will continue on as if there was no error, rather than reverting with the error encountered.

There are 9 instances of this issue:

File: src/MulticallRootRouter.sol

239:              IVirtualAccount(userAccount).call(calls);

248:              IVirtualAccount(userAccount).call(calls);

275:              IVirtualAccount(userAccount).call(calls);

328:              IVirtualAccount(userAccount).call(calls);

337:              IVirtualAccount(userAccount).call(calls);

364:              IVirtualAccount(userAccount).call(calls);

416:              IVirtualAccount(userAccount).call(calls);

425:              IVirtualAccount(userAccount).call(calls);

452:              IVirtualAccount(userAccount).call(calls);

GitHub: 239, 248, 275, 328, 337, 364, 416, 425, 452

[D‑02] Return values of approve() not checked

The general rule is valid, but the instances below are invalid

There is one instance of this issue:

File: src/BaseBranchRouter.sol

168:                 ERC20(_hToken).approve(_localPortAddress, _amount - _deposit);

GitHub: 168

[D‑03] Unsafe downcast

When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs

There are 5 instances of this issue:

File: src/BranchBridgeAgent.sol

/// @audit uint16
171:              abi.encodePacked(uint16(2), _gasLimit, _remoteBranchExecutionGas, rootBridgeAgentAddress)

/// @audit uint16
776:              abi.encodePacked(uint16(2), _gParams.gasLimit, _gParams.remoteBranchExecutionGas, rootBridgeAgentAddress)

GitHub: 171, 776

File: src/RootBridgeAgent.sol

/// @audit uint16
151:              abi.encodePacked(uint16(2), _gasLimit, _remoteBranchExecutionGas, getBranchBridgeAgent[_dstChainId])

/// @audit uint16
829:                  abi.encodePacked(uint16(2), _gParams.gasLimit, _gParams.remoteBranchExecutionGas, callee)

/// @audit uint16
921:                  abi.encodePacked(uint16(2), _gParams.gasLimit, _gParams.remoteBranchExecutionGas, callee)

GitHub: 151, 829, 921

[D‑04] Loss of precision

Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator

There is one instance of this issue:

File: src/BranchPort.sol

490:                  lastManaged[msg.sender][_token] = (block.timestamp / 1 days) * 1 days;

GitHub: 490

[D‑05] Solidity version 0.8.20 may not work on other chains due to PUSH0

The general rule is valid, but the instances below are invalid

There are 44 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/ArbitrumBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/ArbitrumCoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BaseBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/BranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/CoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/CoreRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/MulticallRootRouterLibZip.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootBridgeAgentExecutor.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/RootPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/VirtualAccount.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/BranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenBranchFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/ERC20hTokenRootFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/factories/RootBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/BridgeAgentConstants.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/BridgeAgentStructs.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IArbitrumBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IBranchBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IBranchBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IBranchPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/ICoreBranchRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenBranch.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenBranchFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenRoot.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IERC20hTokenRootFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/ILayerZeroEndpoint.sol

3:   pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroReceiver.sol

3:   pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

3:   pragma solidity >=0.5.0;

GitHub: 3

File: src/interfaces/IMulticall2.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IPortStrategy.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IRootBridgeAgent.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IRootBridgeAgentFactory.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IRootPort.sol

3:   pragma solidity ^0.8.0;

GitHub: 3

File: src/interfaces/IRootRouter.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/interfaces/IVirtualAccount.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenBranch.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

File: src/token/ERC20hTokenRoot.sol

2:   pragma solidity ^0.8.0;

GitHub: 2

[D‑06] approve()/safeApprove() may revert if the current approval is not zero

The general rule is valid, but the instances below are invalid

There is one instance of this issue:

File: src/BaseBranchRouter.sol

168:                 ERC20(_hToken).approve(_localPortAddress, _amount - _deposit);

GitHub: 168

[D‑07] Missing contract-existence checks before low-level calls

The general rule is valid, but the instances below are invalid

There are 27 instances of this issue:

File: src/BaseBranchRouter.sol

98           IBridgeAgent(localBridgeAgentAddress).callOutAndBridge{value: msg.value}(
99               payable(msg.sender), _params, _dParams, _gParams
100          );
101:     }

113          IBridgeAgent(localBridgeAgentAddress).callOutAndBridgeMultiple{value: msg.value}(
114              payable(msg.sender), _params, _dParams, _gParams
115          );
116:     }

GitHub: 98, 113

File: src/BranchBridgeAgent.sol

717          (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata);
718  
719          //  No fallback is requested revert allowing for settlement retry.
720          if (!success) revert ExecutionFailure();
721:     }

GitHub: 717

File: src/CoreBranchRouter.sol

54           IBridgeAgent(localBridgeAgentAddress).callOut{value: msg.value}(payable(msg.sender), payload, _gParams[0]);
55:      }

78           IBridgeAgent(localBridgeAgentAddress).callOutSystem{value: msg.value}(payable(msg.sender), payload, _gParams);
79:      }

184          IBridgeAgent(localBridgeAgentAddress).callOutSystem{value: msg.value}(payable(_refundee), payload, _gParams);
185:     }

232          IBridgeAgent(localBridgeAgentAddress).callOutSystem{value: msg.value}(payable(_refundee), payload, _gParams);
233:     }

GitHub: 54, 78, 184, 232

File: src/CoreRootRouter.sol

139          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
140              payable(_refundee), _refundee, _dstChainId, payload, _gParams[0]
141          );
142:     }

174          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
175              payable(_refundee), _refundee, _dstChainId, payload, _gParams
176          );
177:     }

199          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
200              payable(_refundee), _refundee, _dstChainId, payload, _gParams
201          );
202:     }

226          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
227              payable(_refundee), _refundee, _dstChainId, payload, _gParams
228          );
229:     }

257          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
258              payable(_refundee), _refundee, _dstChainId, payload, _gParams
259          );
260:     }

287          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
288              payable(_refundee), _refundee, _dstChainId, payload, _gParams
289          );
290:     }

437          IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(
438              payable(_refundee), _refundee, _dstChainId, payload, _gParams[0]
439          );
440:     }

GitHub: 139, 174, 199, 226, 257, 287, 437

File: src/MulticallRootRouter.sol

275              IVirtualAccount(userAccount).call(calls);
276  
277              // Withdraw assets from Virtual Account
278              for (uint256 i = 0; i < outputParams.outputTokens.length;) {
279                  IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]);
280: 

248              IVirtualAccount(userAccount).call(calls);
249  
250              // Withdraw assets from Virtual Account
251              IVirtualAccount(userAccount).withdrawERC20(outputParams.outputToken, outputParams.amountOut);
252  
253:             // Bridge Out assets

239              IVirtualAccount(userAccount).call(calls);
240  
241              /// FUNC ID: 2 (multicallSingleOutput)
242          } else if (funcId == 0x02) {
243              // Decode Params
244:             (Call[] memory calls, OutputParams memory outputParams, uint16 dstChainId, GasParams memory gasParams) =

364              IVirtualAccount(userAccount).call(calls);
365  
366              // Withdraw assets from Virtual Account
367              for (uint256 i = 0; i < outputParams.outputTokens.length;) {
368                  IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]);
369: 

337              IVirtualAccount(userAccount).call(calls);
338  
339              // Withdraw assets from Virtual Account
340              IVirtualAccount(userAccount).withdrawERC20(outputParams.outputToken, outputParams.amountOut);
341  
342:             // Bridge Out assets

328              IVirtualAccount(userAccount).call(calls);
329  
330              /// FUNC ID: 2 (multicallSingleOutput)
331          } else if (funcId == 0x02) {
332              // Decode Params
333:             (Call[] memory calls, OutputParams memory outputParams, uint16 dstChainId, GasParams memory gasParams) =

452              IVirtualAccount(userAccount).call(calls);
453  
454              // Withdraw assets from Virtual Account
455              for (uint256 i = 0; i < outputParams.outputTokens.length;) {
456                  IVirtualAccount(userAccount).withdrawERC20(outputParams.outputTokens[i], outputParams.amountsOut[i]);
457: 

425              IVirtualAccount(userAccount).call(calls);
426  
427              // Withdraw assets from Virtual Account
428              IVirtualAccount(userAccount).withdrawERC20(outputParams.outputToken, outputParams.amountOut);
429  
430:             // Bridge Out assets

416              IVirtualAccount(userAccount).call(calls);
417  
418              /// FUNC ID: 2 (multicallSingleOutput)
419          } else if (funcId == 0x02) {
420              // Decode Params
421:             (Call[] memory calls, OutputParams memory outputParams, uint16 dstChainId, GasParams memory gasParams) =

524          IBridgeAgent(_bridgeAgentAddress).callOutAndBridge{value: msg.value}(
525              payable(refundee),
526              recipient,
527              dstChainId,
528              "",
529:             SettlementInput(outputToken, amountOut, depositOut),

566          IBridgeAgent(_bridgeAgentAddress).callOutAndBridgeMultiple{value: msg.value}(
567              payable(refundee),
568              recipient,
569              dstChainId,
570              "",
571:             SettlementMultipleInput(outputTokens, amountsOut, depositsOut),

GitHub: 275, 248, 239, 364, 337, 328, 452, 425, 416, 524, 566

File: src/VirtualAccount.sol

74               if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData);
75   
76               if (!success) revert CallFailed();
77   
78               unchecked {
79:                  ++i;

101              if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);
102  
103              if (!success) revert CallFailed();
104  
105              unchecked {
106:                 ++i;

GitHub: 74, 101

[D‑08] Duplicated require()/revert() checks should be refactored to a modifier Or function to save gas

If the compiler inlines the function, there will be no gas savings. If it doesn't, there's extra runtime overhead due to the JUMP instructions. Either way, this suggestion is not helpful.

There are 12 instances of this issue:

see instances
File: src/BranchBridgeAgent.sol

441:          if (deposit.owner != msg.sender) revert NotDepositOwner();

624:              if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();

GitHub: 441, 624

File: src/BranchPort.sol

560:          if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();

GitHub: 560

File: src/CoreRootRouter.sol

366:          revert();

GitHub: 366

File: src/MulticallRootRouter.sol

204:          revert();

GitHub: 204

File: src/RootBridgeAgent.sol

670:              if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable();

910:          if (callee == address(0)) revert UnrecognizedBridgeAgent();

GitHub: 670, 910

File: src/RootPort.sol

264:          if (_localAddress == address(0)) revert InvalidLocalAddress();

299:          if (!isGlobalAddress[_hToken]) revert UnrecognizedToken();

544:          if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter();

545:          if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();

GitHub: 264, 299, 544, 545

File: src/VirtualAccount.sol

103:              if (!success) revert CallFailed();

GitHub: 103

[D‑09] Duplicated require()/revert() checks should be refactored to a modifier or function

This instance appears only once

There are 56 instances of this issue:

see instances
File: src/ArbitrumBranchPort.sol

39:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

GitHub: 39

File: src/BaseBranchRouter.sol

61:           require(_localBridgeAgentAddress != address(0), "Bridge Agent address cannot be 0");

213:          require(_unlocked == 1);

GitHub: 61, 213

File: src/BranchBridgeAgent.sol

125:          require(_rootBridgeAgentAddress != address(0), "Root Bridge Agent Address cannot be the zero address.");

126           require(
127               _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
128               "Layerzero Endpoint Address cannot be the zero address."
129:          );

130:          require(_localRouterAddress != address(0), "Local Router Address cannot be the zero address.");

131:          require(_localPortAddress != address(0), "Local Port Address cannot be the zero address.");

923:          require(_unlocked == 1);

GitHub: 125, 126, 130, 131, 923

File: src/BranchPort.sol

109:          require(_owner != address(0), "Owner is zero address");

123:          require(coreBranchRouterAddress == address(0), "Contract already initialized");

124:          require(!isBridgeAgentFactory[_bridgeAgentFactory], "Contract already initialized");

126:          require(_coreBranchRouter != address(0), "CoreBranchRouter is zero address");

127:          require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address");

136:          revert("Cannot renounce ownership");

181:          require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed");

213           require(
214               ERC20(_token).balanceOf(address(this)) - currBalance == amountToWithdraw, "Port Strategy Withdraw Failed"
215:          );

332:          require(coreBranchRouterAddress != address(0), "CoreRouter address is zero");

333:          require(_newCoreRouter != address(0), "New CoreRouter address is zero");

567:          require(_unlocked == 1);

GitHub: 109, 123, 124, 126, 127, 136, 181, 213, 332, 333, 567

File: src/CoreRootRouter.sol

84:           require(_setup, "Contract is already initialized");

278:          require(msg.sender == rootPortAddress, "Only root port can call");

GitHub: 84, 278

File: src/MulticallRootRouter.sol

93:           require(_localPortAddress != address(0), "Local Port Address cannot be 0");

94:           require(_multicallAddress != address(0), "Multicall Address cannot be 0");

110:          require(_bridgeAgentAddress != address(0), "Bridge Agent Address cannot be 0");

591:          require(_unlocked == 1);

GitHub: 93, 94, 110, 591

File: src/RootBridgeAgent.sol

111:          require(_lzEndpointAddress != address(0), "Layerzero Enpoint Address cannot be zero address");

112:          require(_localPortAddress != address(0), "Port Address cannot be zero address");

113:          require(_localRouterAddress != address(0), "Router Address cannot be zero address");

1191:         require(_unlocked == 1);

GitHub: 111, 112, 113, 1191

File: src/RootPort.sol

130:          require(_bridgeAgentFactory != address(0), "Bridge Agent Factory cannot be 0 address.");

131:          require(_coreRootRouter != address(0), "Core Root Router cannot be 0 address.");

132:          require(_setup, "Setup ended.");

152:          require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0 address.");

153:          require(_coreLocalBranchBridgeAgent != address(0), "Core Local Branch Bridge Agent cannot be 0 address.");

154:          require(_localBranchPortAddress != address(0), "Local Branch Port Address cannot be 0 address.");

155:          require(isBridgeAgent[_coreRootBridgeAgent], "Core Bridge Agent doesn't exist.");

156:          require(_setupCore, "Core Setup ended.");

167:          revert("Cannot renounce ownership");

GitHub: 130, 131, 132, 152, 153, 154, 155, 156, 167

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

57:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent Address cannot be 0");

84            require(
85                msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
86:           );

87            require(
88                _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
89                "Root Bridge Agent Factory Address does not match."
90:           );

GitHub: 57, 84, 87

File: src/factories/BranchBridgeAgentFactory.sol

61:           require(_rootBridgeAgentFactoryAddress != address(0), "Root Bridge Agent Factory Address cannot be 0");

62            require(
63                _lzEndpointAddress != address(0) || _rootChainId == _localChainId,
64                "Layerzero Endpoint Address cannot be the zero address."
65:           );

66:           require(_localCoreBranchRouterAddress != address(0), "Core Branch Router Address cannot be 0");

67:           require(_localPortAddress != address(0), "Port Address cannot be 0");

68:           require(_owner != address(0), "Owner cannot be 0");

88:           require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0");

120           require(
121               msg.sender == localCoreBranchRouterAddress, "Only the Core Branch Router can create a new Bridge Agent."
122:          );

123           require(
124               _rootBridgeAgentFactoryAddress == rootBridgeAgentFactoryAddress,
125               "Root Bridge Agent Factory Address does not match."
126:          );

GitHub: 61, 62, 66, 67, 68, 88, 120, 123

File: src/factories/ERC20hTokenBranchFactory.sol

43:           require(_localPortAddress != address(0), "Port address cannot be 0");

61:           require(_coreRouter != address(0), "CoreRouter address cannot be 0");

GitHub: 43, 61

File: src/factories/ERC20hTokenRootFactory.sol

35:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

50:           require(_coreRouter != address(0), "CoreRouter address cannot be 0");

GitHub: 35, 50

File: src/factories/RootBridgeAgentFactory.sol

32:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

GitHub: 32

File: src/token/ERC20hTokenRoot.sol

39:           require(_rootPortAddress != address(0), "Root Port Address cannot be 0");

40:           require(_factoryAddress != address(0), "Factory Address cannot be 0");

GitHub: 39, 40

[D‑10] SPDX identifier should be the in the first line of a solidity file

It's already on the first line

There are 44 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/ArbitrumBranchPort.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/ArbitrumCoreBranchRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/BaseBranchRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/BranchBridgeAgent.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/BranchBridgeAgentExecutor.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/BranchPort.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/CoreBranchRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/CoreRootRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/MulticallRootRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/MulticallRootRouterLibZip.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/RootBridgeAgent.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/RootBridgeAgentExecutor.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/RootPort.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/VirtualAccount.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/factories/BranchBridgeAgentFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/factories/ERC20hTokenBranchFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/factories/ERC20hTokenRootFactory.sol

1:    // // SPDX-License-Identifier: MIT

GitHub: 1

File: src/factories/RootBridgeAgentFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/BridgeAgentConstants.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/BridgeAgentStructs.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IArbitrumBranchPort.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IBranchBridgeAgent.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IBranchBridgeAgentFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IBranchPort.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IBranchRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/ICoreBranchRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IERC20hTokenBranch.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IERC20hTokenBranchFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IERC20hTokenRoot.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IERC20hTokenRootFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/ILayerZeroEndpoint.sol

1:    // SPDX-License-Identifier: BUSL-1.1

GitHub: 1

File: src/interfaces/ILayerZeroReceiver.sol

1:    // SPDX-License-Identifier: BUSL-1.1

GitHub: 1

File: src/interfaces/ILayerZeroUserApplicationConfig.sol

1:    // SPDX-License-Identifier: BUSL-1.1

GitHub: 1

File: src/interfaces/IMulticall2.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IPortStrategy.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IRootBridgeAgent.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IRootBridgeAgentFactory.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IRootPort.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IRootRouter.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/interfaces/IVirtualAccount.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/token/ERC20hTokenBranch.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

File: src/token/ERC20hTokenRoot.sol

1:    // SPDX-License-Identifier: MIT

GitHub: 1

[D‑11] Overly complicated arithmetic

At least one bot is incorrectly flagging code comments as 'complicated arithmetic'

There are 18 instances of this issue:

see instances
File: src/ArbitrumCoreBranchRouter.sol

27:    *         -----------------------------

29:    *         -------------+---------------

GitHub: 27, 29

File: src/CoreRootRouter.sol

29:    *         -----------------------------

31:    *         -------------+---------------

GitHub: 29, 31

File: src/MulticallRootRouter.sol

46:    *         -----------------------------

48:    *         -------------+---------------

GitHub: 46, 48

File: src/MulticallRootRouterLibZip.sol

15:    *         -----------------------------

17:    *         -------------+---------------

GitHub: 15, 17

File: src/interfaces/IBranchBridgeAgent.sol

45:    *         ------------------------------

47:    *         -----+------------------------

68:    *  | callOut = 0x0                 |  20b(recipient) + 4b(nonce)      |            -------------           |   ---	   |

GitHub: 45, 47, 68

File: src/interfaces/ICoreBranchRouter.sol

16:    *         -----------------------------

18:    *         -------------+---------------

GitHub: 16, 18

File: src/interfaces/IRootBridgeAgent.sol

43:    *       ------------------------------

45:    *       -----+------------------------

71:    *      | callOutSystem = 0x0   	    |                 4b(nonce)  |            -------------           |   ---	 |

72:    *      | callOut = 0x1                 |                 4b(nonce)  |            -------------           |   ---	 |

75:    *      | callOutSigned = 0x4           |    20b(recip) + 4b(nonce)  |   	      -------------           |   ---    |

GitHub: 43, 45, 71, 72, 75

[D‑12] Prefer double quotes for string quoting

The examples below are not strings. Furthermore it's perfectly reasonable to use single quotes within text (p. 16).

There are 3 instances of this issue:

File: src/MulticallRootRouter.sol

499       /**
500:       *  @notice Function to call 'clearToken' on the Root Port.

523:          //Move output hTokens from Root to Branch and call 'clearToken'.

565:          //Move output hTokens from Root to Branch and call 'clearTokens'.

GitHub: 499, 523, 565

[D‑13] Public functions not used internally can be marked as external to save gas

After Solidity version 0.6.9 both public and external functions save the same amount of gas, and since these files are >0.6.9, these findings are invalid

There are 6 instances of this issue:

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)
588           public
589           override
590:          requiresEndpoint(_endpoint, _srcAddress)

GitHub: 578, 587

File: src/RootBridgeAgent.sol

423:      function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public {

434       function lzReceiveNonBlocking(
435           address _endpoint,
436           uint16 _srcChainId,
437           bytes calldata _srcAddress,
438           bytes calldata _payload
439:      ) public override requiresEndpoint(_endpoint, _srcChainId, _srcAddress) {

GitHub: 423, 434

File: src/VirtualAccount.sol

85:       function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {

GitHub: 85

File: src/token/ERC20hTokenBranch.sol

35:       function burn(uint256 amount) public override onlyOwner {

GitHub: 35

[D‑14] require() / revert() statements should have descriptive reason strings

These are not revert() calls, so these findings are invalid

There are 155 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

126:         if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();

127:         if (_endpoint != rootBridgeAgentAddress) revert LayerZeroUnauthorizedEndpoint();

GitHub: 126, 127

File: src/ArbitrumBranchPort.sol

63:          if (_globalToken == address(0)) revert UnknownGlobalToken();

83:          if (!IRootPort(_rootPortAddress).isGlobalToken(_globalAddress, localChainId)) revert UnknownGlobalToken();

90:          if (_underlyingAddress == address(0)) revert UnknownUnderlyingToken();

GitHub: 63, 83, 90

File: src/ArbitrumCoreBranchRouter.sol

98:              revert UnrecognizedBridgeAgentFactory();

108:             revert UnrecognizedBridgeAgent();

169:             revert UnrecognizedFunctionId();

GitHub: 98, 108, 169

File: src/BaseBranchRouter.sol

124:         revert UnrecognizedFunctionId();

135:         revert UnrecognizedFunctionId();

146:         revert UnrecognizedFunctionId();

207:         if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();

GitHub: 124, 135, 146, 207

File: src/BranchBridgeAgent.sol

354:         if (deposit.owner != msg.sender) revert NotDepositOwner();

412:         if (payload.length == 0) revert DepositRetryUnavailableUseCallout();

424:         if (getDeposit[_depositNonce].owner != msg.sender) revert NotDepositOwner();

439:         if (deposit.status == STATUS_SUCCESS) revert DepositRedeemUnavailable();

440:         if (deposit.owner == address(0)) revert DepositRedeemUnavailable();

441:         if (deposit.owner != msg.sender) revert NotDepositOwner();

696:             revert UnknownFlag();

672:                 revert AlreadyExecutedTransaction();

646:             if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();

624:             if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();

604:             if (executionState[nonce] != STATUS_READY) revert AlreadyExecutedTransaction();

720:         if (!success) revert ExecutionFailure();

750:                 revert ExecutionFailure();

869:         if (_hTokens.length > MAX_TOKENS_LENGTH) revert InvalidInput();

870:         if (_hTokens.length != _tokens.length) revert InvalidInput();

871:         if (_tokens.length != _amounts.length) revert InvalidInput();

872:         if (_amounts.length != _deposits.length) revert InvalidInput();

938:         if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();

939:         if (_endpoint != lzEndpointAddress) revert LayerZeroUnauthorizedEndpoint();

942:         if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

943:         if (rootBridgeAgentAddress != address(uint160(bytes20(_srcAddress[20:])))) revert LayerZeroUnauthorizedCaller();

948:         if (msg.sender != localRouterAddress) revert UnrecognizedRouter();

954:         if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();

GitHub: 354, 412, 424, 439, 440, 441, 696, 672, 646, 624, 604, 720, 750, 869, 870, 871, 872, 938, 939, 942, 943, 948, 954

File: src/BranchPort.sol

149:         if (_amount > _excessReserves(_strategyTokenDebt, _token)) revert InsufficientReserves();

299:         if (length > 255) revert InvalidInputArrays();

300:         if (length != _underlyingAddresses.length) revert InvalidInputArrays();

301:         if (_underlyingAddresses.length != _amounts.length) revert InvalidInputArrays();

302:         if (_amounts.length != _deposits.length) revert InvalidInputArrays();

320:         if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent();

339:         if (isBridgeAgentFactory[_newBridgeAgentFactory]) revert AlreadyAddedBridgeAgentFactory();

364:             revert InvalidMinimumReservesRatio();

387:         if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();

542:         if (msg.sender != coreBranchRouterAddress) revert UnrecognizedCore();

548:         if (!isBridgeAgent[msg.sender]) revert UnrecognizedBridgeAgent();

554:         if (!isBridgeAgentFactory[msg.sender]) revert UnrecognizedBridgeAgentFactory();

560:         if (!isStrategyToken[_token]) revert UnrecognizedStrategyToken();

561:         if (!isPortStrategy[msg.sender][_token]) revert UnrecognizedPortStrategy();

GitHub: 149, 299, 300, 301, 302, 320, 339, 364, 387, 542, 548, 554, 560, 561

File: src/CoreBranchRouter.sol

147:             revert UnrecognizedFunctionId();

212:             revert UnrecognizedBridgeAgentFactory();

222:             revert UnrecognizedBridgeAgent();

268:         if (!IPort(_localPortAddress).isBridgeAgent(_branchBridgeAgent)) revert UnrecognizedBridgeAgent();

GitHub: 147, 212, 222, 268

File: src/CoreRootRouter.sol

113:             revert UnauthorizedCallerNotManager();

117:         if (!IPort(rootPortAddress).isChainId(_dstChainId)) revert InvalidChainId();

120:         if (IBridgeAgent(_rootBridgeAgent).getBranchBridgeAgent(_dstChainId) != address(0)) revert InvalidChainId();

123:         if (!IBridgeAgent(_rootBridgeAgent).isBranchBridgeAgentAllowed(_dstChainId)) revert UnauthorizedChainId();

164:             revert UnrecognizedBridgeAgentFactory();

327:             revert UnrecognizedFunctionId();

345:             revert UnrecognizedFunctionId();

412:         if (_dstChainId == rootChainId) revert InvalidChainId();

415:             revert UnrecognizedGlobalToken();

420:             revert TokenAlreadyAdded();

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();

483:         if (IPort(rootPortAddress).isLocalToken(_localAddress, _dstChainId)) revert TokenAlreadyAdded();

512:         if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();

GitHub: 113, 117, 120, 123, 164, 327, 345, 412, 415, 420, 461, 462, 463, 483, 512

File: src/MulticallRootRouter.sol

198:             revert UnrecognizedFunctionId();

298:             revert UnrecognizedFunctionId();

387:             revert UnrecognizedFunctionId();

475:             revert UnrecognizedFunctionId();

605:         if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedBridgeAgentExecutor();

GitHub: 198, 298, 387, 475, 605

File: src/RootBridgeAgent.sol

244:         if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable();

249:                 revert NotSettlementOwner();

282:         if (settlementOwner == address(0)) revert SettlementRetrieveUnavailable();

287:                 revert NotSettlementOwner();

307:         if (settlement.status == STATUS_SUCCESS) revert SettlementRedeemUnavailable();

308:         if (settlementOwner == address(0)) revert SettlementRedeemUnavailable();

313:                 revert NotSettlementOwner();

357:         if (_dParams.amount < _dParams.deposit) revert InvalidInputParams();

365:                 revert InvalidInputParams();

372:                 revert InvalidInputParams();

396:         if (length > MAX_TOKENS_LENGTH) revert InvalidInputParams();

430:         if (!success) if (msg.sender == getBranchBridgeAgent[localChainId]) revert ExecutionFailure();

733:             revert UnknownFlag();

705:                 revert AlreadyExecutedTransaction();

670:             if (settlementReference.owner == address(0)) revert SettlementRetryUnavailable();

675:                     revert NotSettlementOwner();

623:                 revert AlreadyExecutedTransaction();

583:                 revert AlreadyExecutedTransaction();

545:                 revert AlreadyExecutedTransaction();

519:                 revert AlreadyExecutedTransaction();

496:                 revert AlreadyExecutedTransaction();

473:                 revert AlreadyExecutedTransaction();

450:                 revert AlreadyExecutedTransaction();

757:         if (!success) revert ExecutionFailure();

791:                 revert ExecutionFailure();

818:         if (callee == address(0)) revert UnrecognizedBridgeAgent();

871:         if (_hTokens.length == 0) revert SettlementRetryUnavailableUseCallout();

910:         if (callee == address(0)) revert UnrecognizedBridgeAgent();

1057:        if (_globalAddresses.length > MAX_TOKENS_LENGTH) revert InvalidInputParamsLength();

1060:        if (_globalAddresses.length != _amounts.length) revert InvalidInputParamsLength();

1061:        if (_amounts.length != _deposits.length) revert InvalidInputParamsLength();

1141:        if (_amount == 0 && _deposit == 0) revert InvalidInputParams();

1142:        if (_amount < _deposit) revert InvalidInputParams();

1145:        if (_localAddress == address(0)) revert UnrecognizedLocalAddress();

1146:        if (_underlyingAddress == address(0)) if (_deposit > 0) revert UnrecognizedUnderlyingAddress();

1159:                revert InsufficientBalanceForSettlement();

1171:        if (getBranchBridgeAgent[_branchChainId] != address(0)) revert AlreadyAddedBridgeAgent();

1199:        if (msg.sender != localRouterAddress) revert UnrecognizedRouter();

1205:        if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();

1208:            if (_endpoint != lzEndpointAddress) revert LayerZeroUnauthorizedEndpoint();

1210:            if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

1213:                revert LayerZeroUnauthorizedCaller();

1221:        if (msg.sender != bridgeAgentExecutorAddress) revert UnrecognizedExecutor();

1227:        if (msg.sender != localPortAddress) revert UnrecognizedPort();

1234:            revert UnrecognizedBridgeAgentManager();

GitHub: 244, 249, 282, 287, 307, 308, 313, 357, 365, 372, 396, 430, 733, 705, 670, 675, 623, 583, 545, 519, 496, 473, 450, 757, 791, 818, 871, 910, 1057, 1060, 1061, 1141, 1142, 1145, 1146, 1159, 1171, 1199, 1205, 1208, 1210, 1213, 1221, 1227, 1234

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();

264:         if (_localAddress == address(0)) revert InvalidLocalAddress();

282:         if (!isGlobalAddress[_hToken]) revert UnrecognizedToken();

290:         if (_deposit > 0) if (!ERC20hTokenRoot(_hToken).mint(_recipient, _deposit, _srcChainId)) revert UnableToMint();

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();

342:         if (!ERC20hTokenRoot(_hToken).mint(_to, _amount, localChainId)) revert UnableToMint();

360:         if (_user == address(0)) revert InvalidUserAddress();

383:         if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent();

399:             revert AlreadyAddedBridgeAgent();

402:             revert BridgeAgentNotAllowed();

422:         if (isBridgeAgentFactory[_bridgeAgentFactory]) revert AlreadyAddedBridgeAgentFactory();

448:         if (isChainId[_chainId]) revert AlreadyAddedChain();

485:         if (isGlobalAddress[_ecoTokenGlobalAddress]) revert AlreadyAddedEcosystemToken();

489:             revert AlreadyAddedEcosystemToken();

494:             revert AlreadyAddedEcosystemToken();

510:         if (_coreRootRouter == address(0)) revert InvalidCoreRootRouter();

511:         if (_coreRootBridgeAgent == address(0)) revert InvalidCoreRootBridgeAgent();

528:         if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter();

529:         if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();

544:         if (_coreBranchRouter == address(0)) revert InvalidCoreBranchRouter();

545:         if (_coreBranchBridgeAgent == address(0)) revert InvalidCoreBrancBridgeAgent();

558:         if (!isBridgeAgentFactory[msg.sender]) revert UnrecognizedBridgeAgentFactory();

564:         if (!isBridgeAgent[msg.sender]) revert UnrecognizedBridgeAgent();

570:         if (msg.sender != coreRootRouterAddress) revert UnrecognizedCoreRootRouter();

576:         if (msg.sender != localBranchPortAddress) revert UnrecognizedLocalBranchPort();

GitHub: 245, 246, 247, 264, 282, 290, 299, 309, 320, 330, 341, 342, 360, 383, 399, 402, 422, 448, 485, 489, 494, 510, 511, 528, 529, 544, 545, 558, 564, 570, 576

File: src/VirtualAccount.sol

76:              if (!success) revert CallFailed();

103:             if (!success) revert CallFailed();

111:         if (msg.value != valAccumulator) revert CallFailed();

163:                 revert UnauthorizedCaller();

GitHub: 76, 103, 111, 163

File: src/factories/ERC20hTokenBranchFactory.sol

113:         if (msg.sender != localCoreRouterAddress) revert UnrecognizedCoreRouter();

GitHub: 113

File: src/factories/ERC20hTokenRootFactory.sol

99:                  revert UnrecognizedCoreRouterOrPort();

GitHub: 99

[D‑15] Solmate's SafeTransferLib doesn't check whether the ERC20 contract exists

safeTransferETH() does not call functions on a contract and instead directly sends funds to the contract, so contract existence checks are not needed for these library calls.

There are 3 instances of this issue:

File: src/BranchBridgeAgentExecutor.sol

92:              _recipient.safeTransferETH(address(this).balance);

126:             _recipient.safeTransferETH(address(this).balance);

GitHub: 92, 126

File: src/VirtualAccount.sol

52:          msg.sender.safeTransferETH(_amount);

GitHub: 52

[D‑16] NatSpec: Contract declarations should have @notice tags

The compiler interprets /// or /** comments as this tag if one wasn't explicitly provided

There are 44 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

31   contract ArbitrumBranchBridgeAgent is BranchBridgeAgent {
32       /*///////////////////////////////////////////////////////////////
33                               CONSTRUCTOR
34       //////////////////////////////////////////////////////////////*/
35   
36       /**
37        * @notice Constructor for Arbitrum Branch Bridge Agent.
38        *  @param _localChainId Local Chain Layer Zero Id.
39        *  @param _rootBridgeAgentAddress Root Bridge Agent Address.
40        *  @param _localRouterAddress Local Core Branch Router Address.
41        *  @param _localPortAddress Local Branch Port Address.
42:       */

GitHub: 31

File: src/ArbitrumBranchPort.sol

14:  contract ArbitrumBranchPort is BranchPort, IArbitrumBranchPort {

GitHub: 14

File: src/ArbitrumCoreBranchRouter.sol

37   contract ArbitrumCoreBranchRouter is CoreBranchRouter {
38       /*///////////////////////////////////////////////////////////////
39                                CONSTRUCTOR
40       //////////////////////////////////////////////////////////////*/
41       /**
42        * @notice Constructor for Arbitrum Core Branch Router.
43:       */

GitHub: 37

File: src/BaseBranchRouter.sol

25:  contract BaseBranchRouter is IBranchRouter, Ownable {

GitHub: 25

File: src/BranchBridgeAgent.sol

23:  library DeployBranchBridgeAgent {

45:  contract BranchBridgeAgent is IBranchBridgeAgent, BridgeAgentConstants {

GitHub: 23, 45

File: src/BranchBridgeAgentExecutor.sol

15:  library DeployBranchBridgeAgentExecutor {

29:  contract BranchBridgeAgentExecutor is Ownable, BridgeAgentConstants {

GitHub: 15, 29

File: src/BranchPort.sol

17:  contract BranchPort is Ownable, IBranchPort {

GitHub: 17

File: src/CoreBranchRouter.sol

18   contract CoreBranchRouter is ICoreBranchRouter, BaseBranchRouter {
19:      /// @notice hToken Factory Address.

GitHub: 18

File: src/CoreRootRouter.sol

38   contract CoreRootRouter is IRootRouter, Ownable {
39       /*///////////////////////////////////////////////////////////////
40                       CORE ROOT ROUTER STATE
41       //////////////////////////////////////////////////////////////*/
42   
43:      /// @notice Boolean to indicate if the contract is in set up mode.

GitHub: 38

File: src/MulticallRootRouter.sol

57:  contract MulticallRootRouter is IRootRouter, Ownable {

GitHub: 57

File: src/MulticallRootRouterLibZip.sol

26:  contract MulticallRootRouterLibZip is MulticallRootRouter {

GitHub: 26

File: src/RootBridgeAgent.sol

32:  contract RootBridgeAgent is IRootBridgeAgent, BridgeAgentConstants {

GitHub: 32

File: src/RootBridgeAgentExecutor.sol

13:  library DeployRootBridgeAgentExecutor {

27   contract RootBridgeAgentExecutor is Ownable, BridgeAgentConstants {
28       /*///////////////////////////////////////////////////////////////
29                                   CONSTRUCTOR
30       //////////////////////////////////////////////////////////////*/
31       /**
32        * @notice Constructor for Root Bridge Agent Executor.
33        * @param _rootBridgeAgent the owner of the contract in charge of calling the different execution functions.
34:       */

GitHub: 13, 27

File: src/RootPort.sol

16:  contract RootPort is Ownable, IRootPort {

GitHub: 16

File: src/VirtualAccount.sol

17:  contract VirtualAccount is IVirtualAccount, ERC1155Receiver {

GitHub: 17

File: src/factories/ArbitrumBranchBridgeAgentFactory.sol

17   contract ArbitrumBranchBridgeAgentFactory is BranchBridgeAgentFactory {
18       /*///////////////////////////////////////////////////////////////
19                                CONSTRUCTOR
20       //////////////////////////////////////////////////////////////*/
21   
22       /**
23        * @notice Constructor for Bridge Agent Factory Contract.
24        *  @param _rootChainId Root Chain Layer Zero Id.
25        *  @param _rootBridgeAgentFactoryAddress Root Bridge Agent Factory Address.
26        *  @param _localCoreBranchRouterAddress Local Core Branch Router Address.
27        *  @param _localPortAddress Local Branch Port Address.
28        *  @param _owner Owner of the contract.
29:       */

GitHub: 17

File: src/factories/BranchBridgeAgentFactory.sol

19   contract BranchBridgeAgentFactory is Ownable, IBranchBridgeAgentFactory {
20:      /// @notice Local Chain Id.

GitHub: 19

File: src/factories/ERC20hTokenBranchFactory.sol

12   contract ERC20hTokenBranchFactory is Ownable, IERC20hTokenBranchFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/ERC20hTokenRootFactory.sol

12   contract ERC20hTokenRootFactory is Ownable, IERC20hTokenRootFactory {
13:      /// @notice Local Network Identifier.

GitHub: 12

File: src/factories/RootBridgeAgentFactory.sol

11   contract RootBridgeAgentFactory is IRootBridgeAgentFactory {
12:      /// @notice Root Chain Id

GitHub: 11

File: src/interfaces/BridgeAgentConstants.sol

10   contract BridgeAgentConstants {
11       // Settlement / Deposit Execution Status
12:  

GitHub: 10

File: src/interfaces/IArbitrumBranchPort.sol

16   interface IArbitrumBranchPort is IBranchPort {
17       /*///////////////////////////////////////////////////////////////
18                           EXTERNAL FUNCTIONS
19       //////////////////////////////////////////////////////////////*/
20   
21       /**
22        * @notice Function to deposit underlying/native token amount into Port in exchange for Local hToken.
23        *     @param _depositor underlying/native token depositor.
24        *     @param _recipient hToken receiver.
25        *     @param _underlyingAddress underlying/native token address.
26        *     @param _amount amount of tokens.
27:       */

GitHub: 16

File: src/interfaces/IBranchBridgeAgent.sol

87   interface IBranchBridgeAgent is ILayerZeroReceiver {
88       /*///////////////////////////////////////////////////////////////
89                           VIEW FUNCTIONS
90       //////////////////////////////////////////////////////////////*/
91   
92       /**
93        * @notice External function to return the Branch Chain's Local Port Address.
94        * @return address of the Branch Chain's Local Port.
95:       */

GitHub: 87

File: src/interfaces/IBranchBridgeAgentFactory.sol

12   interface IBranchBridgeAgentFactory {
13       /*///////////////////////////////////////////////////////////////
14                           BRIDGE AGENT FUNCTIONS
15       //////////////////////////////////////////////////////////////*/
16   
17       /**
18        * @notice Creates a new Branch Bridge Agent.
19        * @param newRootRouterAddress New Root Router Address.
20        * @param rootBridgeAgentAddress Root Bridge Agent Address.
21        * @param _rootBridgeAgentFactoryAddress Root Bridge Agent Factory Address.
22        * @return newBridgeAgent New Bridge Agent Address.
23:       */

GitHub: 12

File: src/interfaces/IBranchPort.sol

14   interface IBranchPort {
15       /*///////////////////////////////////////////////////////////////
16                               VIEW FUNCTIONS
17       //////////////////////////////////////////////////////////////*/
18       /**
19        * @notice Returns true if the address is a Bridge Agent.
20        *   @param _bridgeAgent Bridge Agent address.
21        *   @return bool.
22:       */

GitHub: 14

File: src/interfaces/IBranchRouter.sol

21   interface IBranchRouter {
22       /*///////////////////////////////////////////////////////////////
23                               VIEW / STATE
24       //////////////////////////////////////////////////////////////*/
25   
26:      /// @notice External function to return the Branch Chain's Local Port Address.

GitHub: 21

File: src/interfaces/ICoreBranchRouter.sol

27   interface ICoreBranchRouter {
28       /*///////////////////////////////////////////////////////////////
29                           EXTERNAL FUNCTIONS
30       //////////////////////////////////////////////////////////////*/
31   
32       /**
33        * @notice Function to deploy/add a token already present in the global environment to a branch chain.
34        * @param _globalAddress the address of the global virtualized token.
35        * @param _dstChainId the chain to which the token will be added.
36        * @param _gasParams Gas parameters for remote execution.
37:       */

GitHub: 27

File: src/interfaces/IERC20hTokenBranch.sol

11   interface IERC20hTokenBranch {
12       /*///////////////////////////////////////////////////////////////
13                           ERC20 LOGIC
14       //////////////////////////////////////////////////////////////*/
15   
16       /**
17        * @notice Function to mint tokens in the Branch Chain.
18        * @param account Address of the account to receive the tokens.
19        * @param amount Amount of tokens to be minted.
20        * @return Boolean indicating if the operation was successful.
21:       */

GitHub: 11

File: src/interfaces/IERC20hTokenBranchFactory.sol

13   interface IERC20hTokenBranchFactory {
14       /*///////////////////////////////////////////////////////////////
15                               hTOKEN FUNCTIONS
16       //////////////////////////////////////////////////////////////*/
17       /**
18        * @notice Function to create a new Branch hToken.
19        * @param _name Name of the Token.
20        * @param _symbol Symbol of the Token.
21        * @param _decimals Decimals of the Token.
22        * @param _addPrefix Boolean to add or not the chain prefix to the token name and symbol.
23:       */

GitHub: 13

File: src/interfaces/IERC20hTokenRoot.sol

12   interface IERC20hTokenRoot {
13       /*///////////////////////////////////////////////////////////////
14                           VIEW FUNCTIONS
15       //////////////////////////////////////////////////////////////*/
16   
17       /// @notice View Function returns Local Network Identifier.
18:      /// @return Local Network Identifier.

GitHub: 12

File: src/interfaces/IERC20hTokenRootFactory.sol

13   interface IERC20hTokenRootFactory {
14       /*///////////////////////////////////////////////////////////////
15                               hTOKEN FUNCTIONS
16       //////////////////////////////////////////////////////////////*/
17   
18       /**
19        * @notice Function to create a new hToken.
20        * @param _name Name of the Token.
21        * @param _symbol Symbol of the Token.
22        * @param _decimals Decimals of the Token.
23:       */

GitHub: 13

File: src/interfaces/IMulticall2.sol

9:   interface IMulticall2 {

GitHub: 9

File: src/interfaces/IPortStrategy.sol

12   interface IPortStrategy {
13       /*///////////////////////////////////////////////////////////////
14                             TOKEN MANAGEMENT
15       //////////////////////////////////////////////////////////////*/
16   
17       /**
18        * @notice Function to withdraw underlying/native token amount back into Branch Port.
19        *   @param _recipient hToken receiver.
20        *   @param _token native token address.
21        *   @param _amount amount of tokens.
22:       */

GitHub: 12

File: src/interfaces/IRootBridgeAgent.sol

94   interface IRootBridgeAgent is ILayerZeroReceiver {
95       /*///////////////////////////////////////////////////////////////
96                               VIEW FUNCTIONS
97       //////////////////////////////////////////////////////////////*/
98   
99       /**
100       * @notice Function that returns the current settlement nonce.
101       *   @return nonce bridge agent's current settlement nonce
102       *
103:      */

GitHub: 94

File: src/interfaces/IRootBridgeAgentFactory.sol

11   interface IRootBridgeAgentFactory {
12       /*///////////////////////////////////////////////////////////////
13                           BRIDGE AGENT FUNCTIONS
14       //////////////////////////////////////////////////////////////*/
15:  

GitHub: 11

File: src/interfaces/IRootPort.sol

10:  interface ICoreRootRouter {

31   interface IRootPort {
32       /*///////////////////////////////////////////////////////////////
33                           VIEW FUNCTIONS
34       //////////////////////////////////////////////////////////////*/
35       /**
36        * @notice View Function returns True if the chain Id has been added to the system.
37        *  @param _chainId The Layer Zero chainId of the chain.
38        * @return bool True if the chain Id has been added to the system.
39:       */

GitHub: 10, 31

File: src/interfaces/IRootRouter.sol

14   interface IRootRouter {
15       /*///////////////////////////////////////////////////////////////
16                           LAYERZERO FUNCTIONS
17       ///////////////////////////////////////////////////////////////*/
18   
19       /**
20        *     @notice Function to execute Branch Bridge Agent system initiated requests with no asset deposit.
21        *     @param params data received from messaging layer.
22        *     @param srcChainId chain where the request originated from.
23        *
24:       */

GitHub: 14

File: src/interfaces/IVirtualAccount.sol

27   interface IVirtualAccount is IERC721Receiver {
28       /**
29        * @notice Returns the address of the user that owns the VirtualAccount.
30        * @return The address of the user that owns the VirtualAccount.
31:       */

GitHub: 27

File: src/token/ERC20hTokenBranch.sol

12:  contract ERC20hTokenBranch is ERC20, Ownable, IERC20hTokenBranch {

GitHub: 12

File: src/token/ERC20hTokenRoot.sol

12   contract ERC20hTokenRoot is ERC20, Ownable, IERC20hTokenRoot {
13:      /// @inheritdoc IERC20hTokenRoot

GitHub: 12

[D‑17] Event names should use CamelCase

The instances below are already CamelCase (events are supposed to use CamelCase, not lowerCamelCase)

There are 28 instances of this issue:

File: src/interfaces/IBranchBridgeAgent.sol

332:     event LogExecute(uint256 indexed nonce);

333:     event LogFallback(uint256 indexed nonce);

GitHub: 332, 333

File: src/interfaces/IBranchPort.sol

220:     event DebtCreated(address indexed _strategy, address indexed _token, uint256 _amount);

221:     event DebtRepaid(address indexed _strategy, address indexed _token, uint256 _amount);

223:     event StrategyTokenAdded(address indexed _token, uint256 indexed _minimumReservesRatio);

224:     event StrategyTokenToggled(address indexed _token);

226      event PortStrategyAdded(
227          address indexed _portStrategy, address indexed _token, uint256 indexed _dailyManagementLimit
228:     );

229:     event PortStrategyToggled(address indexed _portStrategy, address indexed _token);

230      event PortStrategyUpdated(
231          address indexed _portStrategy, address indexed _token, uint256 indexed _dailyManagementLimit
232:     );

234:     event BridgeAgentFactoryAdded(address indexed _bridgeAgentFactory);

235:     event BridgeAgentFactoryToggled(address indexed _bridgeAgentFactory);

237:     event BridgeAgentToggled(address indexed _bridgeAgent);

239:     event CoreBranchSet(address indexed _coreBranchRouter, address indexed _coreBranchBridgeAgent);

GitHub: 220, 221, 223, 224, 226, 229, 230, 234, 235, 237, 239

File: src/interfaces/IRootBridgeAgent.sol

337:     event LogExecute(uint256 indexed depositNonce, uint256 indexed srcChainId);

338:     event LogFallback(uint256 indexed settlementNonce, uint256 indexed dstChainId);

GitHub: 337, 338

File: src/interfaces/IRootPort.sol

371:     event BridgeAgentFactoryAdded(address indexed bridgeAgentFactory);

372:     event BridgeAgentFactoryToggled(address indexed bridgeAgentFactory);

374:     event BridgeAgentAdded(address indexed bridgeAgent, address indexed manager);

375:     event BridgeAgentToggled(address indexed bridgeAgent);

376:     event BridgeAgentSynced(address indexed bridgeAgent, address indexed rootBridgeAgent, uint256 indexed srcChainId);

378:     event NewChainAdded(uint256 indexed chainId);

380:     event VirtualAccountCreated(address indexed user, address account);

382      event LocalTokenAdded(
383          address indexed underlyingAddress, address indexed localAddress, address indexed globalAddress, uint256 chainId
384:     );

385:     event GlobalTokenAdded(address indexed localAddress, address indexed globalAddress, uint256 indexed chainId);

386:     event EcosystemTokenAdded(address indexed ecoTokenGlobalAddress);

387:     event CoreRootSet(address indexed coreRootRouter, address indexed coreRootBridgeAgent);

388      event CoreBranchSet(
389          address indexed coreBranchRouter, address indexed coreBranchBridgeAgent, uint16 indexed dstChainId
390:     );

391      event CoreBranchSynced(
392          address indexed coreBranchRouter, address indexed coreBranchBridgeAgent, uint16 indexed dstChainId
393:     );

GitHub: 371, 372, 374, 375, 376, 378, 380, 382, 385, 386, 387, 388, 391

[D‑18] Unnecessary look up in if condition

There are 3 instances of this issue:

File: src/BranchBridgeAgent.sol

127:             _lzEndpointAddress != address(0) || _rootChainId == _localChainId,

GitHub: 127

File: src/BranchPort.sol

363:         if (_minimumReservesRatio >= DIVISIONER || _minimumReservesRatio < MIN_RESERVE_RATIO) {

GitHub: 363

File: src/factories/BranchBridgeAgentFactory.sol

63:              _lzEndpointAddress != address(0) || _rootChainId == _localChainId,

GitHub: 63

[D‑19] NatSpec: Function declarations should have @notice tags

The compiler interprets /// or /** comments as this tag if one wasn't explicitly provided

There are 305 instances of this issue:

see instances
File: src/ArbitrumBranchBridgeAgent.sol

43       constructor(
44           uint16 _localChainId,
45           address _rootBridgeAgentAddress,
46           address _localRouterAddress,
47           address _localPortAddress
48       )
49           BranchBridgeAgent(
50               _localChainId,
51               _localChainId,
52               _rootBridgeAgentAddress,
53               address(0),
54               _localRouterAddress,
55               _localPortAddress
56           )
57:      {}

69:      function depositToPort(address underlyingAddress, uint256 amount) external payable lock {

79:      function withdrawFromPort(address localAddress, uint256 amount) external payable lock {

89:      function retrySettlement(uint32, bytes calldata, GasParams[2] calldata, bool) external payable override lock {}

GitHub: 43, 69, 79, 89

File: src/ArbitrumBranchPort.sol

38:      constructor(uint16 _localChainId, address _rootPortAddress, address _owner) BranchPort(_owner) {

50       function depositToPort(address _depositor, address _recipient, address _underlyingAddress, uint256 _deposit)
51           external
52           override
53           lock
54           requiresBridgeAgent
55:      {

73       function withdrawFromPort(address _depositor, address _recipient, address _globalAddress, uint256 _amount)
74           external
75           override
76           lock
77           requiresBridgeAgent
78:      {

GitHub: 38, 50, 73

File: src/ArbitrumCoreBranchRouter.sol

44:      constructor() CoreBranchRouter
@thebrittfactor
Copy link

For transparency, the sponsor has provided their input on the Medium findings for this bot report. Their response is below:

[M‑01] - invalid, that low-level call is being directed at a trusted contract that is part of the system (RootBridgeAgent) that's why it is unchecked.
[M‑02] - don't think this valid. While using safeTransferFrom is safer, here the receiving address is the one that calls this function. So a contract that calls this is expected to be able to handle an NFT transfer or it would not call this at all.
[M‑03] - invalid, the owner in the highlighted contracts is our own on chain governance contract or the on-chain governance "realay" contract which is the CoreBranchRouter.
[M-04] we should use safeApprove here, but not for the reasons stated. If approve fails but execution continues, it will just fail more down the line.

@alcueca
Copy link

alcueca commented Nov 17, 2023

[M-01] is invalid, because all that it does is to send native tokens to a known contract that implements receive().
[M-02] is invalid for the reasons stated by the sponsor.
[M-03] is invalid because it belongs in an analysis report.
[M-04] should be QA. I'm not so convinced that the execution would revert later on, but without a clear impact it would be just a matter of best practices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment