Download audit report
This document presents the security review conducted by Nethermind Security for the bridge smart contracts of Renzo Protocol. Renzo is a restaking solution that abstracts and manages AVS strategies for Liquid Restaking Tokens (LRTs), simplifying Ethereum restaking and making it accessible to a broader audience. By leveraging EigenLayer, Renzo enables users to restake their ETH without the complexities of node operation, AVS strategy selection, or direct smart contract interactions.
The security review focused on the smart contracts designed to enable deposits into the core Renzo protocol from Layer 2 networks. In return for making deposits, users receive xezETH tokens, which serve as the Layer 2 equivalent of ezETH.
The audited code comprises of 896 lines of code written in the Solidity language.
| Audit assessment | |
| Audit Type | Security Review |
| Initial Report | April 15, 2025 |
| Final Report | May 1, 2025 |
| Repository | Contracts |
| Start Commit | c2bdea4177 |
| Final Commit | cfe7249d38 |
| Documentation | Docs |
| Documentation Assessment | High |
| Test Suite Assessment | Low |
Findings by severity
| # | Finding | Severity | Update |
| 1 | Incorrect configuration of token time discount on Arbitrum | Info | Fixed |
| 2 | Incorrect handling of WETH in the sweep(...) function | Info | Fixed |
| 3 | Incorrect oracle configuration for wstETH token on Base | Info | Fixed |
| 4 | The LidoOPValueTransfer contract unnecessarily grants the wstETH allowance to the lidoBridge address | Info | Fixed |
| 5 | Incorrect comments | Best Practices | Fixed |
| 6 | Unused events | Best Practices | Fixed |
Renzo's bridging contract architecture is designed to transfer assets and price data between Layer 2 networks and Ethereum mainnet within the Renzo Protocol. Its primary goal is to allow users on the L2s to mint ezETH by depositing native ETH or other supported collateral tokens. Deposited assets are then bridged to the L1 for staking on Renzo. The bridge system incorporates two main components: a funds bridging mechanism that handles the asset transfers and a cross-chain price feed messaging system that ensures synchronized valuation data between chains.
The bridge system involves several distinct roles and participants:
xRenzoDepositNativeBridge. Users receive optimistically minted cross-chain ezETH tokens (xezETH), representing their claim to the underlying assets once the bridging is complete.HyperlaneSender and HyperlaneReceiver contracts propagate the price of ezETH from Arbitrum to other L2 networks via the Hyperlane infrastructure.4.2.1 L1 Receiver contract
The xRenzoBridgeReceiver contract on Ethereum mainnet receives the assets bridged from L2s. Its core responsibility is to process incoming deposits—converting, unwrapping, and depositing funds into the Renzo protocol.
ETH and stETH tokens are deposited into Renzo Protocol’s RestakeManager contract, triggering the mint of ezETHezETH tokens are transferred into the lockbox, and the L1 bridge contract receives xezETH in return, which is burned to keep L2 and L1 supplies in sync with their collateral backing.4.2.2 L2 Deposit contract
The xRenzoDepositNativeBridge contract is responsible for accepting user deposits on the L2. It immediately mints xezETH tokens in exchange for the deposited assets while batching funds for subsequent bridging to L1.
xezETH, the deposited asset value is measured in ETH using an oracle. This ensures that the minting process reflects an up-to-date ezETH/ETH exchange rate.4.2.3 Value Transfer contracts
The ValueTransfer contracts provide the actual pathway for moving tokens and ETH from L2 to L1. They act as adapters for the underlying bridging infrastructure, which vary for different chains. These include contracts to bridge ETH and wstETH.
4.2.4 Hyperlane messaging
A dedicated oracle contract (RenzoOracleL2) deployed on Arbitrum mainnet receives the price information from L1 using Chainlink. This price feed represents the exchange ratio between ezETH and ETH. The feed ensures that users receive an up-to-date minting rate on their deposits.
RenzoOracleL2 contract and sends this data through Hyperlane to other L2 networks. The HyperlaneReceiver contract on each L2 receives the pricing info. The contract charges fees based on the gas estimates for cross-chain execution.ezETH and xezETH regardless of the network.sweep(...) function bridges the funds using the Value Transfer contracts, ensuring that the deposits are sent to L1.ezETH tokens in return. The ezETH is sent to the protocol’s Lockbox contract. The lockbox mints xezETH in return for the provided ezETH and sends the tokens to the xRenzoBridgeReceiver contract. The receiver immediately burns the xezETH since the tokens were already minted on L2.The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.
Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:
When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.
Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:
When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.
| Impact / Likelihood | Low | Medium | High |
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info / Best Practices | Low | Medium |
| Undetermined | Undetermined | Undetermined | Undetermined |
To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.
| File | Fix commit | Type | Status |
contracts/Bridge/L2/xRenzoDepositNativeBridge.sol | — | Info | Fixed |
The xRenzoDepositNativeBridge contract implements a time-based discount mechanism through the tokenTimeDiscountBasisPoints mapping to account for the time when funds do not generate yield during the L2 to L1 bridging period.
When users deposit funds on L2, there's a time delay before these funds reach L1 and generate yield in the Renzo Protocol. To account for this period of missed yield generation, a discount is applied to the effective value of the deposited tokens (users receive less ezETH than they would if depositing directly on L1).
While this value is correctly configured for tokens on the Base L2 network (9 basis points for native ETH), it is incorrectly set to 0 on Arbitrum:
# @audit-info: Base L2 (correct configuration - 9 basis points)
$ cast call 0x766A4df76Cb091C41De22F75b6BD6733fb730357 \
"tokenTimeDiscountBasisPoints(address)(uint)" \
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
--rpc-url https://base.llamarpc.com
9
# @audit-info: Arbitrum (incorrect configuration - 0 basis points)
$ cast call 0x2EfA125B4fcD5B082a4233BA55b53FeB2Fb4Ff33 \
"tokenTimeDiscountBasisPoints(address)(uint)" \
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
--rpc-url https://1rpc.io/arb
0With the discount set to 0 on Arbitrum, users depositing on this L2 do not have their tokens appropriately discounted for the bridging delay period. This results in Arbitrum users receiving more favorable exchange rates, leading to a slight economic advantage compared to users on other L2 chains.
Recommendation
Consider updating the time discount values on Arbitrum by calling the updateTokenTimeDiscount(...) function.
Resolution
Configured with the correct value (9 bps) on arbitrum.
WETH in the sweep(...) function| File | Fix commit | Type | Status |
contracts/Bridge/L2/xRenzoDepositNativeBridge.sol | — | Info | Fixed |
There's an inconsistency in how the xRenzoDepositNativeBridge contract handles WETH tokens between the deposit(...) and sweep(...) functions. In the deposit(...) function, WETH is unwrapped to native ETH before the deposit begins:
function deposit(
IERC20 _token,
uint256 _amountIn,
// ...
) external nonReentrant returns (uint256) {
// ...
// @audit If the token is WETH, unwrap to ETH.
if (_token == IERC20(address(weth))) {
weth.withdraw(_amountIn);
}
// @audit ETH will be deposited.
return _deposit(...);
}The above code ensures that only native ETH is bridged to L1, and WETH tokens can only be bridged to L1 after unwrapping.
However, the sweep(...) function lacks similar handling for WETH. Even though wETH cannot be bridged to L1 directly, the value of the valueTransferBridges mapping for the WETH token is set to EthArbValueTransfer contract address. The same contract is also used for bridging native ETH deposits to L1 and is only designed to handle native ETH, not ERC20 tokens such as wETH. If a user donates WETH tokens to the xRenzoDepositNativeBridge and calls the sweep(...) function with the wETH address, the following will happen:
xRenzoDepositNativeBridge approves WETH to the bridge, but the transferRemote(...) function of the EthArbValueTransfer contract doesn't use this approval;xRenzoDepositNativeBridge contract;BridgeSwept event with misleading information, suggesting funds were bridged when they were not;Recommendation
Consider setting WETH address in the valueTransferBridges mapping to the zero address since WETH shouldn't be bridged.
wstETH token on Base| File | Fix commit | Type | Status |
contracts/Bridge/L2/xRenzoDepositNativeBridge.sol | — | Info | Fixed |
In the xRenzoDepositNativeBridge contract, price oracles determine the ETH value of deposited collateral tokens. For wstETH (wrapped staked ETH), the oracle should return the wstETH/stETH conversion rate, as wstETH will be unwrapped to stETH when bridged to L1 before being deposited into the Renzo protocol.
This conversion is deterministic and handled by smart contract logic rather than being subject to market fluctuations. On Arbitrum, the oracle is correctly configured to use this conversion rate. However, on Base, the oracle is incorrectly configured to return the wstETH/ETH market rate:
# Get wstETH oracle address from `xRenzoDepositNativeBridge` on Base:
$ cast call 0x766A4df76Cb091C41De22F75b6BD6733fb730357 \
"tokenOracleLookup(address)(address)" \
0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452 \
--rpc-url https://base.llamarpc.com
0x43a5C292A453A3bF3606fa856197f09D7B74251a
# Get Chainlink Oracle description
$ cast call 0x43a5C292A453A3bF3606fa856197f09D7B74251a \
"description()(string)" \
--rpc-url https://base.llamarpc.com
"WSTETH / ETH"
# Get Chainlink Oracle wstETH/ETH rate
$ cast call 0x43a5C292A453A3bF3606fa856197f09D7B74251a \
"latestAnswer()(uint)" \
--rpc-url https://base.llamarpc.com
1197860113406258300By comparison, the Arbitrum wstETH price is 1199433727364042426 wei, which is slightly more advantageous.
The result is that users depositing wstETH on Base receive slightly less xezETH than users depositing the same amount of wstETH on Arbitrum.
Recommendation
Consider updating the price oracle for wstETH on Base to use the wstETH/stETH exchange rate.
Resolution
wstETH oracle have been updated to use wstETH/stETH price from oracle contract at 0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061.
LidoOPValueTransfer contract unnecessarily grants the wstETH allowance to the lidoBridge address| File | Fix commit | Type | Status |
contracts/Bridge/L2/ValueTransfer/OP/LidoOPValueTransfer.sol | cfe7249 | Info | Fixed |
The LidoOPValueTransfer contract is used by the xRenzoDepositNativeBridge contract to bridge the wstETH tokens deposited by users on Base to the Ethereum mainnet. The transferRemote(...) function from the LidoOPValueTransfer contract calls the withdrawTo(...) function from the L2ERC20TokenBridge contract to bridge the wstETH tokens over the OP stack's Native Bridge.
function transferRemote(
// ...
address token,
uint256 amount
) external payable returns (bytes32 transferId) {
// ...
if (token != address(wstETH)) revert InvalidTokenReceived();
// @audit wstETH tokens wait to be bridged in the LidoOPValueTransfer contract.
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
// @audit-issue The wstETH allowance of the Lido bridge is unnecessarily
// increased. The bridge contract does not utilize the allowance logic.
IERC20(token).safeIncreaseAllowance(address(lidoBridge), amount);
// ...
// @audit The withdrawTo(...) function on the bridge calls the bridgeBurn(...)
// function, which bypasses token allowances.
lidoBridge.withdrawTo(token, l1xRenzoBridge, amount, 100000, extraData);
// ...
}The withdrawTo(...) function will burn the wstETH tokens from the LidoOPValueTransfer contract and issue the cross-chain message to the Ethereum mainnet. Since the L2ERC20TokenBridge contract has a special minter and burner permissions, it does not require any previous ERC20 allowance to burn the wstETH tokens. The allowance provided to this contract in the transferRemote(...) function will remain unutilized. It can be observed in the current Base deployment that the LidoOPValueTransfer contract currently has 1730e18 wstETH allowance for the L2ERC20TokenBridge contract.
Recommendation
Consider adding additional test cases around sweeping the tokens on Base and after that removing the redundant call to the increaseAllowance(...) function.
Resolution
Redundant call to the increaseAllowance(...) function removed at Code Ref.
| File | Fix commit | Type | Status |
contracts/Bridge/* | cfe7249 | Best Practices | Fixed |
The codebase contains several comments which are incorrect. Below is a non-exhaustive list of such code comments:
EthArbValueTransfer.sol: The NatSpec for the 2nd param of the transferRemote(...) function mentions that the token should always be wstETH, while the function handles native Ether only. This is also the case for EthOPValueTransfer.sol;EthArbValueTransfer.sol: In the transferRemote(...) function there is a comment // Do not allow ETH, while the function is supposed to exclusively handle native Ether. This is also the case for EthOPValueTransfer.sol;xRenzoDepositNativeBridge.sol: The NatSpec above the setSupportedToken(...) function doesn't describe the _valueTransferBridge param;HyperlaneSender.sol: The NatSpec comments above the pause(...) and unpause(...) functions are inverted;Recommendation
To make the code more readable, consider removing or changing the incorrect comments outlined above.
Resolution
Comments fixed at Code Ref.
| File | Fix commit | Type | Status |
contracts/Bridge/L2/xRenzoDepositNativeBridge.sol | cfe7249 | Best Practices | Fixed |
The codebase contains event declarations that are not utilized by the protocol contracts. The following is a non-exhaustive list of unused events:
xRenzoDepositNativeBridge.sol contains unused BridgeSweeperAddressUpdated event;Recommendation
Consider removing unused events to keep the code clean and readable.
Resolution
Unused BridgeSweeperAddressUpdated event removed at Code Ref.
Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:
These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.
The documentation for the Renzo protocol is comprehensive and well-structured, providing detailed information on its architecture and functionality. The official website includes thorough explanations of the protocol's features and use cases, allowing readers to understand the core mechanics of Renzo.
Additionally, the code itself is well-commented with extensive NatSpec annotations, making the codebase highly readable and facilitating a smooth review process. The Renzo team has made sure to provide clear, technical insights that helped the Nethermind Security team in understanding the project's inner workings and core logic.
The Renzo team was also proactive in making themselves available for sync calls to address any questions or concerns raised during the review. These discussions ensured that the security team had all the necessary context to perform a thorough analysis.
The Renzo protocol's bridging smart contracts are undertested. The test suite does not catch changes to the business logic of the contracts. To assess the test coverage for these files, the Nethermind Security team applied a technique called mutation testing to uncover the untested paths. This technique introduces slight modifications to the code called "mutations" or "mutants." An example of a mutation is, for example, changing the operator in an expression (a + b) to (a - b), or removing a require(a > b) statement entirely from the code. With these changes, the code no longer follows the expected business logic of the application, and the test suite should reflect that by failing.
Evaluation of the test suite with mutation testing consists of two phases:
Only one modification can be tested at a time. If the contract has ten mutations, the test suite must run ten times (once for every mutation). If any of the tests fail, it means that the test suite caught the change in the code. Whenever that happens, the particular mutant is considered "slain" or "killed" and is removed from the mutant's set. If that does not occur, a new test case can be added to cover the code branch to "kill" the mutant.
The following table outlines the results of the analysis performed on Renzo's bridging smart contracts. The first column lists the contracts that were tested using mutation testing. The second column indicates the number of mutants generated and how many were "slain" (i.e., caught by the test suite). The third column provides the percentage of mutants slain, reflecting the effectiveness of the test suite in covering the particular contract. The higher the score, the better the test suite is at finding bugs.
| Contract | Mutants (slain / total generated) | Score |
LidoArbValueTransfer.sol | 1 / 10 | 10.00% |
xRenzoDepositNativeBridge.sol | 10 / 267 | 3.75% |
RenzoOracleL2.sol | 45 / 47 | 95.74% |
HyperlaneReceiver.sol | 14 / 16 | 87.50% |
xRenzoBridgeReceiver.sol | 30 / 34 | 88.24% |
HyperlaneSender.sol | 27 / 32 | 84.38% |
EthArbValueTransfer.sol | 1 / 7 | 14.29% |
EthOPValueTransfer.sol | 1 / 7 | 14.29% |
LidoOPValueTransfer.sol | 0 / 10 | 0.00% |
| Total | 129 / 430 | 30.00% |
Remarks about the Renzo Protocol Test Suite
The current test suite for the Renzo Protocol is minimal and does not provide adequate coverage for the deployed contracts. While some unit tests exist, they do not sufficiently verify the behavior of the system as a whole. In particular, there is a lack of end-to-end testing across core protocol flows.
While the transmission of pricing information from the oracle via Hyperlane is covered by tests, the core xRenzoDepositNativeBridge contract and associated Value Transfer contracts remain significantly undertested, leaving critical protocol flows without sufficient verification.
To improve the reliability of the protocol, it is recommended to begin developing end-to-end tests on a forked network. Given that most contracts are already deployed on mainnet, constructing realistic test scenarios should be feasible and would significantly increase confidence in the system’s correctness.
Although the logic implemented in the contracts is relatively simple, expanding the test suite to include more cases—especially across boundaries between components—will help catch edge cases and regressions early.
All the relevant issues raised by the AuditAgent have been incorporated into this report. The AuditAgent is an AI-powered smart contract auditing tool that analyses code, detects vulnerabilities, and provides actionable fixes. It accelerates the security analysis process, complementing human expertise with advanced AI models to deliver efficient and comprehensive smart contract audits. Available at https://app.auditagent.nethermind.io.
As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.
This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.
The Leading Engineers of Blockchain Infrastructure