Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
86ad952
Reimplement OutboundQueue
vgeddes May 31, 2023
3c27d1d
Add MessageQueue pallet to BridgeHub runtime
vgeddes Jun 1, 2023
f056cc8
Relayer no longer requires snowbridge-query-events tool for querying …
vgeddes Jun 1, 2023
bd1773d
Refactor tractor
vgeddes Jun 3, 2023
da66429
fixes
vgeddes Jun 12, 2023
ff6e3d9
Merge branch 'main' into vincent/outbound-queue
vgeddes Jun 12, 2023
630dce2
rework runtime api for outbound-queue
vgeddes Jun 14, 2023
15bf1c6
misc
vgeddes Jun 14, 2023
16f4efc
Merge branch 'main' into vincent/outbound-queue
vgeddes Jun 15, 2023
a953e50
Fix ethereum unit test
vgeddes Jun 15, 2023
dd83989
update things
vgeddes Jun 15, 2023
3db3ed5
Reduce stack depth in DeployScript.sol
vgeddes Jun 15, 2023
2622339
Revert Merkle proof verifier contract (#861)
doubledup Jun 15, 2023
71981c6
Optimize merkle proof verifier
vgeddes Jun 15, 2023
71b0f21
Revert changes to relayer merkle package
doubledup Jun 15, 2023
a903c6d
Update contract bindings
vgeddes Jun 15, 2023
248f6bb
fix parachain unit tests
vgeddes Jun 15, 2023
2c1d052
More relayer fixes
vgeddes Jun 15, 2023
b817948
Fix message flow in Polkadot->Ethereum direction
vgeddes Jun 16, 2023
0175e36
linting
vgeddes Jun 16, 2023
2c749e1
revert changes to IParachainClient interface
vgeddes Jun 16, 2023
56b9d83
update cumulus
vgeddes Jun 16, 2023
12705ea
merged in companion PR
vgeddes Jun 16, 2023
ca363c1
Merge branch 'main' into vincent/outbound-queue
vgeddes Jun 16, 2023
e32608d
improve encoding of parachain header in ParachainClient.sol
vgeddes Jun 16, 2023
1058bd5
Add copyright/license boilerplate
vgeddes Jun 16, 2023
109a805
Port over ethereum location converter
vgeddes Jun 16, 2023
9c62531
remove obsolete eth1 POW code
vgeddes Jun 16, 2023
e648446
fix unit tests
vgeddes Jun 16, 2023
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
3 changes: 1 addition & 2 deletions .github/workflows/ethereum.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
run: forge test
- name: Coverage
working-directory: core/packages/contracts
run: forge coverage --report=lcov
run: forge coverage --report=lcov --via-ir
- name: Lint
working-directory: core/packages/contracts
run: pnpm lint
Expand All @@ -48,4 +48,3 @@ jobs:
with:
working-directory: core/packages/contracts
files: lcov.info

2 changes: 1 addition & 1 deletion core/packages/contracts/scripts/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class ValidatorSet {
let leaves = wallets.map((w) => keccakFromHexString(w.address))
let tree = new MerkleTree(leaves, keccak, {
sortLeaves: false,
sortPairs: true,
sortPairs: false,
})

this.wallets = wallets
Expand Down
13 changes: 13 additions & 0 deletions core/packages/contracts/src/Auth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {AccessControl} from "openzeppelin/access/AccessControl.sol";

contract Auth is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

constructor() {
_grantRole(ADMIN_ROLE, msg.sender);
_setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE);
}
}
11 changes: 4 additions & 7 deletions core/packages/contracts/src/BeefyClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.19;

import {ECDSA} from "openzeppelin/utils/cryptography/ECDSA.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol";
import {MerkleProof} from "./utils/MerkleProof.sol";
import {Bitfield} from "./utils/Bitfield.sol";
import {MMRProof} from "./utils/MMRProof.sol";
import {ScaleCodec} from "./ScaleCodec.sol";
Expand Down Expand Up @@ -441,10 +441,7 @@ contract BeefyClient is Ownable {
}

// Check that validator is actually in a validator set
//
// NOTE: This currently insecure due to a regression documented in SNO-427.
// Basically `proof.index` (the merkle leaf index) is not being validated.
if (!isValidatorInSet(vset, proof.account, proof.proof)) {
if (!isValidatorInSet(vset, proof.account, proof.index, proof.proof)) {
revert InvalidValidatorProof();
}

Expand Down Expand Up @@ -498,13 +495,13 @@ contract BeefyClient is Ownable {
* @param proof Merkle proof required for validation of the address
* @return true if the validator is in the set
*/
function isValidatorInSet(ValidatorSet memory vset, address addr, bytes32[] calldata proof)
function isValidatorInSet(ValidatorSet memory vset, address addr, uint256 index, bytes32[] calldata proof)
internal
pure
returns (bool)
{
bytes32 hashedLeaf = keccak256(abi.encodePacked(addr));
return MerkleProof.verify(proof, vset.root, hashedLeaf);
return MerkleProof.verify(vset.root, hashedLeaf, index, vset.length, proof);
}

/**
Expand Down
61 changes: 41 additions & 20 deletions core/packages/contracts/src/DeployScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,55 +11,73 @@ import {OutboundQueue} from "../src/OutboundQueue.sol";
import {NativeTokens} from "../src/NativeTokens.sol";
import {TokenVault} from "../src/TokenVault.sol";
import {Vault} from "../src/Vault.sol";
import {IVault} from "../src/IVault.sol";
import {UpgradeProxy} from "../src/UpgradeProxy.sol";
import {SovereignTreasury} from "../src/SovereignTreasury.sol";
import {Registry} from "../src/Registry.sol";
import {ParaID} from "../src/Types.sol";

contract DeployScript is Script {
Registry public registry;
Vault public vault;
BeefyClient public beefyClient;
ParachainClient public parachainClient;
InboundQueue public inboundQueue;
OutboundQueue public outboundQueue;
TokenVault public tokenVault;
NativeTokens public nativeTokens;
UpgradeProxy public upgradeProxy;

function setUp() public {}

function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.rememberKey(privateKey);
vm.startBroadcast(deployer);

// Registry
registry = new Registry();
registry.grantRole(registry.REGISTER_ROLE(), deployer);

// SovereignTreasury
Vault vault = new Vault();
SovereignTreasury treasury = new SovereignTreasury(vault);
vault = new Vault();
SovereignTreasury treasury = new SovereignTreasury(registry, vault);

// BeefyClient
uint256 randaoCommitDelay = vm.envUint("RANDAO_COMMIT_DELAY");
uint256 randaoCommitExpiration = vm.envUint("RANDAO_COMMIT_EXP");
BeefyClient beefyClient = new BeefyClient(randaoCommitDelay, randaoCommitExpiration);
beefyClient = new BeefyClient(randaoCommitDelay, randaoCommitExpiration);

// ParachainClient
uint32 paraId = uint32(vm.envUint("BRIDGE_HUB_PARAID"));
ParachainClient parachainClient = new ParachainClient(beefyClient, paraId);
parachainClient = new ParachainClient(beefyClient, paraId);

// InboundQueue
uint256 relayerReward = vm.envUint("RELAYER_REWARD");
InboundQueue inboundQueue = new InboundQueue(parachainClient, vault, relayerReward);
inboundQueue = new InboundQueue(registry, parachainClient, vault, relayerReward);
registry.registerContract(keccak256("InboundQueue"), address(inboundQueue));

// OutboundQueue
uint256 relayerFee = vm.envUint("RELAYER_FEE");
OutboundQueue outboundQueue = new OutboundQueue(vault, relayerFee);
outboundQueue = new OutboundQueue(registry, vault, relayerFee);
registry.registerContract(keccak256("OutboundQueue"), address(outboundQueue));

// NativeTokens
TokenVault tokenVault = new TokenVault();
NativeTokens nativeTokens = new NativeTokens(
tokenVault = new TokenVault();
nativeTokens = new NativeTokens(
registry,
tokenVault,
outboundQueue,
ParaID.wrap(uint32(vm.envUint("ASSET_HUB_PARAID"))),
vm.envUint("CREATE_TOKEN_FEE")
vm.envUint("CREATE_TOKEN_FEE"),
bytes2(vm.envBytes("CREATE_CALL_INDEX")),
bytes2(vm.envBytes("SET_METADATA_CALL_INDEX"))
);
inboundQueue.updateHandler(1, IRecipient(nativeTokens));
registry.registerContract(keccak256("NativeTokens"), address(nativeTokens));

// Deploy WETH for testing
new WETH9();

// Upgrades
UpgradeProxy upgradeProxy = new UpgradeProxy(ParaID.wrap(paraId));
upgradeProxy = new UpgradeProxy(registry, ParaID.wrap(paraId));

// Allow inbound queue to send messages to handlers
nativeTokens.grantRole(nativeTokens.SENDER_ROLE(), address(inboundQueue));
Expand All @@ -82,24 +100,27 @@ contract DeployScript is Script {
// Move ownership of everything to Upgrades app

treasury.grantRole(treasury.ADMIN_ROLE(), address(upgradeProxy));
treasury.revokeRole(treasury.ADMIN_ROLE(), address(this));
treasury.revokeRole(treasury.ADMIN_ROLE(), deployer);

nativeTokens.grantRole(nativeTokens.ADMIN_ROLE(), address(upgradeProxy));
nativeTokens.revokeRole(nativeTokens.ADMIN_ROLE(), address(this));
nativeTokens.revokeRole(nativeTokens.ADMIN_ROLE(), deployer);

vault.grantRole(vault.ADMIN_ROLE(), address(upgradeProxy));
vault.revokeRole(vault.ADMIN_ROLE(), address(this));
vault.revokeRole(vault.ADMIN_ROLE(), deployer);

tokenVault.grantRole(tokenVault.ADMIN_ROLE(), address(upgradeProxy));
tokenVault.revokeRole(tokenVault.ADMIN_ROLE(), address(this));
tokenVault.revokeRole(tokenVault.ADMIN_ROLE(), deployer);

inboundQueue.grantRole(inboundQueue.ADMIN_ROLE(), address(upgradeProxy));
inboundQueue.revokeRole(inboundQueue.ADMIN_ROLE(), address(this));
inboundQueue.revokeRole(inboundQueue.ADMIN_ROLE(), deployer);

outboundQueue.grantRole(outboundQueue.ADMIN_ROLE(), address(upgradeProxy));
outboundQueue.revokeRole(outboundQueue.ADMIN_ROLE(), address(this));
outboundQueue.revokeRole(outboundQueue.ADMIN_ROLE(), deployer);

registry.grantRole(outboundQueue.ADMIN_ROLE(), address(upgradeProxy));
registry.revokeRole(outboundQueue.ADMIN_ROLE(), deployer);

upgradeProxy.revokeRole(upgradeProxy.ADMIN_ROLE(), address(this));
upgradeProxy.revokeRole(upgradeProxy.ADMIN_ROLE(), deployer);

vm.stopBroadcast();
}
Expand Down
35 changes: 35 additions & 0 deletions core/packages/contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {AccessControl} from "openzeppelin/access/AccessControl.sol";
import {Registry} from "./Registry.sol";
import {IOutboundQueue} from "./IOutboundQueue.sol";
import {IRecipient} from "./IRecipient.sol";
import {ParaID} from "./Types.sol";
import {Auth} from "./Auth.sol";
import {RegistryLookup} from "./RegistryLookup.sol";

abstract contract Gateway is Auth, RegistryLookup, IRecipient {
bytes32 public constant SENDER_ROLE = keccak256("SENDER_ROLE");
bytes32 public constant OUTBOUND_QUEUE = keccak256("OutboundQueue");

/* Errors */

error Unauthorized();

constructor(Registry registry) RegistryLookup(registry) {
_setRoleAdmin(SENDER_ROLE, ADMIN_ROLE);
}

function handle(ParaID origin, bytes calldata message) external virtual;

function outboundQueue() internal view returns (IOutboundQueue) {
return IOutboundQueue(resolve(OUTBOUND_QUEUE));
}

function ensureOrigin(ParaID a, ParaID b) internal pure {
if (a != b) {
revert Unauthorized();
}
}
}
6 changes: 0 additions & 6 deletions core/packages/contracts/src/IUpgradeTask.sol

This file was deleted.

10 changes: 0 additions & 10 deletions core/packages/contracts/src/IVault.sol

This file was deleted.

50 changes: 14 additions & 36 deletions core/packages/contracts/src/InboundQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,27 @@ pragma solidity ^0.8.19;
import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol";
import {AccessControl} from "openzeppelin/access/AccessControl.sol";
import {IParachainClient} from "./ParachainClient.sol";
import {Registry} from "./Registry.sol";
import {RegistryLookup} from "./RegistryLookup.sol";
import {Auth} from "./Auth.sol";
import {Vault} from "./Vault.sol";

import {IRecipient} from "./IRecipient.sol";
import {IVault} from "./IVault.sol";
import {ParaID} from "./Types.sol";

contract InboundQueue is AccessControl {
contract InboundQueue is Auth, RegistryLookup {
Comment thread
yrong marked this conversation as resolved.
// Nonce for each origin
mapping(ParaID origin => uint64) public nonce;

// Registered message handlers
mapping(uint16 handlerID => IRecipient) public handlers;

// Light client message verifier
IParachainClient public parachainClient;

// Relayers are rewarded from this vault
IVault public vault;
Vault public immutable vault;

// The relayer reward for submitting a message
uint256 public reward;

// The governance contract which is a proxy for Polkadot governance, administers via this role
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

// Relayers must provide enough gas to cover message dispatch plus a buffer
uint256 public gasToForward = 500000;
uint256 public constant GAS_BUFFER = 24000;
Expand All @@ -35,7 +33,7 @@ contract InboundQueue is AccessControl {
struct Message {
ParaID origin;
uint64 nonce;
uint16 handler;
bytes32 recipient;
bytes payload;
}

Expand All @@ -51,15 +49,14 @@ contract InboundQueue is AccessControl {
event RewardUpdated(uint256 reward);
event GasToForwardUpdated(uint256 gasToForward);


error InvalidProof();
error InvalidNonce();
error InvalidHandler();
error InvalidRecipient();
error NotEnoughGas();

constructor(IParachainClient _parachainClient, IVault _vault, uint256 _reward) {
_grantRole(ADMIN_ROLE, msg.sender);
_setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE);
constructor(Registry registry, IParachainClient _parachainClient, Vault _vault, uint256 _reward)
RegistryLookup(registry)
Comment thread
yrong marked this conversation as resolved.
{
parachainClient = _parachainClient;
vault = _vault;
reward = _reward;
Expand Down Expand Up @@ -89,11 +86,6 @@ contract InboundQueue is AccessControl {
// should top up the funds and have a relayer resend the message.
vault.withdraw(message.origin, payable(msg.sender), reward);

IRecipient handler = handlers[message.handler];
if (address(handler) == address(0)) {
revert InvalidHandler();
}

// Ensure relayers pass enough gas for message to execute.
// Otherwise malicious relayers can break the bridge by allowing handlers to run out gas.
// Resubmission of the message by honest relayers will fail as the tracked nonce
Expand All @@ -102,30 +94,16 @@ contract InboundQueue is AccessControl {
revert NotEnoughGas();
}

address recipient = resolve(message.recipient);
DispatchResult result = DispatchResult.Success;
try handler.handle{gas: gasToForward}(message.origin, message.payload) {}
try IRecipient(recipient).handle{gas: gasToForward}(message.origin, message.payload) {}
catch {
result = DispatchResult.Failure;
}

emit MessageDispatched(message.origin, message.nonce, result);
}

function updateHandler(uint16 id, IRecipient handler) external onlyRole(ADMIN_ROLE) {
handlers[id] = handler;
emit HandlerUpdated(id, handler);
}

function updateParachainClient(IParachainClient _parachainClient) external onlyRole(ADMIN_ROLE) {
parachainClient = _parachainClient;
emit ParachainClientUpdated(address(_parachainClient));
}

function updateVault(IVault _vault) external onlyRole(ADMIN_ROLE) {
vault = _vault;
emit VaultUpdated(address(_vault));
}

function updateReward(uint256 _reward) external onlyRole(ADMIN_ROLE) {
reward = _reward;
emit RewardUpdated(_reward);
Expand Down
Loading