Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
url = https://github.com/bgd-labs/aave-address-book
[submodule "lib/governance-crosschain-bridges"]
path = lib/governance-crosschain-bridges
url = https://github.com/aave/governance-crosschain-bridges
url = https://github.com/0xdapper/governance-crosschain-bridges
branch = zkevm-l2-executor
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ arbitrum = "${RPC_ARBITRUM}"
fantom = "${RPC_FANTOM}"
harmony = "${RPC_HARMONY}"
metis = "${RPC_METIS}"
zkevm = "${RPC_ZKEVM}"

[etherscan]
mainnet={key="${ETHERSCAN_API_KEY_MAINNET}",chainId=1}
Expand Down
62 changes: 62 additions & 0 deletions src/crosschainforwarders/CrosschainForwarderPolygonZkEVM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {L2BridgeExecutor} from 'governance-crosschain-bridges/contracts/bridges/ZkEVMBridgeExecutor.sol';

interface IPolygonZkEVMBridge {
function bridgeMessage(
uint32 destinationNetwork,
address destinationAddress,
bool forceUpdateGlobalExitRoot,
bytes calldata metadata
) external payable;
}

/**
* @title A generic executor for proposals targeting the polygon zkEVM v3 pool
* @author BGD Labs
* @notice You can **only** use this executor when the polygon payload has a `execute()` signature without parameters
* @notice You can **only** use this executor when the polygon payload is expected to be executed via `DELEGATECALL`
* @dev This executor is a generic wrapper to be used with the Polygon ZkEVM Bridge (https://github.com/0xPolygonHermez/zkevm-contracts/blob/main/contracts/PolygonZkEVMBridge.sol)
* It encodes a parameterless `execute()` with delegate calls and a specified target.
* This encoded abi is then sent to the PolygonZkEVMBridge L1 to be synced to the PolygonZkEVMBridge L2 on the polygon zkevm network.
* Once synced the POLYGON_ZKEVM_BRIDGE_EXECUTOR will queue the execution of the payload.
*/
contract CrosschainForwarderPolygonZkEVM {
IPolygonZkEVMBridge public constant POLYGON_ZKEVM_BRIDGE =
IPolygonZkEVMBridge(0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe);
address public immutable POLYGON_ZKEVM_BRIDGE_EXECUTOR;
uint32 internal constant ZKEVM_NETWORK_ID = 1;

constructor(address zkEvmExecutor) {
POLYGON_ZKEVM_BRIDGE_EXECUTOR = zkEvmExecutor;
}

/**
* @dev this function will be executed once the proposal passes the mainnet vote.
* @param l2PayloadContract the polygon contract containing the `execute()` signature.
*/
function execute(address l2PayloadContract) public {
address[] memory targets = new address[](1);
targets[0] = l2PayloadContract;
uint256[] memory values = new uint256[](1);
values[0] = 0;
string[] memory signatures = new string[](1);
signatures[0] = 'execute()';
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = '';
bool[] memory withDelegatecalls = new bool[](1);
withDelegatecalls[0] = true;

bytes memory actions = abi.encodeCall(
L2BridgeExecutor.queue,
(targets, values, signatures, calldatas, withDelegatecalls)
);
POLYGON_ZKEVM_BRIDGE.bridgeMessage(
ZKEVM_NETWORK_ID,
POLYGON_ZKEVM_BRIDGE_EXECUTOR,
true,
actions
);
}
}
105 changes: 105 additions & 0 deletions tests/crosschainforwarders/PolygonZkEVMCrossChainForwarderTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import 'forge-std/Test.sol';
import {AaveMisc, AaveGovernanceV2} from 'aave-address-book/AaveAddressBook.sol';
import {GovHelpers} from '../../src/GovHelpers.sol';
import {ProtocolV3TestBase} from '../../src/ProtocolV3TestBase.sol';
import {PayloadWithEmit} from '../mocks/PayloadWithEmit.sol';
import {CrosschainForwarderPolygonZkEVM} from '../../src/crosschainforwarders/CrosschainForwarderPolygonZkEVM.sol';
import {ZkEVMBridgeExecutor} from 'governance-crosschain-bridges/contracts/bridges/ZkEVMBridgeExecutor.sol';

/**
* This test covers syncing between mainnet and polygon.
*/
contract PolygonZkEVMCrossChainForwarderTest is ProtocolV3TestBase {
event TestEvent();
// the identifiers of the forks
uint256 mainnetFork;
uint256 zkevmFork;

CrosschainForwarderPolygonZkEVM forwarder;
ZkEVMBridgeExecutor executor;
PayloadWithEmit public payloadWithEmit;

address constant zkevmBridge = 0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe;

function setUp() public {
mainnetFork = vm.createSelectFork(vm.rpcUrl('mainnet'), 17670229);
zkevmFork = vm.createSelectFork(vm.rpcUrl('zkevm'), 2384958);

// deploy executor on L2
vm.selectFork(zkevmFork);
executor = new ZkEVMBridgeExecutor(
AaveGovernanceV2.SHORT_EXECUTOR,
172800,
259200,
28800,
604800,
address(0)
);
payloadWithEmit = new PayloadWithEmit();

// deploy forwarder on mainnet with linked executor
vm.selectFork(mainnetFork);
forwarder = new CrosschainForwarderPolygonZkEVM(address(executor));
}

// utility to transform memory to calldata so array range access is available
function _cutBytes(bytes calldata input) public pure returns (bytes calldata) {
return input[64:];
}

function testProposalE2E() public {
// 1. create l1 proposal
vm.selectFork(mainnetFork);
vm.startPrank(AaveMisc.ECOSYSTEM_RESERVE);
GovHelpers.Payload[] memory payloads = new GovHelpers.Payload[](1);
payloads[0] = GovHelpers.Payload({
value: 0,
withDelegatecall: true,
target: address(forwarder),
signature: 'execute(address)',
callData: abi.encode(address(payloadWithEmit))
});

uint256 proposalId = GovHelpers.createProposal(
payloads,
0xf6e50d5a3f824f5ab4ffa15fb79f4fa1871b8bf7af9e9b32c1aaaa9ea633006d
);
vm.stopPrank();

// 2. execute proposal and record logs so we can extract the emitted StateSynced event
vm.recordLogs();
GovHelpers.passVoteAndExecute(vm, proposalId);

Vm.Log[] memory entries = vm.getRecordedLogs();
Vm.Log memory bridgeEventLog;
bool bridgeEventFound = false;
for (uint256 i = 0; i < entries.length; i++) {
if (
entries[i].topics.length > 0 &&
entries[i].topics[0] == 0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b
) {
bridgeEventLog = entries[i];
bridgeEventFound = true;
}
}
assertTrue(bridgeEventFound, 'Bridge event not found');
(, , address originAddress, , address destinationAddress, , bytes memory metadata, ) = abi
.decode(bridgeEventLog.data, (uint8, uint32, address, uint32, address, uint, bytes, uint32));
assertEq(destinationAddress, address(executor));
assertEq(originAddress, address(AaveGovernanceV2.SHORT_EXECUTOR), 'Origin address mismatch');

// 3. mock the receive on l2 with the data emitted on BridgeEvent
emit log_bytes(metadata);
vm.selectFork(zkevmFork);
vm.prank(zkevmBridge);
executor.onMessageReceived(originAddress, 0, metadata);

// 4. Forward time & execute proposal
vm.expectEmit(true, true, true, true);
emit TestEvent();
GovHelpers.executeLatestActionSet(vm, address(executor));
}
}