Skip to content

Commit

Permalink
Add Bridge EVM Contract Project (#16449)
Browse files Browse the repository at this point in the history
## 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
Bridgerz authored Mar 19, 2024
1 parent 768fcb6 commit 2d13f08
Show file tree
Hide file tree
Showing 33 changed files with 4,548 additions and 0 deletions.
5 changes: 5 additions & 0 deletions bridge/evm/.env.example
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
13 changes: 13 additions & 0 deletions bridge/evm/.gitignore
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/*
62 changes: 62 additions & 0 deletions bridge/evm/README.md
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)
160 changes: 160 additions & 0 deletions bridge/evm/contracts/BridgeCommittee.sol
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;
}
}
Loading

0 comments on commit 2d13f08

Please sign in to comment.