Skip to content

Commit

Permalink
update rerouter adapter to support new forwarder interface
Browse files Browse the repository at this point in the history
Signed-off-by: bennett <bennett@umaproject.org>
  • Loading branch information
bmzig committed Oct 1, 2024
1 parent ce3a425 commit 1b1ceb5
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 9 deletions.
46 changes: 39 additions & 7 deletions contracts/chain-adapters/Rerouter_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.0;

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

/**
* @notice Contract containing logic to send messages from L1 to "L3" networks that do not have direct connections
Expand All @@ -28,19 +30,40 @@ contract Rerouter_Adapter is AdapterInterface {
// 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;
// L2_CHAIN_ID is the chain ID of the network which is "in between" the L1 and L3. This network will contain the forwarder.
uint256 public immutable L2_CHAIN_ID;
// L3_CHAIN_ID is the chain ID of the network which contains the target spoke pool. This chain id is passed to the
// forwarder contract so that it may determine the correct L2-L3 bridge to use to arrive at L3.
uint256 public immutable L3_CHAIN_ID;
// Interface of the Hub Pool. Used to query state related to L1-L2 token mappings.
HubPoolInterface public immutable HUB_POOL;

error RelayMessageFailed();
error RelayTokensFailed(address l1Token);
error L2RouteNotWhitelisted(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.
* @param _l2ChainId Chain ID of the network which contains the forwarder. It is the network which is intermediate in message
* transmission from L1 to L3.
* @param _l3ChainId Chain ID of the network which contains the spoke pool which corresponds to this adapter instance.
* @param _hubPool Address of the hub pool deployed on L1.
*/
constructor(address _l1Adapter, address _l2Target) {
constructor(
address _l1Adapter,
address _l2Target,
uint256 _l2ChainId,
uint256 _l3ChainId,
HubPoolInterface _hubPool
) {
L1_ADAPTER = _l1Adapter;
L2_TARGET = _l2Target;
L2_CHAIN_ID = _l2ChainId;
L3_CHAIN_ID = _l3ChainId;
HUB_POOL = _hubPool;
}

/**
Expand All @@ -54,7 +77,7 @@ contract Rerouter_Adapter is AdapterInterface {
* 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));
bytes memory wrappedMessage = abi.encodeCall(ForwarderInterface.relayMessage, (target, L3_CHAIN_ID, message));
(bool success, ) = L1_ADAPTER.delegatecall(
abi.encodeCall(AdapterInterface.relayMessage, (L2_TARGET, wrappedMessage))
);
Expand All @@ -64,26 +87,35 @@ contract Rerouter_Adapter is AdapterInterface {
/**
* @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`.
* @param l3Token L3 token to receive.
* @param amount Amount of L1 tokens to deposit and L3 tokens to receive.
* @param target The address of the contract which should ultimately receive `amount` of `l3Token`.
* @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,
address l3Token,
uint256 amount,
address target
) external payable override {
// Fetch the address of the L2 token, as defined in the Hub Pool. This is to complete a proper token bridge from L1 to L2.
address l2Token = HUB_POOL.poolRebalanceRoute(L2_CHAIN_ID, l1Token);
// If l2Token is the zero address, then this means that the L2 token is not enabled as a pool rebalance route. This is similar
// to the check in the hub pool contract found here: https://github.com/across-protocol/contracts/blob/a2afefecba57177a62be35be092516d0c106097e/contracts/HubPool.sol#L890
if (l2Token == address(0)) revert L2RouteNotWhitelisted(l1Token);

// 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));
bytes memory message = abi.encodeCall(
ForwarderInterface.relayTokens,
(l2Token, l3Token, amount, L3_CHAIN_ID, target)
);
(success, ) = L1_ADAPTER.delegatecall(abi.encodeCall(AdapterInterface.relayMessage, (L2_TARGET, message)));
if (!success) revert RelayMessageFailed();
}
Expand Down
47 changes: 45 additions & 2 deletions test/evm/foundry/local/Rerouter_Adapter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,32 @@ 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 { FinderInterface } from "@uma/core/contracts/data-verification-mechanism/interfaces/FinderInterface.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";
import { HubPoolInterface } from "../../../../contracts/interfaces/HubPoolInterface.sol";
import { HubPool } from "../../../../contracts/HubPool.sol";
import { LpTokenFactoryInterface } from "../../../../contracts/interfaces/LpTokenFactoryInterface.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) {}
constructor(
address _l1Adapter,
address _l2Target,
uint256 _l2ChainId,
uint256 _l3ChainId,
HubPoolInterface _hubPool
) Rerouter_Adapter(_l1Adapter, _l2Target, _l2ChainId, _l3ChainId, _hubPool) {}

receive() external payable {}
}
Expand All @@ -43,17 +55,42 @@ contract RerouterAdapterTest is Test {
WETH9 l2Weth;
MockBedrockCrossDomainMessenger crossDomainMessenger;
MockBedrockL1StandardBridge standardBridge;
HubPool hubPool;

address l2Target;
address owner;

uint256 constant L2_CHAIN_ID = 10;
uint256 constant L3_CHAIN_ID = 100;

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

// Temporary values to initialize the hub pool. We do not set a new LP token, nor do we dispute, so these
// do not need to be contracts.
address finder = makeAddr("finder");
address lpTokenFactory = makeAddr("lpTokenFactory");
address timer = makeAddr("timer");

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

vm.startPrank(owner);
hubPool = new HubPool(
LpTokenFactoryInterface(lpTokenFactory),
FinderInterface(finder),
WETH9Interface(address(l1Weth)),
timer
);
// Whitelist l1Token and l1Weth for relaying on L2. Note that the hub pool does checks to ensure that the L3 token is whitelisted when it performs
// a token bridge.
hubPool.setPoolRebalanceRoute(L2_CHAIN_ID, address(l1Token), address(l2Token));
hubPool.setPoolRebalanceRoute(L2_CHAIN_ID, address(l1Weth), address(l2Weth));
vm.stopPrank();

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

Expand All @@ -64,7 +101,13 @@ contract RerouterAdapterTest is Test {
IERC20(address(0)),
ITokenMessenger(address(0))
);
rerouterAdapter = new Mock_Rerouter_Adapter(address(optimismAdapter), l2Target);
rerouterAdapter = new Mock_Rerouter_Adapter(
address(optimismAdapter),
l2Target,
L2_CHAIN_ID,
L3_CHAIN_ID,
hubPool
);
}

// Messages should be indiscriminately sent to the l2Forwarder.
Expand Down

0 comments on commit 1b1ceb5

Please sign in to comment.