Skip to content

Add OFTTransportAdapter to support cross-chain token transfers of USDT0 via OFT messaging protocol #902

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

Merged
merged 23 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
088599a
first draft of OFTTransportAdapter
grasphoper Feb 25, 2025
d9bcdd0
update yarn.lock file
grasphoper Feb 25, 2025
cc464db
Revert "update yarn.lock file"
grasphoper Feb 26, 2025
dc3da61
add yarn.lock compatible with master
grasphoper Feb 26, 2025
6898995
polish OFTTransportAdapter, add OFT support to Arbitrum_Adapter on L1…
grasphoper Feb 26, 2025
8d23a10
polish + fix missing approval
grasphoper Feb 26, 2025
27452fb
add context for dstEid
grasphoper Feb 26, 2025
68ea57e
address most of the PR comments about contracts
grasphoper Feb 27, 2025
abd7b98
update deploy scripts, add tests for OFT messaging, polish contracts
grasphoper Feb 28, 2025
9f18a3d
cleanup comments and extraneous log file
grasphoper Feb 28, 2025
5402488
revert package.json prepublish change
grasphoper Mar 4, 2025
146f218
generalize oft adapter to support multiple tokens. Introduce OFTAddre…
grasphoper Mar 5, 2025
9f8ac2e
add __gap to ArbitrumSpokePool, update stale comments on OFTTransport…
grasphoper Mar 5, 2025
5fd6313
update some comments; adjust fee cap naming
grasphoper Mar 5, 2025
5aea206
address PR comments
grasphoper Mar 5, 2025
6f27e94
address PR comments
grasphoper Mar 6, 2025
d95be4c
fix deploy script, remove incorrect values from consts
grasphoper Mar 6, 2025
1819983
improve comment
grasphoper Mar 6, 2025
0984f31
add oftFeeCap as a param to arbitrum adapter construction
grasphoper Mar 12, 2025
cbf9a0f
move OFT functionality to SpokePool for easy further integration and …
grasphoper Mar 12, 2025
75b6577
remove layerzero from foundry remappings
grasphoper Mar 12, 2025
086f4a9
address licensing comment; add permalink to LZ OFT code on github
grasphoper Mar 12, 2025
6a0e0f4
fix a couple of comment typos
grasphoper Mar 12, 2025
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
7 changes: 5 additions & 2 deletions contracts/AlephZero_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ contract AlephZero_SpokePool is Arbitrum_SpokePool {
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer,
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
ITokenMessenger _cctpTokenMessenger,
// _oftFeeCap can be set to 0 for AlephZero_SpokePool as AlephZero does not support OFT transfers.
uint256 _oftFeeCap
)
Arbitrum_SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
_l2Usdc,
_cctpTokenMessenger
_cctpTokenMessenger,
_oftFeeCap
)
{} // solhint-disable-line no-empty-blocks
}
15 changes: 13 additions & 2 deletions contracts/Arbitrum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer,
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
ITokenMessenger _cctpTokenMessenger,
// _oftFeeCap can be set to 1 ether for Arbitrum, but has to be custom-set for other chains that might inherit this adapter, like AlephZero
uint256 _oftFeeCap
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, _oftFeeCap)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down Expand Up @@ -83,9 +85,13 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {
**************************************/

function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override {
address oftMessenger = _getOftMessenger(l2TokenAddress);

// If the l2TokenAddress is UDSC, we need to use the CCTP bridge.
if (_isCCTPEnabled() && l2TokenAddress == address(usdcToken)) {
_transferUsdc(withdrawalRecipient, amountToReturn);
} else if (oftMessenger != address(0)) {
_transferViaOFT(IERC20(l2TokenAddress), IOFT(oftMessenger), withdrawalRecipient, amountToReturn);
} else {
// Check that the Ethereum counterpart of the L2 token is stored on this contract.
address ethereumTokenToBridge = whitelistedTokens[l2TokenAddress];
Expand All @@ -112,4 +118,9 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {

// Apply AVM-specific transformation to cross domain admin address on L1.
function _requireAdminSender() internal override onlyFromCrossDomainAdmin {}

// Reserve storage slots for future versions of this base contract to add state variables without
// affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables
// are added. This is at bottom of contract to make sure it's always at the end of storage.
uint256[1000] private __gap;
}
5 changes: 4 additions & 1 deletion contracts/Ethereum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;

// Ethereum_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP) {} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Ethereum SpokePool.
Expand Down
5 changes: 4 additions & 1 deletion contracts/Linea_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract Linea_SpokePool is SpokePool {
using SafeERC20 for IERC20;

// Linea_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might in the future, right? We should probably pass this in the constructor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is, when we support this, we'll have to make changes to _bridgeTokensToHubPool function in this same contract. I think it makes sense to add it to constructor then, just not to add additional params to chains that will not support this prematurely. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another solution could be this:

I add OFT code to every contract that "can potentially support OFT" and then in _bridgeTokensToHubPool it's going to be behind a zero address check. When we decide to turn it on and support a token, the code will be ready. Hmm, perhaps do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cause currently only Arbitrum_SpokePool / Arbitrum_Adapter are using the OFT logic fully, and we'd need to change code for others to do the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked off-site about this. Will leave code like it is for now to not grow this PR any more


/**
* @notice Address of Linea's Canonical Message Service contract on L2.
*/
Expand Down Expand Up @@ -51,7 +54,7 @@ contract Linea_SpokePool is SpokePool {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP) {} // solhint-disable-line no-empty-blocks

/**
* @notice Initialize Linea-specific SpokePool.
Expand Down
6 changes: 5 additions & 1 deletion contracts/Ovm_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ interface IL2ERC20Bridge {
*/
contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
using SafeERC20 for IERC20;

// Ovm_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

// "l1Gas" parameter used in call to bridge tokens from this contract back to L1 via IL2ERC20Bridge. Currently
// unused by bridge but included for future compatibility.
uint32 public l1Gas;
Expand Down Expand Up @@ -71,7 +75,7 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down
5 changes: 4 additions & 1 deletion contracts/PolygonZkEVM_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ interface IBridgeMessageReceiver {
contract PolygonZkEVM_SpokePool is SpokePool, IBridgeMessageReceiver {
using SafeERC20 for IERC20;

// PolygonZkEVM_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

// Address of Polygon zkEVM's Canonical Bridge on L2.
IPolygonZkEVMBridge public l2PolygonZkEVMBridge;

Expand Down Expand Up @@ -93,7 +96,7 @@ contract PolygonZkEVM_SpokePool is SpokePool, IBridgeMessageReceiver {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP) {} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Polygon zkEVM SpokePool.
Expand Down
5 changes: 4 additions & 1 deletion contracts/Polygon_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ interface IFxMessageProcessor {
contract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter {
using SafeERC20Upgradeable for PolygonIERC20Upgradeable;

// Polygon_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

// Address of FxChild which sends and receives messages to and from L1.
address public fxChild;

Expand Down Expand Up @@ -87,7 +90,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down
5 changes: 4 additions & 1 deletion contracts/Scroll_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface IL2GatewayRouterExtended is IL2GatewayRouter {
contract Scroll_SpokePool is SpokePool {
using SafeERC20Upgradeable for IERC20Upgradeable;

// Scroll_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

/**
* @notice The address of the official l2GatewayRouter contract for Scroll for bridging tokens from L2 -> L1
* @dev We can find these (main/test)net deployments here: https://docs.scroll.io/en/developers/scroll-contracts/#scroll-contracts
Expand Down Expand Up @@ -48,7 +51,7 @@ contract Scroll_SpokePool is SpokePool {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP) {} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Scroll SpokePool.
Expand Down
34 changes: 30 additions & 4 deletions contracts/SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "./upgradeable/MultiCallerUpgradeable.sol";
import "./upgradeable/EIP712CrossChainUpgradeable.sol";
import "./upgradeable/AddressLibUpgradeable.sol";
import "./libraries/AddressConverters.sol";
import "./libraries/OFTTransportAdapter.sol";

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
Expand All @@ -37,7 +38,8 @@ abstract contract SpokePool is
ReentrancyGuardUpgradeable,
MultiCallerUpgradeable,
EIP712CrossChainUpgradeable,
IDestinationSettler
IDestinationSettler,
OFTTransportAdapter
{
using SafeERC20Upgradeable for IERC20Upgradeable;
using AddressLibUpgradeable for address;
Expand Down Expand Up @@ -111,6 +113,9 @@ abstract contract SpokePool is
// reason (eg blacklist) to track their outstanding liability, thereby letting them claim it later.
mapping(address => mapping(address => uint256)) public relayerRefund;

// Mapping of L2 token address to L2 IOFT messenger address. Required to support bridging via OFT standard
mapping(address => address) public oftMessengers;

/**************************************************************
* CONSTANT/IMMUTABLE VARIABLES *
**************************************************************/
Expand Down Expand Up @@ -196,6 +201,7 @@ abstract contract SpokePool is
event EmergencyDeletedRootBundle(uint256 indexed rootBundleId);
event PausedDeposits(bool isPaused);
event PausedFills(bool isPaused);
event SetOFTMessenger(address indexed token, address indexed messenger);

/**
* @notice Construct the SpokePool. Normally, logic contracts used in upgradeable proxies shouldn't
Expand All @@ -211,13 +217,15 @@ abstract contract SpokePool is
* into the past from the block time of the deposit.
* @param _fillDeadlineBuffer fillDeadlineBuffer to set. Fill deadlines can't be set more than this amount
* into the future from the block time of the deposit.
* @param _oftFeeCap fee cap in native token when paying for cross-chain OFT transfers
*/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) {
uint32 _fillDeadlineBuffer,
uint256 _oftFeeCap
) OFTTransportAdapter(OFTEIds.Ethereum, _oftFeeCap) {
wrappedNativeToken = WETH9Interface(_wrappedNativeTokenAddress);
depositQuoteTimeBuffer = _depositQuoteTimeBuffer;
fillDeadlineBuffer = _fillDeadlineBuffer;
Expand Down Expand Up @@ -363,6 +371,15 @@ abstract contract SpokePool is
emit EmergencyDeletedRootBundle(rootBundleId);
}

/**
* @notice Add token -> OFTMessenger relationship. Callable only by admin.
* @param token token address on the current chain
* @param messenger IOFT contract address on the current chain for the specified token. Acts as a 'mailbox'
*/
function setOftMessenger(address token, address messenger) public onlyAdmin nonReentrant {
_setOftMessenger(token, messenger);
}

/**************************************
* LEGACY DEPOSITOR FUNCTIONS *
**************************************/
Expand Down Expand Up @@ -1739,6 +1756,15 @@ abstract contract SpokePool is
else return keccak256(message);
}

function _setOftMessenger(address _token, address _messenger) internal {
oftMessengers[_token] = _messenger;
emit SetOFTMessenger(_token, _messenger);
}

function _getOftMessenger(address _token) internal view returns (address) {
return oftMessengers[_token];
}

// Implementing contract needs to override this to ensure that only the appropriate cross chain admin can execute
// certain admin functions. For L2 contracts, the cross chain admin refers to some L1 address or contract, and for
// L1, this would just be the same admin of the HubPool.
Expand All @@ -1750,5 +1776,5 @@ abstract contract SpokePool is
// Reserve storage slots for future versions of this base contract to add state variables without
// affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables
// are added. This is at bottom of contract to make sure it's always at the end of storage.
uint256[998] private __gap;
uint256[997] private __gap;
}
5 changes: 4 additions & 1 deletion contracts/Succinct_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import "./external/interfaces/SuccinctInterfaces.sol";
* @notice Succinct Spoke pool.
*/
contract Succinct_SpokePool is SpokePool, ITelepathyHandler {
// Succinct_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

// Address of the succinct AMB contract.
address public succinctTargetAmb;

Expand Down Expand Up @@ -47,7 +50,7 @@ contract Succinct_SpokePool is SpokePool, ITelepathyHandler {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP) {} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Succinct SpokePool.
Expand Down
5 changes: 4 additions & 1 deletion contracts/ZkSync_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ contract ZkSync_SpokePool is SpokePool {
// while changing only constructor parameters can lead to substantial fee savings. So, the following params
// are all set by passing in constructor params where possible.

// ZkSync_SpokePool does not use OFT messaging, setting the cap to 0
uint256 private constant OFT_FEE_CAP = 0;

// ETH on ZkSync implements a subset of the ERC-20 interface, with additional built-in support to bridge to L1.
address public l2Eth;

Expand All @@ -41,7 +44,7 @@ contract ZkSync_SpokePool is SpokePool {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, OFT_FEE_CAP) {} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the ZkSync SpokePool.
Expand Down
33 changes: 30 additions & 3 deletions contracts/chain-adapters/Arbitrum_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IOFT } from "../interfaces/IOFT.sol";
import "../external/interfaces/CCTPInterfaces.sol";
import "../libraries/CircleCCTPAdapter.sol";
import "../libraries/OFTTransportAdapter.sol";
import { ArbitrumInboxLike as ArbitrumL1InboxLike, ArbitrumL1ERC20GatewayLike } from "../interfaces/ArbitrumBridge.sol";
import { OFTAddressBook } from "../libraries/OFTAddressBook.sol";

/**
* @notice Contract containing logic to send messages from L1 to Arbitrum.
Expand All @@ -18,7 +21,7 @@
*/

// solhint-disable-next-line contract-name-camelcase
contract Arbitrum_Adapter is AdapterInterface, CircleCCTPAdapter {
contract Arbitrum_Adapter is AdapterInterface, CircleCCTPAdapter, OFTTransportAdapter {
using SafeERC20 for IERC20;

// Amount of ETH allocated to pay for the base submission fee. The base submission fee is a parameter unique to
Expand All @@ -43,35 +46,46 @@
address public constant L1_DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;

// This address on L2 receives extra ETH that is left over after relaying a message via the inbox.
address public immutable L2_REFUND_L2_ADDRESS;

Check warning on line 49 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 49 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

// Inbox system contract to send messages to Arbitrum. Token bridges use this to send tokens to L2.
// https://github.com/OffchainLabs/nitro-contracts/blob/f7894d3a6d4035ba60f51a7f1334f0f2d4f02dce/src/bridge/Inbox.sol
ArbitrumL1InboxLike public immutable L1_INBOX;

Check warning on line 53 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 53 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

// Router contract to send tokens to Arbitrum. Routes to correct gateway to bridge tokens. Internally this
// contract calls the Inbox.
// Generic gateway: https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/gateway/L1ArbitrumGateway.sol
ArbitrumL1ERC20GatewayLike public immutable L1_ERC20_GATEWAY_ROUTER;

Check warning on line 58 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 58 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

// Helper contract to help us map token -> OFT messenger for OFT-enabled tokens
OFTAddressBook public immutable OFT_ADDRESS_BOOK;

Check warning on line 61 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

Check warning on line 61 in contracts/chain-adapters/Arbitrum_Adapter.sol

View workflow job for this annotation

GitHub Actions / Lint (20)

Variable name must be in mixedCase

/**
* @notice Constructs new Adapter.
* @param _l1ArbitrumInbox Inbox helper contract to send messages to Arbitrum.
* @param _l1ERC20GatewayRouter ERC20 gateway router contract to send tokens to Arbitrum.
* @param _l2RefundL2Address L2 address to receive gas refunds on after a message is relayed.
* @param _l1Usdc USDC address on L1.
* @param _cctpTokenMessenger TokenMessenger contract to bridge via CCTP.
* @param _oftAddressBook OFTAddressBook contract to help identify token -> oftMessenger relationship for OFT bridging.
* @param _oftFeeCap A fee cap we apply to OFT bridge native payment. A good default is 1 ether
*/
constructor(
ArbitrumL1InboxLike _l1ArbitrumInbox,
ArbitrumL1ERC20GatewayLike _l1ERC20GatewayRouter,
address _l2RefundL2Address,
IERC20 _l1Usdc,
ITokenMessenger _cctpTokenMessenger
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Arbitrum) {
ITokenMessenger _cctpTokenMessenger,
OFTAddressBook _oftAddressBook,
uint256 _oftFeeCap
)
CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Arbitrum)
OFTTransportAdapter(OFTEIds.Arbitrum, _oftFeeCap)
{
L1_INBOX = _l1ArbitrumInbox;
L1_ERC20_GATEWAY_ROUTER = _l1ERC20GatewayRouter;
L2_REFUND_L2_ADDRESS = _l2RefundL2Address;
OFT_ADDRESS_BOOK = _oftAddressBook;
}

/**
Expand Down Expand Up @@ -113,9 +127,13 @@
uint256 amount,
address to
) external payable override {
address oftMessenger = _getOftMessenger(l1Token);

// Check if this token is USDC, which requires a custom bridge via CCTP.
if (_isCCTPEnabled() && l1Token == address(usdcToken)) {
_transferUsdc(to, amount);
} else if (oftMessenger != address(0)) {
_transferViaOFT(IERC20(l1Token), IOFT(oftMessenger), to, amount);
}
// If not, we can use the Arbitrum gateway
else {
Expand Down Expand Up @@ -178,4 +196,13 @@
require(address(this).balance >= requiredL1CallValue, "Insufficient ETH balance");
return requiredL1CallValue;
}

/**
* @notice Queries for an OFT messenger from an OFT address book contract
* @param _token Token to query the messenger for
* @return messenger OFT messenger contract
*/
function _getOftMessenger(address _token) internal view returns (address) {
return OFT_ADDRESS_BOOK.oftMessengers(_token);
}
}
Loading
Loading