diff --git a/deployments/blast-sepolia/solcInputs/6769901144c5351dc52d7ed689ed2938.json b/deployments/blast-sepolia/solcInputs/6769901144c5351dc52d7ed689ed2938.json index 8bf4279dc..13d719362 100644 --- a/deployments/blast-sepolia/solcInputs/6769901144c5351dc52d7ed689ed2938.json +++ b/deployments/blast-sepolia/solcInputs/6769901144c5351dc52d7ed689ed2938.json @@ -296,19 +296,19 @@ "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\nimport \"../external/interfaces/WETH9Interface.sol\";\n\n// @dev Use local modified CrossDomainEnabled contract instead of one exported by eth-optimism because we need\n// this contract's state variables to be `immutable` because of the delegateCall call.\nimport \"./CrossDomainEnabled.sol\";\nimport \"@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n/**\n * @notice Contract containing logic to send messages from L1 to Boba. This is a modified version of the Optimism adapter\n * that excludes the custom bridging logic.\n * @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be\n * called via delegatecall, which will execute this contract's logic within the context of the originating contract.\n * For example, the HubPool will delegatecall these functions, therefore its only necessary that the HubPool's methods\n * that call this contract's logic guard against reentrancy.\n */\n\n// solhint-disable-next-line contract-name-camelcase\ncontract Boba_Adapter is CrossDomainEnabled, AdapterInterface {\n using SafeERC20 for IERC20;\n uint32 public immutable L2_GAS_LIMIT = 2_000_000;\n\n WETH9Interface public immutable L1_WETH;\n\n IL1StandardBridge public immutable L1_STANDARD_BRIDGE;\n\n /**\n * @notice Constructs new Adapter.\n * @param _l1Weth WETH address on L1.\n * @param _crossDomainMessenger XDomainMessenger Boba system contract.\n * @param _l1StandardBridge Standard bridge contract.\n */\n constructor(\n WETH9Interface _l1Weth,\n address _crossDomainMessenger,\n IL1StandardBridge _l1StandardBridge\n ) CrossDomainEnabled(_crossDomainMessenger) {\n L1_WETH = _l1Weth;\n L1_STANDARD_BRIDGE = _l1StandardBridge;\n }\n\n /**\n * @notice Send cross-chain message to target on Boba.\n * @param target Contract on Boba that will receive message.\n * @param message Data to send to target.\n */\n function relayMessage(address target, bytes calldata message) external payable override {\n sendCrossDomainMessage(target, uint32(L2_GAS_LIMIT), message);\n emit MessageRelayed(target, message);\n }\n\n /**\n * @notice Bridge tokens to Boba.\n * @param l1Token L1 token to deposit.\n * @param l2Token L2 token to receive.\n * @param amount Amount of L1 tokens to deposit and L2 tokens to receive.\n * @param to Bridge recipient.\n */\n function relayTokens(\n address l1Token,\n address l2Token,\n uint256 amount,\n address to\n ) external payable override {\n // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge.\n if (l1Token == address(L1_WETH)) {\n L1_WETH.withdraw(amount);\n L1_STANDARD_BRIDGE.depositETHTo{ value: amount }(to, L2_GAS_LIMIT, \"\");\n } else {\n IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE;\n\n IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount);\n _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, \"\");\n }\n emit TokensRelayed(l1Token, l2Token, amount, to);\n }\n}\n" }, "contracts/chain-adapters/CrossDomainEnabled.sol": { - "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/* Interface Imports */\nimport { ICrossDomainMessenger } from \"@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol\";\n\n/**\n * @title CrossDomainEnabled\n * @dev Helper contract for contracts performing cross-domain communications between L1 and Optimism.\n * @dev This modifies the eth-optimism/CrossDomainEnabled contract only by changing state variables to be\n * immutable for use in contracts like the Optimism_Adapter which use delegateCall().\n */\ncontract CrossDomainEnabled {\n // Messenger contract used to send and recieve messages from the other domain.\n address public immutable MESSENGER;\n\n /**\n * @param _messenger Address of the CrossDomainMessenger on the current layer.\n */\n constructor(address _messenger) {\n MESSENGER = _messenger;\n }\n\n /**\n * Gets the messenger, usually from storage. This function is exposed in case a child contract\n * needs to override.\n * @return The address of the cross-domain messenger contract which should be used.\n */\n function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {\n return ICrossDomainMessenger(MESSENGER);\n }\n\n /**\n * Sends a message to an account on another domain\n * @param _crossDomainTarget The intended recipient on the destination domain\n * @param _gasLimit The gasLimit for the receipt of the message on the target domain.\n * @param _message The data to send to the target (usually calldata to a function with\n * onlyFromCrossDomainAccount())\n */\n function sendCrossDomainMessage(\n address _crossDomainTarget,\n uint32 _gasLimit,\n bytes calldata _message\n ) internal {\n // slither-disable-next-line reentrancy-events, reentrancy-benign\n getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);\n }\n}\n" + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/* Interface Imports */\nimport { ICrossDomainMessenger } from \"@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol\";\n\n/**\n * @title CrossDomainEnabled\n * @dev Helper contract for contracts performing cross-domain communications between L1 and Optimism.\n * @dev This modifies the eth-optimism/CrossDomainEnabled contract only by changing state variables to be\n * immutable for use in contracts like the Optimism_Adapter which use delegateCall().\n */\ncontract CrossDomainEnabled {\n // Messenger contract used to send and receive messages from the other domain.\n address public immutable MESSENGER;\n\n /**\n * @param _messenger Address of the CrossDomainMessenger on the current layer.\n */\n constructor(address _messenger) {\n MESSENGER = _messenger;\n }\n\n /**\n * Gets the messenger, usually from storage. This function is exposed in case a child contract\n * needs to override.\n * @return The address of the cross-domain messenger contract which should be used.\n */\n function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {\n return ICrossDomainMessenger(MESSENGER);\n }\n\n /**\n * Sends a message to an account on another domain\n * @param _crossDomainTarget The intended recipient on the destination domain\n * @param _gasLimit The gasLimit for the receipt of the message on the target domain.\n * @param _message The data to send to the target (usually calldata to a function with\n * onlyFromCrossDomainAccount())\n */\n function sendCrossDomainMessage(\n address _crossDomainTarget,\n uint32 _gasLimit,\n bytes calldata _message\n ) internal {\n // slither-disable-next-line reentrancy-events, reentrancy-benign\n getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);\n }\n}\n" }, "contracts/chain-adapters/Ethereum_Adapter.sol": { "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n/**\n * @notice Contract containing logic to send messages from L1 to Ethereum SpokePool.\n * @notice This contract should always be deployed on the same chain as the HubPool, as it acts as a pass-through\n * contract between HubPool and SpokePool on the same chain. Its named \"Ethereum_Adapter\" because a core assumption\n * is that the HubPool will be deployed on Ethereum, so this adapter will be used to communicate between HubPool\n * and the Ethereum_SpokePool.\n * @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be\n * called via delegatecall, which will execute this contract's logic within the context of the originating contract.\n * For example, the HubPool will delegatecall these functions, therefore its only necessary that the HubPool's methods\n * that call this contract's logic guard against reentrancy.\n */\n\n// solhint-disable-next-line contract-name-camelcase\ncontract Ethereum_Adapter is AdapterInterface {\n using SafeERC20 for IERC20;\n\n /**\n * @notice Send message to target on Ethereum.\n * @notice This function, and contract overall, is not useful in practice except that the HubPool\n * expects to interact with the SpokePool via an Adapter, so when communicating to the Ethereum_SpokePool, it must\n * send messages via this pass-through contract.\n * @param target Contract that will receive message.\n * @param message Data to send to target.\n */\n function relayMessage(address target, bytes calldata message) external payable override {\n _executeCall(target, message);\n emit MessageRelayed(target, message);\n }\n\n /**\n * @notice Send tokens to target.\n * @param l1Token L1 token to send.\n * @param l2Token Unused parameter in this contract.\n * @param amount Amount of L1 tokens to send.\n * @param to recipient.\n */\n function relayTokens(\n address l1Token,\n address l2Token, // l2Token is unused for ethereum since we are assuming that the HubPool is only deployed\n // on this network.\n uint256 amount,\n address to\n ) external payable override {\n IERC20(l1Token).safeTransfer(to, amount);\n emit TokensRelayed(l1Token, l2Token, amount, to);\n }\n\n // Note: this snippet of code is copied from Governor.sol. Source: https://github.com/UMAprotocol/protocol/blob/5b37ea818a28479c01e458389a83c3e736306b17/packages/core/contracts/oracle/implementation/Governor.sol#L190-L207\n function _executeCall(address to, bytes memory data) private {\n // Note: this snippet of code is copied from Governor.sol and modified to not include any \"value\" field.\n\n bool success;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let inputData := add(data, 0x20)\n let inputDataSize := mload(data)\n // Hardcode value to be 0 for relayed governance calls in order to avoid addressing complexity of bridging\n // value cross-chain.\n success := call(gas(), to, 0, inputData, inputDataSize, 0, 0)\n }\n require(success, \"execute call failed\");\n }\n}\n" }, "contracts/chain-adapters/Ethereum_RescueAdapter.sol": { - "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n/**\n * @notice This adapter is built for emergencies to rescue funds from a Hub in the event of a misconfiguration or\n * security issue.\n */\n// solhint-disable-next-line contract-name-camelcase\ncontract Ethereum_RescueAdapter is AdapterInterface {\n using SafeERC20 for IERC20;\n\n address public immutable rescueAddress;\n\n /**\n * @notice Constructs new Adapter.\n * @param _rescueAddress Rescue address to send funds to.\n */\n constructor(address _rescueAddress) {\n rescueAddress = _rescueAddress;\n }\n\n /**\n * @notice Rescues the tokens from the calling contract.\n * @param message The encoded address of the ERC20 to send to the rescue addres.\n */\n function relayMessage(address, bytes memory message) external payable override {\n IERC20 tokenAddress = IERC20(abi.decode(message, (address)));\n\n // Transfer full balance of tokens to the rescue address.\n tokenAddress.safeTransfer(rescueAddress, tokenAddress.balanceOf(address(this)));\n }\n\n /**\n * @notice Should never be called.\n */\n function relayTokens(\n address,\n address,\n uint256,\n address\n ) external payable override {\n revert(\"relayTokens disabled\");\n }\n}\n" + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n/**\n * @notice This adapter is built for emergencies to rescue funds from a Hub in the event of a misconfiguration or\n * security issue.\n */\n// solhint-disable-next-line contract-name-camelcase\ncontract Ethereum_RescueAdapter is AdapterInterface {\n using SafeERC20 for IERC20;\n\n address public immutable rescueAddress;\n\n /**\n * @notice Constructs new Adapter.\n * @param _rescueAddress Rescue address to send funds to.\n */\n constructor(address _rescueAddress) {\n rescueAddress = _rescueAddress;\n }\n\n /**\n * @notice Rescues the tokens from the calling contract.\n * @param message The encoded address of the ERC20 to send to the rescue address.\n */\n function relayMessage(address, bytes memory message) external payable override {\n IERC20 tokenAddress = IERC20(abi.decode(message, (address)));\n\n // Transfer full balance of tokens to the rescue address.\n tokenAddress.safeTransfer(rescueAddress, tokenAddress.balanceOf(address(this)));\n }\n\n /**\n * @notice Should never be called.\n */\n function relayTokens(\n address,\n address,\n uint256,\n address\n ) external payable override {\n revert(\"relayTokens disabled\");\n }\n}\n" }, "contracts/chain-adapters/interfaces/AdapterInterface.sol": { "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\n/**\n * @notice Sends cross chain messages and tokens to contracts on a specific L2 network.\n * This interface is implemented by an adapter contract that is deployed on L1.\n */\n\ninterface AdapterInterface {\n event MessageRelayed(address target, bytes message);\n\n event TokensRelayed(address l1Token, address l2Token, uint256 amount, address to);\n\n /**\n * @notice Send message to `target` on L2.\n * @dev This method is marked payable because relaying the message might require a fee\n * to be paid by the sender to forward the message to L2. However, it will not send msg.value\n * to the target contract on L2.\n * @param target L2 address to send message to.\n * @param message Message to send to `target`.\n */\n function relayMessage(address target, bytes calldata message) external payable;\n\n /**\n * @notice Send `amount` of `l1Token` to `to` on L2. `l2Token` is the L2 address equivalent of `l1Token`.\n * @dev This method is marked payable because relaying the message might require a fee\n * to be paid by the sender to forward the message to L2. However, it will not send msg.value\n * to the target contract on L2.\n * @param l1Token L1 token to bridge.\n * @param l2Token L2 token to receive.\n * @param amount Amount of `l1Token` to bridge.\n * @param to Bridge recipient.\n */\n function relayTokens(\n address l1Token,\n address l2Token,\n uint256 amount,\n address to\n ) external payable;\n}\n" }, "contracts/chain-adapters/Linea_Adapter.sol": { - "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\nimport \"../external/interfaces/WETH9Interface.sol\";\n\nimport { IMessageService, ITokenBridge, IUSDCBridge } from \"../external/interfaces/LineaInterfaces.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n// solhint-disable-next-line contract-name-camelcase\ncontract Linea_Adapter is AdapterInterface {\n using SafeERC20 for IERC20;\n\n WETH9Interface public immutable L1_WETH;\n IMessageService public immutable L1_MESSAGE_SERVICE;\n ITokenBridge public immutable L1_TOKEN_BRIDGE;\n IUSDCBridge public immutable L1_USDC_BRIDGE;\n\n /**\n * @notice Constructs new Adapter.\n * @param _l1Weth WETH address on L1.\n * @param _l1MessageService Canonical message service contract on L1.\n * @param _l1TokenBridge Canonical token bridge contract on L1.\n * @param _l1UsdcBridge L1 USDC Bridge to ConsenSys's L2 Linea.\n */\n constructor(\n WETH9Interface _l1Weth,\n IMessageService _l1MessageService,\n ITokenBridge _l1TokenBridge,\n IUSDCBridge _l1UsdcBridge\n ) {\n L1_WETH = _l1Weth;\n L1_MESSAGE_SERVICE = _l1MessageService;\n L1_TOKEN_BRIDGE = _l1TokenBridge;\n L1_USDC_BRIDGE = _l1UsdcBridge;\n }\n\n /**\n * @notice Send cross-chain message to target on Linea.\n * @param target Contract on Linea that will receive message.\n * @param message Data to send to target.\n */\n function relayMessage(address target, bytes calldata message) external payable override {\n // Linea currently does not support auto-claiming of cross-chain messages that have\n // non-empty calldata. As we need to manually claim these messages, we can set the\n // message fees to 0.\n L1_MESSAGE_SERVICE.sendMessage(target, 0, message);\n emit MessageRelayed(target, message);\n }\n\n /**\n * @notice Bridge tokens to Linea.\n * @param l1Token L1 token to deposit.\n * @param l2Token L2 token to receive.\n * @param amount Amount of L1 tokens to deposit and L2 tokens to receive.\n * @param to Bridge recipient.\n */\n function relayTokens(\n address l1Token,\n address l2Token,\n uint256 amount,\n address to\n ) external payable override {\n // If the l1Token is WETH then unwrap it to ETH then send the ETH directly\n // via the Canoncial Message Service.\n if (l1Token == address(L1_WETH)) {\n L1_WETH.withdraw(amount);\n L1_MESSAGE_SERVICE.sendMessage{ value: amount }(to, 0, \"\");\n }\n // If the l1Token is USDC, then we need sent it via the USDC Bridge.\n else if (l1Token == L1_USDC_BRIDGE.usdc()) {\n IERC20(l1Token).safeIncreaseAllowance(address(L1_USDC_BRIDGE), amount);\n L1_USDC_BRIDGE.depositTo(amount, to);\n }\n // For other tokens, we can use the Canonical Token Bridge.\n else {\n IERC20(l1Token).safeIncreaseAllowance(address(L1_TOKEN_BRIDGE), amount);\n L1_TOKEN_BRIDGE.bridgeToken(l1Token, amount, to);\n }\n\n emit TokensRelayed(l1Token, l2Token, amount, to);\n }\n}\n" + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\nimport \"../external/interfaces/WETH9Interface.sol\";\n\nimport { IMessageService, ITokenBridge, IUSDCBridge } from \"../external/interfaces/LineaInterfaces.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n// solhint-disable-next-line contract-name-camelcase\ncontract Linea_Adapter is AdapterInterface {\n using SafeERC20 for IERC20;\n\n WETH9Interface public immutable L1_WETH;\n IMessageService public immutable L1_MESSAGE_SERVICE;\n ITokenBridge public immutable L1_TOKEN_BRIDGE;\n IUSDCBridge public immutable L1_USDC_BRIDGE;\n\n /**\n * @notice Constructs new Adapter.\n * @param _l1Weth WETH address on L1.\n * @param _l1MessageService Canonical message service contract on L1.\n * @param _l1TokenBridge Canonical token bridge contract on L1.\n * @param _l1UsdcBridge L1 USDC Bridge to ConsenSys's L2 Linea.\n */\n constructor(\n WETH9Interface _l1Weth,\n IMessageService _l1MessageService,\n ITokenBridge _l1TokenBridge,\n IUSDCBridge _l1UsdcBridge\n ) {\n L1_WETH = _l1Weth;\n L1_MESSAGE_SERVICE = _l1MessageService;\n L1_TOKEN_BRIDGE = _l1TokenBridge;\n L1_USDC_BRIDGE = _l1UsdcBridge;\n }\n\n /**\n * @notice Send cross-chain message to target on Linea.\n * @param target Contract on Linea that will receive message.\n * @param message Data to send to target.\n */\n function relayMessage(address target, bytes calldata message) external payable override {\n // Linea currently does not support auto-claiming of cross-chain messages that have\n // non-empty calldata. As we need to manually claim these messages, we can set the\n // message fees to 0.\n L1_MESSAGE_SERVICE.sendMessage(target, 0, message);\n emit MessageRelayed(target, message);\n }\n\n /**\n * @notice Bridge tokens to Linea.\n * @param l1Token L1 token to deposit.\n * @param l2Token L2 token to receive.\n * @param amount Amount of L1 tokens to deposit and L2 tokens to receive.\n * @param to Bridge recipient.\n */\n function relayTokens(\n address l1Token,\n address l2Token,\n uint256 amount,\n address to\n ) external payable override {\n // If the l1Token is WETH then unwrap it to ETH then send the ETH directly\n // via the Canonical Message Service.\n if (l1Token == address(L1_WETH)) {\n L1_WETH.withdraw(amount);\n L1_MESSAGE_SERVICE.sendMessage{ value: amount }(to, 0, \"\");\n }\n // If the l1Token is USDC, then we need sent it via the USDC Bridge.\n else if (l1Token == L1_USDC_BRIDGE.usdc()) {\n IERC20(l1Token).safeIncreaseAllowance(address(L1_USDC_BRIDGE), amount);\n L1_USDC_BRIDGE.depositTo(amount, to);\n }\n // For other tokens, we can use the Canonical Token Bridge.\n else {\n IERC20(l1Token).safeIncreaseAllowance(address(L1_TOKEN_BRIDGE), amount);\n L1_TOKEN_BRIDGE.bridgeToken(l1Token, amount, to);\n }\n\n emit TokensRelayed(l1Token, l2Token, amount, to);\n }\n}\n" }, "contracts/chain-adapters/Lisk_Adapter.sol": { "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./interfaces/AdapterInterface.sol\";\nimport \"../external/interfaces/WETH9Interface.sol\";\n\n// @dev Use local modified CrossDomainEnabled contract instead of one exported by eth-optimism because we need\n// this contract's state variables to be `immutable` because of the delegateCall call.\nimport \"./CrossDomainEnabled.sol\";\nimport \"@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\nimport \"../libraries/CircleCCTPAdapter.sol\";\nimport \"../external/interfaces/CCTPInterfaces.sol\";\n\n/**\n * @notice Contract containing logic to send messages from L1 to Lisk. This is a clone of the Base/Mode adapter\n * @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be\n * called via delegatecall, which will execute this contract's logic within the context of the originating contract.\n * For example, the HubPool will delegatecall these functions, therefore its only necessary that the HubPool's methods\n * that call this contract's logic guard against reentrancy.\n * @custom:security-contact bugs@umaproject.org\n */\n\n// solhint-disable-next-line contract-name-camelcase\ncontract Lisk_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter {\n using SafeERC20 for IERC20;\n uint32 public constant L2_GAS_LIMIT = 200_000;\n\n WETH9Interface public immutable L1_WETH;\n\n IL1StandardBridge public immutable L1_STANDARD_BRIDGE;\n\n /**\n * @notice Constructs new Adapter.\n * @param _l1Weth WETH address on L1.\n * @param _crossDomainMessenger XDomainMessenger Lisk system contract.\n * @param _l1StandardBridge Standard bridge contract.\n * @param _l1Usdc USDC address on L1.\n */\n constructor(\n WETH9Interface _l1Weth,\n address _crossDomainMessenger,\n IL1StandardBridge _l1StandardBridge,\n IERC20 _l1Usdc\n )\n CrossDomainEnabled(_crossDomainMessenger)\n CircleCCTPAdapter(\n _l1Usdc,\n // Hardcode cctp messenger to 0x0 to disable CCTP bridging.\n ITokenMessenger(address(0)),\n CircleDomainIds.UNINTIALIZED\n )\n {\n L1_WETH = _l1Weth;\n L1_STANDARD_BRIDGE = _l1StandardBridge;\n }\n\n /**\n * @notice Send cross-chain message to target on Lisk.\n * @param target Contract on Lisk that will receive message.\n * @param message Data to send to target.\n */\n function relayMessage(address target, bytes calldata message) external payable override {\n sendCrossDomainMessage(target, L2_GAS_LIMIT, message);\n emit MessageRelayed(target, message);\n }\n\n /**\n * @notice Bridge tokens to Lisk.\n * @param l1Token L1 token to deposit.\n * @param l2Token L2 token to receive.\n * @param amount Amount of L1 tokens to deposit and L2 tokens to receive.\n * @param to Bridge recipient.\n */\n function relayTokens(\n address l1Token,\n address l2Token,\n uint256 amount,\n address to\n ) external payable override {\n // If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge.\n if (l1Token == address(L1_WETH)) {\n L1_WETH.withdraw(amount);\n L1_STANDARD_BRIDGE.depositETHTo{ value: amount }(to, L2_GAS_LIMIT, \"\");\n }\n // Check if this token is USDC, which requires a custom bridge via CCTP.\n else if (_isCCTPEnabled() && l1Token == address(usdcToken)) {\n _transferUsdc(to, amount);\n } else {\n IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE;\n\n IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount);\n _l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, \"\");\n }\n emit TokensRelayed(l1Token, l2Token, amount, to);\n }\n}\n" diff --git a/deployments/mainnet/solcInputs/2cc45423fd0828d019c5d9fb29bc6fa5.json b/deployments/mainnet/solcInputs/2cc45423fd0828d019c5d9fb29bc6fa5.json index 61367e2c2..0242fd8d7 100644 --- a/deployments/mainnet/solcInputs/2cc45423fd0828d019c5d9fb29bc6fa5.json +++ b/deployments/mainnet/solcInputs/2cc45423fd0828d019c5d9fb29bc6fa5.json @@ -443,7 +443,7 @@ "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./SpokePool.sol\";\nimport \"./PolygonTokenBridger.sol\";\nimport \"./external/interfaces/WETH9Interface.sol\";\nimport \"./interfaces/SpokePoolInterface.sol\";\nimport \"./libraries/CircleCCTPAdapter.sol\";\n\n/**\n * @notice IFxMessageProcessor represents interface to process messages.\n */\ninterface IFxMessageProcessor {\n /**\n * @notice Called by FxChild upon receiving L1 message that targets this contract. Performs an additional check\n * that the L1 caller was the expected cross domain admin, and then delegate calls.\n * @notice Polygon bridge only executes this external function on the target Polygon contract when relaying\n * messages from L1, so all functions on this SpokePool are expected to originate via this call.\n * @dev stateId value isn't used because it isn't relevant for this method. It doesn't care what state sync\n * triggered this call.\n * @param rootMessageSender Original L1 sender of data.\n * @param data ABI encoded function call to execute on this contract.\n */\n function processMessageFromRoot(\n uint256 stateId,\n address rootMessageSender,\n bytes calldata data\n ) external;\n}\n\n/**\n * @notice Polygon specific SpokePool.\n * @custom:security-contact bugs@across.to\n */\ncontract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter {\n using SafeERC20Upgradeable for PolygonIERC20Upgradeable;\n\n // Address of FxChild which sends and receives messages to and from L1.\n address public fxChild;\n\n // Contract deployed on L1 and L2 processes all cross-chain transfers between this contract and the HubPool.\n // Required because bridging tokens from Polygon to Ethereum has special constraints.\n PolygonTokenBridger public polygonTokenBridger;\n\n // Internal variable that only flips temporarily to true upon receiving messages from L1. Used to authenticate that\n // the caller is the fxChild AND that the fxChild called processMessageFromRoot\n bool private callValidated;\n\n error MulticallExecuteLeaf();\n\n event SetFxChild(address indexed newFxChild);\n event SetPolygonTokenBridger(address indexed polygonTokenBridger);\n event ReceivedMessageFromL1(address indexed caller, address indexed rootMessageSender);\n\n error CallValidatedAlreadySet();\n error CallValidatedNotSet();\n error DelegateCallFailed();\n error NotHubPool();\n error NotFxChild();\n\n // Note: validating calls this way ensures that strange calls coming from the fxChild won't be misinterpreted.\n // Put differently, just checking that msg.sender == fxChild is not sufficient.\n // All calls that have admin privileges must be fired from within the processMessageFromRoot method that's gone\n // through validation where the sender is checked and the root (mainnet) sender is also validated.\n // This modifier sets the callValidated variable so this condition can be checked in _requireAdminSender().\n modifier validateInternalCalls() {\n // Make sure callValidated is set to True only once at beginning of processMessageFromRoot, which prevents\n // processMessageFromRoot from being re-entered.\n if (callValidated) revert CallValidatedAlreadySet();\n\n // This sets a variable indicating that we're now inside a validated call.\n // Note: this is used by other methods to ensure that this call has been validated by this method and is not\n // spoofed. See comment for `_requireAdminSender` for more details.\n callValidated = true;\n\n _;\n\n // Reset callValidated to false to disallow admin calls after this method exits.\n callValidated = false;\n }\n\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address _wrappedNativeTokenAddress,\n uint32 _depositQuoteTimeBuffer,\n uint32 _fillDeadlineBuffer,\n IERC20 _l2Usdc,\n ITokenMessenger _cctpTokenMessenger\n )\n SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)\n CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)\n {} // solhint-disable-line no-empty-blocks\n\n /**\n * @notice Construct the Polygon SpokePool.\n * @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate\n * relay hash collisions.\n * @param _polygonTokenBridger Token routing contract that sends tokens from here to HubPool. Changeable by Admin.\n * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.\n * @param _hubPool Hub pool address to set. Can be changed by admin.\n * @param _fxChild FxChild contract, changeable by Admin.\n */\n function initialize(\n uint32 _initialDepositId,\n PolygonTokenBridger _polygonTokenBridger,\n address _crossDomainAdmin,\n address _hubPool,\n address _fxChild\n ) public initializer {\n callValidated = false;\n __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool);\n _setPolygonTokenBridger(payable(_polygonTokenBridger));\n //slither-disable-next-line missing-zero-check\n _setFxChild(_fxChild);\n }\n\n /********************************************************\n * POLYGON-SPECIFIC CROSS-CHAIN ADMIN FUNCTIONS *\n ********************************************************/\n\n /**\n * @notice Change FxChild address. Callable only by admin via processMessageFromRoot.\n * @param newFxChild New FxChild.\n */\n function setFxChild(address newFxChild) public onlyAdmin nonReentrant {\n _setFxChild(newFxChild);\n }\n\n /**\n * @notice Change polygonTokenBridger address. Callable only by admin via processMessageFromRoot.\n * @param newPolygonTokenBridger New Polygon Token Bridger contract.\n */\n function setPolygonTokenBridger(address payable newPolygonTokenBridger) public onlyAdmin nonReentrant {\n _setPolygonTokenBridger(newPolygonTokenBridger);\n }\n\n /**\n * @notice Called by FxChild upon receiving L1 message that targets this contract. Performs an additional check\n * that the L1 caller was the expected cross domain admin, and then delegate calls.\n * @notice Polygon bridge only executes this external function on the target Polygon contract when relaying\n * messages from L1, so all functions on this SpokePool are expected to originate via this call.\n * @dev stateId value isn't used because it isn't relevant for this method. It doesn't care what state sync\n * triggered this call.\n * @param rootMessageSender Original L1 sender of data.\n * @param data ABI encoded function call to execute on this contract.\n */\n function processMessageFromRoot(\n uint256, /*stateId*/\n address rootMessageSender,\n bytes calldata data\n ) public validateInternalCalls {\n // Validation logic.\n if (msg.sender != fxChild) revert NotFxChild();\n if (rootMessageSender != crossDomainAdmin) revert NotHubPool();\n\n // This uses delegatecall to take the information in the message and process it as a function call on this contract.\n /// This is a safe delegatecall because its made to address(this) so there is no risk of delegating to a\n /// selfdestruct().\n //slither-disable-start low-level-calls\n /// @custom:oz-upgrades-unsafe-allow delegatecall\n (bool success, ) = address(this).delegatecall(data);\n //slither-disable-end low-level-calls\n if (!success) revert DelegateCallFailed();\n\n emit ReceivedMessageFromL1(msg.sender, rootMessageSender);\n }\n\n /**\n * @notice Allows the caller to trigger the wrapping of any unwrapped matic tokens.\n * @dev Unlike other ERC20 transfers, Matic transfers from L1 -> L2 bridging don't result in an L2 call into\n * the contract receiving the tokens, so wrapping must be done via a separate transaction. In other words,\n * we can't rely upon a `fallback()` method being triggered to wrap MATIC upon receiving it.\n */\n function wrap() public nonReentrant {\n _wrap();\n }\n\n /**\n * @notice Override multicall so that it cannot include executeRelayerRefundLeaf\n * as one of the calls combined with other public function calls.\n * @dev Multicalling a single transaction will always succeed.\n * @dev Multicalling execute functions without combining other public function calls will succeed.\n * @dev Multicalling public function calls without combining execute functions will succeed.\n */\n function _validateMulticallData(bytes[] calldata data) internal pure override {\n bool hasOtherPublicFunctionCall = false;\n bool hasExecutedLeafCall = false;\n for (uint256 i = 0; i < data.length; i++) {\n bytes4 selector = bytes4(data[i][:4]);\n if (selector == SpokePoolInterface.executeRelayerRefundLeaf.selector) {\n if (hasOtherPublicFunctionCall) revert MulticallExecuteLeaf();\n hasExecutedLeafCall = true;\n } else {\n if (hasExecutedLeafCall) revert MulticallExecuteLeaf();\n hasOtherPublicFunctionCall = true;\n }\n }\n }\n\n /**\n * @notice This function can send an L2 to L1 message so we are extra cautious about preventing a griefing vector\n * whereby someone batches this call with a bunch of other calls and produces a very large L2 burn transaction.\n * This might make the L2 -> L1 message fail due to exceeding the L1 calldata limit.\n */\n\n function executeRelayerRefundLeaf(\n uint32 rootBundleId,\n SpokePoolInterface.RelayerRefundLeaf memory relayerRefundLeaf,\n bytes32[] memory proof\n ) public payable override {\n // AddressLibUpgradeable.isContract isn't a sufficient check because it checks the contract code size of\n // msg.sender which is 0 if called from a constructor function on msg.sender. This is why we check if\n // msg.sender is equal to tx.origin which is fine as long as Polygon supports the tx.origin opcode.\n // solhint-disable-next-line avoid-tx-origin\n if (relayerRefundLeaf.amountToReturn > 0 && msg.sender != tx.origin) revert NotEOA();\n super.executeRelayerRefundLeaf(rootBundleId, relayerRefundLeaf, proof);\n }\n\n /**************************************\n * INTERNAL FUNCTIONS *\n **************************************/\n\n function _setFxChild(address _fxChild) internal {\n //slither-disable-next-line missing-zero-check\n fxChild = _fxChild;\n emit SetFxChild(_fxChild);\n }\n\n function _setPolygonTokenBridger(address payable _polygonTokenBridger) internal {\n polygonTokenBridger = PolygonTokenBridger(_polygonTokenBridger);\n emit SetPolygonTokenBridger(address(_polygonTokenBridger));\n }\n\n function _preExecuteLeafHook(address) internal override {\n // Wraps MATIC --> WMATIC before distributing tokens from this contract.\n _wrap();\n }\n\n function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override {\n // If the token is USDC, we need to use the CCTP bridge to transfer it to the hub pool.\n if (_isCCTPEnabled() && l2TokenAddress == address(usdcToken)) {\n _transferUsdc(hubPool, amountToReturn);\n } else {\n PolygonIERC20Upgradeable(l2TokenAddress).safeIncreaseAllowance(\n address(polygonTokenBridger),\n amountToReturn\n );\n // Note: WrappedNativeToken is WMATIC on matic, so this tells the tokenbridger that this is an unwrappable native token.\n polygonTokenBridger.send(PolygonIERC20Upgradeable(l2TokenAddress), amountToReturn);\n }\n }\n\n function _wrap() internal {\n uint256 balance = address(this).balance;\n //slither-disable-next-line arbitrary-send-eth\n if (balance > 0) wrappedNativeToken.deposit{ value: balance }();\n }\n\n // @dev: This contract will trigger admin functions internally via the `processMessageFromRoot`, which is why\n // the `callValidated` check is made below and why we use the `validateInternalCalls` modifier on\n // `processMessageFromRoot`. This prevents calling the admin functions from any other method besides\n // `processMessageFromRoot`.\n function _requireAdminSender() internal view override {\n if (!callValidated) revert CallValidatedNotSet();\n }\n}\n" }, "contracts/PolygonTokenBridger.sol": { - "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./Lockable.sol\";\nimport \"./external/interfaces/WETH9Interface.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\n\n// Polygon Registry contract that stores their addresses.\ninterface PolygonRegistry {\n function erc20Predicate() external returns (address);\n}\n\n// Polygon ERC20Predicate contract that handles Plasma exits (only used for Matic).\ninterface PolygonERC20Predicate {\n function startExitWithBurntTokens(bytes calldata data) external;\n}\n\n// ERC20s (on polygon) compatible with polygon's bridge have a withdraw method.\ninterface PolygonIERC20Upgradeable is IERC20Upgradeable {\n function withdraw(uint256 amount) external;\n}\n\ninterface MaticToken {\n function withdraw(uint256 amount) external payable;\n}\n\n/**\n * @notice Contract deployed on Ethereum and Polygon to facilitate token transfers from Polygon to the HubPool and back.\n * @dev Because Polygon only allows withdrawals from a particular address to go to that same address on mainnet, we need to\n * have some sort of contract that can guarantee identical addresses on Polygon and Ethereum. This contract is intended\n * to be completely immutable, so it's guaranteed that the contract on each side is configured identically as long as\n * it is created via create2. create2 is an alternative creation method that uses a different address determination\n * mechanism from normal create.\n * Normal create: address = hash(deployer_address, deployer_nonce)\n * create2: address = hash(0xFF, sender, salt, bytecode)\n * This ultimately allows create2 to generate deterministic addresses that don't depend on the transaction count of the\n * sender.\n * @custom:security-contact bugs@across.to\n */\ncontract PolygonTokenBridger is Lockable {\n using SafeERC20Upgradeable for PolygonIERC20Upgradeable;\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n // Gas token for Polygon.\n MaticToken public constant MATIC = MaticToken(0x0000000000000000000000000000000000001010);\n\n // Should be set to HubPool on Ethereum, or unused on Polygon.\n address public immutable destination;\n\n // Registry that stores L1 polygon addresses.\n PolygonRegistry public immutable l1PolygonRegistry;\n\n // WETH contract on Ethereum.\n WETH9Interface public immutable l1Weth;\n\n // Wrapped Matic on Polygon\n address public immutable l2WrappedMatic;\n\n // Chain id for the L1 that this contract is deployed on or communicates with.\n // For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be\n // the mainnet chainId 1.\n uint256 public immutable l1ChainId;\n\n // Chain id for the L2 that this contract is deployed on or communicates with.\n // For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be\n // the polygon chainId 137.\n uint256 public immutable l2ChainId;\n\n modifier onlyChainId(uint256 chainId) {\n _requireChainId(chainId);\n _;\n }\n\n /**\n * @notice Constructs Token Bridger contract.\n * @param _destination Where to send tokens to for this network.\n * @param _l1PolygonRegistry L1 registry that stores updated addresses of polygon contracts. This should always be\n * set to the L1 registry regardless if whether it's deployed on L2 or L1.\n * @param _l1Weth L1 WETH address.\n * @param _l2WrappedMatic L2 address of wrapped matic token.\n * @param _l1ChainId the chain id for the L1 in this environment.\n * @param _l2ChainId the chain id for the L2 in this environment.\n */\n constructor(\n address _destination,\n PolygonRegistry _l1PolygonRegistry,\n WETH9Interface _l1Weth,\n address _l2WrappedMatic,\n uint256 _l1ChainId,\n uint256 _l2ChainId\n ) {\n //slither-disable-next-line missing-zero-check\n destination = _destination;\n l1PolygonRegistry = _l1PolygonRegistry;\n l1Weth = _l1Weth;\n //slither-disable-next-line missing-zero-check\n l2WrappedMatic = _l2WrappedMatic;\n l1ChainId = _l1ChainId;\n l2ChainId = _l2ChainId;\n }\n\n /**\n * @notice Called by Polygon SpokePool to send tokens over bridge to contract with the same address as this.\n * @notice The caller of this function must approve this contract to spend amount of token.\n * @param token Token to bridge.\n * @param amount Amount to bridge.\n */\n function send(PolygonIERC20Upgradeable token, uint256 amount) public nonReentrant onlyChainId(l2ChainId) {\n token.safeTransferFrom(msg.sender, address(this), amount);\n\n // In the wMatic case, this unwraps. For other ERC20s, this is the burn/send action.\n token.withdraw(token.balanceOf(address(this)));\n\n // This takes the token that was withdrawn and calls withdraw on the \"native\" ERC20.\n if (address(token) == l2WrappedMatic) MATIC.withdraw{ value: address(this).balance }(address(this).balance);\n }\n\n /**\n * @notice Called by someone to send tokens to the destination, which should be set to the HubPool.\n * @param token Token to send to destination.\n */\n function retrieve(IERC20Upgradeable token) public nonReentrant onlyChainId(l1ChainId) {\n if (address(token) == address(l1Weth)) {\n // For WETH, there is a pre-deposit step to ensure any ETH that has been sent to the contract is captured.\n //slither-disable-next-line arbitrary-send-eth\n l1Weth.deposit{ value: address(this).balance }();\n }\n token.safeTransfer(destination, token.balanceOf(address(this)));\n }\n\n /**\n * @notice Called to initiate an l1 exit (withdrawal) of matic tokens that have been sent over the plasma bridge.\n * @param data the proof data to trigger the exit. Can be generated using the maticjs-plasma package.\n */\n function callExit(bytes memory data) public nonReentrant onlyChainId(l1ChainId) {\n PolygonERC20Predicate erc20Predicate = PolygonERC20Predicate(l1PolygonRegistry.erc20Predicate());\n erc20Predicate.startExitWithBurntTokens(data);\n }\n\n receive() external payable {\n // This method is empty to avoid any gas expendatures that might cause transfers to fail.\n // Note: the fact that there is _no_ code in this function means that matic can be erroneously transferred in\n // to the contract on the polygon side. These tokens would be locked indefinitely since the receive function\n // cannot be called on the polygon side. While this does have some downsides, the lack of any functionality\n // in this function means that it has no chance of running out of gas on transfers, which is a much more\n // important benefit. This just makes the matic token risk similar to that of ERC20s that are erroneously\n // sent to the contract.\n }\n\n function _requireChainId(uint256 chainId) internal view {\n require(block.chainid == chainId, \"Cannot run method on this chain\");\n }\n}\n" + "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./Lockable.sol\";\nimport \"./external/interfaces/WETH9Interface.sol\";\n\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\n\n// Polygon Registry contract that stores their addresses.\ninterface PolygonRegistry {\n function erc20Predicate() external returns (address);\n}\n\n// Polygon ERC20Predicate contract that handles Plasma exits (only used for Matic).\ninterface PolygonERC20Predicate {\n function startExitWithBurntTokens(bytes calldata data) external;\n}\n\n// ERC20s (on polygon) compatible with polygon's bridge have a withdraw method.\ninterface PolygonIERC20Upgradeable is IERC20Upgradeable {\n function withdraw(uint256 amount) external;\n}\n\ninterface MaticToken {\n function withdraw(uint256 amount) external payable;\n}\n\n/**\n * @notice Contract deployed on Ethereum and Polygon to facilitate token transfers from Polygon to the HubPool and back.\n * @dev Because Polygon only allows withdrawals from a particular address to go to that same address on mainnet, we need to\n * have some sort of contract that can guarantee identical addresses on Polygon and Ethereum. This contract is intended\n * to be completely immutable, so it's guaranteed that the contract on each side is configured identically as long as\n * it is created via create2. create2 is an alternative creation method that uses a different address determination\n * mechanism from normal create.\n * Normal create: address = hash(deployer_address, deployer_nonce)\n * create2: address = hash(0xFF, sender, salt, bytecode)\n * This ultimately allows create2 to generate deterministic addresses that don't depend on the transaction count of the\n * sender.\n * @custom:security-contact bugs@across.to\n */\ncontract PolygonTokenBridger is Lockable {\n using SafeERC20Upgradeable for PolygonIERC20Upgradeable;\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n // Gas token for Polygon.\n MaticToken public constant MATIC = MaticToken(0x0000000000000000000000000000000000001010);\n\n // Should be set to HubPool on Ethereum, or unused on Polygon.\n address public immutable destination;\n\n // Registry that stores L1 polygon addresses.\n PolygonRegistry public immutable l1PolygonRegistry;\n\n // WETH contract on Ethereum.\n WETH9Interface public immutable l1Weth;\n\n // Wrapped Matic on Polygon\n address public immutable l2WrappedMatic;\n\n // Chain id for the L1 that this contract is deployed on or communicates with.\n // For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be\n // the mainnet chainId 1.\n uint256 public immutable l1ChainId;\n\n // Chain id for the L2 that this contract is deployed on or communicates with.\n // For example: if this contract were meant to facilitate transfers from polygon to mainnet, this value would be\n // the polygon chainId 137.\n uint256 public immutable l2ChainId;\n\n modifier onlyChainId(uint256 chainId) {\n _requireChainId(chainId);\n _;\n }\n\n /**\n * @notice Constructs Token Bridger contract.\n * @param _destination Where to send tokens to for this network.\n * @param _l1PolygonRegistry L1 registry that stores updated addresses of polygon contracts. This should always be\n * set to the L1 registry regardless if whether it's deployed on L2 or L1.\n * @param _l1Weth L1 WETH address.\n * @param _l2WrappedMatic L2 address of wrapped matic token.\n * @param _l1ChainId the chain id for the L1 in this environment.\n * @param _l2ChainId the chain id for the L2 in this environment.\n */\n constructor(\n address _destination,\n PolygonRegistry _l1PolygonRegistry,\n WETH9Interface _l1Weth,\n address _l2WrappedMatic,\n uint256 _l1ChainId,\n uint256 _l2ChainId\n ) {\n //slither-disable-next-line missing-zero-check\n destination = _destination;\n l1PolygonRegistry = _l1PolygonRegistry;\n l1Weth = _l1Weth;\n //slither-disable-next-line missing-zero-check\n l2WrappedMatic = _l2WrappedMatic;\n l1ChainId = _l1ChainId;\n l2ChainId = _l2ChainId;\n }\n\n /**\n * @notice Called by Polygon SpokePool to send tokens over bridge to contract with the same address as this.\n * @notice The caller of this function must approve this contract to spend amount of token.\n * @param token Token to bridge.\n * @param amount Amount to bridge.\n */\n function send(PolygonIERC20Upgradeable token, uint256 amount) public nonReentrant onlyChainId(l2ChainId) {\n token.safeTransferFrom(msg.sender, address(this), amount);\n\n // In the wMatic case, this unwraps. For other ERC20s, this is the burn/send action.\n token.withdraw(token.balanceOf(address(this)));\n\n // This takes the token that was withdrawn and calls withdraw on the \"native\" ERC20.\n if (address(token) == l2WrappedMatic) MATIC.withdraw{ value: address(this).balance }(address(this).balance);\n }\n\n /**\n * @notice Called by someone to send tokens to the destination, which should be set to the HubPool.\n * @param token Token to send to destination.\n */\n function retrieve(IERC20Upgradeable token) public nonReentrant onlyChainId(l1ChainId) {\n if (address(token) == address(l1Weth)) {\n // For WETH, there is a pre-deposit step to ensure any ETH that has been sent to the contract is captured.\n //slither-disable-next-line arbitrary-send-eth\n l1Weth.deposit{ value: address(this).balance }();\n }\n token.safeTransfer(destination, token.balanceOf(address(this)));\n }\n\n /**\n * @notice Called to initiate an l1 exit (withdrawal) of matic tokens that have been sent over the plasma bridge.\n * @param data the proof data to trigger the exit. Can be generated using the maticjs-plasma package.\n */\n function callExit(bytes memory data) public nonReentrant onlyChainId(l1ChainId) {\n PolygonERC20Predicate erc20Predicate = PolygonERC20Predicate(l1PolygonRegistry.erc20Predicate());\n erc20Predicate.startExitWithBurntTokens(data);\n }\n\n receive() external payable {\n // This method is empty to avoid any gas expenditures that might cause transfers to fail.\n // Note: the fact that there is _no_ code in this function means that matic can be erroneously transferred in\n // to the contract on the polygon side. These tokens would be locked indefinitely since the receive function\n // cannot be called on the polygon side. While this does have some downsides, the lack of any functionality\n // in this function means that it has no chance of running out of gas on transfers, which is a much more\n // important benefit. This just makes the matic token risk similar to that of ERC20s that are erroneously\n // sent to the contract.\n }\n\n function _requireChainId(uint256 chainId) internal view {\n require(block.chainid == chainId, \"Cannot run method on this chain\");\n }\n}\n" }, "contracts/PolygonZkEVM_SpokePool.sol": { "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport \"./SpokePool.sol\";\nimport \"./external/interfaces/IPolygonZkEVMBridge.sol\";\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n/**\n * @notice Define interface for PolygonZkEVM Bridge message receiver\n * See https://github.com/0xPolygonHermez/zkevm-contracts/blob/53e95f3a236d8bea87c27cb8714a5d21496a3b20/contracts/interfaces/IBridgeMessageReceiver.sol\n */\ninterface IBridgeMessageReceiver {\n /**\n * @notice This will be called by the Polygon zkEVM Bridge on L2 to relay a message sent from the HubPool.\n * @param originAddress Address of the original message sender on L1.\n * @param originNetwork Polygon zkEVM's internal network id of source chain.\n * @param data Data to be received and executed on this contract.\n */\n function onMessageReceived(\n address originAddress,\n uint32 originNetwork,\n bytes memory data\n ) external payable;\n}\n\n/**\n * @notice Polygon zkEVM Spoke pool.\n * @custom:security-contact bugs@across.to\n */\ncontract PolygonZkEVM_SpokePool is SpokePool, IBridgeMessageReceiver {\n using SafeERC20 for IERC20;\n\n // Address of Polygon zkEVM's Canonical Bridge on L2.\n IPolygonZkEVMBridge public l2PolygonZkEVMBridge;\n\n // Polygon zkEVM's internal network id for L1.\n uint32 public constant POLYGON_ZKEVM_L1_NETWORK_ID = 0;\n\n // Warning: this variable should _never_ be touched outside of this contract. It is intentionally set to be\n // private. Leaving it set to true can permanently disable admin calls.\n bool private adminCallValidated;\n\n /**************************************\n * ERRORS *\n **************************************/\n error AdminCallValidatedAlreadySet();\n error CallerNotBridge();\n error OriginSenderNotCrossDomain();\n error SourceChainNotHubChain();\n error AdminCallNotValidated();\n\n /**************************************\n * EVENTS *\n **************************************/\n event SetPolygonZkEVMBridge(address indexed newPolygonZkEVMBridge, address indexed oldPolygonZkEVMBridge);\n event ReceivedMessageFromL1(address indexed caller, address indexed originAddress);\n\n // Note: validating calls this way ensures that strange calls coming from the onMessageReceived won't be\n // misinterpreted. Put differently, just checking that originAddress == crossDomainAdmint is not sufficient.\n // All calls that have admin privileges must be fired from within the onMessageReceived method that's gone\n // through validation where the sender is checked and the sender from the other chain is also validated.\n // This modifier sets the adminCallValidated variable so this condition can be checked in _requireAdminSender().\n modifier validateInternalCalls() {\n // Make sure adminCallValidated is set to True only once at beginning of onMessageReceived, which prevents\n // onMessageReceived from being re-entered.\n if (adminCallValidated) {\n revert AdminCallValidatedAlreadySet();\n }\n\n // This sets a variable indicating that we're now inside a validated call.\n // Note: this is used by other methods to ensure that this call has been validated by this method and is not\n // spoofed.\n adminCallValidated = true;\n\n _;\n\n // Reset adminCallValidated to false to disallow admin calls after this method exits.\n adminCallValidated = false;\n }\n\n /**\n * @notice Construct Polygon zkEVM specific SpokePool.\n * @param _wrappedNativeTokenAddress Address of WETH on Polygon zkEVM.\n * @param _depositQuoteTimeBuffer Quote timestamps can't be set more than this amount\n * into the past from the block time of the deposit.\n * @param _fillDeadlineBuffer Fill deadlines can't be set more than this amount\n * into the future from the block time of the deposit.\n */\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address _wrappedNativeTokenAddress,\n uint32 _depositQuoteTimeBuffer,\n uint32 _fillDeadlineBuffer\n ) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks\n\n /**\n * @notice Construct the Polygon zkEVM SpokePool.\n * @param _l2PolygonZkEVMBridge Address of Polygon zkEVM's canonical bridge contract on L2.\n * @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate\n * @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.\n * @param _hubPool Hub pool address to set. Can be changed by admin.\n */\n function initialize(\n IPolygonZkEVMBridge _l2PolygonZkEVMBridge,\n uint32 _initialDepositId,\n address _crossDomainAdmin,\n address _hubPool\n ) public initializer {\n __SpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool);\n _setL2PolygonZkEVMBridge(_l2PolygonZkEVMBridge);\n }\n\n /**\n * @notice Admin can reset the Polygon zkEVM bridge contract address.\n * @param _l2PolygonZkEVMBridge Address of the new canonical bridge.\n */\n function setL2PolygonZkEVMBridge(IPolygonZkEVMBridge _l2PolygonZkEVMBridge) external onlyAdmin {\n _setL2PolygonZkEVMBridge(_l2PolygonZkEVMBridge);\n }\n\n /**\n * @notice This will be called by the Polygon zkEVM Bridge on L2 to relay a message sent from the HubPool.\n * @param _originAddress Address of the original message sender on L1.\n * @param _originNetwork Polygon zkEVM's internal network id of source chain.\n * @param _data Data to be received and executed on this contract.\n */\n function onMessageReceived(\n address _originAddress,\n uint32 _originNetwork,\n bytes memory _data\n ) external payable override validateInternalCalls {\n if (msg.sender != address(l2PolygonZkEVMBridge)) {\n revert CallerNotBridge();\n }\n if (_originAddress != crossDomainAdmin) {\n revert OriginSenderNotCrossDomain();\n }\n if (_originNetwork != POLYGON_ZKEVM_L1_NETWORK_ID) {\n revert SourceChainNotHubChain();\n }\n\n /// @custom:oz-upgrades-unsafe-allow delegatecall\n (bool success, ) = address(this).delegatecall(_data);\n require(success, \"delegatecall failed\");\n\n emit ReceivedMessageFromL1(msg.sender, _originAddress);\n }\n\n /**************************************\n * INTERNAL FUNCTIONS *\n **************************************/\n\n /**\n * @notice Wraps any ETH into WETH before executing base function. This is necessary because SpokePool receives\n * ETH over the canonical token bridge instead of WETH.\n */\n function _preExecuteLeafHook(address l2TokenAddress) internal override {\n if (l2TokenAddress == address(wrappedNativeToken)) _depositEthToWeth();\n }\n\n // Wrap any ETH owned by this contract so we can send expected L2 token to recipient. This is necessary because\n // this SpokePool will receive ETH from the canonical token bridge instead of WETH. This may not be neccessary\n // if ETH on Polygon zkEVM is treated as ETH and the fallback() function is triggered when this contract receives\n // ETH. We will have to test this but this function for now allows the contract to safely convert all of its\n // held ETH into WETH at the cost of higher gas costs.\n function _depositEthToWeth() internal {\n //slither-disable-next-line arbitrary-send-eth\n if (address(this).balance > 0) wrappedNativeToken.deposit{ value: address(this).balance }();\n }\n\n function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override {\n // SpokePool is expected to receive ETH from the L1 HubPool, then we need to first unwrap it to ETH and then\n // send ETH directly via the native L2 bridge.\n if (l2TokenAddress == address(wrappedNativeToken)) {\n WETH9Interface(l2TokenAddress).withdraw(amountToReturn); // Unwrap into ETH.\n l2PolygonZkEVMBridge.bridgeAsset{ value: amountToReturn }(\n POLYGON_ZKEVM_L1_NETWORK_ID,\n hubPool,\n amountToReturn,\n address(0),\n true, // Indicates if the new global exit root is updated or not, which is true for asset bridges\n \"\"\n );\n } else {\n IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2PolygonZkEVMBridge), amountToReturn);\n l2PolygonZkEVMBridge.bridgeAsset(\n POLYGON_ZKEVM_L1_NETWORK_ID,\n hubPool,\n amountToReturn,\n l2TokenAddress,\n true, // Indicates if the new global exit root is updated or not, which is true for asset bridges\n \"\"\n );\n }\n }\n\n // Check that the onMessageReceived method has validated the method to ensure the sender is authenticated.\n function _requireAdminSender() internal view override {\n if (!adminCallValidated) {\n revert AdminCallNotValidated();\n }\n }\n\n function _setL2PolygonZkEVMBridge(IPolygonZkEVMBridge _newL2PolygonZkEVMBridge) internal {\n address oldL2PolygonZkEVMBridge = address(l2PolygonZkEVMBridge);\n l2PolygonZkEVMBridge = _newL2PolygonZkEVMBridge;\n emit SetPolygonZkEVMBridge(address(_newL2PolygonZkEVMBridge), oldL2PolygonZkEVMBridge);\n }\n}\n"