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 7 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
64 changes: 64 additions & 0 deletions contracts/chain-adapters/Rerouter_Adapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 a target (not necessarily a spoke pool) on L2.
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
* @notice The contract receiving messages on L2 will be "spoke pool like" functions, e.g. "relayRootBundle" and
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
* "relaySpokePoolAdminFunction".
* @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
address public immutable l1Adapter;
bmzig marked this conversation as resolved.
Show resolved Hide resolved
address public immutable l2Target;

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

/**
* @notice Constructs new Adapter for sending tokens/messages to an L2 target.
* @param _l1Adapter Address of the adapter contract on mainnet which implements message transfers
* and token relays.
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
* @param _l2Target Address of the L2 contract which receives the token and message relays.
*/
constructor(address _l1Adapter, address _l2Target) {
l1Adapter = _l1Adapter;
l2Target = _l2Target;
}

/**
* @notice Send cross-chain message to a target on L2.
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
* @notice The original target field is omitted since messages are unconditionally sent to `l2Target`.
* @param message Data to send to target.
*/
function relayMessage(address, bytes memory message) external payable override {
(bool success, ) = l1Adapter.delegatecall(abi.encodeCall(AdapterInterface.relayMessage, (l2Target, message)));
if (!success) revert RelayMessageFailed();
}

/**
* @notice Bridge tokens to a target on L2.
* @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.
* @notice the "to" field is discarded since we unconditionally relay tokens to `l2Target`.
*/
function relayTokens(
address l1Token,
address l2Token,
uint256 amount,
address
) external payable override {
(bool success, ) = l1Adapter.delegatecall(
abi.encodeCall(AdapterInterface.relayTokens, (l1Token, l2Token, amount, l2Target))
);
if (!success) revert RelayTokensFailed(l1Token);
}
}
141 changes: 141 additions & 0 deletions test/evm/foundry/local/Arbitrum_L3_Adapter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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 { ITokenMessenger } from "../../../../contracts/external/interfaces/CCTPInterfaces.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 MinimalWeth is Token_ERC20 {
constructor(string memory name, string memory symbol) Token_ERC20(name, symbol) {}

function withdraw(uint256 amount) public {
_burn(msg.sender, amount);
(bool success, ) = payable(msg.sender).call{ value: amount }("");
require(success);
}
}

contract CrossDomainMessenger {
event MessageSent(address indexed target);

function sendMessage(
address target,
bytes calldata,
uint32
) external {
emit MessageSent(target);
}
}

contract StandardBridge {
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
event ETHDepositInitiated(address indexed to, uint256 amount);

function depositERC20To(
address l1Token,
address l2Token,
address to,
uint256 amount,
uint32,
bytes calldata
) external {
Token_ERC20(l1Token).burn(msg.sender, amount);
Token_ERC20(l2Token).mint(to, amount);
}

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

contract ArbitrumL3AdapterTest is Test {
Rerouter_Adapter l3Adapter;
Optimism_Adapter optimismAdapter;

Token_ERC20 l1Token;
Token_ERC20 l2Token;
Token_ERC20 l1Weth;
Token_ERC20 l2Weth;
CrossDomainMessenger crossDomainMessenger;
StandardBridge standardBridge;

address l2Target;

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

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

crossDomainMessenger = new CrossDomainMessenger();
standardBridge = new StandardBridge();

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

// Messages should be indiscriminately sent to the l2Forwarder.
function testRelayMessage(address target, bytes memory message) public {
vm.expectEmit(address(crossDomainMessenger));
emit CrossDomainMessenger.MessageSent(l2Target);
l3Adapter.relayMessage(target, message);
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
}

// Sending Weth should call depositETHTo().
function testRelayWeth(uint256 amountToSend, address random) public {
vm.deal(address(l1Weth), amountToSend);
l1Weth.mint(address(l3Adapter), amountToSend);
assertEq(amountToSend, l1Weth.totalSupply());
vm.expectEmit(address(standardBridge));
emit StandardBridge.ETHDepositInitiated(l2Target, amountToSend);
l3Adapter.relayTokens(address(l1Weth), address(l2Weth), amountToSend, random);
assertEq(0, l1Weth.totalSupply());
}

// Sending any random token should call depositERC20To().
function testRelayToken(uint256 amountToSend, address random) public {
l1Token.mint(address(l3Adapter), amountToSend);
assertEq(amountToSend, l1Token.totalSupply());
l3Adapter.relayTokens(address(l1Token), address(l2Token), amountToSend, random);
assertEq(amountToSend, l2Token.balanceOf(l2Target));
assertEq(amountToSend, l2Token.totalSupply());
assertEq(0, l1Token.totalSupply());
}
}
Loading