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

feat: wormhole bridging for governance #116

Merged
merged 15 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions abis/0.8.23/OptimismMessenger.json

Large diffs are not rendered by default.

354 changes: 354 additions & 0 deletions abis/0.8.23/WormholeMessenger.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions abis/test/WormholeRelayer.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions contracts/bridges/OptimismMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract OptimismMessenger {
// Foreign governor address on L1 that is authorized to propagate the transaction execution across the bridge
address public foreignGovernor;

/// @dev HomeMediator constructor.
/// @dev OptimismMessenger constructor.
/// @param _CDMContractProxyHome CDM Contract Proxy (Home) address (Optimism).
/// @param _foreignGovernor Foreign Governor address (ETH).
constructor(address _CDMContractProxyHome, address _foreignGovernor) {
Expand All @@ -77,7 +77,7 @@ contract OptimismMessenger {

/// @dev Changes the Foreign Governor address (original Timelock).
/// @notice The only way to change the Foreign Governor address is by the Timelock on L1 to request that change.
/// This triggers a self-contract transaction of HomeMediator that changes the Foreign Governor address.
/// This triggers a self-contract transaction of OptimismMessenger that changes the Foreign Governor address.
/// @param newForeignGovernor New Foreign Governor address.
function changeForeignGovernor(address newForeignGovernor) external {
// Check if the change is authorized by the previous governor itself
Expand Down
209 changes: 209 additions & 0 deletions contracts/bridges/WormholeMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @dev Provided zero address.
error ZeroAddress();

/// @dev Provided zero value.
error ZeroValue();

/// @dev Only self contract is allowed to call the function.
/// @param sender Sender address.
/// @param instance Required contract instance address.
error SelfCallOnly(address sender, address instance);

/// @dev Only `wormholeRelayer` is allowed to call the function.
/// @param sender Sender address.
/// @param wormholeRelayer Required L2 Wormhole Relayer address.
error wormholeRelayerOnly(address sender, address wormholeRelayer);

/// @dev Wrong source chain Id.
/// @param received Chain Id received.
/// @param required Required chain Id.
error WrongSourceChainId(uint256 received, uint256 required);

/// @dev Only on behalf of `sourceGovernor` the function is allowed to process the data.
/// @param sender Sender address.
/// @param sourceGovernor Required Source Governor address.
error SourceGovernorOnly(address sender, address sourceGovernor);

/// @dev The message with a specified hash has already been delivered.
/// @param deliveryHash Delivery hash.
error AlreadyDelivered(bytes32 deliveryHash);

/// @dev Provided incorrect data length.
/// @param expected Expected minimum data length.
/// @param provided Provided data length.
error IncorrectDataLength(uint256 expected, uint256 provided);

/// @dev Provided value is bigger than the actual balance.
/// @param value Provided value.
/// @param balance Actual balance.
error InsufficientBalance(uint256 value, uint256 balance);

/// @dev Target execution failed.
/// @param target Target address.
/// @param value Provided value.
/// @param payload Provided payload.
error TargetExecFailed(address target, uint256 value, bytes payload);

/// @title WormholeMessenger - Smart contract for the governor bridge communication via wormhole
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract WormholeMessenger {
event FundsReceived(address indexed sender, uint256 value);
event SourceGovernorUpdated(address indexed sourceMessageSender);
event MessageReceived(address indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain);

// Default payload data length includes the number of bytes of at least one address (20 bytes or 160 bits),
// value (12 bytes or 96 bits) and the payload size (4 bytes or 32 bits)
uint256 public constant DEFAULT_DATA_LENGTH = 36;
// L2 Wormhole Relayer address that receives the message across the bridge from the source L1 network
address public immutable wormholeRelayer;
// Source governor chain Id
uint16 public immutable sourceGovernorChainId;
// Source governor address on L1 that is authorized to propagate the transaction execution across the bridge
address public sourceGovernor;
// Mapping of delivery hashes
mapping(bytes32 => bool) public mapDeliveryHashes;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are two major differences for the wormhole contract - we need to check the source chain Id, and the delivery hash such that it does not repeat.


/// @dev WormholeMessenger constructor.
/// @param _wormholeRelayer L2 Wormhole Relayer address.
/// @param _sourceGovernor Source governor address (ETH).
/// @param _sourceGovernorChainId Source governor wormhole format chain Id.
constructor(address _wormholeRelayer, address _sourceGovernor, uint16 _sourceGovernorChainId) {
// Check for zero addresses
if (_wormholeRelayer == address(0) || _sourceGovernor == address(0)) {
revert ZeroAddress();
}

// Check source governor chain Id
if (_sourceGovernorChainId == 0) {
revert ZeroValue();
}

wormholeRelayer = _wormholeRelayer;
sourceGovernor = _sourceGovernor;
sourceGovernorChainId = _sourceGovernorChainId;
}

/// @dev Receives native network token.
receive() external payable {
emit FundsReceived(msg.sender, msg.value);
}

/// @dev Changes the Source Governor address (original Timelock).
/// @notice The only way to change the Source Governor address is by the Timelock on L1 to request that change.
/// This triggers a self-contract transaction of WormholeMessenger that changes the Source Governor address.
/// @param newSourceGovernor New Source Governor address.
function changeSourceGovernor(address newSourceGovernor) external {
// Check if the change is authorized by the previous governor itself
// This is possible only if all the checks in the message process function pass and the contract calls itself
if (msg.sender != address(this)) {
revert SelfCallOnly(msg.sender, address(this));
}

// Check for the zero address
if (newSourceGovernor == address(0)) {
revert ZeroAddress();
}

sourceGovernor = newSourceGovernor;
emit SourceGovernorUpdated(newSourceGovernor);
}

/// @dev Processes a message received from L2 Wormhole Relayer contract.
/// @notice The sender must be the Source Governor address (Timelock).
/// @param data Bytes message sent from L2 Wormhole Relayer contract. The data must be encoded as a set of
/// continuous transactions packed into a single buffer, where each transaction is composed as follows:
/// - target address of 20 bytes (160 bits);
/// - value of 12 bytes (96 bits), as a limit for all of Autonolas ecosystem contracts;
/// - payload length of 4 bytes (32 bits), as 2^32 - 1 characters is more than enough to fill a whole block;
/// - payload as bytes, with the length equal to the specified payload length.
/// @param sourceAddress The (wormhole format) address on the sending chain which requested this delivery.
/// @param sourceChain The wormhole chain Id where this delivery was requested.
/// @param deliveryHash - the VAA hash of the deliveryVAA.
function receiveWormholeMessages(
bytes memory data,
bytes[] memory,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external payable {
// Check L2 Wormhole Relayer address
if (msg.sender != wormholeRelayer) {
revert wormholeRelayerOnly(msg.sender, wormholeRelayer);
}

// Check the source chain Id
if (sourceChain != sourceGovernorChainId) {
revert WrongSourceChainId(sourceChain, sourceGovernorChainId);
}

// Check for the source governor address
address governor = sourceGovernor;
address bridgeGovernor = address(uint160(uint256(sourceAddress)));
if (bridgeGovernor != governor) {
revert SourceGovernorOnly(bridgeGovernor, governor);
}

// Check the delivery hash uniqueness
if (mapDeliveryHashes[deliveryHash]) {
revert AlreadyDelivered(deliveryHash);
}
mapDeliveryHashes[deliveryHash] = true;

// Check for the correct data length
uint256 dataLength = data.length;
if (dataLength < DEFAULT_DATA_LENGTH) {
revert IncorrectDataLength(DEFAULT_DATA_LENGTH, data.length);
}

// Unpack and process the data
for (uint256 i = 0; i < dataLength;) {
address target;
uint96 value;
uint32 payloadLength;
// solhint-disable-next-line no-inline-assembly
assembly {
// First 20 bytes is the address (160 bits)
i := add(i, 20)
target := mload(add(data, i))
// Offset the data by 12 bytes of value (96 bits)
i := add(i, 12)
value := mload(add(data, i))
// Offset the data by 4 bytes of payload length (32 bits)
i := add(i, 4)
payloadLength := mload(add(data, i))
}

// Check for the zero address
if (target == address(0)) {
revert ZeroAddress();
}

// Check for the value compared to the contract's balance
if (value > address(this).balance) {
revert InsufficientBalance(value, address(this).balance);
}

// Get the payload
bytes memory payload = new bytes(payloadLength);
for (uint256 j = 0; j < payloadLength; ++j) {
payload[j] = data[i + j];
}
// Offset the data by the payload number of bytes
i += payloadLength;

// Call the target with the provided payload
(bool success, ) = target.call{value: value}(payload);
if (!success) {
revert TargetExecFailed(target, value, payload);
}
}

// Emit received message
emit MessageReceived(governor, data, deliveryHash, sourceChain);
}
}
46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,34 @@
"devDependencies": {
"@gnosis.pm/safe-contracts": "^1.3.0",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.6",
"@nomicfoundation/hardhat-network-helpers": "^1.0.8",
"@nomicfoundation/hardhat-toolbox": "^2.0.1",
"@nomiclabs/hardhat-ethers": "^2.2.2",
"@nomiclabs/hardhat-etherscan": "^3.1.6",
"@openzeppelin/contracts": "=4.8.3",
"@typechain/ethers-v5": "^10.2.0",
"@typechain/hardhat": "^6.1.5",
"@types/mocha": "^10.0.1",
"chai": "^4.3.7",
"eslint": "^8.34.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.9",
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
Comment on lines +24 to +25
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating hardhat packages such that the default EVM is Paris, and we don't have issues with the deployment on networks that don't deal with PUSH0 yet.

"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@typechain/ethers-v5": "^11.1.2",
"@typechain/hardhat": "^9.1.0",
"@types/mocha": "^10.0.3",
"chai": "^4.3.10",
"eslint": "^8.52.0",
"ethers": "^5.7.2",
"hardhat": "^2.12.7",
"hardhat-contract-sizer": "^2.8.0",
"hardhat-deploy": "^0.11.23",
"hardhat": "^2.18.2",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.11.43",
"hardhat-deploy-ethers": "^0.3.0-beta.13",
"hardhat-gas-reporter": "^1.0.9",
"hardhat-tracer": "^2.0.0",
"solidity-coverage": "^0.8.2"
"hardhat-tracer": "^2.6.0",
"solidity-coverage": "^0.8.5"
},
"dependencies": {
"@openzeppelin/contracts": "=4.8.3",
"@anders-t/ethers-ledger": "^1.0.4",
"@ethersproject/contracts": "^5.6.2",
"@ethersproject/providers": "^5.6.8",
"@ethersproject/solidity": "^5.6.1",
"@ethersproject/wallet": "^5.6.2",
"eth-permit": "^0.2.1",
"ethereum-sources-downloader": "^0.1.19",
"fx-portal": "^1.0.3",
"solhint": "^3.4.0"
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@ethersproject/solidity": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"eth-permit": "^0.2.3",
"ethereum-sources-downloader": "^0.1.21",
"solhint": "^3.6.2",
"fx-portal": "^1.0.3"
}
}
47 changes: 47 additions & 0 deletions scripts/deployment/bridges/wormhole/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Bridge-related deployment scripts
This process is the same as described in the original deployment procedure: [deployment](https://github.com/valory-xyz/autonolas-governance/blob/main/scripts/deployment).

## Steps to engage
The project has submodules to get the dependencies. Make sure you run `git clone --recursive` or init the submodules yourself.
The dependency list is managed by the `package.json` file, and the setup parameters are stored in the `hardhat.config.js` file.
Simply run the following command to install the project:
```
yarn install
```
command and compiled with the
```
npx hardhat compile
```

Create a `globals.json` file in the root folder, or copy it from the file with pre-defined parameters (i.e., `scripts/deployment/bridges/celo/globals_celo_alfajores.json` for the chiado testnet).

Parameters of the `globals.json` file:
- `contractVerification`: flag for verifying contracts in deployment scripts (`true`) or skipping it (`false`);
- `useLedger`: flag whether to use the hardware wallet (`true`) or proceed with the seed-phrase accounts (`false`);
- `derivationPath`: string with the derivation path;
- `gasPriceInGwei`: gas price in Gwei;
- `L2WormholeRelayerAddress`: (Celo) WormholeRelayer address serving as a system processor of inbound calls across the bridge;
- `timelockAddress`: Timelock address on the root L1 network;

The script file name identifies the number of deployment steps taken up to the number in the file name.

Export network-related API keys defined in `hardhat.config.js` file that correspond to the required network.

To run the script, use the following command:
`npx hardhat run scripts/deployment/bridges/script_name --network network_type`,
where `script_number_and_name` is a script number and name, i.e. `deploy_01_home_mediator.js`, `network_type` is a network type corresponding to the `hardhat.config.js` network configuration.

## Validity checks and contract verification
Each script controls the obtained values by checking them against the expected ones. Also, each script has a contract verification procedure.
If a contract is deployed with arguments, these arguments are taken from the corresponding `verify_number_and_name` file, where `number_and_name` corresponds to the deployment script number and name.

## Data packing for cross-bridge transactions
In order to correctly pack the data and supply it to the Timelock such that it is correctly processed across the bridge,
use the following script: [cross-bridge data packing](https://github.com/valory-xyz/autonolas-governance/blob/main/scripts/deployment/bridges/pack-data.js).







Loading
Loading