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

improve(OVM_SpokePool): Use bridgeERC20To to bridge native L2 tokens if admin decides to support them #520

Merged
merged 10 commits into from
Jun 17, 2024
28 changes: 2 additions & 26 deletions contracts/Blast_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,8 @@ contract Blast_SpokePool is Ovm_SpokePool {
if (l2TokenAddress == USDB || l2TokenAddress == address(wrappedNativeToken)) {
_claimYield(IERC20Rebasing(l2TokenAddress));
}
// If the token being bridged is WETH then we need to first unwrap it to ETH and then send ETH over the
// canonical bridge. On Optimism, this is address 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000.
if (l2TokenAddress == address(wrappedNativeToken)) {
WETH9Interface(l2TokenAddress).withdraw(amountToReturn); // Unwrap into ETH.
l2TokenAddress = l2Eth; // Set the l2TokenAddress to ETH.
IL2ERC20Bridge(Lib_PredeployAddresses.L2_STANDARD_BRIDGE).withdrawTo{ value: amountToReturn }(
l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
hubPool, // _to. Withdraw, over the bridge, to the l1 pool contract.
amountToReturn, // _amount.
l1Gas, // _l1Gas. Unused, but included for potential forward compatibility considerations
"" // _data. We don't need to send any data for the bridging action.
);
}
// If the token is USDB then use the L2BlastBridge
else if (l2TokenAddress == USDB) {
if (l2TokenAddress == USDB) {
IL2ERC20Bridge(L2_BLAST_BRIDGE).bridgeERC20To(
l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
L1_USDB,
Expand All @@ -151,17 +138,6 @@ contract Blast_SpokePool is Ovm_SpokePool {
l1Gas,
""
);
} else
IL2ERC20Bridge(
tokenBridges[l2TokenAddress] == address(0)
? Lib_PredeployAddresses.L2_STANDARD_BRIDGE
: tokenBridges[l2TokenAddress]
).withdrawTo(
l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
hubPool, // _to. Withdraw, over the bridge, to the l1 pool contract.
amountToReturn, // _amount.
l1Gas, // _l1Gas. Unused, but included for potential forward compatibility considerations
"" // _data. We don't need to send any data for the bridging action.
);
} else super._bridgeTokensToHubPool(amountToReturn, l2TokenAddress);
}
}
16 changes: 16 additions & 0 deletions contracts/Optimism_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import "./external/interfaces/CCTPInterfaces.sol";
* @notice Optimism Spoke pool.
*/
contract Optimism_SpokePool is Ovm_SpokePool {
// Address of custom bridge used to bridge Synthetix-related assets like SNX.
address private constant SYNTHETIX_BRIDGE = 0x136b1EC699c62b0606854056f02dC7Bb80482d63;

// Address of SNX ERC20
address private constant SNX = 0x8700dAec35aF8Ff88c16BdF0418774CB3D7599B4;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
address _wrappedNativeTokenAddress,
Expand Down Expand Up @@ -40,4 +46,14 @@ contract Optimism_SpokePool is Ovm_SpokePool {
) public initializer {
__OvmSpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, Lib_PredeployAddresses.OVM_ETH);
}

function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal virtual override {
// Handle custom SNX bridge which doesn't conform to the standard bridge interface.
if (l2TokenAddress == SNX)
SynthetixBridgeToBase(SYNTHETIX_BRIDGE).withdrawTo(
hubPool, // _to. Withdraw, over the bridge, to the l1 pool contract.
amountToReturn // _amount.
);
else super._bridgeTokensToHubPool(amountToReturn, l2TokenAddress);
}
}
57 changes: 41 additions & 16 deletions contracts/Ovm_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface IL2ERC20Bridge {
* @notice OVM specific SpokePool. Uses OVM cross-domain-enabled logic to implement admin only access to functions. * Optimism, Base, and Boba each implement this spoke pool and set their chain specific contract addresses for l2Eth and l2Weth.
*/
contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
using SafeERC20 for IERC20;
// "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 All @@ -50,18 +51,18 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
// replaced by the above constant.
address private __deprecated_messenger;

// Address of custom bridge used to bridge Synthetix-related assets like SNX.
address private constant SYNTHETIX_BRIDGE = 0x136b1EC699c62b0606854056f02dC7Bb80482d63;

// Address of SNX ERC20
address private constant SNX = 0x8700dAec35aF8Ff88c16BdF0418774CB3D7599B4;

// Stores alternative token bridges to use for L2 tokens that don't go over the standard bridge. This is needed
// to support non-standard ERC20 tokens on Optimism, such as DIA and SNX which both use custom bridges.
mapping(address => address) public tokenBridges;

// Stores mapping of L2 tokens to L1 equivalent tokens. If a mapping is defined for a given L2 token, then
// the mapped L1 token can be used in _bridgeTokensToHubPool which can then call bridgeERC20To, which
// requires specfiying an L1 token.
mapping(address => address) public remoteL1Tokens;

event SetL1Gas(uint32 indexed newL1Gas);
event SetL2TokenBridge(address indexed l2Token, address indexed tokenBridge);
event SetRemoteL1Token(address indexed l2Token, address indexed l1Token);

error NotCrossDomainAdmin();

Expand Down Expand Up @@ -111,6 +112,11 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
emit SetL1Gas(newl1Gas);
}

function setRemoteL1Token(address l2Token, address l1Token) public onlyAdmin nonReentrant {
remoteL1Tokens[l2Token] = l1Token;
emit SetRemoteL1Token(l2Token, l1Token);
}

/**
* @notice Set bridge contract for L2 token used to withdraw back to L1.
* @dev If this mapping isn't set for an L2 token, then the standard bridge will be used to bridge this token.
Expand Down Expand Up @@ -160,24 +166,43 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
else if (_isCCTPEnabled() && l2TokenAddress == address(usdcToken)) {
_transferUsdc(hubPool, amountToReturn);
}
// Handle custom SNX bridge which doesn't conform to the standard bridge interface.
else if (l2TokenAddress == SNX)
SynthetixBridgeToBase(SYNTHETIX_BRIDGE).withdrawTo(
hubPool, // _to. Withdraw, over the bridge, to the l1 pool contract.
amountToReturn // _amount.
);
else
IL2ERC20Bridge(
// Note we'll default to withdrawTo instead of bridgeERC20To here because we can assume
// we'll only bridge back L2 tokens that are "non-native", i.e. they have a canonical L1 token
// that maps to this L2. If we wanted to bridge "native L2" tokens we'd need to call
// bridgeERC20To and give allowance to the tokenBridge to spend l2Token from this contract. We'd
// also need to know the L1 equivalent token address.
else {
IL2ERC20Bridge tokenBridge = IL2ERC20Bridge(
tokenBridges[l2TokenAddress] == address(0)
? Lib_PredeployAddresses.L2_STANDARD_BRIDGE
: tokenBridges[l2TokenAddress]
).withdrawTo(
);
if (remoteL1Tokens[l2TokenAddress] != address(0)) {
// If there is a mapping for this L2 token to an L1 token, then use the L1 token address and
// call bridgeERC20To.
IERC20(l2TokenAddress).safeIncreaseAllowance(address(tokenBridge), amountToReturn);
address remoteL1Token = remoteL1Tokens[l2TokenAddress];
tokenBridge.bridgeERC20To(
l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
remoteL1Token, // Remote token to be received on l1 side. If the
// remoteL1Token on the other chain does not recognize the local token as the correct
// pair token, the ERC20 bridge will fail and the tokens will be returned to sender on
// this chain.
hubPool, // _to
amountToReturn, // _amount
l1Gas, // _l1Gas
"" // _data
);
} else {
tokenBridge.withdrawTo(
l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
hubPool, // _to. Withdraw, over the bridge, to the l1 pool contract.
amountToReturn, // _amount.
l1Gas, // _l1Gas. Unused, but included for potential forward compatibility considerations
"" // _data. We don't need to send any data for the bridging action.
);
}
}
}

// Apply OVM-specific transformation to cross domain admin address on L1.
Expand All @@ -188,5 +213,5 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
// 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 its always at the end of storage.
uint256[1000] private __gap;
uint256[999] private __gap;
}
2 changes: 2 additions & 0 deletions contracts/test/MockBedrockStandardBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ contract MockBedrockL2StandardBridge is IL2ERC20Bridge {
uint256 _minGasLimit,
bytes calldata _extraData
) external {
// Check that caller has approved this contract to pull funds, mirroring mainnet's behavior
IERC20(_localToken).transferFrom(msg.sender, address(this), _amount);
// do nothing
}
}
2 changes: 1 addition & 1 deletion scripts/checkStorageLayout.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

CONTRACTS=("Arbitrum_SpokePool" "Optimism_SpokePool" "Polygon_SpokePool" "Linea_SpokePool" "ZkSync_SpokePool" "Ethereum_SpokePool" "Base_SpokePool" "Mode_SpokePool")
CONTRACTS=("Arbitrum_SpokePool" "Optimism_SpokePool" "Polygon_SpokePool" "Linea_SpokePool" "ZkSync_SpokePool" "Ethereum_SpokePool" "Base_SpokePool" "Mode_SpokePool" "Blast_SpokePool")
if [[ "$1" == "--overwrite" ]]; then
for CONTRACT in "${CONTRACTS[@]}"; do
echo "Overwrite flag detected. Creating new storage layout snapshot of the $CONTRACT contract"
Expand Down
8 changes: 7 additions & 1 deletion storage-layouts/Base_SpokePool.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,15 @@
},
{
"contract": "contracts/Base_SpokePool.sol:Base_SpokePool",
"label": "__gap",
"label": "remoteL1Tokens",
"offset": 0,
"slot": "3165"
},
{
"contract": "contracts/Base_SpokePool.sol:Base_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "3166"
}
]
}
190 changes: 190 additions & 0 deletions storage-layouts/Blast_SpokePool.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
{
"storage": [
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "_initialized",
"offset": 0,
"slot": "0"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "_initializing",
"offset": 1,
"slot": "0"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "1"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "51"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "_status",
"offset": 0,
"slot": "101"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "102"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "151"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "_HASHED_NAME",
"offset": 0,
"slot": "1151"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "_HASHED_VERSION",
"offset": 0,
"slot": "1152"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "1153"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "crossDomainAdmin",
"offset": 0,
"slot": "2153"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "hubPool",
"offset": 0,
"slot": "2154"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "DEPRECATED_wrappedNativeToken",
"offset": 0,
"slot": "2155"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "DEPRECATED_depositQuoteTimeBuffer",
"offset": 20,
"slot": "2155"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "numberOfDeposits",
"offset": 24,
"slot": "2155"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "pausedFills",
"offset": 28,
"slot": "2155"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "pausedDeposits",
"offset": 29,
"slot": "2155"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "rootBundles",
"offset": 0,
"slot": "2156"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "enabledDepositRoutes",
"offset": 0,
"slot": "2157"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "DEPRECATED_relayFills",
"offset": 0,
"slot": "2158"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "DEPRECATED_fillCounter",
"offset": 0,
"slot": "2159"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "DEPRECATED_depositCounter",
"offset": 0,
"slot": "2160"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "DEPRECATED_refundsRequested",
"offset": 0,
"slot": "2161"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "fillStatuses",
"offset": 0,
"slot": "2162"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "2163"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "l1Gas",
"offset": 0,
"slot": "3162"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "l2Eth",
"offset": 4,
"slot": "3162"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__deprecated_messenger",
"offset": 0,
"slot": "3163"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "tokenBridges",
"offset": 0,
"slot": "3164"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "remoteL1Tokens",
"offset": 0,
"slot": "3165"
},
{
"contract": "contracts/Blast_SpokePool.sol:Blast_SpokePool",
"label": "__gap",
"offset": 0,
"slot": "3166"
}
]
}
Loading
Loading