Assessment reports>Facet Bridge>Discussion>Changes to Optimism bridge contracts

Changes to Optimism bridge contracts

This section documents the most notable changes made to the Optimism bridge contracts. Note that some trivial changes may not be explicitly discussed, including, for example, changes made to support access of contract storage in a way compatible with the upgradability pattern adopted by Facet Bridge.

StandardBridge

This is the base contract from which both the L1 and the L2 bridge contracts inherit from.

The high-level logic flow of ERC-20 bridging operations in both directions follows close to the previous implementation. The most significant difference between Facet and a default Optimism chain is the need to implement replay logic for L1 → L2 bridging operations, since L2 transactions may be dropped in some edge cases. Refer to section ref for a more in-depth discussion.

To support the replay logic, this contract has seen the introduction of two functions:

replayERC20Deposit

This unpermissioned function can be invoked exclusively on the L1 bridge. It allows to trigger a replay of the L2 transaction finalizing an L1 → L2 bridging operation.

finalizeBridgeERC20Replayable

This function replaces the finalizeBridgeERC20 entry point on the L2 bridge normally invoked by the L1 bridge contract. The function adds a check that ensures the bridging operation being attempted has not already been processed. If not already finalized, the function marks the operation as such and invokes the unmodified finalizeBridgeERC20 function, which finalizes the operation by minting or releasing the assets to the recipient.

L1StandardBridge

The only major modification to the L1StandardBridge contract has been the addition of a depositWethTo external function, which allows to bridge native L1 ETH by first wrapping it into WETH and bridging the wrapped asset as any other ERC-20.

In the version of the code being initially reviewed, this function had an unintuitive quirk. Since the function receives ETH and wraps them, the bridge contract has ownership of the WETH. Therefore, the ERC-20 bridging was initiated using as a sender address the address of the bridge. This flow was secure but meant that the funds appeared as if they originated from the bridge contract, for example in events emitted by the bridge contracts. Using the bridge address as source was necessary because the address of the sender is used by the internal function _initiateERC20Deposit as the source of the bridged assets (as the from parameter in a transferFrom call).

This quirk was addressed in commit by adding an argument to _initiateERC20Deposit, which allows to skip the transferFrom call. This allows depositWethTo to call the internal function with the correct sender address without triggering a transfer of WETH from the sender. This modification does not add a security issue, since all other usages of the internal function correctly trigger the ERC-20 transfer.

L2StandardBridge

The L2StandardBridge contract logic had no significant modifications.

L1CrossDomainMessenger

This contract was modified to disable the _sendMessage function. This modification reflects on the CrossDomainMessenger contract, effectively disabling the external sendMessage function, normally used on OP-based L1s to send a message to the L2.

Since Facet uses a different mechanism to send messages to the L2, the function was disabled by reverting with an error message inviting to use the LibFacet.sendFacetTransaction function instead.

An additional effect of disabling this function is that the L1 bridge functions normally used to initiate native L1 ETH bridging (bridgeETHTo, bridgeETH, depositETH, depositETHTo, and the fallback receive function) will revert, as they all use _sendMessage. This is intended, since Facet L2 native currency is not ETH equivalent. Users are meant to bridge WETH instead.

Zellic © 2025Back to top ↑