Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add scroll support #17

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# Aave Governance Cross-Chain Bridges

This repository contains smart contracts and related code for Aave cross-chain bridge executors. This is intended to extend Aave Governance on Ethereum to other networks. This repository currently contains contracts to support bridging to Polygon, Arbitrum and Optimism.
This repository contains smart contracts and related code for Aave cross-chain bridge executors. This is intended to extend Aave Governance on Ethereum to other networks. This repository currently contains contracts to support bridging to Polygon, Arbitrum, Optimism and Scroll.

The core contract is the `BridgeExecutorBase`, an abstract contract that contains the logic to facilitate the queueing, delay, and execution of sets of actions on downstream networks. This base contract needs to be extended with the functionality required for cross-chain transactions on a specific downstream network.

Expand Down Expand Up @@ -225,6 +225,55 @@ Therefore, the `msg.sender` of the cross-chain transaction on Optimism is the OV
- `maximumDelay` - maximum allowed delay
- `guardian` - the admin address of this contract with the permission to cancel ActionsSets

## Scroll Governance Bridge

### Scroll Governance Bridge Architecture

Additional documentation around the Scroll Bridging setup can be found at the links below:

- [Scroll Docs `L1 and L2 Bridging`](https://docs.scroll.io/en/developers/l1-and-l2-bridging/)

### Scroll Bridge Contracts Functionality

After going through the Lido governance, the proposal payload will be a call to the following function in the L1 Scroll Messenger contract on Ethereum:

```solidity
/// @notice Send cross chain message from L1 to L2 or L2 to L1.
/// @param target The address of account who receive 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;

```

From the function above, the `target` is the contract that will be called on Scroll (in this case it is the `ScrollBridgeExecutor` contract). The `value` is the amount of ether passing to target contract while call, and should be zero in our case. The `message` is the encoded data for the cross-chain transaction: the encoded data for `queue(targets, values, signatures, calldatas, withDelegatecalls)`. The `gasLimit` field pertain to gas management on Scroll and should be defined per Scroll documentation.

When this transaction is sent cross-chain, the `msg.sender` that sends the message to the Scroll Messenger is stored at the L2 Scroll Messenger and queryable using the following function:

```solidity
/// @notice Return the sender of a cross domain message.
function xDomainMessageSender() external view returns (address);

```

Therefore, the `msg.sender` of the cross-chain transaction on Scroll is the L2 Scroll Messenger contract, and the L1 sender is the Lido Governance Executor contract. For this reason, the Lido Governance Executor contract address should be provided to the `ScrollBridgeExecutor` contract in the constructor. This address will be saved and used to permit the queue function so that only calls from this address can successfully queue the ActionsSet in the `BridgeExecutorBase`.

### Deploying the ScrollBridgeExecutor

- `l2ScrollMessenger` - the address of the L2 Scroll Messenger contract
- `ethereumGovernanceExecutor` - the address that will have permission to queue ActionSets. This should be the Lido Governance Executor.
- `delay` - the time required to pass after the ActionsSet is queued, before execution
- `gracePeriod` - once execution time passes, you can execute this until the grace period ends
- `minimumDelay` - minimum allowed delay
- `maximumDelay` - maximum allowed delay
- `guardian` - the admin address of this contract with the permission to cancel ActionsSets

## License

[BSD-3-Clause](./LICENSE.md)
58 changes: 58 additions & 0 deletions contracts/bridges/ScrollBridgeExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.10;

import {IScrollMessenger} from '../dependencies/scroll/interfaces/IScrollMessenger.sol';
import {L2BridgeExecutor} from './L2BridgeExecutor.sol';

/**
* @title ScrollBridgeExecutor
* @author Aave, Scroll
* @notice Implementation of the Scroll Bridge Executor, able to receive cross-chain transactions from Ethereum
* @dev Queuing an ActionsSet into this Executor can only be done by the `L2ScrollMessenger` and having
* the EthereumGovernanceExecutor as xDomainMessageSender
*/
contract ScrollBridgeExecutor is L2BridgeExecutor {
// Address of the `L2ScrollMessenger` contract, in charge of redirecting cross-chain transactions in L2
address public immutable L2_SCROLL_MESSENGER;

/// @inheritdoc L2BridgeExecutor
modifier onlyEthereumGovernanceExecutor() override {
if (
msg.sender != L2_SCROLL_MESSENGER ||
IScrollMessenger(L2_SCROLL_MESSENGER).xDomainMessageSender() != _ethereumGovernanceExecutor
) revert UnauthorizedEthereumExecutor();
_;
}

/**
* @dev Constructor
*
* @param l2ScrollMessenger The address of the `L2ScrollMessenger` contract.
* @param ethereumGovernanceExecutor The address of the `EthereumGovernanceExecutor` contract.
* @param delay The delay before which an actions set can be executed
* @param gracePeriod The time period after a delay during which an actions set can be executed
* @param minimumDelay The minimum bound a delay can be set to
* @param maximumDelay The maximum bound a delay can be set to
* @param guardian The address of the guardian, which can cancel queued proposals (can be zero)
*/
constructor(
address l2ScrollMessenger,
address ethereumGovernanceExecutor,
uint256 delay,
uint256 gracePeriod,
uint256 minimumDelay,
uint256 maximumDelay,
address guardian
)
L2BridgeExecutor(
ethereumGovernanceExecutor,
delay,
gracePeriod,
minimumDelay,
maximumDelay,
guardian
)
{
L2_SCROLL_MESSENGER = l2ScrollMessenger;
}
}
55 changes: 55 additions & 0 deletions contracts/dependencies/scroll/interfaces/IScrollMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.9.0;

interface IScrollMessenger {
/**********
* 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 Mutating Functions *
*****************************/

/// @notice Send cross chain message from L1 to L2 or L2 to L1.
/// @param target The address of account who receive 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;
}
42 changes: 42 additions & 0 deletions contracts/mocks/MockL1ScrollMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.10;

import {IScrollMessenger} from '../dependencies/scroll/interfaces/IScrollMessenger.sol';

import {MockL2ScrollMessenger} from "./MockL2ScrollMessenger.sol";

contract MockL1ScrollMessenger is IScrollMessenger {
address private sender;
address private l2Messenger;

function setSender(address _sender) external {
sender = _sender;
}

function setL2Messenger(address _l2Messenger) external {
l2Messenger = _l2Messenger;
}

function xDomainMessageSender() external view override returns (address) {
return sender;
}

function sendMessage(
address _target,
uint256 _value,
bytes calldata _message,
uint256 _gasLimit
) external payable override {
MockL2ScrollMessenger(l2Messenger).redirect{value: _value}(msg.sender, _target, _value, _message, _gasLimit);
}

function redirect(
address _target,
uint256 _value,
bytes calldata _message,
uint256 _gasLimit
) external payable {
bool success;
(success, ) = _target.call{value: _value, gas: _gasLimit}(_message);
}
}
51 changes: 51 additions & 0 deletions contracts/mocks/MockL2ScrollMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.10;

import {IScrollMessenger} from '../dependencies/scroll/interfaces/IScrollMessenger.sol';

import {MockL1ScrollMessenger} from "./MockL1ScrollMessenger.sol";

contract MockL2ScrollMessenger is IScrollMessenger {
address private sender;
address private l1Messenger;

function setSender(address _sender) external {
sender = _sender;
}

function setL1Messenger(address _l1Messenger) external {
l1Messenger = _l1Messenger;
}

function xDomainMessageSender() external view override returns (address) {
return sender;
}

function sendMessage(
address _target,
uint256 _value,
bytes calldata _message,
uint256 _gasLimit
) external payable override {
MockL1ScrollMessenger(l1Messenger).redirect{value:_value}(_target, _value, _message, _gasLimit);
}

// This error must be defined here or else Hardhat will not recognize the selector
error UnauthorizedEthereumExecutor();

function redirect(
address _xDomainMessageSender,
address _target,
uint256 _value,
bytes calldata _message,
uint256 _gasLimit
) external payable {
sender = _xDomainMessageSender;
(bool success, bytes memory data) = _target.call{value: _value, gas: _gasLimit}(_message);
if (!success) {
assembly {
revert(add(data, 32), mload(data))
}
}
}
}
44 changes: 44 additions & 0 deletions deploy/gov-bridge-scroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { DeployFunction } from 'hardhat-deploy/types';
import { ADDRESSES, CONSTANTS } from '../helpers/gov-constants';
import { eScrollNetwork } from '../helpers/types';

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deploy, log } = deployments;
const { deployer } = await getNamedAccounts();

log(`Deployer: ${deployer}\n`);

const scrollGov = await deployments.getOrNull('ScrollGov');

if (scrollGov) {
log(`Reusing scroll governance at: ${scrollGov.address}`);
} else {
let L2_SCROLL_MESSENGER = ADDRESSES['L2_SCROLL_MESSENGER'];
let SCROLL_GOV_EXECUTOR = ADDRESSES['SCROLL_GOV_EXECUTOR'];
if (hre.network.name == eScrollNetwork.scrollSepolia) {
L2_SCROLL_MESSENGER = ADDRESSES['L2_SCROLL_MESSENGER_SEPOLIA'];
SCROLL_GOV_EXECUTOR = ADDRESSES['SCROLL_GOV_EXECUTOR_SEPOLIA'];
}

await deploy('ScrollGov', {
args: [
L2_SCROLL_MESSENGER,
SCROLL_GOV_EXECUTOR,
CONSTANTS['DELAY'],
CONSTANTS['GRACE_PERIOD'],
CONSTANTS['MIN_DELAY'],
CONSTANTS['MAX_DELAY'],
ADDRESSES['SCROLL_GUARDIAN'],
],
contract: 'ScrollBridgeExecutor',
from: deployer,
log: true,
});
}
};

export default func;
func.dependencies = [];
func.tags = ['ScrollGov'];
24 changes: 24 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
eNetwork,
eOptimismNetwork,
ePolygonNetwork,
eScrollNetwork,
eXDaiNetwork,
} from './helpers/types';
import { NETWORKS_RPC_URL } from './helper-hardhat-config';
Expand All @@ -46,6 +47,7 @@ const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
const FORKING_BLOCK_NUMBER = process.env.FORKING_BLOCK_NUMBER;
const ARBISCAN_KEY = process.env.ARBISCAN_KEY || '';
const OPTIMISTIC_ETHERSCAN_KEY = process.env.OPTIMISTIC_ETHERSCAN_KEY || '';
const SCROLLSCAN_KEY = process.env.SCROLLSCAN_KEY || '';
const TENDERLY_PROJECT = process.env.TENDERLY_PROJECT || '';
const TENDERLY_USERNAME = process.env.TENDERLY_USERNAME || '';

Expand Down Expand Up @@ -111,7 +113,27 @@ const hardhatConfig: HardhatUserConfig = {
apiKey: {
optimisticEthereum: OPTIMISTIC_ETHERSCAN_KEY,
arbitrumOne: ARBISCAN_KEY,
[eScrollNetwork.scroll]: SCROLLSCAN_KEY,
[eScrollNetwork.scrollSepolia]: SCROLLSCAN_KEY,
},
customChains: [
{
network: eScrollNetwork.scrollSepolia,
chainId: 534351,
urls: {
apiURL: 'https://api-sepolia.scrollscan.com/api',
browserURL: 'https://sepolia.scrollscan.com/',
},
},
{
network: eScrollNetwork.scroll,
chainId: 534352,
urls: {
apiURL: 'https://api.scrollscan.com/api',
browserURL: 'https://scrollscan.com/',
},
},
],
},
tenderly: {
project: TENDERLY_PROJECT,
Expand Down Expand Up @@ -161,6 +183,8 @@ const hardhatConfig: HardhatUserConfig = {
l1: 'kovan',
},
},
[eScrollNetwork.scroll]: getCommonNetworkConfig(eScrollNetwork.scroll, 534352),
[eScrollNetwork.scrollSepolia]: getCommonNetworkConfig(eScrollNetwork.scrollSepolia, 534351),
hardhat: {
accounts: accounts.map(({ secretKey, balance }: { secretKey: string; balance: string }) => ({
privateKey: secretKey,
Expand Down
3 changes: 3 additions & 0 deletions helper-hardhat-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
eOptimismNetwork,
ePolygonNetwork,
eXDaiNetwork,
eScrollNetwork,
iParamsPerNetwork,
} from './helpers/types';

Expand Down Expand Up @@ -37,4 +38,6 @@ export const NETWORKS_RPC_URL: iParamsPerNetwork<string> = {
[eArbitrumNetwork.arbitrumTestnet]: `https://rinkeby.arbitrum.io/rpc`,
[eOptimismNetwork.main]: `https://opt-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`,
[eOptimismNetwork.testnet]: `https://opt-kovan.g.alchemy.com/v2/${ALCHEMY_KEY}`,
[eScrollNetwork.scroll]: "https://rpc.scroll.io",
[eScrollNetwork.scrollSepolia]: "https://sepolia-rpc.scroll.io",
};
Loading