Platform: Code4rena
Start Date: 22/09/2023
Pot Size: $100,000 USDC
Total HM: 15
Participants: 175
Period: 14 days
Judge: alcueca
Total Solo HM: 4
Id: 287
League: ETH
Rank: 25/175
Findings: 3
Award: $309.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: LokiThe5th
Also found by: 0xadrii, 33BYTEZZZ, 3docSec, Bauchibred, DevABDee, Koolex, Kow, Limbooo, QiuhaoLi, Tendency, ast3ros, ihtishamsudo, kodyvim, lsaudit, neumo, peakbolt, windhustler
40.0102 USDC - $40.01
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L578-L584 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L423-L431
It is highly recommended User Applications implement the ILayerZeroApplicationConfig
. Implementing this interface will provide you with forceResumeReceive
which, in the worst case can allow the owner/multisig to unblock the queue of messages if something unexpected happens.
If anyone can call and deny, the contract is not suited to handle exceptions and doesn't implement the forceReceive
function, meaning the channel can be griefed and I don't believe there's a way to remedy.
The contract needs to implement [forceResumeReceive
](https://github.com/LayerZero-Labs/solidity-examples/blob/d38c679863d84cf58bc7a9761ed300a0be9b3bd1/contracts/lzApp/LzApp.sol#L63) to allow to remove malicious messages that may be received.
BranchBridgeAgent and RootBridgeAgent don’t implement a function to retry failed messages:https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L578 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L423
Recommended Implementation by LayerZero:https://github.com/LayerZero-Labs/solidity-examples/blob/857b289b6cb1df043457c907eb19f75d394719b9/contracts/lzApp/NonblockingLzApp.sol#L46
Manual
Use the LzApp functions instead of implementing your own or implement the forceResumeReceive
function to unblock the channel.
DoS
#0 - c4-pre-sort
2023-10-11T11:22:36Z
0xA5DF marked the issue as duplicate of #875
#1 - c4-pre-sort
2023-10-11T11:22:41Z
0xA5DF marked the issue as sufficient quality report
#2 - alcueca
2023-10-22T04:50:29Z
Misses the attack angle via underfunded transactions or other malicious revert that makes it a valid DoS
#3 - c4-judge
2023-10-22T04:50:34Z
alcueca marked the issue as partial-50
#4 - c4-judge
2023-10-22T04:50:39Z
alcueca marked the issue as satisfactory
#5 - c4-judge
2023-10-22T04:56:27Z
alcueca marked the issue as duplicate of #399
#6 - c4-judge
2023-10-27T10:18:17Z
alcueca changed the severity to 2 (Med Risk)
🌟 Selected for report: MrPotatoMagic
Also found by: 0xAadi, 0xDING99YA, 0xDemon, 0xRstStn, 0xSmartContract, 0xStriker, 0xWaitress, 0xbrett8571, 0xfuje, 0xsagetony, 0xsurena, 33BYTEZZZ, 3docSec, 7ashraf, ABA, ABAIKUNANBAEV, Aamir, Audinarey, Bauchibred, Black_Box_DD, Daniel526, DanielArmstrong, DanielTan_MetaTrust, Dinesh11G, Eurovickk, Franklin, Inspecktor, John, Jorgect, Joshuajee, K42, Kek, Koolex, LokiThe5th, MIQUINHO, Myd, NoTechBG, QiuhaoLi, SanketKogekar, Sathish9098, Sentry, Soul22, SovaSlava, Stormreckson, Tendency, Topmark, Udsen, V1235816, Viktor_Cortess, Viraz, Yanchuan, ZdravkoHr, Zims, albahaca, albertwh1te, alexweb3, alexxander, ast3ros, audityourcontracts, bareli, bin2chen, bronze_pickaxe, c0pp3rscr3w3r, cartlex_, castle_chain, chaduke, debo, ether_sky, gumgumzum, imare, its_basu, jaraxxus, jasonxiale, josephdara, kodyvim, ladboy233, lanrebayode77, lsaudit, mert_eren, minhtrng, n1punp, nadin, niroh, nmirchev8, orion, peakbolt, perseverancesuccess, pfapostol, ptsanev, rvierdiiev, saneryee, shaflow2, te_aut, terrancrypt, twcctop, unsafesol, ustas, versiyonbir, windhustler, yongskiws, zhaojie, ziyou-
25.6785 USDC - $25.68
There are multiple instances of constructors missing the address(0) checks which can mistakenly set the contracts to address(0).
Add a address(0) checks in the constructor
Do not hardcode zero bytes (bytes(0)) as adapterParamers. Pass them as a parameter instead. They can be used for custom functionality. e.g. receive airdropped native gas from the relayer on destination
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L946 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L793
Instead of hardcoding , pass it as a parameter
In the codebase, there exists a notable discrepancy between the bridgeInMultiple and bridgeOutMultiple functions. While the bridgeOutMultiple function sensibly incorporates a length check to ensure that the input arrays are not greater than 255 elements in length, this critical check is conspicuously absent in the bridgeInMultiple function. The absence of this array length validation in bridgeInMultiple opens up the possibility of unexpected behavior if the input arrays exceed the specified limit. It is essential to maintain consistency in array length checks between these functions to ensure the secure and predictable operation of the contract.
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L299 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L254
Add the same check present in bridgeOutMultiple to bridgeInMultiple
Use OpenZeppelin reentrancy guard instead of implementing your own.
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BaseBranchRouter.sol#L211-L217 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L921-l927 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L565-L572 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/MulticallRootRouter.sol#L589-L595 https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L1189-L1195
#0 - c4-pre-sort
2023-10-15T12:51:37Z
0xA5DF marked the issue as sufficient quality report
#1 - 0xA5DF
2023-10-15T12:51:47Z
L1 is in bot report
#2 - c4-judge
2023-10-21T05:38:10Z
alcueca marked the issue as grade-a
🌟 Selected for report: MrPotatoMagic
Also found by: 0xHelium, 0xSmartContract, 0xbrett8571, 0xsagetony, 33BYTEZZZ, Bauchibred, K42, Littlebeast, LokiThe5th, Oxsadeeq, SAAJ, Sathish9098, ZdravkoHr, albertwh1te, alexxander, catellatech, chaduke, hunter_w3b, ihtishamsudo, invitedtea, jauvany, klau5, kodyvim, lsaudit, pavankv, pfapostol, yongskiws
243.335 USDC - $243.33
1. Analysis of the Codebase:
The Maia DAO project demonstrates a commitment to best practices and code quality. Several unique aspects and existing patterns can be observed:
2. Architecture Feedback:
At the core of Branch architecture is the BranchPort, with one port being dedicated to each branch or “chain”. These BranchPorts store and lock the user's tokens for cross-chain messages. The CoreBranchRouter and BranchBridgAgent serve as crucial entry points for various operations within the system.
The router and the BridgeAgent, are users’ entry points for users to interact with the system. Each branch has a BrigeAgentFactory that allows for the deployment of additional BridgeAgents. Each BridgeAgent is attached to a BranchRouter. This includes ArbitrumBranch as well.
The Root, only exists on Arbtrum. The is separate from the AbritrumBranch though which acts more as a separate Branch for Arbitrum.
The Root contracts have one RootPort with one CoreRootRouter (with one RootBridgeAgent). There can also be multiple RootBridgeAgentFactories which can create multiple RootBridgeAgents. Systematically, each BridgeAgent is attached to one Root Router.
This architecture lends itself to be a bit confusing. With two different contracts in which the user can enter the system it unnecessarily overexposes the users to more systemic risk. It's not clear why this decision was made, if possible considering limiting entry points to only one contract similar to Uniswaps Router and Pool System.
The following diagram illustrates the execution flow for cross-chain deposits.
Execution Flow of Adding a Bridge Agent
Contracts Overview:
ArbitrumBranchBridgeAgent.sol
retrySettlement
is not currently implemented. As a standard practice, consider removing redundant code from the contracts and adding it back when needed in the future.BaseBranchRouter.sol
_transferAndApproveMultipleTokens
function.BranchBridgeAgent.sol
callOutAndBridgeMultiple
, have a sanity check to make sure that the length of arrays of token does not exceed 255, since the payload restricts the length to be as a uint8
type.excessivelySafeCall
function lacks a return value check._zroPaymentAddress
is hardcoded to address(0)
which prevents the Maia DAO contracts from using LayerZero tokens as fees in the future. The second example is the _adapterParams
set to bytes(0)
which is used for custom functionality.BranchPort.sol
bridgeInMultiple
function should include a sanity check to ensure that length **>** **255**
, similar to the check present in bridgeOutMultiple
.setCoreBranchRouter
function should include a check to verify if _coreBranchBridgeAgent
is already added to the bridgeAgents
array. Implement an isBridgeAgent[_coreBranchBridgeAgent]
check within the function or else it may result in duplicate values.addPortStrategy
can be used to toggle a port strategy back to true if the owner ends up being malicious. Consider the following, port strategy is added via addPortStrategy
its then toggled off with togglePortStrategy
it can then be toggled back on without calling the togglePortStrategy
function as it calling addPortStrategy
again with the same _portStrategy
address will toggle the bool back to true. Consider adding a check that the port strategy doesn’t already exist in the addPortStrategy
function.addBridgeAgent
and toggleBrigdeAgent
.MulticallRootRouter.sol
safeApprove
is deprecated. Instead, use safeIncreaseAllowance
and safeDecreaseAllowance
in the _approveMultipleAndCallOut
and _approveAndCallOut
functions.RootBridgeAgent.sol
estimateFees
function of LayerZero, the _payInZRO
parameter is hardcoded to false
. To allow for payment via ZRO tokens in the future, consider passing it as an argument instead of hardcoding it.send
function via LayerZero endpoint, the _zroPaymentAddress
parameter is hardcoded to address(0)
. To accommodate future payments via ZRO tokens, consider passing this parameter as an argument._adapterParams
parameter should not be set to bytes(0)
. Instead of hardcoding it to bytes(0)
, consider passing it as a function argument. This parameter is used for custom functionality that may not be required now but could be useful in the future.VirtualAccount.sol
ERC1155
tokens, there is currently no function provided to withdraw ERC1155
tokens from the contract.3. Centralization Risks:
While Maia DAO's design aims to maintain decentralization, there are centralization risks:
4. Systemic Risks:
Systemic risks relate to potential issues affecting the entire system. In Maia DAO, these include:
uint8
type variables, are not exceeded is crucial to prevent issues.Highly recommend reviewing the LayerZero docs and reimplementing the endpoint using their contracts available via npm. An overwhelming majority of the issues we found we related to improper implementation leading to the failling of messages or blocking of channels. It is always recommended to follow the docs and suggested implementation as much as possible to avoid issues such as these. Asking for assistance in their Discord is also an option if you’re ensure about secure implementation.
5. Other Recommendations:
To enhance the Maia DAO project, the following recommendations are made:
ERC1155
token withdrawal functionality is provided in the VirtualAccount.sol contract.addLocalToken
function.6. Time Spent:
The analysis was conducted within approximately 60 hours, ensuring a thorough examination of the codebase, architecture, risks, and recommendations.
Overall Best Practices to Follow
Ownable2Step.sol
.nonReentrant
modifier over a custom lock
modifier.chainId
are currently defined with a uint16
data type (e.g., localChainId
, rootChainId
, etc.). The maximum value a uint16
type can hold is 65535. If the project intends to deploy the contract on networks with a chainId
greater than 65535, such as Aurora (1313161554) or Harmony Mainnet (1666600000), the deployment will fail due to overflow. As a best practice, consider changing the type to uint256
.immutable
and involving third-party components, conduct thorough research. In the context of the current contract, lzEndpointAddress
is defined as immutable
. If LayerZero plans to migrate to a new version due to a bug or security risk, all contracts could become unusable.payable
functions, always verify whether msg.value > 0
to prevent transactions from failing and provide a better user experience.148 hours
#0 - c4-pre-sort
2023-10-15T14:10:43Z
0xA5DF marked the issue as sufficient quality report
#1 - alcueca
2023-10-20T12:07:50Z
Adequate system description. Original diagram. Contract-by-contract analysis. Valuable recommendations.
#2 - c4-judge
2023-10-20T12:07:51Z
alcueca marked the issue as grade-a