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

Contracts/communication synapse module #2050

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d3d9307
Isolate some common logic into abstract contract
ChiTimesChi Feb 16, 2024
b4cd92a
Scaffold `ThresholdECDSAModule`
ChiTimesChi Feb 16, 2024
f7c13cd
Add more views
ChiTimesChi Feb 16, 2024
dff5724
Implement management tests
ChiTimesChi Feb 16, 2024
9360662
Implement management actions
ChiTimesChi Feb 16, 2024
da19b81
Add `InterchainDB` Mock
ChiTimesChi Feb 16, 2024
71e5dc0
Merge branch 'contracts/communication' into contracts/communication-s…
ChiTimesChi Feb 16, 2024
d1b7c25
Reorg events following #2049
ChiTimesChi Feb 16, 2024
8051a7e
Also emit ethSingedMsg hash
ChiTimesChi Feb 16, 2024
1668581
More `InterchainModule` errors
ChiTimesChi Feb 16, 2024
25f7f03
Implement tests for fee collector management
ChiTimesChi Feb 16, 2024
d632f51
Fix: use the entry hash for signing
ChiTimesChi Feb 16, 2024
9af670e
Implement `feeCollector` management
ChiTimesChi Feb 16, 2024
cd3dccf
Start doing source tests
ChiTimesChi Feb 16, 2024
05fe117
Add GasOracle interface
ChiTimesChi Feb 16, 2024
3e96e60
Update `verifyEntry()` interface
ChiTimesChi Feb 16, 2024
82ba2df
Add source chain tests
ChiTimesChi Feb 16, 2024
8f94f6b
Implement source chain funcs
ChiTimesChi Feb 16, 2024
02d7cba
Add tests: `bytes` for signatures instead of `bytes[]`
ChiTimesChi Feb 16, 2024
a532869
Implement library update
ChiTimesChi Feb 16, 2024
8b0caa2
Simplify math
ChiTimesChi Feb 16, 2024
50b16a4
Implement `verifyEntry()`
ChiTimesChi Feb 16, 2024
de1cc35
Add tests for destination chain
ChiTimesChi Feb 19, 2024
494ee35
Don't use infinity as default threshold
ChiTimesChi Feb 19, 2024
696c4e0
Check source chain id in any InterchainModule
ChiTimesChi Feb 19, 2024
900a52e
Deprecate old SynapseModule
ChiTimesChi Feb 19, 2024
e724c38
Fix ClientV1 test
ChiTimesChi Feb 19, 2024
41949cd
Chore: cleanup
ChiTimesChi Feb 19, 2024
da01a1e
Chore: renamoooor
ChiTimesChi Feb 19, 2024
268d32e
Revert "Fix ClientV1 test"
ChiTimesChi Feb 19, 2024
553ff99
Revert "Chore: cleanup"
ChiTimesChi Feb 19, 2024
4b93e3f
Merge branch 'contracts/communication' into contracts/communication-s…
ChiTimesChi Feb 19, 2024
0192d56
Fix ClientV1 test
ChiTimesChi Feb 19, 2024
980d1ea
Chore: cleanup
ChiTimesChi Feb 19, 2024
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
12 changes: 0 additions & 12 deletions packages/contracts-communication/contracts/InterchainModule.sol

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {InterchainEntry} from "../libs/InterchainEntry.sol";

abstract contract InterchainModuleEvents {
event VerificationRequested(uint256 indexed destChainId, bytes entry, bytes32 ethSignedEntryHash);

event EntryVerified(InterchainEntry entry);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {InterchainEntry} from "../libs/InterchainEntry.sol";

abstract contract SynapseModuleEvents {
event VerificationRequested(uint256 indexed destChainId, InterchainEntry entry, bytes32 signableEntryHash);
event EntryVerified(InterchainEntry entry, bytes32 signableEntryHash);
event VerifierAdded(address verifier);
event VerifierRemoved(address verifier);
event ThresholdChanged(uint256 threshold);

event FeeCollectorChanged(address feeCollector);
event GasOracleChanged(address gasOracle);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGasOracle {
/// @notice Convert a value from the native token of a remote chain to the local native token.
/// @dev Will revert if no price is available for the remote chain.
/// @param remoteChainId The chain id of the remote chain.
/// @param value The value to convert.
function convertRemoteValueToLocalUnits(uint256 remoteChainId, uint256 value) external view returns (uint256);

/// @notice Estimate the cost of execution a transaction on a remote chain,
/// and convert it to the local native token.
/// @dev Will revert if no price is available for the remote chain.
/// @param remoteChainId The chain id of the remote chain.
/// @param gasLimit The gas limit of the transaction.
/// @param calldataSize The size of the transaction calldata.
function estimateTxCostInLocalUnits(
uint256 remoteChainId,
uint256 gasLimit,
uint256 calldataSize
)
external
view
returns (uint256);

/// @notice Estimate the cost of execution a transaction on a remote chain,
/// and return it as is in the remote chain's native token.
/// @dev Will revert if no price is available for the remote chain.
/// @param remoteChainId The chain id of the remote chain.
/// @param gasLimit The gas limit of the transaction.
/// @param calldataSize The size of the transaction calldata.
function estimateTxCostInRemoteUnits(
uint256 remoteChainId,
uint256 gasLimit,
uint256 calldataSize
)
external
view
returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import {InterchainEntry} from "../libs/InterchainEntry.sol";
/// @notice Every Module may opt a different method to confirm the verified entries on destination chain,
/// therefore this is not a part of a common interface.
interface IInterchainModule {
error InterchainModule__NotInterchainDB();
error InterchainModule__IncorrectSourceChainId(uint256 chainId);
error InterchainModule__InsufficientFee(uint256 actual, uint256 required);
error InterchainModule__SameChainId();

/// @notice Request the verification of an entry in the Interchain DataBase by the module.
/// Note: a fee is paid to the module for verification, and could be retrieved by using `getModuleFee`.
/// Note: this will eventually trigger `InterchainDB.verifyEntry(entry)` function on destination chain,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {InterchainEntry} from "../libs/InterchainEntry.sol";

interface ISynapseModule {
/// @notice Sets the address of the InterchainDB contract to be used for verifying entries.
/// @dev This function can only be called by the contract owner.
/// @param _interchainDB The address of the InterchainDB contract.
function setInterchainDB(address _interchainDB) external;

/// @notice Sets the required threshold for verification.
/// @dev This function updates the threshold value that determines the minimum number of verifications required for an entry to be considered valid. Can only be called by the contract owner.
/// @param _threshold The new threshold value.
function setRequiredThreshold(uint256 _threshold) external;

/// @notice Updates the list of verifier addresses.
/// @dev This function sets the addresses that are allowed to act as verifiers for entries. Can only be called by the contract owner.
/// @param _verifiers An array of addresses to be set as verifiers.
function setVerifiers(address[] calldata _verifiers) external;

/// @notice Requests off-chain verification of an interchain entry for a specified destination chain. This function requires a fee.
/// @dev This function can only be called by the InterchainDB contract. It checks if the sent value covers the module fee for the requested destination chain, then proceeds to pay the fee for execution. Emits a VerificationRequested event upon success.
/// @param destChainId The ID of the destination chain where the entry needs to be verified.
/// @param entry The interchain entry to be verified.
function requestVerification(uint256 destChainId, InterchainEntry memory entry) external payable;

/// @notice Verifies an interchain entry using a set of verifier signatures.
/// @dev This function checks if the provided signatures meet the required threshold for verification.
/// It then calls the InterchainDB contract to verify the entry. Requires that the number of valid signatures meets or exceeds the required threshold.
/// @param entry The interchain entry to be verified.
/// @param signatures An array of signatures used to verify the entry.
function verifyEntry(InterchainEntry memory entry, bytes[] calldata signatures) external;
import {IInterchainModule} from "./IInterchainModule.sol";

interface ISynapseModule is IInterchainModule {
error SynapseModule__GasOracleNotContract(address gasOracle);

// ═══════════════════════════════════════════════ PERMISSIONED ════════════════════════════════════════════════════

/// @notice Adds a new verifier to the module.
/// @dev Could be only called by the owner. Will revert if the verifier is already added.
/// @param verifier The address of the verifier to add
function addVerifier(address verifier) external;

/// @notice Removes a verifier from the module.
/// @dev Could be only called by the owner. Will revert if the verifier is not added.
/// @param verifier The address of the verifier to remove
function removeVerifier(address verifier) external;

/// @notice Sets the threshold of the module.
/// @dev Could be only called by the owner. Will revert if the threshold is zero.
/// @param threshold The new threshold value
function setThreshold(uint256 threshold) external;

/// @notice Sets the address of the fee collector, which will have the verification fees forwarded to it.
/// @dev Could be only called by the owner.
/// @param feeCollector_ The address of the fee collector
function setFeeCollector(address feeCollector_) external;

/// @notice Sets the address of the gas oracle to be used for estimating the verification fees.
/// @dev Could be only called by the owner. Will revert if the gas oracle is not a contract.
/// @param gasOracle_ The address of the gas oracle contract
function setGasOracle(address gasOracle_) external;

// ══════════════════════════════════════════════ PERMISSIONLESS ═══════════════════════════════════════════════════

/// @notice Verifies an entry using a set of verifier signatures.
/// If the threshold is met, the entry will be marked as verified in the Interchain DataBase.
/// @dev List of recovered signers from the signatures must be sorted in the ascending order.
/// @param encodedEntry The encoded entry to verify
/// @param signatures Signatures used to verify the entry, concatenated
function verifyEntry(bytes calldata encodedEntry, bytes calldata signatures) external;

// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════

/// @notice Returns the address of the fee collector for the module.
function feeCollector() external view returns (address);

/// @notice Returns the address of the gas oracle used for estimating the verification fees.
function gasOracle() external view returns (address);

/// @notice Returns the list of verifiers for the module.
function getVerifiers() external view returns (address[] memory);

/// @notice Gets the threshold of the module.
/// This is the minimum number of signatures required for verification.
function getThreshold() external view returns (uint256);

/// @notice Checks if the given account is a verifier for the module.
function isVerifier(address account) external view returns (bool);
}
21 changes: 16 additions & 5 deletions packages/contracts-communication/contracts/libs/ThresholdECDSA.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ library ThresholdECDSALib {
using EnumerableSet for EnumerableSet.AddressSet;

error ThresholdECDSA__AlreadySigner(address account);
error ThresholdECDSA__IncorrectSignaturesLength(uint256 length);
error ThresholdECDSA__InvalidSignature(bytes signature);
error ThresholdECDSA__NotEnoughSignatures(uint256 threshold);
error ThresholdECDSA__NotSigner(address account);
error ThresholdECDSA__RecoveredSignersNotSorted();
error ThresholdECDSA__ZeroThreshold();

uint256 private constant SIGNATURE_LENGTH = 65;

/// @notice Adds a new signer to the list of signers.
/// @dev Will revert if the account is already a signer.
function addSigner(ThresholdECDSA storage self, address account) internal {
Expand Down Expand Up @@ -69,21 +72,28 @@ library ThresholdECDSALib {
/// - Any of the payloads is not a valid signature payload.
/// - The number of signatures is less than the threshold.
/// - The recovered list of signers is not sorted in the ascending order.
function verifySignedHash(ThresholdECDSA storage self, bytes32 hash, bytes[] memory signatures) internal view {
function verifySignedHash(ThresholdECDSA storage self, bytes32 hash, bytes calldata signatures) internal view {
// Figure out the signaturesAmount of signatures provided
uint256 signaturesAmount = signatures.length / SIGNATURE_LENGTH;
if (signaturesAmount == 0 || signaturesAmount * SIGNATURE_LENGTH != signatures.length) {
revert ThresholdECDSA__IncorrectSignaturesLength(signatures.length);
}
// First, check that threshold is configured and enough signatures are provided
uint256 threshold = self._threshold;
if (threshold == 0) {
revert ThresholdECDSA__ZeroThreshold();
}
if (signatures.length < threshold) {
if (signaturesAmount < threshold) {
revert ThresholdECDSA__NotEnoughSignatures(threshold);
}
uint256 offset = 0;
uint256 validSignatures = 0;
address lastSigner = address(0);
for (uint256 i = 0; i < signatures.length; ++i) {
(address recovered, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signatures[i]);
for (uint256 i = 0; i < signaturesAmount; ++i) {
bytes memory signature = signatures[offset:offset + SIGNATURE_LENGTH];
(address recovered, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signature);
if (error != ECDSA.RecoverError.NoError) {
revert ThresholdECDSA__InvalidSignature(signatures[i]);
revert ThresholdECDSA__InvalidSignature(signature);
}
// Check that the recovered addresses list is strictly increasing
if (recovered <= lastSigner) {
Expand All @@ -94,6 +104,7 @@ library ThresholdECDSALib {
if (isSigner(self, recovered)) {
validSignatures += 1;
}
offset += SIGNATURE_LENGTH;
}
if (validSignatures < threshold) {
revert ThresholdECDSA__NotEnoughSignatures(threshold);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {InterchainModuleEvents} from "../events/InterchainModuleEvents.sol";
import {IInterchainDB} from "../interfaces/IInterchainDB.sol";
import {IInterchainModule} from "../interfaces/IInterchainModule.sol";

import {InterchainEntry} from "../libs/InterchainEntry.sol";

import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

/// @notice Common logic for all Interchain Modules.
abstract contract InterchainModule is InterchainModuleEvents, IInterchainModule {
address public immutable INTERCHAIN_DB;

constructor(address interchainDB) {
INTERCHAIN_DB = interchainDB;
}

/// @inheritdoc IInterchainModule
function requestVerification(uint256 destChainId, InterchainEntry memory entry) external payable {
if (msg.sender != INTERCHAIN_DB) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that if interchainDB address ever changes, all modules will need to re-deploy

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the intended behavior imo, as the module should be compatible only with the latest InterchainDB contract.

In the unlikely event that InterchainDB needs to be updated and redeployed, its design shouldn't be constrained by the existing DB <> Module interaction workflow.

revert InterchainModule__NotInterchainDB();
}
if (destChainId == block.chainid) {
revert InterchainModule__SameChainId();
}
if (entry.srcChainId != block.chainid) {
revert InterchainModule__IncorrectSourceChainId({chainId: entry.srcChainId});
}
uint256 requiredFee = _getModuleFee(destChainId);
if (msg.value < requiredFee) {
revert InterchainModule__InsufficientFee({actual: msg.value, required: requiredFee});
}
bytes memory encodedEntry = abi.encode(entry);
bytes32 ethSignedEntryHash = MessageHashUtils.toEthSignedMessageHash(keccak256(encodedEntry));
_requestVerification(destChainId, encodedEntry);
emit VerificationRequested(destChainId, encodedEntry, ethSignedEntryHash);
}

/// @inheritdoc IInterchainModule
function getModuleFee(uint256 destChainId) external view returns (uint256) {
return _getModuleFee(destChainId);
}

/// @dev Should be called once the Module has verified the entry and needs to signal this
/// to the InterchainDB.
function _verifyEntry(bytes memory encodedEntry) internal {
InterchainEntry memory entry = abi.decode(encodedEntry, (InterchainEntry));
if (entry.srcChainId == block.chainid) {
revert InterchainModule__SameChainId();
}
IInterchainDB(INTERCHAIN_DB).verifyEntry(entry);
emit EntryVerified(entry);
}

/// @dev Internal logic to request the verification of an entry on the destination chain.
function _requestVerification(uint256 destChainId, bytes memory encodedEntry) internal virtual;

/// @dev Internal logic to get the module fee for verifying an entry on the specified destination chain.
function _getModuleFee(uint256 destChainId) internal view virtual returns (uint256);
}

This file was deleted.

Loading
Loading