Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: passthrough L1->L3 adapter to send messages to L3 via an L2 forwarder #607

Merged
merged 54 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
15e815b
feat: add L3 adapter to send messages to an L2 forwarder
bmzig Sep 16, 2024
f9c0d62
feat: add L2 forwarder interface
bmzig Sep 17, 2024
b360237
Merge branch 'master' into bz/l3Adapter
bmzig Sep 17, 2024
2daf57b
change name of l2Adapter to adapter
bmzig Sep 17, 2024
cea0337
sync with upstream changes
bmzig Sep 18, 2024
0de9303
refactor and rename
bmzig Sep 19, 2024
0371348
Merge branch 'master' into bz/l2ForwarderInterface
bmzig Sep 20, 2024
fd94012
clean up interfaces
bmzig Sep 23, 2024
93d40cf
add test
bmzig Sep 23, 2024
00aeb40
rename test
bmzig Sep 23, 2024
f2e3c41
simplify forwarder base and make it a proxy
bmzig Sep 23, 2024
f251354
disable implementation initialization
bmzig Sep 23, 2024
1e984cc
change names
bmzig Sep 24, 2024
fe66b78
remove EIP 712
bmzig Sep 24, 2024
814498b
add/improve comments
bmzig Sep 24, 2024
43f03b8
add security contact
bmzig Sep 24, 2024
cc386a7
update comment
bmzig Sep 24, 2024
a9cf8b1
update comments
bmzig Sep 24, 2024
6cbbfd4
add comment about cross chain contracts
bmzig Sep 24, 2024
6d8a633
fix comments and refactor test
bmzig Sep 25, 2024
4076b6b
comments
bmzig Sep 25, 2024
110a8e9
comments
bmzig Sep 25, 2024
ac7b0de
wrap message
bmzig Sep 25, 2024
19813c6
update forwarder base
bmzig Sep 25, 2024
d87d09c
comment
bmzig Sep 25, 2024
d78ad6f
extend AdapterInterface
bmzig Sep 25, 2024
2c97c2a
Merge branch 'master' into bz/l2ForwarderInterface
bmzig Sep 26, 2024
8ee13c0
refactor: move all Arbitrum interfaces to a single location
bmzig Sep 26, 2024
9544a96
Merge branch 'bz/refactorArbInterfaces' into bz/l2ForwarderInterface
bmzig Sep 26, 2024
f914722
Merge branch 'master' into bz/l3Adapter
bmzig Sep 26, 2024
a282d83
update rerouter adapter with newest version
bmzig Sep 27, 2024
1339b5f
remove dependency on chain id
bmzig Sep 27, 2024
c3950ac
new recursive format
bmzig Sep 27, 2024
2aac0f7
use events
bmzig Sep 27, 2024
06c8877
add remote token mapping and update comments
bmzig Sep 27, 2024
01b1a38
Merge branch 'master' into bz/l2ForwarderInterface
bmzig Sep 27, 2024
77bc3d5
remove unused file
bmzig Sep 27, 2024
af95f41
merge master and update comments to not explicity mention L3
bmzig Sep 27, 2024
64d3503
add more checks and improve comments
bmzig Sep 27, 2024
1274920
rename immutable vars
bmzig Sep 30, 2024
d330993
naming
bmzig Sep 30, 2024
5271df8
update comments
bmzig Sep 30, 2024
7aa7d11
comments
bmzig Sep 30, 2024
20c89a7
clean up comments
nicholaspai Sep 30, 2024
e8590d4
Update ForwarderBase.sol
nicholaspai Sep 30, 2024
cbe2953
Update ForwarderBase.sol
nicholaspai Sep 30, 2024
14c5c41
Update contracts/chain-adapters/ForwarderBase.sol
nicholaspai Sep 30, 2024
242775d
Update ForwarderBase.sol
nicholaspai Sep 30, 2024
87cdbe6
fix: lint
pxrl Oct 1, 2024
41b4856
reduce scope so that we only accomodate L3s. Use chain IDs as keys.
bmzig Oct 1, 2024
ce3a425
Merge branch 'bz/l2ForwarderInterface' into bz/l3Adapter
bmzig Oct 1, 2024
1b1ceb5
update rerouter adapter to support new forwarder interface
bmzig Oct 1, 2024
074acc7
Rerouter -> Router
bmzig Oct 1, 2024
8796c40
Merge branch 'master' into bz/l3Adapter
bmzig Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions contracts/chain-adapters/Rerouter_Adapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { AdapterInterface } from "./interfaces/AdapterInterface.sol";

/**
* @notice Contract containing logic to send messages from L1 to "L3" networks that do not have direct connections
* with L1. L3's are defined as networks that connect to L1 indirectly via L2, and this contract sends
* messages to those L3's by rerouting them via those L2's. This contract is called a "Rerouter" because it uses
* (i.e. delegatecall's) existing L2 adapter logic to send a message first from L1 to L2 and then from L2 to L3.
* @dev Due to the constraints of the `SetCrossChainContracts` event as outlined in UMIP-157 and how the HubPool
* delegatecalls adapters like this one, all messages relayed through this
* adapter have target addresses on the L3's. However, these target addresses do not exist on L2 where all messages are
* rerouted through. Therefore, this contract is designed to be used in tandem with "L2 Forwarder Adapters" which help
* get the messages from L1 to L3 via L2's.
* @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
* called via delegatecall, which will execute this contract's logic within the context of the originating contract.
* For example, the HubPool will delegatecall these functions, therefore its only necessary that the HubPool's methods
* that call this contract's logic guard against reentrancy.
* @custom:security-contact bugs@across.to
*/

// solhint-disable-next-line contract-name-camelcase
contract Rerouter_Adapter is AdapterInterface {
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
// Adapter designed to relay messages from L1 to L2 addresses and delegatecalled by this contract to reroute
// messages to L3 via the L2_TARGET.
address public immutable L1_ADAPTER;
// L2_TARGET is a "Forwarder" contract that will help relay messages from L1 to L3. Messages are "rerouted" through
// the L2_TARGET.
address public immutable L2_TARGET;

error RelayMessageFailed();
error RelayTokensFailed(address l1Token);

/**
* @notice Constructs new Adapter. This contract will re-route messages destined for an L3 to L2_TARGET via the L1_ADAPTER contract.
* @param _l1Adapter Address of the adapter contract on mainnet which implements message transfers
* and token relays to the L2 where _l2Target is deployed.
* @param _l2Target Address of the L2 contract which receives the token and message relays in order to forward them to an L3.
*/
constructor(address _l1Adapter, address _l2Target) {
L1_ADAPTER = _l1Adapter;
L2_TARGET = _l2Target;
}

/**
* @notice Send cross-chain message to a target on L2 which will forward messages to the intended remote target on an L3.
* @param target Address of the remote contract which receives `message` after it has been forwarded by all intermediate
* contracts.
* @param message Data to send to `target`.
* @dev The message passed into this function is wrapped into a `relayMessage` function call, which is then passed
* to L2. The `L2_TARGET` contract implements AdapterInterface, so upon arrival on L2, the arguments to the L2 contract's
* `relayMessage` call will be these `target` and `message` values. From there, the forwarder derives the next appropriate
* method to send `message` to the following layers and ultimately to the target on L3.
*/
function relayMessage(address target, bytes memory message) external payable override {
bytes memory wrappedMessage = abi.encodeCall(AdapterInterface.relayMessage, (target, message));
(bool success, ) = L1_ADAPTER.delegatecall(
abi.encodeCall(AdapterInterface.relayMessage, (L2_TARGET, wrappedMessage))
);
if (!success) revert RelayMessageFailed();
}

/**
* @notice Bridge tokens to a target on L2 and follow up the token bridge with a call to continue bridging the sent tokens.
* @param l1Token L1 token to deposit.
* @param l2Token L2 token to receive.
* @param amount Amount of L1 tokens to deposit and L2 tokens to receive.
* @param target The address of the contract which should ultimately receive `amount` of `l1Token`.
* @dev When sending tokens, we follow-up with a message describing the amount of tokens we wish to continue bridging.
* This allows forwarders to know how much of some token to allocate to a certain target.
*/
function relayTokens(
address l1Token,
address l2Token,
uint256 amount,
address target
) external payable override {
// Relay tokens to the forwarder.
(bool success, ) = L1_ADAPTER.delegatecall(
abi.encodeCall(AdapterInterface.relayTokens, (l1Token, l2Token, amount, L2_TARGET))
);
if (!success) revert RelayTokensFailed(l1Token);

// Follow-up token relay with a message to continue the token relay on L2.
bytes memory message = abi.encodeCall(AdapterInterface.relayTokens, (l1Token, l2Token, amount, target));
bmzig marked this conversation as resolved.
Show resolved Hide resolved
(success, ) = L1_ADAPTER.delegatecall(abi.encodeCall(AdapterInterface.relayMessage, (L2_TARGET, message)));
if (!success) revert RelayMessageFailed();
}
}
37 changes: 37 additions & 0 deletions contracts/test/MockBedrockStandardBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,40 @@ contract MockBedrockL2StandardBridge is IL2ERC20Bridge {
// do nothing
}
}

contract MockBedrockL1StandardBridge {
event ETHDepositInitiated(address indexed to, uint256 amount);
event ERC20DepositInitiated(address indexed to, address l1Token, address l2Token, uint256 amount);

function depositERC20To(
address l1Token,
address l2Token,
address to,
uint256 amount,
uint32,
bytes calldata
) external {
IERC20(l1Token).transferFrom(msg.sender, address(this), amount);
emit ERC20DepositInitiated(to, l1Token, l2Token, amount);
}

function depositETHTo(
address to,
uint32,
bytes calldata
) external payable {
emit ETHDepositInitiated(to, msg.value);
}
}

contract MockBedrockCrossDomainMessenger {
event MessageSent(address indexed target);

function sendMessage(
address target,
bytes calldata,
uint32
) external {
emit MessageSent(target);
}
}
111 changes: 111 additions & 0 deletions test/evm/foundry/local/Rerouter_Adapter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { Test } from "forge-std/Test.sol";
import { MockERC20 } from "forge-std/mocks/MockERC20.sol";
import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { IL1StandardBridge } from "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol";
import { Rerouter_Adapter } from "../../../../contracts/chain-adapters/Rerouter_Adapter.sol";
import { Optimism_Adapter } from "../../../../contracts/chain-adapters/Optimism_Adapter.sol";
import { WETH9Interface } from "../../../../contracts/external/interfaces/WETH9Interface.sol";
import { WETH9 } from "../../../../contracts/external/WETH9.sol";
import { ITokenMessenger } from "../../../../contracts/external/interfaces/CCTPInterfaces.sol";
import { MockBedrockL1StandardBridge, MockBedrockCrossDomainMessenger } from "../../../../contracts/test/MockBedrockStandardBridge.sol";

// We normally delegatecall these from the hub pool, which has receive(). In this test, we call the adapter
// directly, so in order to withdraw Weth, we need to have receive().
contract Mock_Rerouter_Adapter is Rerouter_Adapter {
constructor(address _l1Adapter, address _l2Target) Rerouter_Adapter(_l1Adapter, _l2Target) {}

receive() external payable {}
}

contract Token_ERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function mint(address to, uint256 value) public virtual {
_mint(to, value);
}

function burn(address from, uint256 value) public virtual {
_burn(from, value);
}
}

contract RerouterAdapterTest is Test {
Rerouter_Adapter rerouterAdapter;
Optimism_Adapter optimismAdapter;

Token_ERC20 l1Token;
Token_ERC20 l2Token;
WETH9 l1Weth;
WETH9 l2Weth;
MockBedrockCrossDomainMessenger crossDomainMessenger;
MockBedrockL1StandardBridge standardBridge;

address l2Target;

function setUp() public {
l2Target = makeAddr("l2Target");

l1Token = new Token_ERC20("l1Token", "l1Token");
l2Token = new Token_ERC20("l2Token", "l2Token");
l1Weth = new WETH9();
l2Weth = new WETH9();

crossDomainMessenger = new MockBedrockCrossDomainMessenger();
standardBridge = new MockBedrockL1StandardBridge();

optimismAdapter = new Optimism_Adapter(
WETH9Interface(address(l1Weth)),
address(crossDomainMessenger),
IL1StandardBridge(address(standardBridge)),
IERC20(address(0)),
ITokenMessenger(address(0))
);
rerouterAdapter = new Mock_Rerouter_Adapter(address(optimismAdapter), l2Target);
}

// Messages should be indiscriminately sent to the l2Forwarder.
function testRelayMessage(address target, bytes memory message) public {
vm.assume(target != l2Target);
vm.expectEmit(address(crossDomainMessenger));
emit MockBedrockCrossDomainMessenger.MessageSent(l2Target);
rerouterAdapter.relayMessage(target, message);
}

// Sending Weth should call depositETHTo().
function testRelayWeth(uint256 amountToSend, address random) public {
// Prevent fuzz testing with amountToSend * 2 > 2^256
amountToSend = uint256(bound(amountToSend, 1, 2**254));
vm.deal(address(l1Weth), amountToSend);
vm.deal(address(rerouterAdapter), amountToSend);

vm.startPrank(address(rerouterAdapter));
l1Weth.deposit{ value: amountToSend }();
vm.stopPrank();

assertEq(amountToSend * 2, l1Weth.totalSupply());
vm.expectEmit(address(standardBridge));
emit MockBedrockL1StandardBridge.ETHDepositInitiated(l2Target, amountToSend);
rerouterAdapter.relayTokens(address(l1Weth), address(l2Weth), amountToSend, random);
assertEq(0, l1Weth.balanceOf(address(rerouterAdapter)));
}

// Sending any random token should call depositERC20To().
function testRelayToken(uint256 amountToSend, address random) public {
l1Token.mint(address(rerouterAdapter), amountToSend);
assertEq(amountToSend, l1Token.totalSupply());

vm.expectEmit(address(standardBridge));
emit MockBedrockL1StandardBridge.ERC20DepositInitiated(
l2Target,
address(l1Token),
address(l2Token),
amountToSend
);
rerouterAdapter.relayTokens(address(l1Token), address(l2Token), amountToSend, random);
assertEq(0, l1Token.balanceOf(address(rerouterAdapter)));
}
}
Loading