From ecd4059e1254a4357ce16a2098b18f22acb5a0fb Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Tue, 11 Apr 2023 19:23:04 -0700 Subject: [PATCH 1/2] Add PolygonZkEvm contracts --- config/constants.ts | 11 ++++ config/networks/goerli.ts | 16 ++++++ config/utils.ts | 24 ++++++--- contracts/bridges/L2_PolygonZkEvmBridge.sol | 51 +++++++++++++++++++ .../messengers/IL2PolygonZkEvmMessenger.sol | 13 +++++ contracts/test/Mock_L2_PolygonZkEvmBridge.sol | 27 ++++++++++ .../mockPolygonZkEvm_L1Bridge.sol | 6 +++ .../mockPolygonZkEvm_L2Bridge.sol | 6 +++ hardhat.config.ts | 17 ++++++- scripts/deployAndSetupHop/deploy.ts | 3 +- scripts/deployAndSetupHop/setupL1.ts | 10 +++- .../messageStateUpdates/getMessengerTxData.ts | 15 ++++-- scripts/other/deployStakingRewards.ts | 12 +++-- .../other/sendMessageOverCanonicalBridge.ts | 6 ++- scripts/shared/utils.ts | 30 ++++++++++- test/shared/contractFunctionWrappers.ts | 19 ++++++- test/shared/utils.ts | 8 +++ .../wrappers/ArbitrumMessengerWrapper.test.ts | 4 +- 18 files changed, 257 insertions(+), 21 deletions(-) create mode 100644 contracts/bridges/L2_PolygonZkEvmBridge.sol create mode 100644 contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol create mode 100644 contracts/test/Mock_L2_PolygonZkEvmBridge.sol create mode 100644 contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol create mode 100644 contracts/test/polygonzkevm/mockPolygonZkEvm_L2Bridge.sol diff --git a/config/constants.ts b/config/constants.ts index 4b113d0..df9d517 100644 --- a/config/constants.ts +++ b/config/constants.ts @@ -37,6 +37,9 @@ export const CHAIN_IDS: any = { }, SCROLL: { SCROLL_TESTNET: BigNumber.from('534353') + }, + POLYGONZKEVM: { + POLYGONZKEVM_TESTNET: BigNumber.from('1442') } } @@ -85,6 +88,9 @@ export const CHAIN_IDS_TO_ACTIVATE: any = { }, SCROLL: { SCROLL_TESTNET: BigNumber.from('534353') + }, + POLYGONZKEVM: { + POLYGONZKEVM_TESTNET: BigNumber.from('1442') } } } @@ -288,6 +294,10 @@ export const L2_CANONICAL_TOKEN_ADDRESSES: any = { }, SCROLL_TESTNET: { ETH: '0x5300000000000000000000000000000000000004' // Canonical deployment + }, + POLYGONZKEVM_TESTNET: { + // TODO: find address + ETH: '0x0000000000000000000000000000000000000000' // Canonical deployment } } @@ -300,5 +310,6 @@ export const HOP_DAO_ADDRESS = '0xeeA8422a08258e73c139Fc32a25e10410c14bd7a' export const CONSENSYS_ZK_EVM_MESSAGE_FEE = '10000000000000000' export const ZKSYNC_MESSAGE_FEE = 'TODO' // TODO: zksync export const SCROLL_ZK_EVM_MESSAGE_FEE = '10000000000000000' // TODO +export const POLYGONZKEVM_MESSAGE_FEE = '0' export const DefaultChallengePeriod: string = '86400' diff --git a/config/networks/goerli.ts b/config/networks/goerli.ts index 54a4635..8830461 100644 --- a/config/networks/goerli.ts +++ b/config/networks/goerli.ts @@ -160,5 +160,21 @@ export const networkData: NetworkData = { ...DEFAULT_NETWORK_DATA.ETH } } + }, + polygonzkevm: { + l2NetworkName: 'polygonzkevm', + l1ChainId, + l2ChainId: CHAIN_IDS.POLYGONZKEVM.POLYGONZKEVM_TESTNET.toString(), + l1MessengerAddress: '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7', + l2TokenBridgeAddress: '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7', + l2MessengerAddress: '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7', + tokens: { + ETH: { + l1CanonicalTokenAddress: L1_CANONICAL_TOKEN_ADDRESSES.GOERLI.ETH, + l2CanonicalTokenAddress: + L2_CANONICAL_TOKEN_ADDRESSES.POLYGONZKEVM_TESTNET.ETH, + ...DEFAULT_NETWORK_DATA.ETH + } + } } } diff --git a/config/utils.ts b/config/utils.ts index 813dc34..eb7d461 100644 --- a/config/utils.ts +++ b/config/utils.ts @@ -49,11 +49,7 @@ export const getMessengerWrapperDefaults = ( const gasLimit: number = 1000000 const ambAddress: string = getXDaiAmbAddresses(l1ChainId) - data.push( - ...defaults, - gasLimit, - ambAddress - ) + data.push(...defaults, gasLimit, ambAddress) } else if (isChainIdPolygon(l2ChainId)) { const checkpointManager: string = getPolygonCheckpointManagerAddress( l1ChainId @@ -73,6 +69,8 @@ export const getMessengerWrapperDefaults = ( data.push(...defaults) } else if (isChainIdScroll(l2ChainId)) { data.push(...defaults) + } else if (isChainIdPolygonZkEvm(l2ChainId)) { + data.push(...defaults) } return data @@ -109,6 +107,8 @@ export const getL2BridgeDefaults = ( // no additional data } else if (isChainIdScroll(chainId)) { // no additional data + } else if (isChainIdPolygonZkEvm(chainId)) { + // no additional data } defaults.push( @@ -208,6 +208,14 @@ export const isChainIdScroll = (chainId: BigNumber): boolean => { return false } +export const isChainIdPolygonZkEvm = (chainId: BigNumber): boolean => { + if (chainId.eq(CHAIN_IDS.POLYGONZKEVM.POLYGONZKEVM_TESTNET)) { + return true + } + + return false +} + export const isChainIdMainnet = (chainId: BigNumber): boolean => { if (chainId.eq(CHAIN_IDS.ETHEREUM.MAINNET)) { return true @@ -241,7 +249,8 @@ export const isChainIdTestnet = (chainId: BigNumber): boolean => { chainId.eq(CHAIN_IDS.CONSENSYS.CONSENSYS_TESTNET) || chainId.eq(CHAIN_IDS.ZKSYNC.ZKSYNC_TESTNET) || chainId.eq(CHAIN_IDS.SCROLL.SCROLL_TESTNET) || - chainId.eq(CHAIN_IDS.BASE.BASE_TESTNET) + chainId.eq(CHAIN_IDS.BASE.BASE_TESTNET) || + chainId.eq(CHAIN_IDS.POLYGONZKEVM.POLYGONZKEVM_TESTNET) ) { return true } @@ -329,7 +338,8 @@ export const getTxOverridesPerChain = (l2ChainId: BigNumber): Overrides => { isChainIdConsensys(l2ChainId) || isChainIdZkSync(l2ChainId) || isChainIdBase(l2ChainId) || - isChainIdScroll(l2ChainId) + isChainIdScroll(l2ChainId) || + isChainIdPolygonZkEvm(l2ChainId) ) { return {} } else if (isChainIdXDai(l2ChainId)) { diff --git a/contracts/bridges/L2_PolygonZkEvmBridge.sol b/contracts/bridges/L2_PolygonZkEvmBridge.sol new file mode 100644 index 0000000..422f80b --- /dev/null +++ b/contracts/bridges/L2_PolygonZkEvmBridge.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "../interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol"; +import "./L2_Bridge.sol"; + +/** + * @dev A MessengerWrapper for the Polygon zkEVM - https://polygon.technology/polygon-zkevm + */ + +contract L2_PolygonZkEvmBridge is L2_Bridge { + IL2PolygonZkEvmMessenger public polygonZkEvmMessengerAddress; + + constructor ( + IL2PolygonZkEvmMessenger _polygonZkEvmMessengerAddress, + address l1Governance, + HopBridgeToken hToken, + address l1BridgeAddress, + uint256[] memory activeChainIds, + address[] memory bonders + ) + public + L2_Bridge( + l1Governance, + hToken, + l1BridgeAddress, + activeChainIds, + bonders + ) + { + polygonZkEvmMessengerAddress = _polygonZkEvmMessengerAddress; + } + + function _sendCrossDomainMessage(bytes memory message) internal override { + uint32 destinationNetwork = 0; // L1 + bool forceUpdateGlobalExitRoot = true; + polygonZkEvmMessengerAddress.bridgeMessage{value: 0}( + destinationNetwork, + l1BridgeAddress, + forceUpdateGlobalExitRoot, + message + ); + } + + // TODO: verify sender + function _verifySender(address expectedSender) internal override { + require(msg.sender == address(polygonZkEvmMessengerAddress), "L2_PLGNZK_BRG: Caller is not the expected sender"); + } +} diff --git a/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol b/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol new file mode 100644 index 0000000..950d892 --- /dev/null +++ b/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol @@ -0,0 +1,13 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +/// @title The bridge interface implemented on both chains +interface IL2PolygonZkEvmMessenger { + function bridgeMessage( + uint32 destinationNetwork, + address destinationAddress, + bool forceUpdateGlobalExitRoot, + bytes calldata metadata + ) external payable; +} diff --git a/contracts/test/Mock_L2_PolygonZkEvmBridge.sol b/contracts/test/Mock_L2_PolygonZkEvmBridge.sol new file mode 100644 index 0000000..443520c --- /dev/null +++ b/contracts/test/Mock_L2_PolygonZkEvmBridge.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "../bridges/L2_PolygonZkEvmBridge.sol"; + +contract Mock_L2_PolygonZkEvmBridge is L2_PolygonZkEvmBridge { + constructor ( + IL2PolygonZkEvmMessenger messenger, + address l1Governance, + HopBridgeToken hToken, + address l1BridgeAddress, + uint256[] memory activeChainIds, + address[] memory bonders + ) + public + L2_PolygonZkEvmBridge( + messenger, + l1Governance, + hToken, + l1BridgeAddress, + activeChainIds, + bonders + ) + {} +} diff --git a/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol b/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol new file mode 100644 index 0000000..4f26c77 --- /dev/null +++ b/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +contract mockPolygonZkEvm_L1Bridge { +} diff --git a/contracts/test/polygonzkevm/mockPolygonZkEvm_L2Bridge.sol b/contracts/test/polygonzkevm/mockPolygonZkEvm_L2Bridge.sol new file mode 100644 index 0000000..069aceb --- /dev/null +++ b/contracts/test/polygonzkevm/mockPolygonZkEvm_L2Bridge.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +contract mockPolygonZkEvm_L2Bridge { +} diff --git a/hardhat.config.ts b/hardhat.config.ts index e535cf6..a1a53d0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -139,6 +139,12 @@ export default { accounts: desiredAccounts, chainId: CHAIN_IDS.SCROLL.SCROLL_TESTNET.toNumber(), timeout + }, + polygonzkevm_testnet: { + url: process.env.RPC_ENDPOINT_POLYGONZKEVM_TESTNET, + accounts: desiredAccounts, + chainId: CHAIN_IDS.POLYGONZKEVM.POLYGONZKEVM_TESTNET.toNumber(), + timeout } }, zksolc: { @@ -220,7 +226,8 @@ export default { consensys_testnet: process.env.CONSENSYS_API_KEY, zksync_testnet: process.env.ZKSYNC_API_KEY, base_testnet: process.env.BASE_API_KEY, - scroll_testnet: process.env.SCROLL_API_KEY + scroll_testnet: process.env.SCROLL_API_KEY, + polygonzkevm_testnet: process.env.POLYGONZKEVM_API_KEY }, customChains: [ { @@ -278,6 +285,14 @@ export default { apiURL: 'https://blockscout.scroll.io/api', browserURL: 'https://blockscout.scroll.io' } + }, + { + network: 'polygon_testnet', + chainId: 1442, + urls: { + apiURL: 'https://testnet-zkevm.polygonscan.com/api', + browserURL: 'https://testnet-zkevm.polygonscan.com' + } } ] } diff --git a/scripts/deployAndSetupHop/deploy.ts b/scripts/deployAndSetupHop/deploy.ts index 4ceb4d8..ba1be1a 100644 --- a/scripts/deployAndSetupHop/deploy.ts +++ b/scripts/deployAndSetupHop/deploy.ts @@ -297,7 +297,8 @@ function handleCustomL2NetworkName ( l2NetworkName === 'nova' || l2NetworkName === 'consensys' || l2NetworkName === 'zksync' || - l2NetworkName === 'scroll' + l2NetworkName === 'scroll' || + l2NetworkName === 'polygonzkevm' ) { if (l1NetworkName === 'mainnet') { return `${l2NetworkName}_mainnet` diff --git a/scripts/deployAndSetupHop/setupL1.ts b/scripts/deployAndSetupHop/setupL1.ts index 58ad924..63c7b58 100644 --- a/scripts/deployAndSetupHop/setupL1.ts +++ b/scripts/deployAndSetupHop/setupL1.ts @@ -2,7 +2,14 @@ require('dotenv').config() import { ethers as l2Ethers } from 'ethers' import { ethers } from 'hardhat' -import { BigNumber, ContractFactory, Signer, Contract, providers, utils as ethersUtils } from 'ethers' +import { + BigNumber, + ContractFactory, + Signer, + Contract, + providers, + utils as ethersUtils +} from 'ethers' import { getContractFactories, @@ -28,6 +35,7 @@ import { isChainIdConsensys, isChainIdBase, isChainIdScroll, + isChainIdPolygonZkEvm, getActiveChainIds } from '../../config/utils' diff --git a/scripts/messageStateUpdates/getMessengerTxData.ts b/scripts/messageStateUpdates/getMessengerTxData.ts index f615e77..4e5d562 100644 --- a/scripts/messageStateUpdates/getMessengerTxData.ts +++ b/scripts/messageStateUpdates/getMessengerTxData.ts @@ -20,7 +20,8 @@ const chains: Record = { Consensys: 'consensys', ZkSync: 'zksync', Base: 'base', - Scroll: 'scroll' + Scroll: 'scroll', + PolygonZkEvm: 'polygonzkevm' } const tokens: string[] = [ 'USDC', @@ -81,6 +82,9 @@ const targetAddresses: Record> = { }, scroll: { ETH: 'TODO' // TODO: scroll - for prod deployment + }, + polygonzkevm: { + ETH: 'TODO' // TODO: polygonzkevm - for prod deployment } } @@ -140,6 +144,9 @@ const l2BridgeAddresses: Record> = { }, scroll: { ETH: 'TODO' // TODO: scroll - for prod deployment + }, + polygonzkevm: { + ETH: 'TODO' // TODO: polygonzkevm - for prod deployment } } @@ -344,7 +351,9 @@ const logData = ( chain === chains.Arbitrum || chain === chains.Nova || chain === chains.Consensys || - chain === chains.Scroll + chain === chains.Scroll || + chain === chains.Base || + chain === chains.PolgyonZkEvm ) { console.log(`value to send: ${fee}`) } @@ -354,4 +363,4 @@ main() .catch(error => { console.error(error) }) - .finally(() => process.exit(0)) \ No newline at end of file + .finally(() => process.exit(0)) diff --git a/scripts/other/deployStakingRewards.ts b/scripts/other/deployStakingRewards.ts index 7747596..ef659fa 100644 --- a/scripts/other/deployStakingRewards.ts +++ b/scripts/other/deployStakingRewards.ts @@ -7,7 +7,6 @@ const hre = require('hardhat') // $ npm run deploy:staking-rewards async function main () { - const rewardsDistribution = '' const rewardsToken = '' const stakingToken = '' @@ -23,11 +22,18 @@ async function main () { { signer } ) - const rewards = await StakingRewards.deploy(rewardsDistribution, rewardsToken, stakingToken) + const rewards = await StakingRewards.deploy( + rewardsDistribution, + rewardsToken, + stakingToken + ) await rewards.deployed() console.log('rewards address:', rewards.address) - console.log('deployed bytecode:', await ethers.provider.getCode(rewards.address)) + console.log( + 'deployed bytecode:', + await ethers.provider.getCode(rewards.address) + ) console.log('complete') } diff --git a/scripts/other/sendMessageOverCanonicalBridge.ts b/scripts/other/sendMessageOverCanonicalBridge.ts index 3589644..0a13373 100644 --- a/scripts/other/sendMessageOverCanonicalBridge.ts +++ b/scripts/other/sendMessageOverCanonicalBridge.ts @@ -31,14 +31,16 @@ async function main () { '80001': 'mumbai', '420': 'optimism', '421613': 'arbitrum', - '59140': 'consensys' + '59140': 'consensys', + '1442': 'polygonzkevm' } const l2BridgeAddresses: Record = { '80001': '0x34E8251051687BfF4EA23C18e466b3Ed13492abd', '421613': '0xb276BC046DFf5024D20A3947475eA20C9F08eB1F', '420': '0x2708E5C7087d4C6D295c8B58b2D452c360D505C7', - '59140': '0x3E4a3a4796d16c0Cd582C382691998f7c06420B6' + '59140': '0x3E4a3a4796d16c0Cd582C382691998f7c06420B6', + '1442': '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7' } for (const l2ChainId of l2ChainIds) { diff --git a/scripts/shared/utils.ts b/scripts/shared/utils.ts index 7a13064..17161ed 100644 --- a/scripts/shared/utils.ts +++ b/scripts/shared/utils.ts @@ -19,7 +19,8 @@ import { isChainIdConsensys, isChainIdZkSync, isChainIdBase, - isChainIdScroll + isChainIdScroll, + isChainIdPolygonZkEvm } from '../../config/utils' import { @@ -127,6 +128,8 @@ const getNetworkSpecificFactories = async ( return getBaseContractFactories(signer, ethers) } else if (isChainIdScroll(chainId)) { return getScrollContractFactories(signer, ethers) + } else if (isChainIdPolygonZkEvm(chainId)) { + return getPolygonZkEvmContractFactories(signer, ethers) } else { return { L1_Messenger: null, @@ -317,6 +320,31 @@ const getScrollContractFactories = async (signer: Signer, ethers: any) => { } } +const getPolygonZkEvmContractFactories = async ( + signer: Signer, + ethers: any +) => { + const L1_Messenger: ContractFactory = await ethers.getContractFactory( + 'contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol:mockPolygonZkEvm_L1Bridge', + { signer } + ) + const L1_MessengerWrapper: ContractFactory = await ethers.getContractFactory( + 'contracts/wrappers/PolygonZkEvmMessengerWrapper.sol:PolygonZkEvmMessengerWrapper', + { signer } + ) + const L2_Bridge: ContractFactory = await ethers.getContractFactory( + 'contracts/bridges/L2_PolygonZkEvmBridge.sol:L2_PolygonZkEvmBridge', + { signer } + ) + + return { + L1_Messenger, + L1_MessengerWrapper, + L2_Bridge, + L2_MessengerProxy: null + } +} + const configFilepath = path.resolve( __dirname, '../deployAndSetupHop/deploy_config.json' diff --git a/test/shared/contractFunctionWrappers.ts b/test/shared/contractFunctionWrappers.ts index 0715204..32267c5 100644 --- a/test/shared/contractFunctionWrappers.ts +++ b/test/shared/contractFunctionWrappers.ts @@ -25,6 +25,7 @@ import { isChainIdZkSync, isChainIdBase, isChainIdScroll, + isChainIdPolygonZkEvm, isChainIdL1, generateArbitrumAliasAddress } from '../../config/utils' @@ -32,6 +33,7 @@ import { CONSENSYS_ZK_EVM_MESSAGE_FEE, SCROLL_ZK_EVM_MESSAGE_FEE, ZKSYNC_MESSAGE_FEE, + POLYGONZKEVM_MESSAGE_FEE, CHAIN_IDS, DEFAULT_DEADLINE, TIMESTAMP_VARIANCE, @@ -39,7 +41,7 @@ import { H_TO_C_SWAP_INDICES, C_TO_H_SWAP_INDICES, DEFAULT_MAX_GAS, - DEFAULT_GAS_PRICE_BID, + DEFAULT_GAS_PRICE_BID } from '../../config/constants' /** @@ -181,6 +183,21 @@ export const executeCanonicalMessengerSendMessage = async ( tx = await l1_messenger .connect(sender) .sendMessage(...optimismParams, modifiedGasPrice) + } else if (isChainIdPolygonZkEvm(l2ChainId)) { + const destinationNetwork = 1 // 0 = L1, 1 = L2 + const forceUpdateGlobalExitRoot = true + const polygonZkEvmParams = [ + destinationNetwork, + l2_bridge.address, + forceUpdateGlobalExitRoot, + message + ] + const overrides = { + ...modifiedGasPrice + } + tx = await l1_messenger + .connect(sender) + .bridgeMessage(...polygonZkEvmParams, overrides) } else { tx = await l1_messenger .connect(sender) diff --git a/test/shared/utils.ts b/test/shared/utils.ts index f36a495..af00ec4 100644 --- a/test/shared/utils.ts +++ b/test/shared/utils.ts @@ -44,6 +44,7 @@ import { isChainIdBase, isChainIdMainnet, isChainIdGoerli, + isChainIdPolygonZkEvm, getPolygonCheckpointManagerAddress } from '../../config/utils' @@ -423,6 +424,13 @@ export const getL2SpecificArtifact = (chainId: BigNumber) => { 'contracts/test/Mock_L1_Messenger.sol:Mock_L1_Messenger' l1_messengerWrapperArtifact = 'contracts/wrappers/BaseMessengerWrapper.sol:BaseMessengerWrapper' + } else if (isChainIdPolygonZkEvm(chainId)) { + l2_bridgeArtifact = + 'Mock_L2_PolygonZkEvmBridge.sol:Mock_L2_PolygonZkEvmBridge' + l1_messengerArtifact = + 'contracts/test/Mock_L1_Messenger.sol:Mock_L1_Messenger' + l1_messengerWrapperArtifact = + 'contracts/wrappers/PolygonZkEvmMessengerWrapper.sol:PolygonZkEvmMessengerWrapper' } return { diff --git a/test/wrappers/ArbitrumMessengerWrapper.test.ts b/test/wrappers/ArbitrumMessengerWrapper.test.ts index 0b860d9..30cb393 100644 --- a/test/wrappers/ArbitrumMessengerWrapper.test.ts +++ b/test/wrappers/ArbitrumMessengerWrapper.test.ts @@ -44,7 +44,9 @@ describe('Arbitrum Messenger Wrapper', () => { const expectedL1BridgeAddress: string = l1_bridge.address const expectedL2BridgeAddress: string = l2_bridge.address const expectedArbInbox: string = l1_messenger.address - const expectedMaxSubmissionCost: BigNumber = BigNumber.from('10000000000000000') + const expectedMaxSubmissionCost: BigNumber = BigNumber.from( + '10000000000000000' + ) const expectedL1MessengerWrapperAlias: string = generateArbitrumAliasAddress( l1_messengerWrapper.address ) From 89cb28050829c05f55837db98aeae7f07caab70a Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sun, 16 Apr 2023 14:08:08 -0700 Subject: [PATCH 2/2] Add PolygonZkEvm messenger proxy --- contracts/bridges/L2_PolygonZkEvmBridge.sol | 30 +++---- .../bridges/L2_PolygonZkEvmMessengerProxy.sol | 80 +++++++++++++++++++ .../bridges/IBridgeMessageReceiver.sol | 15 ++++ .../messengers/IL2PolygonZkEvmMessenger.sol | 2 +- .../I_L2_PolygonZkEvmMessengerProxy.sol | 13 +++ contracts/test/Mock_L2_PolygonZkEvmBridge.sol | 3 +- .../mockPolygonZkEvm_L1Bridge.sol | 50 ++++++++++++ scripts/shared/utils.ts | 6 +- test/shared/fixtures.ts | 3 + 9 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 contracts/bridges/L2_PolygonZkEvmMessengerProxy.sol create mode 100644 contracts/interfaces/polygonzkevm/bridges/IBridgeMessageReceiver.sol create mode 100644 contracts/interfaces/polygonzkevm/messengers/I_L2_PolygonZkEvmMessengerProxy.sol diff --git a/contracts/bridges/L2_PolygonZkEvmBridge.sol b/contracts/bridges/L2_PolygonZkEvmBridge.sol index 422f80b..4d38d63 100644 --- a/contracts/bridges/L2_PolygonZkEvmBridge.sol +++ b/contracts/bridges/L2_PolygonZkEvmBridge.sol @@ -3,7 +3,7 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; -import "../interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol"; +import "../interfaces/polygonzkevm/messengers/I_L2_PolygonZkEvmMessengerProxy.sol"; import "./L2_Bridge.sol"; /** @@ -11,10 +11,10 @@ import "./L2_Bridge.sol"; */ contract L2_PolygonZkEvmBridge is L2_Bridge { - IL2PolygonZkEvmMessenger public polygonZkEvmMessengerAddress; + I_L2_PolygonZkEvmMessengerProxy public messengerProxy; constructor ( - IL2PolygonZkEvmMessenger _polygonZkEvmMessengerAddress, + I_L2_PolygonZkEvmMessengerProxy _messengerProxy, address l1Governance, HopBridgeToken hToken, address l1BridgeAddress, @@ -30,22 +30,24 @@ contract L2_PolygonZkEvmBridge is L2_Bridge { bonders ) { - polygonZkEvmMessengerAddress = _polygonZkEvmMessengerAddress; + messengerProxy = _messengerProxy; } function _sendCrossDomainMessage(bytes memory message) internal override { - uint32 destinationNetwork = 0; // L1 - bool forceUpdateGlobalExitRoot = true; - polygonZkEvmMessengerAddress.bridgeMessage{value: 0}( - destinationNetwork, - l1BridgeAddress, - forceUpdateGlobalExitRoot, - message - ); + messengerProxy.sendCrossDomainMessage(message); } - // TODO: verify sender function _verifySender(address expectedSender) internal override { - require(msg.sender == address(polygonZkEvmMessengerAddress), "L2_PLGNZK_BRG: Caller is not the expected sender"); + require(msg.sender == address(messengerProxy), "L2_PLGNZK_BRG: Caller is not the expected sender"); + // Verify that cross-domain sender is expectedSender + require(messengerProxy.xDomainMessageSender() == expectedSender, "L2_PLGNZK_BRG: Invalid cross-domain sender"); + } + + /** + * @dev Allows the L1 Bridge to set the messengerProxy proxy + * @param _messengerProxy The new messengerProxy address + */ + function setMessengerProxy(I_L2_PolygonZkEvmMessengerProxy _messengerProxy) external onlyGovernance { + messengerProxy = _messengerProxy; } } diff --git a/contracts/bridges/L2_PolygonZkEvmMessengerProxy.sol b/contracts/bridges/L2_PolygonZkEvmMessengerProxy.sol new file mode 100644 index 0000000..4879ea4 --- /dev/null +++ b/contracts/bridges/L2_PolygonZkEvmMessengerProxy.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// @unsupported: ovm + +pragma solidity 0.8.9; +pragma experimental ABIEncoderV2; + +import "../polygon/ReentrancyGuard.sol"; +import "../interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol"; +import "../interfaces/polygonzkevm/bridges/IBridgeMessageReceiver.sol"; + +contract L2Bridge { + function commitTransfers(uint256 destinationChainId) external {} +} + +contract L2_PolygonZkEvmMessengerProxy is IBridgeMessageReceiver, ReentrancyGuard { + address public l2Bridge; + address public l1Bridge; + address public xDomainMessageSender; + address public polygonZkEvmMessenger; + bool private canCommit = false; + + address constant public DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD; + + modifier onlyL2Bridge { + require(msg.sender == l2Bridge, "L2_PLGNZK_MSG: Sender must be the L2 Bridge"); + _; + } + + modifier validateSender(address sender) { + require(sender == msg.sender, "L2_PLGNZK_MSG: Invalid sender"); + _; + } + + constructor(address _l1Bridge, address _polygonZkEvmMessenger) public { + l1Bridge = _l1Bridge; + polygonZkEvmMessenger = _polygonZkEvmMessenger; + xDomainMessageSender = DEAD_ADDRESS; + } + + function setL2Bridge(address _l2Bridge) external { + require(l2Bridge == address(0), "L2_PLGNZK_MSG: L2 Bridge already set"); + l2Bridge = _l2Bridge; + } + + function commitTransfers(uint256 destinationChainId) external { + require(msg.sender == tx.origin, "L2_PLGNZK_MSG: Sender must be origin"); + canCommit = true; + L2Bridge(l2Bridge).commitTransfers(destinationChainId); + canCommit = false; + } + + function sendCrossDomainMessage(bytes memory message) external onlyL2Bridge { + require(canCommit, "L2_PLGNZK_MSG: Unable to commit"); + uint32 destinationNetwork = 0; + bool forceUpdateGlobalExitRoot = true; + IL2PolygonZkEvmMessenger(polygonZkEvmMessenger).bridgeMessage{value: 0}( + destinationNetwork, + l1Bridge, + forceUpdateGlobalExitRoot, + message + ); + } + + function onMessageReceived( + address originAddress, + uint32, /* originNetwork */ + bytes memory data + ) + external + override + payable + validateSender(originAddress) + nonReentrant + { + xDomainMessageSender = originAddress; + (bool success,) = l2Bridge.call(data); + require(success, "L2_PLGNZK_MSG: Failed to proxy message"); + xDomainMessageSender = DEAD_ADDRESS; + } +} \ No newline at end of file diff --git a/contracts/interfaces/polygonzkevm/bridges/IBridgeMessageReceiver.sol b/contracts/interfaces/polygonzkevm/bridges/IBridgeMessageReceiver.sol new file mode 100644 index 0000000..266cc51 --- /dev/null +++ b/contracts/interfaces/polygonzkevm/bridges/IBridgeMessageReceiver.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0 + +pragma solidity ^0.8.0; + +/** + * @dev Define interface for PolygonZkEVM Bridge message receiver + * https://github.com/0xPolygonHermez/zkevm-contracts/blob/main/contracts/interfaces/IBridgeMessageReceiver.sol + */ +interface IBridgeMessageReceiver { + function onMessageReceived( + address originAddress, + uint32 originNetwork, + bytes memory data + ) external payable; +} diff --git a/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol b/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol index 950d892..f43be07 100644 --- a/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol +++ b/contracts/interfaces/polygonzkevm/messengers/IL2PolygonZkEvmMessenger.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Unlicense -pragma solidity ^0.6.12; +pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; /// @title The bridge interface implemented on both chains diff --git a/contracts/interfaces/polygonzkevm/messengers/I_L2_PolygonZkEvmMessengerProxy.sol b/contracts/interfaces/polygonzkevm/messengers/I_L2_PolygonZkEvmMessengerProxy.sol new file mode 100644 index 0000000..1ed1eb9 --- /dev/null +++ b/contracts/interfaces/polygonzkevm/messengers/I_L2_PolygonZkEvmMessengerProxy.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +interface I_L2_PolygonZkEvmMessengerProxy { + function sendCrossDomainMessage(bytes memory message) external; + function xDomainMessageSender() external view returns (address); + function onMessageReceived( + address originAddress, + uint32 originNetwork, + bytes memory data + ) external; +} diff --git a/contracts/test/Mock_L2_PolygonZkEvmBridge.sol b/contracts/test/Mock_L2_PolygonZkEvmBridge.sol index 443520c..0d38df6 100644 --- a/contracts/test/Mock_L2_PolygonZkEvmBridge.sol +++ b/contracts/test/Mock_L2_PolygonZkEvmBridge.sol @@ -4,10 +4,11 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "../bridges/L2_PolygonZkEvmBridge.sol"; +import "../interfaces/polygonzkevm/messengers/I_L2_PolygonZkEvmMessengerProxy.sol"; contract Mock_L2_PolygonZkEvmBridge is L2_PolygonZkEvmBridge { constructor ( - IL2PolygonZkEvmMessenger messenger, + I_L2_PolygonZkEvmMessengerProxy messenger, address l1Governance, HopBridgeToken hToken, address l1BridgeAddress, diff --git a/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol b/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol index 4f26c77..fdd07cc 100644 --- a/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol +++ b/contracts/test/polygonzkevm/mockPolygonZkEvm_L1Bridge.sol @@ -3,4 +3,54 @@ pragma solidity 0.6.12; contract mockPolygonZkEvm_L1Bridge { + /********** + * Events * + **********/ + + /// @notice Emitted when a cross domain message is sent. + /// @param sender The address of the sender who initiates the message. + /// @param target The address of target contract to call. + /// @param value The amount of value passed to the target contract. + /// @param messageNonce The nonce of the message. + /// @param gasLimit The optional gas limit passed to L1 or L2. + /// @param message The calldata passed to the target contract. + event SentMessage( + address indexed sender, + address indexed target, + uint256 value, + uint256 messageNonce, + uint256 gasLimit, + bytes message + ); + + /// @notice Emitted when a cross domain message is relayed successfully. + /// @param messageHash The hash of the message. + event RelayedMessage(bytes32 indexed messageHash); + + /// @notice Emitted when a cross domain message is failed to relay. + /// @param messageHash The hash of the message. + event FailedRelayedMessage(bytes32 indexed messageHash); + + /************************* + * Public View Functions * + *************************/ + + /// @notice Return the sender of a cross domain message. + function xDomainMessageSender() external view returns (address) {} + + /**************************** + * Public Mutated Functions * + ****************************/ + + /// @notice Send cross chain message from L1 to L2 or L2 to L1. + /// @param target The address of account who recieve the message. + /// @param value The amount of ether passed when call target contract. + /// @param message The content of the message. + /// @param gasLimit Gas limit required to complete the message relay on corresponding chain. + function sendMessage( + address target, + uint256 value, + bytes calldata message, + uint256 gasLimit + ) external payable {} } diff --git a/scripts/shared/utils.ts b/scripts/shared/utils.ts index 17161ed..b310931 100644 --- a/scripts/shared/utils.ts +++ b/scripts/shared/utils.ts @@ -336,12 +336,16 @@ const getPolygonZkEvmContractFactories = async ( 'contracts/bridges/L2_PolygonZkEvmBridge.sol:L2_PolygonZkEvmBridge', { signer } ) + const L2_MessengerProxy: ContractFactory = await ethers.getContractFactory( + 'contracts/bridges/L2_PolygonZkEvmMessengerProxy.sol:L2_PolygonZkEvmMessengerProxy', + { signer } + ) return { L1_Messenger, L1_MessengerWrapper, L2_Bridge, - L2_MessengerProxy: null + L2_MessengerProxy } } diff --git a/test/shared/fixtures.ts b/test/shared/fixtures.ts index d01d951..ba4258e 100644 --- a/test/shared/fixtures.ts +++ b/test/shared/fixtures.ts @@ -73,6 +73,9 @@ export async function fixture ( const L2_MessengerProxy = await ethers.getContractFactory( 'contracts/bridges/L2_PolygonMessengerProxy.sol:L2_PolygonMessengerProxy' ) + const L2_PolygonZkEvmMessengerProxy = await ethers.getContractFactory( + 'contracts/bridges/L2_PolygonZkEvmMessengerProxy.sol:L2_PolygonZkEvmMessengerProxy' + ) const FxRoot = await ethers.getContractFactory( 'contracts/test/MockFxRoot.sol:MockFxRoot'