-
Notifications
You must be signed in to change notification settings - Fork 11.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Bridge EVM Contract Project (#16449)
## Description This adds the foundry project to the workspace. ## Test Plan The foundry project currently includes unit tests / fuzz tests with plans to include invariance tests soon. It is also intended that E2E bridge tests will deploy the bridge contracts to an anvil node. Note: Typically foundry leverages git modules to manage dependencies. This is replaced with a build script found in the `sui-bridge` crate.
- Loading branch information
Showing
33 changed files
with
4,548 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
PRIVATE_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
INFURA_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
ETHERSCAN_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
SEPOLIA_RPC_URL=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
MAINNET_RPC_URL=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
cache* | ||
.env | ||
|
||
network_config.json | ||
.vscode | ||
cache/ | ||
out/ | ||
*.txt | ||
!remappings.txt | ||
lcov.info | ||
broadcast/**/31337 | ||
|
||
lib/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# 🏄♂️ Quick Start | ||
|
||
This project leverages [Foundry](https://github.com/foundry-rs/foundry) to manage dependencies, contract compilation, testing, deployment, and on chain interactions via Solidity scripting. | ||
|
||
#### Environment configuration | ||
|
||
Duplicate rename the `.env.example` file to `.env`. You'll need accounts and api keys for **Infura** and **Etherscan** as well as the necessary RPC URLs. Be sure to add the required values in your newly created `.env` file. | ||
|
||
> **Note** | ||
> The OZ foundry upgrades library uses node to verify upgrade safety. Make sure you have node version 18.17 or higher as well as npm version 10.4 or higher installed. | ||
#### Dependencies | ||
|
||
```bash | ||
forge install | ||
``` | ||
|
||
#### Compilation | ||
|
||
To compile your contracts, run: | ||
|
||
```bash | ||
forge compile | ||
``` | ||
|
||
#### Testing | ||
|
||
```bash | ||
forge clean | ||
forge test --ffi | ||
``` | ||
|
||
#### Coverage | ||
|
||
```bash | ||
forge coverage | ||
``` | ||
|
||
#### Deployment | ||
|
||
> **Note** | ||
> Make sure the deployment config file for the target chain is created in the `deploy_configs` folder. | ||
> The file should be named `<chainID>.json` and should have the same fields and in the same order (alphabetical) as the `example.json`. | ||
```bash | ||
forge clean | ||
forge script script/deploy_bridge.s.sol --rpc-url <<alias>> --broadcast --verify --ffi | ||
``` | ||
**Local deployment** | ||
```bash | ||
forge clean | ||
forge script script/deploy_bridge.s.sol --fork-url anvil --broadcast --ffi | ||
``` | ||
All deployments are saved in the `broadcast` directory. | ||
#### External Resources | ||
- [Writing OpenZeppelin Upgrades with Foundry](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades?tab=readme-ov-file) | ||
- [OpenZeppelin Upgrade Requirements](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core#define-reference-contracts) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "./interfaces/IBridgeCommittee.sol"; | ||
import "./utils/CommitteeUpgradeable.sol"; | ||
|
||
/// @title BridgeCommittee | ||
/// @notice This contract manages the committee members of the SuiBridge. The committee members are | ||
/// responsible for signing messages used to update various bridge state including the committee itself. | ||
/// The contract also provides functions to manage a blocklist of committee members whose signatures are invalid | ||
/// once they are blocklisted. | ||
contract BridgeCommittee is IBridgeCommittee, CommitteeUpgradeable { | ||
/* ========== STATE VARIABLES ========== */ | ||
|
||
mapping(address committeeMember => uint16 stakeAmount) public committeeStake; | ||
mapping(address committeeMember => uint8 index) public committeeIndex; | ||
mapping(address committeeMember => bool isBlocklisted) public blocklist; | ||
IBridgeConfig public config; | ||
|
||
/* ========== INITIALIZER ========== */ | ||
|
||
/// @notice Initializes the contract with the provided parameters. | ||
/// @dev should be called directly after deployment (see OpenZeppelin upgradeable standards). | ||
/// the provided arrays must have the same length and the total stake provided must equal 10000. | ||
/// @param _config The address of the BridgeConfig contract. | ||
/// @param committee addresses of the committee members. | ||
/// @param stake amounts of the committee members. | ||
function initialize( | ||
address _config, | ||
address[] memory committee, | ||
uint16[] memory stake, | ||
uint16 minStakeRequired | ||
) external initializer { | ||
__CommitteeUpgradeable_init(address(this)); | ||
__UUPSUpgradeable_init(); | ||
|
||
uint256 _committeeLength = committee.length; | ||
|
||
require( | ||
_committeeLength == stake.length, | ||
"BridgeCommittee: Committee and stake arrays must be of the same length" | ||
); | ||
|
||
config = IBridgeConfig(_config); | ||
|
||
uint16 totalStake; | ||
for (uint16 i; i < _committeeLength; i++) { | ||
require( | ||
committeeStake[committee[i]] == 0, "BridgeCommittee: Duplicate committee member" | ||
); | ||
committeeStake[committee[i]] = stake[i]; | ||
committeeIndex[committee[i]] = uint8(i); | ||
totalStake += stake[i]; | ||
} | ||
|
||
require(totalStake >= minStakeRequired, "BridgeCommittee: total stake is less than minimum"); // 10000 == 100% | ||
} | ||
|
||
/* ========== EXTERNAL FUNCTIONS ========== */ | ||
|
||
/// @notice Verifies the provided signatures for the given message by aggregating and validating the | ||
/// stake of each signer against the required stake of the given message type. | ||
/// @dev The function will revert if the total stake of the signers is less than the required stake. | ||
/// @param signatures The array of signatures to be verified. | ||
/// @param message The `BridgeMessage.Message` to be verified. | ||
function verifySignatures(bytes[] memory signatures, BridgeMessage.Message memory message) | ||
external | ||
view | ||
override | ||
{ | ||
uint32 requiredStake = BridgeMessage.requiredStake(message); | ||
|
||
uint16 approvalStake; | ||
address signer; | ||
uint256 bitmap; | ||
|
||
// Check validity of each signature and aggregate the approval stake | ||
for (uint16 i; i < signatures.length; i++) { | ||
bytes memory signature = signatures[i]; | ||
// recover the signer from the signature | ||
(bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); | ||
|
||
(signer,,) = ECDSA.tryRecover(BridgeMessage.computeHash(message), v, r, s); | ||
|
||
// skip if signer is block listed or has no stake | ||
if (blocklist[signer] || committeeStake[signer] == 0) continue; | ||
|
||
uint8 index = committeeIndex[signer]; | ||
uint256 mask = 1 << index; | ||
if (bitmap & mask == 0) { | ||
bitmap |= mask; | ||
} else { | ||
// skip if duplicate signature | ||
continue; | ||
} | ||
|
||
approvalStake += committeeStake[signer]; | ||
} | ||
|
||
require(approvalStake >= requiredStake, "BridgeCommittee: Insufficient stake amount"); | ||
} | ||
|
||
/// @notice Updates the blocklist status of the provided addresses if provided signatures are valid. | ||
/// @param signatures The array of signatures to validate the message. | ||
/// @param message BridgeMessage containing the update blocklist payload. | ||
function updateBlocklistWithSignatures( | ||
bytes[] memory signatures, | ||
BridgeMessage.Message memory message | ||
) | ||
external | ||
nonReentrant | ||
verifyMessageAndSignatures(message, signatures, BridgeMessage.BLOCKLIST) | ||
{ | ||
// decode the blocklist payload | ||
(bool isBlocklisted, address[] memory _blocklist) = | ||
BridgeMessage.decodeBlocklistPayload(message.payload); | ||
|
||
// update the blocklist | ||
_updateBlocklist(_blocklist, isBlocklisted); | ||
} | ||
|
||
/* ========== INTERNAL FUNCTIONS ========== */ | ||
|
||
/// @notice Updates the blocklist status of the provided addresses. | ||
/// @param _blocklist The addresses to update the blocklist status. | ||
/// @param isBlocklisted new blocklist status. | ||
function _updateBlocklist(address[] memory _blocklist, bool isBlocklisted) private { | ||
// check original blocklist value of each validator | ||
for (uint16 i; i < _blocklist.length; i++) { | ||
blocklist[_blocklist[i]] = isBlocklisted; | ||
} | ||
|
||
emit BlocklistUpdated(_blocklist, isBlocklisted); | ||
} | ||
|
||
/// @notice Splits the provided signature into its r, s, and v components. | ||
/// @param sig The signature to be split. | ||
/// @return r The r component of the signature. | ||
/// @return s The s component of the signature. | ||
/// @return v The v component of the signature. | ||
function splitSignature(bytes memory sig) | ||
internal | ||
pure | ||
returns (bytes32 r, bytes32 s, uint8 v) | ||
{ | ||
require(sig.length == 65, "BridgeCommittee: Invalid signature length"); | ||
// ecrecover takes the signature parameters, and the only way to get them | ||
// currently is to use assembly. | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
r := mload(add(sig, 32)) | ||
s := mload(add(sig, 64)) | ||
v := byte(0, mload(add(sig, 96))) | ||
} | ||
|
||
//adjust for ethereum signature verification | ||
if (v < 27) v += 27; | ||
} | ||
} |
Oops, something went wrong.