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

ZIL-5483: Bridge - ChainId support for multichain calls + others #282

Merged
merged 12 commits into from
Nov 28, 2023
47 changes: 47 additions & 0 deletions .github/workflows/ci-bridge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: "CI for Product Bridge"

on:
pull_request:
WuBruno marked this conversation as resolved.
Show resolved Hide resolved
paths:
- products/bridge/**
- ".github/workflows/**"

jobs:
testing:
runs-on: ubuntu-latest
name: "Linting"
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install
working-directory: products/bridge/smart-contracts

- name: Run tests
run: pnpm test
working-directory: products/bridge/smart-contracts
9 changes: 6 additions & 3 deletions products/bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,16 @@ The MVP bridge will run on a gossip network with validators managed by Zilliqa

- [ ] **Smart Contracts** - on hardhat (E2E testing & deployment) + foundry (Unit testing + fuzzing)
- [ ] Integrate foundry to support effective unit testing on contracts
- [ ] Finish remaining TODO tests
- [x] Finish remaining TODO tests
- [x] Update error handling on contracts
- [ ] Write deployment scripts
- [ ] Support CREATE2
- [ ] [Deterministic Deployment Proxy](https://github.com/Arachnid/deterministic-deployment-proxy)
- [ ] Integrate mechanism for gas reversal
- [ ] Multichain support - appending chain-ids to event calls
- [ ] Fuzz + variant testing
- [x] Multichain support - appending chain-ids to event calls
- [ ] Fuzz + invariant testing
- [ ] Synchronizing validators cross-chain
- [x] CI automated testing
- [ ] **Off-Chain Validator Nodes** & **Validator Node Lib**
- Binary and lib would be developed together. Lib will be refactored out later to be used for ZQ2
- [ ] Determine type of connection to use to connect to non-zilliqa chains
Expand Down
80 changes: 63 additions & 17 deletions products/bridge/smart-contracts/contracts/Bridged.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,83 @@ import "./Relayer.sol";
abstract contract Bridged is Initializable {
Relayer private _relayer;

function initialize(Relayer relayer) public initializer {
_relayer = relayer;
}
error NotRelayer(address relayer);

modifier onlyRelayer() {
require(msg.sender == address(_relayer), "Must be called by relayer");
if (msg.sender != address(_relayer)) {
revert NotRelayer(msg.sender);
}
_;
}

function dispatched(
address target,
bytes memory call
) public payable onlyRelayer returns (bool success, bytes memory response) {
console.log("dispatched()");
(success, response) = target.call{value: msg.value, gas: 100000}(call);
function __Bridged_init(Relayer relayer_) public onlyInitializing {
_relayer = relayer_;
}

function queried(
address target,
bytes memory call
) public view onlyRelayer returns (bool success, bytes memory response) {
console.log("queried()");
(success, response) = target.staticcall{gas: 100000}(call);
function relayer() public view returns (Relayer) {
return _relayer;
}

function relay(
uint targetChainId,
address target,
bytes memory call,
bool readonly,
bytes4 callback
) internal returns (uint nonce) {
nonce = _relayer.relay(target, call, readonly, callback);
nonce = _relayer.relay(targetChainId, target, call, readonly, callback);
}

function _dispatched(
address target,
bytes calldata call
) internal onlyRelayer returns (bool success, bytes memory response) {
(success, response) = target.call{value: msg.value, gas: 100000}(call);
}

function dispatched(
uint sourceChainId,
address target,
bytes calldata call
)
external
payable
virtual
onlyRelayer
returns (bool success, bytes memory response)
{
(success, response) = _dispatched(target, call);
WuBruno marked this conversation as resolved.
Show resolved Hide resolved
}

function queried(
address target,
bytes calldata call
) external view virtual returns (bool success, bytes memory response) {
(success, response) = target.staticcall{gas: 100000}(call);
}
}

abstract contract BridgedTwin is Initializable, Bridged {
uint private _twinChainId;

error InvalidChainId(uint chainId);
error NotTwinChain(uint chainId);

modifier onlyTwinChain(uint twinChainId_) {
if (_twinChainId != twinChainId_) {
revert NotTwinChain(twinChainId_);
}
_;
}

function __BridgedTwin_init(uint twinChainId_) public onlyInitializing {
if (twinChainId_ == 0 || twinChainId_ == block.chainid) {
revert InvalidChainId(twinChainId_);
}
_twinChainId = twinChainId_;
}

function twinChainId() public view returns (uint) {
return _twinChainId;
}
}
22 changes: 14 additions & 8 deletions products/bridge/smart-contracts/contracts/Collector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@ pragma solidity ^0.8.20;
import "./ValidatorManager.sol";

contract Collector {
ValidatorManager private validatorManager;
ValidatorManager private _validatorManager;

event Echoed(bytes32 indexed hash, bytes signature);

constructor(ValidatorManager _validatorManager) {
validatorManager = _validatorManager;
error InvalidSignature();

constructor(ValidatorManager validatorManager_) {
_validatorManager = validatorManager_;
}

function echo(bytes32 hash, bytes memory signature) public {
require(
validatorManager.validateSignature(hash, signature),
"Wrong validator"
);
function echo(bytes32 hash, bytes calldata signature) public {
if (!_validatorManager.validateSignature(hash, signature)) {
revert InvalidSignature();
}
emit Echoed(hash, signature);
}

function validatorManager() public view returns (ValidatorManager) {
return _validatorManager;
}
}
64 changes: 47 additions & 17 deletions products/bridge/smart-contracts/contracts/ERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,79 @@ import "./Bridged.sol";

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";

contract BridgedERC20 is ERC20, ERC20Burnable {
address _bridge;

error NotBridge(address);

modifier onlyBridge() {
if (msg.sender != _bridge) {
revert NotBridge(msg.sender);
}
_;
}

constructor(
string memory name_,
string memory symbol_,
string memory name,
string memory symbol,
address bridge_
) ERC20(name_, symbol_) {
) ERC20(name, symbol) {
_bridge = bridge_;
_mint(msg.sender, 1000);
}

modifier onlyBridge() {
require(msg.sender == _bridge, "Not the bridge");
_;
}

function mint(address to, uint256 amount) public onlyBridge {
function mint(address to, uint256 amount) external onlyBridge {
_mint(to, amount);
}

function burn(address from, uint256 amount) public onlyBridge {
function burn(address from, uint256 amount) external onlyBridge {
burnFrom(from, amount);
}

function bridge() public view returns (address) {
return _bridge;
}
}

contract MyToken is BridgedERC20 {
constructor(address bridge_) BridgedERC20("MyToken", "MTK", bridge_) {}
}

contract ERC20Bridge is Bridged {
contract ERC20Bridge is Initializable, Bridged, BridgedTwin {
event Started(address, address, uint);
event Succeeded();
event Failed(string);

function initialize(Relayer relayer, uint twinChainId) public initializer {
__Bridged_init(relayer);
__BridgedTwin_init(twinChainId);
}

// This might be unecessary as bridge and exit will already restrict these calls
function dispatched(
uint sourceChainId,
WuBruno marked this conversation as resolved.
Show resolved Hide resolved
address target,
bytes calldata call
)
external
payable
override
onlyTwinChain(sourceChainId)
returns (bool success, bytes memory response)
{
(success, response) = _dispatched(target, call);
}

function bridge(
address token,
address owner,
uint value
) public returns (uint nonce) {
) external returns (uint nonce) {
MyToken(token).transferFrom(owner, address(this), value);
nonce = relay(
twinChainId(),
token,
abi.encodeWithSignature("mint(address,uint256)", owner, value),
false,
Expand All @@ -58,9 +90,10 @@ contract ERC20Bridge is Bridged {
address token,
address owner,
uint value
) public returns (uint nonce) {
) external returns (uint nonce) {
MyToken(token).burn(owner, value);
nonce = relay(
twinChainId(),
token,
abi.encodeWithSignature("transfer(address,uint256)", owner, value),
false,
Expand All @@ -69,14 +102,11 @@ contract ERC20Bridge is Bridged {
emit Started(token, owner, value);
}

event Succeeded();
event Failed(string);

function finish(
bool success,
bytes calldata res,
uint nonce
) public onlyRelayer {
) external onlyRelayer {
if (success) {
emit Succeeded();
} else {
Expand Down
Loading
Loading