- Total Prize Pool: $100,000 USDC
- HM awards: $69,712.50 USDC
- Analysis awards: $4,225 USDC
- QA awards: $2,112.50 USDC
- Bot Race awards: $6,337.50 USDC
- Gas awards: $2,112.50 USDC
- Judge awards: $9,000 USDC
- Lookout awards: $6,000 USDC
- Scout awards: $500 USDC
- Join C4 Discord to register
- Submit findings using the C4 form
- Read our guidelines for more details
- Starts September 22, 2023 20:00 UTC
- Ends October 06, 2023 20:00 UTC
Automated findings output for the audit can be found here within 24 hours of audit opening.
Note for C4 wardens: Anything included in the automated findings output is considered a publicly known issue and is ineligible for awards.
- Some chains may demand different block timestamp settings.
- Using your Virtual Account as refundee for a the creation of cross-chain messages with or without settlement attached as well as retrySettlement or retryDeposit will result in gas refunds of excess gas on branch chains being inaccessible since the refundee is not an EOA able to claim receive the funds on any branch chain.
- Floating pragma, code will be deployed with 0.8.19.
- There are contracts that allow to renounce ownership and do not override renounceOwnership function from solady library.
- Our protocol has permissionless factories where anyone can create with for example deposit poison erc20 tokens in ports or create malicious routers. While contracts generated by these are not in scope, if it does affect other contracts or other balances, it is in scope.
- All admin functions are either only callable by other contracts in this system or by Hermes Protocol's Governance. Except for each RootBridgeAgent Manager that is open to whoever creates it. Misuse of these functions by Hermes Governance is out of scope, for example adding a Port strategy to an iliquid token or an iliquid chain. But misuse by the Manager of RootBridgeAgent is valid only if it affects other contracts or other balances.
Maia DAO V2 Ecosytem docs section that explains the business logic and technical references for Ulysses Protocol, can be found here.
Ulysses scope for this audit focuses on Ulysses Omnichain our Liquidity and Execution Platform built on top of Layer Zero.
This can be divided in two main features:
-
Virtualized liquidity is achieved by connecting Ports within a Pool and Spoke architecture, comprising both the Root Chain and multiple Branch Chains. These contracts are responsible for managing token balances and address mappings across environments. In addition, means that an asset deposited from a specific chain, is recognized as a different asset from the "same" asset but from a different chain (ex: arb ETH is different from mainnet ETH).
-
Arbitrary Cross-Chain Execution is facilitated by an expandable set of routers such as the Multicall Root Router that can be permissionlessly deployed through the Bridge Agent Factories. For more insight on Bridge Agents, please refer to our documentation here. Our Virtual Account contract simplifies remote asset management and interaction within the Root chain.
While this audit has all the Ulysses Omnichain components and features in scope, there are specific concerns that we would like to highlight for the wardens to pay special attention to. These are:
- BranchPort's Strategy Token and Port Strategy related functions.
- Omnichain balance accounting.
- Omnichain execution management aspects, particularly related to transaction nonce retry, as well as the retrieve and redeem patterns:
srChain
settlement and deposits should either havestatus
set toSTATUS_SUCCESS
andSTATUS_FAILED
depending on their redeemability by the user on the source.dstChain
settlement and deposit execution should haveexecutionState
set toSTATUS_READY
,STATUS_DONE
orSTATUS_RETRIEVE
according to user input fallback and destination execution outcome.
Previous Audits by Zellic and Code4rena can be found in the audits folder. There are three audits, two of them featuring Ulysses:
See scope.txt
Contract | SLOC | Purpose | Libraries Used |
---|---|---|---|
src/ArbitrumBranchBridgeAgent.sol | 56 | This contract is used for interfacing with Users/Routers acting as a middleman. | |
src/ArbitrumBranchPort.sol | 60 | Ulysses Port implementation for Arbitrum Branch Chain deployment |
solady |
src/ArbitrumCoreBranchRouter.sol | 76 | Core Branch Router implementation for Arbitrum deployment. | solmate |
src/MulticallRootRouter.sol | 323 | Root Router implementation for interfacing with third-party dApps present in the Root Omnichain Environment. | solady |
src/CoreRootRouter.sol | 257 | Core Root Router implementation for Root Environment deployment. | solady, solmate |
src/RootBridgeAgentExecutor.sol | 198 | This contract is used for requesting token settlement clearance and executing transaction requests from the branch chains. | solady |
src/CoreBranchRouter.sol | 147 | Core Branch Router implementation for deployment in Branch Chains. | solmate |
src/RootBridgeAgent.sol | 710 | Responsible for interfacing with Users and Routers acting as a middleman. | solady |
src/VirtualAccount.sol | 94 | A Virtual Account allows users to manage assets and perform interactions remotely. | solady, solmate, @openzeppelin/contracts |
src/BranchPort.sol | 288 | This contract is used to manage the deposit and withdrawal of underlying assets from the Branch Chain in response to Branch Bridge Agents' requests. | solady, solmate |
src/MulticallRootRouterLibZip.sol | 12 | Root Router implementation for interfacing with third-party dApps present in the Root Omnichain Environment. | solady |
src/BranchBridgeAgent.sol | 553 | Contract for deployment in Branch Chains of Omnichain System, responsible for interfacing with Users and Routers. | |
src/BaseBranchRouter.sol | 117 | Base Branch Contract for interfacing with Branch Bridge Agents. | solady, solmate |
src/RootPort.sol | 327 | This contract is used to manage the deposit and withdrawal of assets between the Root Omnichain Environment and every Branch Chain in response to Root Bridge Agents requests. | solady |
src/BranchBridgeAgentExecutor.sol | 61 | This contract is used for requesting token deposit clearance and executing transactions in response to requests from the root environment. | solady |
src/token/ERC20hTokenRoot.sol | 32 | Root Chain 1:1 ERC20 representation of a token deposited in a Branch Chain's Port. | solmate, solady |
src/token/ERC20hTokenBranch.sol | 23 | Branch Chains 1:1 ERC20 representation of a token deposited in a Branch Chain's Port. | solmate, solady |
src/factories/BranchBridgeAgentFactory.sol | 77 | Factory contract for allowing permissionless deployment of new Branch Bridge Agents. | solady |
src/factories/RootBridgeAgentFactory.sol | 26 | Factory contract used to deploy new Root Bridge Agents. | |
src/factories/ERC20hTokenBranchFactory.sol | 51 | Factory contract allowing for permissionless deployment of new Branch hTokens in Branch Chains of Ulysses Omnichain Liquidity Protocol. | solady |
src/factories/ERC20hTokenRootFactory.sol | 47 | Factory contract allowing for permissionless deployment of new Root hTokens in the Root Chain of Ulysses Omnichain Liquidity Protocol. | solmate, solady |
src/factories/ArbitrumBranchBridgeAgentFactory.sol | 52 | Factory contract for allowing permissionless deployment of new Arbitrum Branch Bridge Agents. | |
src/interfaces/BridgeAgentStructs.sol | 77 | File with Bridge Agent Structs | |
src/interfaces/IRootBridgeAgent.sol | 99 | Interface for Root Bridge Agent Contract. | |
src/interfaces/BridgeAgentConstants.sol | 23 | Interface for Bridge Agent Constants. | |
src/interfaces/IERC20hTokenBranchFactory.sol | 9 | Interface for ERC20 hToken Branch Factory. | |
src/interfaces/IPortStrategy.sol | 5 | Interface to be implemented by Brach Port Strategy contracts. | |
src/interfaces/ICoreBranchRouter.sol | 10 | Interface for Core Branch Router. | |
src/interfaces/IERC20hTokenBranch.sol | 5 | Interface for ERC20 hToken Branch. | |
src/interfaces/IBranchBridgeAgent.sol | 90 | Interface for Branch Bridge Agent. | |
src/interfaces/IBranchBridgeAgentFactory.sol | 8 | Interface for Branch Bridge Agent Factory. | |
src/interfaces/IRootPort.sol | 124 | Interfaces for Root Port. | |
src/interfaces/ILayerZeroEndpoint.sol | 42 | Interface for LayerZero Endpoint. | |
src/interfaces/IERC20hTokenRoot.sol | 9 | Interface for ERC20 hToken Root. | |
src/interfaces/IBranchPort.sol | 70 | Interface for Branch Port. | |
src/interfaces/IVirtualAccount.sol | 22 | Interface for Virtual Account. | @openzeppelin/contracts |
src/interfaces/IArbitrumBranchPort.sol | 10 | Interface for Arbitrum Branch Port. | |
src/interfaces/ILayerZeroUserApplicationConfig.sol | 7 | Interface for LayerZero User Application Config. | |
src/interfaces/IMulticall2.sol | 12 | Interface for Multicall2. | |
src/interfaces/IBranchRouter.sol | 31 | Interface for Branch Router. | |
src/interfaces/IRootBridgeAgentFactory.sol | 4 | Interface for Root Bridge Agent Factory. | |
src/interfaces/IERC20hTokenRootFactory.sol | 8 | Interface for ERC20 hToken Root Factory | |
src/interfaces/IRootRouter.sol | 27 | Interface for Root Router. | |
src/interfaces/ILayerZeroReceiver.sol | 4 | Interface for LayerZero Receiver. |
Everything out of "src" is out of scope, namely "lib" and "test".
Branch / Root Bridge Agent and Bridge Agent Executor packed payload decoding and encoding.
Please list specific ERC20 that your protocol is anticipated to interact with. Could be "any" (literally anything, fee on transfer tokens, ERC777 tokens and so forth) or a list of tokens you envision using on launch.
Arbitrum's deployment of UniswapV3 and Balancer.
Virtual Account should be able to keep and use UniswapV3 NFT's.
Root contracts are to be deployed on Arbitrum and Branch contracts in several L1 and L2 networks such as Ethereum mainnet, Polygon, Base and Optimism
Please list all trusted roles (e.g. operators, slashers, pausers, etc.), the privileges they hold, and any conditions under which privilege escalation is expected/allowable:
Only our governance has access to key admin state changing functions present in the RootPort
and CoreRootRouter
and the Root Bridge Agent deployer (referred to in the codebase as manager) is responsible for allowing new branch chains to connect to their Root Bridge Agent in order to prevent griefing.
In the event of a DOS, could you outline a minimum duration after which you would consider a finding to be valid? This question is asked in the context of most systems' capacity to handle DoS attacks gracefully for a certain period.
Unless there is the need to upgrade and migrate any component of Ulysses via governance ( e.g. Bridge Agents or Core Routers) downtime should be negligible to ensure assets are available at any time to their different users.
Is any part of your implementation intended to conform to any EIP's? If yes, please list the contracts in this format:
ERC20hTokenBranch
: Should comply withERC20/EIP20
ERC20hTokenRoot
: Should comply withERC20/EIP20
- Double spending of deposit and settlement nonces / assets (Bridge Agents and Bridge Agent Executors).
- Griefing of user deposits and settlements (Bridge Agents).
- Bricking of Bridge Agent and subsequent Omnichain dApps that rely on it.
- Circumventing Bridge Agent's encoding rules to manipulate remote chain state.
- The total balance of any given Virtualized Liquidity Token should never be greater than the amount of Underlying Tokens deposited in the asset's origin chain Branch Port.
- A Deposit / Settlement can never be redeemable and retryable at the same time.
- If you have a public code repo, please share it here:
- How many contracts are in scope?: 50
- Total SLoC for these contracts?: 4281
- How many external imports are there?: 33
- How many separate interfaces and struct definitions are there for the contracts within scope?: 42
- Does most of your code generally use composition or inheritance?: Inheritance
- How many external calls?: 17
- What is the overall line coverage percentage provided by your tests?: 69%
- Is this an upgrade of an existing system?: False
- Check all that apply (e.g. timelock, NFT, AMM, ERC20, rollups, etc.): Uses L2, Multi-Chain, Side-Chain, ERC-20 Token, Timelock function
- Is there a need to understand a separate part of the codebase / get context in order to audit this part of the protocol?: Yes
- Please describe required context: Layerzero Messaging layer, namely Endpoint contract: https://github.com/LayerZero-Labs/LayerZero/blob/main/contracts/Endpoint.sol
- Does it use an oracle?: No
- Describe any novel or unique curve logic or mathematical models your code uses: None
- Is this either a fork of or an alternate implementation of another project?: No
- Does it use a side-chain?: True
- Describe any specific areas you would like addressed: Please try to break token deposits and settlements patterns - retry, retrieve, and redeem - to avoid double spending, reentrancy and race conditions. Ensure proper asset management of different ports. Encoding and decoding of cross-chain payloads/data on bridge agents and routers.
Here is an example of a full script to run the first time you build the contracts in both Windows and Linux:
- Remove
.example
from the provided.env
file and edit the uncommentedRPC
andRPC_API_KEY
values to your preferences. These values will be used by our fork testing suite.
forge install
forge build
forge test --gas-report
forge snapshot --diff
Default gas price is 10,000, but you can change it by adding --gas-price <gas price>
to the command or by setting the gas_price
property in the foundry.toml file.
Install libraries using forge and compile contracts.
forge install
forge build
only uses native foundry tools (VM.fork)
- open the file '.env.sample' and populate the API_KEY and RPC_URL of the chains ARBITRUM, AVAX and FTM (FTM public RPC should be used). Add any other chain you want. Afterwards remove '.sample' from the file name.
- If you're creating a new test file extend 'LzForkTest' contract
- override the internal function 'setUpLzChains()' to start forks for the chains useful for your testing purposes you'll need to indicate the network chainId, name and the chain's Layer Zero 'Endpoint.sol' address.
- In the test's 'setUp() make sure to invoke the 'setUpLzChains()' function
Whenever you need to change chain during there are 6 functions at your disposal:
- switchLzChain and switchChain: changes the current VM chain, updates any pending packet for the destination chain and executes them. Receives either the layer zero chain Id (e.g. 100 or 110) or the network chainId (e.g. 1 or 42161)
- switchLzChainWithoutExecutePackets and switchChainWithoutExecutePackets: changes the current VM chain, updates any pending packet for the destination chain and without executing them. Receives either the layer zero chain Id (e.g. 100 or 110) or the network chainId (e.g. 1 or 42161)
- switchLzChainWithoutExecutePacketsOrUpdate and switchChainWithoutExecutePacketsOrUpdate: changes the current VM chain, without updating any pending packets for the destination chain and without executing them. Receives either the layer zero chain Id (e.g. 100 or 110) or the network chainId (e.g. 1 or 42161)
If you encounter any issues, please update slither to 0.9.3, the latest version at the moment.
To run slither from root, please specify the src directory.
slither "./src/*"
We have a slither config file that turns on optimization and adds forge remappings.
The output is provided in ./slither.txt