-
Notifications
You must be signed in to change notification settings - Fork 4
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
Changes from 5 commits
d132297
8bdc020
cab06d9
bf07cdc
50a336a
9ba2981
806ae93
3f0494e
cb8c79a
55f4548
ecff952
567f049
b2e2d59
e8f0cf0
0972178
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
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; | ||
|
||
/// @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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
"@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" | ||
} | ||
} |
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). | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
There was a problem hiding this comment.
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.