From eceeec84b353aa2cd6ca189ff810b94cedf939ed Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 3 May 2024 23:14:03 +0100 Subject: [PATCH 1/9] docs: SynapseExecutionServiceV1 --- .../execution/SynapseExecutionServiceV1.sol | 57 +++++++++++++++---- .../interfaces/IExecutionService.sol | 18 ------ .../interfaces/ISynapseExecutionServiceV1.sol | 17 ------ 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol b/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol index e525de9eb6..0556e097f8 100644 --- a/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol +++ b/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol @@ -24,12 +24,18 @@ contract SynapseExecutionServiceV1 is uint256 claimerFraction; } - // keccak256(abi.encode(uint256(keccak256("Synapse.ExecutionService.V1")) - 1)) & ~bytes32(uint256(0xff)); + /// @dev The storage location of the SynapseExecutionServiceV1Storage struct as per ERC-7201. + /// keccak256(abi.encode(uint256(keccak256("Synapse.ExecutionService.V1")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant SYNAPSE_EXECUTION_SERVICE_V1_STORAGE_LOCATION = 0xabc861e0f8da03757893d41bb54770e6953c799ce2884f80d6b14b66ba8e3100; + /// @dev Precision for the markup math. uint256 private constant WAD = 10 ** 18; + /// @notice Role responsible for managing the SynapseExecutionService contract. + /// Can set all the parameters defined in the SynapseExecutionServiceV1Storage struct. bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); + /// @notice Role to track the Interchain Client contracts. + /// Can request the execution of transactions by calling the requestTxExecution function. bytes32 public constant IC_CLIENT_ROLE = keccak256("IC_CLIENT_ROLE"); constructor() { @@ -37,11 +43,16 @@ contract SynapseExecutionServiceV1 is _disableInitializers(); } - function initialize(address admin) external virtual initializer { + /// @notice Initializes the SynapseExecutionService contract by setting the initial admin. + /// @dev Needs to be called atomically after the proxy is deployed. + function initialize(address admin) external initializer { _grantRole(DEFAULT_ADMIN_ROLE, admin); } - /// @inheritdoc ISynapseExecutionServiceV1 + /// @notice Sets the fraction of the accumulated fees to be paid to caller of `claimFees`. + /// This encourages rational actors to call the function as soon as claim fee is higher than the gas cost. + /// @dev Could be only called by the governor. The fraction could not exceed 1% (1e16). + /// @param claimerFraction_ The fraction of the fees to be paid to the claimer (100% = 1e18) function setClaimerFraction(uint256 claimerFraction_) external virtual onlyRole(GOVERNOR_ROLE) { if (claimerFraction_ > MAX_CLAIMER_FRACTION) { revert ClaimableFees__ClaimerFractionAboveMax(claimerFraction_, MAX_CLAIMER_FRACTION); @@ -51,7 +62,10 @@ contract SynapseExecutionServiceV1 is emit ClaimerFractionSet(claimerFraction_); } - /// @inheritdoc ISynapseExecutionServiceV1 + /// @notice Allows the contract governor to set the address of the EOA account that will be used + /// to execute transactions on the remote chains. This address will also be used as the recipient + /// of the execution fees collected by the contract. + /// @dev Could be only called by the governor. Will revert if the zero address is passed. function setExecutorEOA(address executorEOA_) external virtual onlyRole(GOVERNOR_ROLE) { if (executorEOA_ == address(0)) { revert SynapseExecutionService__ExecutorZeroAddress(); @@ -62,7 +76,9 @@ contract SynapseExecutionServiceV1 is emit FeeRecipientSet(executorEOA_); } - /// @inheritdoc ISynapseExecutionServiceV1 + /// @notice Allows the contract governor to set the address of the gas oracle. The gas oracle + /// is used for estimating the gas cost of the transactions. + /// @dev Could be only called by the governor. Will revert if the passed address is not a contract. function setGasOracle(address gasOracle_) external virtual onlyRole(GOVERNOR_ROLE) { if (gasOracle_.code.length == 0) { revert SynapseExecutionService__GasOracleNotContract(gasOracle_); @@ -72,14 +88,28 @@ contract SynapseExecutionServiceV1 is emit GasOracleSet(gasOracle_); } - /// @inheritdoc ISynapseExecutionServiceV1 + /// @notice Allows the contract governor to set the global markup that the Execution Service charges + /// on top of the GasOracle's gas cost estimates. + /// Zero markup means that the Execution Service charges the exact gas cost estimated by the GasOracle. + /// The markup is denominated in Wei, 1e18 being 100%. + /// @dev Could be only called by the governor. function setGlobalMarkup(uint256 globalMarkup_) external virtual onlyRole(GOVERNOR_ROLE) { SynapseExecutionServiceV1Storage storage $ = _getSynapseExecutionServiceV1Storage(); $.globalMarkup = globalMarkup_; emit GlobalMarkupSet(globalMarkup_); } - /// @inheritdoc IExecutionService + /// @notice Request the execution of an Interchain Transaction on a remote chain in exchange for + /// the execution fee, attached to the transaction as `msg.value`. + /// Note: the off-chain actor needs to fetch the transaction payload from the InterchainClient + /// event with the same transactionId, then execute the transaction on the remote chain: + /// `dstInterchainClient.executeTransaction(transactionPayload)` + /// @dev Could only be called by `InterchainClient` contracts. + /// Will revert if the execution fee is not big enough. + /// @param dstChainId The chain id of the destination chain. + /// @param txPayloadSize The size of the transaction payload to use for the execution. + /// @param transactionId The id of the transaction to execute. + /// @param options The options to use for the execution. function requestTxExecution( uint64 dstChainId, uint256 txPayloadSize, @@ -98,7 +128,10 @@ contract SynapseExecutionServiceV1 is emit ExecutionRequested({transactionId: transactionId, client: msg.sender, executionFee: msg.value}); } - /// @inheritdoc IExecutionService + /// @notice Get the execution fee for executing an Interchain Transaction on a remote chain. + /// @param dstChainId The chain id of the destination chain. + /// @param txPayloadSize The size of the transaction payload to use for the execution. + /// @param options The options to use for the execution. function getExecutionFee( uint64 dstChainId, uint256 txPayloadSize, @@ -134,19 +167,21 @@ contract SynapseExecutionServiceV1 is executionFee += executionFee * globalMarkup() / WAD; } - /// @inheritdoc IExecutionService + /// @notice Address of the EOA account that will be used to execute transactions on the remote chains. function executorEOA() public view virtual returns (address) { SynapseExecutionServiceV1Storage storage $ = _getSynapseExecutionServiceV1Storage(); return $.executorEOA; } - /// @inheritdoc ISynapseExecutionServiceV1 + /// @notice Address of the gas oracle used for estimating the gas cost of the transactions. function gasOracle() public view virtual returns (address) { SynapseExecutionServiceV1Storage storage $ = _getSynapseExecutionServiceV1Storage(); return $.gasOracle; } - /// @inheritdoc ISynapseExecutionServiceV1 + /// @notice The markup that the Execution Service charges on top of the GasOracle's gas cost estimates. + /// Zero markup means that the Execution Service charges the exact gas cost estimated by the GasOracle. + /// The markup is denominated in Wei, 1e18 being 100%. function globalMarkup() public view virtual returns (uint256) { SynapseExecutionServiceV1Storage storage $ = _getSynapseExecutionServiceV1Storage(); return $.globalMarkup; diff --git a/packages/contracts-communication/contracts/interfaces/IExecutionService.sol b/packages/contracts-communication/contracts/interfaces/IExecutionService.sol index 307fd39f52..56fc97673f 100644 --- a/packages/contracts-communication/contracts/interfaces/IExecutionService.sol +++ b/packages/contracts-communication/contracts/interfaces/IExecutionService.sol @@ -2,17 +2,6 @@ pragma solidity ^0.8.0; interface IExecutionService { - /// @notice Request the execution of an Interchain Transaction on a remote chain in exchange for - /// the execution fee, attached to the transaction as `msg.value`. - /// Note: the off-chain actor needs to fetch the transaction payload from the InterchainClient - /// event with the same transactionId, then execute the transaction on the remote chain: - /// `dstInterchainClient.executeTransaction(transactionPayload)` - /// @dev Could only be called by `InterchainClient` contracts. - /// Will revert if the execution fee is not big enough. - /// @param dstChainId The chain id of the destination chain. - /// @param txPayloadSize The size of the transaction payload to use for the execution. - /// @param transactionId The id of the transaction to execute. - /// @param options The options to use for the execution. function requestTxExecution( uint64 dstChainId, uint256 txPayloadSize, @@ -22,14 +11,7 @@ interface IExecutionService { external payable; - /// @notice Get the address of the EOA account that will be used to execute transactions on the - /// remote chains. function executorEOA() external view returns (address); - - /// @notice Get the execution fee for executing an Interchain Transaction on a remote chain. - /// @param dstChainId The chain id of the destination chain. - /// @param txPayloadSize The size of the transaction payload to use for the execution. - /// @param options The options to use for the execution. function getExecutionFee( uint64 dstChainId, uint256 txPayloadSize, diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol b/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol index f4aca51f7a..b6b47e7468 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol @@ -10,28 +10,11 @@ interface ISynapseExecutionServiceV1 is IExecutionService { error SynapseExecutionService__GasOracleZeroAddress(); error SynapseExecutionService__OptionsVersionNotSupported(uint16 version); - /// @notice Sets the fraction of the accumulated fees to be paid to caller of `claimFees`. - /// This encourages rational actors to call the function as soon as claim fee is higher than the gas cost. - /// @dev Could be only called by the owner. Could not exceed 1%. - /// @param claimerFraction The fraction of the fees to be paid to the claimer (100% = 1e18) function setClaimerFraction(uint256 claimerFraction) external; - - /// @notice Allows the contract governor to set the address of the EOA account that will be used - /// to execute transactions on the remote chains. function setExecutorEOA(address executorEOA_) external; - - /// @notice Allows the contract governor to set the address of the gas oracle. function setGasOracle(address gasOracle_) external; - - /// @notice Allows the contract governor to set the global markup that the Execution Service charges - /// on top of the GasOracle's gas cost estimates. function setGlobalMarkup(uint256 globalMarkup_) external; - /// @notice Address of the gas oracle used for estimating the gas cost of the transactions. function gasOracle() external view returns (address); - - /// @notice The markup that the Execution Service charges on top of the GasOracle's gas cost estimates. - /// Zero markup means that the Execution Service charges the exact gas cost estimated by the GasOracle. - /// The markup is denominated in Wei, 1e18 being 100%. function globalMarkup() external view returns (uint256); } From e42c5ec42a034998bd393b26104de4ffc1ff8bbc Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 3 May 2024 23:22:54 +0100 Subject: [PATCH 2/9] docs: SynapseGasOracleV1 --- .../contracts/interfaces/IGasOracle.sol | 17 ----- .../interfaces/ISynapseGasOracle.sol | 5 -- .../interfaces/ISynapseGasOracleV1.sol | 32 --------- .../contracts/oracles/SynapseGasOracleV1.sol | 67 +++++++++++++++---- 4 files changed, 54 insertions(+), 67 deletions(-) diff --git a/packages/contracts-communication/contracts/interfaces/IGasOracle.sol b/packages/contracts-communication/contracts/interfaces/IGasOracle.sol index dbe37cfda3..332d6f3aab 100644 --- a/packages/contracts-communication/contracts/interfaces/IGasOracle.sol +++ b/packages/contracts-communication/contracts/interfaces/IGasOracle.sol @@ -2,18 +2,8 @@ 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(uint64 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( uint64 remoteChainId, uint256 gasLimit, @@ -22,13 +12,6 @@ interface IGasOracle { 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( uint64 remoteChainId, uint256 gasLimit, diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol index 0d0b3b9237..45782242cc 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol @@ -4,12 +4,7 @@ pragma solidity ^0.8.0; import {IGasOracle} from "./IGasOracle.sol"; interface ISynapseGasOracle is IGasOracle { - /// @notice Allows Synapse Module to pass the gas data from a remote chain to the Gas Oracle. - /// @dev Could only be called by Synapse Module. - /// @param srcChainId The chain id of the remote chain. - /// @param data The gas data from the remote chain. function receiveRemoteGasData(uint64 srcChainId, bytes calldata data) external; - /// @notice Gets the gas data for the local chain. function getLocalGasData() external view returns (bytes memory); } diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol index 1b783f058e..c66019c89a 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol @@ -18,44 +18,12 @@ interface ISynapseGasOracleV1 is ISynapseGasOracle { error SynapseGasOracleV1__NativePriceNotSet(uint64 chainId); error SynapseGasOracleV1__NativePriceZero(); - /// @notice Allows the contract owner to set the native token price of the local chain. - /// @dev Could only be called by the contract owner. Will revert if the native token price is 0. - /// @param nativePrice The price of the local chain's native token in Ethereum Mainnet's wei. function setLocalNativePrice(uint256 nativePrice) external; - - /// @notice Allows the contract owner to set the gas data for a remote chain. - /// @dev Could only be called by the contract owner. - /// Will revert if the native token price is 0, or if the chain id is not a remote chain id. - /// @param chainId The chain id of the remote chain. - /// @param data The gas data for the remote chain. function setRemoteGasData(uint64 chainId, RemoteGasData memory data) external; - - /// @notice Allows the contract owner to set the price of remote chain's calldata. - /// @dev Could only be called by the contract owner. - /// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0. - /// @param chainId The chain id of the remote chain. - /// @param calldataPrice The price of 1 byte of calldata in the remote chain's wei. function setRemoteCallDataPrice(uint64 chainId, uint256 calldataPrice) external; - - /// @notice Allows the contract owner to set the gas price of the remote chain. - /// @dev Could only be called by the contract owner. - /// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0. - /// @param chainId The chain id of the remote chain. - /// @param gasPrice The gas price of the remote chain, in remote chain's wei. function setRemoteGasPrice(uint64 chainId, uint256 gasPrice) external; - - /// @notice Allows the contract owner to set the price of the remote chain's native token. - /// @dev Could only be called by the contract owner. - /// Will revert if the chain id is not a remote chain id, or if the price is 0. - /// @param chainId The chain id of the remote chain. - /// @param nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei. function setRemoteNativePrice(uint64 chainId, uint256 nativePrice) external; - /// @notice Gets the price of the local chain's native token in Ethereum Mainnet's wei. function getLocalNativePrice() external view returns (uint256); - - /// @notice Gets the gas data for a remote chain. - /// @dev Will revert if the chain id is not a remote chain id. - /// @param chainId The chain id of the remote chain. function getRemoteGasData(uint64 chainId) external view returns (RemoteGasData memory); } diff --git a/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol b/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol index 10820c3a30..fdc1ba8a25 100644 --- a/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol +++ b/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol @@ -9,7 +9,12 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOracleV1 { + /// @dev Price of the local chain's native token, expressed in Ethereum Mainnet's wei. uint256 internal _localNativePrice; + /// @dev Gas data for tracked remote chains: + /// - calldataPrice The price of 1 byte of calldata in the remote chain's wei. + /// - gasPrice The gas price of the remote chain, in remote chain's wei. + /// - nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei. mapping(uint64 chainId => RemoteGasData data) internal _remoteGasData; /// @dev Checks that the chain ID is not the local chain ID. @@ -40,7 +45,9 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra // ════════════════════════════════════════════════ ONLY OWNER ═════════════════════════════════════════════════════ - /// @inheritdoc ISynapseGasOracleV1 + /// @notice Allows the contract owner to set the native token price of the local chain. + /// @dev Could only be called by the contract owner. Will revert if the native token price is 0. + /// @param nativePrice The price of the local chain's native token in Ethereum Mainnet's wei. function setLocalNativePrice(uint256 nativePrice) external onlyOwner onlyNonZeroNativePrice(nativePrice) { if (_localNativePrice != nativePrice) { _localNativePrice = nativePrice; @@ -48,7 +55,11 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra } } - /// @inheritdoc ISynapseGasOracleV1 + /// @notice Allows the contract owner to set the gas data for a remote chain. + /// @dev Could only be called by the contract owner. + /// Will revert if the native token price is 0, or if the chain id is not a remote chain id. + /// @param chainId The chain id of the remote chain. + /// @param data The gas data for the remote chain. function setRemoteGasData( uint64 chainId, RemoteGasData memory data @@ -63,7 +74,11 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra _setRemoteNativePrice(chainId, data.nativePrice); } - /// @inheritdoc ISynapseGasOracleV1 + /// @notice Allows the contract owner to set the price of remote chain's calldata. + /// @dev Could only be called by the contract owner. + /// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0. + /// @param chainId The chain id of the remote chain. + /// @param calldataPrice The price of 1 byte of calldata in the remote chain's wei. function setRemoteCallDataPrice( uint64 chainId, uint256 calldataPrice @@ -76,7 +91,11 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra _setRemoteCallDataPrice(chainId, calldataPrice); } - /// @inheritdoc ISynapseGasOracleV1 + /// @notice Allows the contract owner to set the gas price of the remote chain. + /// @dev Could only be called by the contract owner. + /// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0. + /// @param chainId The chain id of the remote chain. + /// @param gasPrice The gas price of the remote chain, in remote chain's wei. function setRemoteGasPrice( uint64 chainId, uint256 gasPrice @@ -89,7 +108,11 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra _setRemoteGasPrice(chainId, gasPrice); } - /// @inheritdoc ISynapseGasOracleV1 + /// @notice Allows the contract owner to set the price of the remote chain's native token. + /// @dev Could only be called by the contract owner. + /// Will revert if the chain id is not a remote chain id, or if the price is 0. + /// @param chainId The chain id of the remote chain. + /// @param nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei. function setRemoteNativePrice( uint64 chainId, uint256 nativePrice @@ -105,20 +128,26 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra // ════════════════════════════════════════════════ ONLY MODULE ════════════════════════════════════════════════════ // solhint-disable no-empty-blocks - /// @inheritdoc ISynapseGasOracle + /// @notice Allows Synapse Module to pass the gas data from a remote chain to the Gas Oracle. + /// @dev Could only be called by Synapse Module. + /// @param srcChainId The chain id of the remote chain. + /// @param data The gas data from the remote chain. function receiveRemoteGasData(uint64 srcChainId, bytes calldata data) external { // The V1 version has this function as a no-op, hence we skip the permission check. } // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @inheritdoc ISynapseGasOracle + /// @notice Gets the gas data for the local chain. function getLocalGasData() external view returns (bytes memory) { // The V1 version has this function as a no-op. } // solhint-enable no-empty-blocks - /// @inheritdoc 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( uint64 remoteChainId, uint256 value @@ -133,7 +162,12 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra return _convertRemoteValueToLocalUnits(remoteChainId, value); } - /// @inheritdoc IGasOracle + /// @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( uint64 remoteChainId, uint256 gasLimit, @@ -150,7 +184,12 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra return _convertRemoteValueToLocalUnits(remoteChainId, remoteTxCost); } - /// @inheritdoc IGasOracle + /// @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( uint64 remoteChainId, uint256 gasLimit, @@ -166,13 +205,15 @@ contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOra return _estimateTxCostInRemoteUnits(remoteChainId, gasLimit, calldataSize); } - /// @inheritdoc ISynapseGasOracleV1 + /// @notice Gets the price of the local chain's native token in Ethereum Mainnet's wei. function getLocalNativePrice() external view returns (uint256) { return _localNativePrice; } - /// @inheritdoc ISynapseGasOracleV1 - function getRemoteGasData(uint64 chainId) external view returns (RemoteGasData memory) { + /// @notice Gets the gas data for a remote chain. + /// @dev Will revert if the chain id is not a remote chain id. + /// @param chainId The chain id of the remote chain. + function getRemoteGasData(uint64 chainId) external view onlyRemoteChainId(chainId) returns (RemoteGasData memory) { return _remoteGasData[chainId]; } From 843213cbbfc671d55b9b9bff8efa4bd65a2c3481 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Fri, 3 May 2024 23:35:04 +0100 Subject: [PATCH 3/9] docs: Interchain app templates --- .../contracts/apps/AbstractICApp.sol | 16 ++++- .../contracts/apps/ICAppV1.sol | 59 ++++++++++++++----- .../contracts/interfaces/IInterchainApp.sol | 14 ----- .../contracts/interfaces/IInterchainAppV1.sol | 51 ---------------- 4 files changed, 57 insertions(+), 83 deletions(-) diff --git a/packages/contracts-communication/contracts/apps/AbstractICApp.sol b/packages/contracts-communication/contracts/apps/AbstractICApp.sol index fbed689c9f..358c3d641a 100644 --- a/packages/contracts-communication/contracts/apps/AbstractICApp.sol +++ b/packages/contracts-communication/contracts/apps/AbstractICApp.sol @@ -21,7 +21,13 @@ abstract contract AbstractICApp is AbstractICAppEvents, IInterchainApp { error InterchainApp__ReceiverZeroAddress(uint64 chainId); error InterchainApp__SrcSenderNotAllowed(uint64 srcChainId, bytes32 sender); - /// @inheritdoc IInterchainApp + /// @notice Allows the Interchain Client to pass the message to the Interchain App. + /// @dev App is responsible for keeping track of interchain clients, and must verify the message sender. + /// @param srcChainId Chain ID of the source chain, where the message was sent from. + /// @param sender Sender address on the source chain, as a bytes32 value. + /// @param dbNonce The Interchain DB nonce of the batch containing the message entry. + /// @param entryIndex The index of the message entry within the batch. + /// @param message The message being sent. function appReceive( uint64 srcChainId, bytes32 sender, @@ -44,7 +50,13 @@ abstract contract AbstractICApp is AbstractICAppEvents, IInterchainApp { _receiveMessage(srcChainId, sender, dbNonce, entryIndex, message); } - /// @inheritdoc IInterchainApp + /// @notice Returns the verification configuration of the Interchain App. + /// @dev This configuration is used by the Interchain Client to verify that message has been confirmed + /// by the Interchain Modules on the destination chain. + /// Note: V1 version of AppConfig includes the required responses count, and optimistic period after which + /// the message is considered confirmed by the module. Following versions may include additional fields. + /// @return appConfig The versioned configuration of the Interchain App, encoded as bytes. + /// @return modules The list of Interchain Modules that app is trusting to confirm the messages. function getReceivingConfig() external view returns (bytes memory appConfig, address[] memory modules) { appConfig = _getAppConfig(); modules = _getModules(); diff --git a/packages/contracts-communication/contracts/apps/ICAppV1.sol b/packages/contracts-communication/contracts/apps/ICAppV1.sol index b5cc0bede7..970392a636 100644 --- a/packages/contracts-communication/contracts/apps/ICAppV1.sol +++ b/packages/contracts-communication/contracts/apps/ICAppV1.sol @@ -41,32 +41,48 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA _grantRole(DEFAULT_ADMIN_ROLE, admin); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to add the interchain client to the allowed clients set, + /// and optionally set the latest client to this one. + /// Note: only the allowed clients can send messages to this app. + /// Note: the latest client is used for sending messages from this app. + /// @param client The address of the interchain client to add. + /// @param updateLatest Whether to set the latest client to this one. function addInterchainClient(address client, bool updateLatest) external onlyRole(IC_GOVERNOR_ROLE) { _addClient(client, updateLatest); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to remove the interchain client from the allowed clients set. + /// If the client is the latest client, the latest client is set to the zero address. + /// @param client The address of the interchain client to remove. function removeInterchainClient(address client) external onlyRole(IC_GOVERNOR_ROLE) { _removeClient(client); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to set the address of the latest interchain client. + /// @dev The new latest client must be an allowed client or zero address. + /// Setting the client to zero address effectively pauses the app ability to send messages, + /// while allowing to receive them. + /// @param client The address of the latest interchain client. function setLatestInterchainClient(address client) external onlyRole(IC_GOVERNOR_ROLE) { _setLatestClient(client); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to link the remote app for the given chain ID. + /// - This address will be used as the receiver for the messages sent from this chain. + /// - This address will be the only trusted sender for the messages sent to this chain. + /// @param chainId The remote chain ID. + /// @param remoteApp The address of the remote app to link. function linkRemoteApp(uint64 chainId, bytes32 remoteApp) external onlyRole(IC_GOVERNOR_ROLE) { _linkRemoteApp(chainId, remoteApp); } - /// @inheritdoc IInterchainAppV1 + /// @notice Thin wrapper for `linkRemoteApp` to accept EVM address as a parameter. function linkRemoteAppEVM(uint64 chainId, address remoteApp) external onlyRole(IC_GOVERNOR_ROLE) { _linkRemoteApp(chainId, remoteApp.addressToBytes32()); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to add the module to the trusted modules set. + /// - This set of modules will be used to verify both sent and received messages. function addTrustedModule(address module) external onlyRole(IC_GOVERNOR_ROLE) { if (module == address(0)) { revert InterchainApp__ModuleZeroAddress(); @@ -78,7 +94,7 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA emit TrustedModuleAdded(module); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to remove the module from the trusted modules set. function removeTrustedModule(address module) external onlyRole(IC_GOVERNOR_ROLE) { bool removed = _trustedModules.remove(module); if (!removed) { @@ -87,7 +103,9 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA emit TrustedModuleRemoved(module); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to set the app config for the current app. App config includes: + /// - requiredResponses: the number of module responses required for accepting the message + /// - optimisticPeriod: the minimum time after which the module responses are considered final function setAppConfigV1(uint256 requiredResponses, uint256 optimisticPeriod) external onlyRole(IC_GOVERNOR_ROLE) { if (requiredResponses == 0 || optimisticPeriod == 0) { revert InterchainApp__AppConfigInvalid(requiredResponses, optimisticPeriod); @@ -97,7 +115,9 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA emit AppConfigV1Set(requiredResponses, optimisticPeriod); } - /// @inheritdoc IInterchainAppV1 + /// @notice Allows the governor to set the address of the Execution Service. + /// This address will be used to request execution of the messages sent from this chain, + /// by supplying the Service's execution fee. function setExecutionService(address executionService) external onlyRole(IC_GOVERNOR_ROLE) { _executionService = executionService; emit ExecutionServiceSet(executionService); @@ -105,7 +125,11 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @inheritdoc IInterchainAppV1 + /// @notice Returns the app config for the current app: + /// - requiredResponses: the number of module responses required for accepting the message + /// - optimisticPeriod: the minimum time after which the module responses are considered final + /// - guardFlag: the flag indicating the guard type (0 - none, 1 - client's default, 2 - custom) + /// - guard: the address of the guard contract (if the guardFlag is set to 2) function getAppConfigV1() public view returns (AppConfigV1 memory) { (uint8 guardFlag, address guard) = _getGuardConfig(); return AppConfigV1({ @@ -116,28 +140,30 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA }); } - /// @inheritdoc IInterchainAppV1 + /// @notice Returns the address of the Execution Service used by this app for sending messages. // solhint-disable-next-line ordering function getExecutionService() external view returns (address) { return _executionService; } - /// @inheritdoc IInterchainAppV1 + /// @notice Returns the list of Interchain Clients allowed to send messages to this app. function getInterchainClients() external view returns (address[] memory) { return _interchainClients.values(); } - /// @inheritdoc IInterchainAppV1 + /// @notice Returns the address of the latest interchain client. + /// This address is used for sending messages from this app. function getLatestInterchainClient() external view returns (address) { return _latestClient; } - /// @inheritdoc IInterchainAppV1 + /// @notice Returns the linked app address (as bytes32) for the given chain ID. function getLinkedApp(uint64 chainId) external view returns (bytes32) { return _linkedApp[chainId]; } - /// @inheritdoc IInterchainAppV1 + /// @notice Thin wrapper for `getLinkedApp` to return the linked app address as EVM address. + /// @dev Will revert if the linked app address is not an EVM address. function getLinkedAppEVM(uint64 chainId) external view returns (address linkedAppEVM) { bytes32 linkedApp = _linkedApp[chainId]; linkedAppEVM = linkedApp.bytes32ToAddress(); @@ -146,7 +172,8 @@ abstract contract ICAppV1 is AbstractICApp, AccessControlEnumerable, InterchainA } } - /// @inheritdoc IInterchainAppV1 + /// @notice Returns the list of Interchain Modules trusted by this app. + /// This set of modules will be used to verify both sent and received messages. function getModules() external view returns (address[] memory) { return _trustedModules.values(); } diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol b/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol index 05af2ab5ae..6b702b375c 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol @@ -3,13 +3,6 @@ pragma solidity ^0.8.0; /// @notice Minimal interface for the Interchain App to work with the Interchain Client. interface IInterchainApp { - /// @notice Allows the Interchain Client to pass the message to the Interchain App. - /// @dev App is responsible for keeping track of interchain clients, and must verify the message sender. - /// @param srcChainId Chain ID of the source chain, where the message was sent from. - /// @param sender Sender address on the source chain, as a bytes32 value. - /// @param dbNonce The Interchain DB nonce of the batch containing the message entry. - /// @param entryIndex The index of the message entry within the batch. - /// @param message The message being sent. function appReceive( uint64 srcChainId, bytes32 sender, @@ -20,12 +13,5 @@ interface IInterchainApp { external payable; - /// @notice Returns the verification configuration of the Interchain App. - /// @dev This configuration is used by the Interchain Client to verify that message has been confirmed - /// by the Interchain Modules on the destination chain. - /// Note: V1 version of AppConfig includes the required responses count, and optimistic period after which - /// the message is considered confirmed by the module. Following versions may include additional fields. - /// @return appConfig The versioned configuration of the Interchain App, encoded as bytes. - /// @return modules The list of Interchain Modules that app is trusting to confirm the messages. function getReceivingConfig() external view returns (bytes memory appConfig, address[] memory modules); } diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainAppV1.sol b/packages/contracts-communication/contracts/interfaces/IInterchainAppV1.sol index 80590dc25b..a932b3d1f0 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainAppV1.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainAppV1.sol @@ -13,76 +13,25 @@ interface IInterchainAppV1 is IInterchainApp { error InterchainApp__ModuleZeroAddress(); error InterchainApp__RemoteAppZeroAddress(); - /// @notice Allows the owner to add the interchain client to the allowed clients set, - /// and optionally set the latest client to this one. - /// Note: only the allowed clients can send messages to this app. - /// Note: the latest client is used for sending messages from this app. - /// @param client The address of the interchain client to add. - /// @param updateLatest Whether to set the latest client to this one. function addInterchainClient(address client, bool updateLatest) external; - - /// @notice Allows the owner to remove the interchain client from the allowed clients set. - /// If the client is the latest client, the latest client is set to the zero address. - /// @param client The address of the interchain client to remove. function removeInterchainClient(address client) external; - - /// @notice Allows the owner to set the address of the latest interchain client. - /// @dev The new latest client must be an allowed client or zero address. - /// Setting the client to zero address effectively pauses the app ability to send messages, - /// while allowing to receive them. - /// @param client The address of the latest interchain client. function setLatestInterchainClient(address client) external; - /// @notice Allows the owner to link the remote app for the given chain ID. - /// - This address will be used as the receiver for the messages sent from this chain. - /// - This address will be the only trusted sender for the messages sent to this chain. - /// @param chainId The remote chain ID. - /// @param remoteApp The address of the remote app to link. function linkRemoteApp(uint64 chainId, bytes32 remoteApp) external; - - /// @notice Thin wrapper for `linkRemoteApp` to accept EVM address as a parameter. function linkRemoteAppEVM(uint64 chainId, address remoteApp) external; - /// @notice Allows the owner to add the module to the trusted modules set. - /// - This set of modules will be used to verify both sent and received messages. function addTrustedModule(address module) external; - - /// @notice Allows the owner to remove the module from the trusted modules set. function removeTrustedModule(address module) external; - - /// @notice Allows the owner to set the app config for the current app. App config includes: - /// - requiredResponses: the number of module responses required for accepting the message - /// - optimisticPeriod: the minimum time after which the module responses are considered final function setAppConfigV1(uint256 requiredResponses, uint256 optimisticPeriod) external; - - /// @notice Allows the owner to set the address of the Execution Service. - /// This address will be used to request execution of the messages sent from this chain, - /// by supplying the Service's execution fee. function setExecutionService(address executionService) external; // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @notice Returns the app config for the current app: requiredResponses and optimisticPeriod. function getAppConfigV1() external view returns (AppConfigV1 memory); - - /// @notice Returns the address of the Execution Service used by this app for sending messages. function getExecutionService() external view returns (address); - - /// @notice Returns the list of Interchain Clients allowed to send messages to this app. function getInterchainClients() external view returns (address[] memory); - - /// @notice Returns the address of the latest interchain client. - /// This address is used for sending messages from this app. function getLatestInterchainClient() external view returns (address); - - /// @notice Returns the linked app address (as bytes32) for the given chain ID. function getLinkedApp(uint64 chainId) external view returns (bytes32); - - /// @notice Thin wrapper for `getLinkedApp` to return the linked app address as EVM address. - /// @dev Will revert if the linked app address is not an EVM address. function getLinkedAppEVM(uint64 chainId) external view returns (address); - - /// @notice Returns the list of Interchain Modules trusted by this app. - /// This set of modules will be used to verify both sent and received messages. function getModules() external view returns (address[] memory); } From bc88c04933e9979300c51f8b149d3d8bf3b52009 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 4 May 2024 00:02:26 +0100 Subject: [PATCH 4/9] docs: InterchainClientV1 --- .../contracts/InterchainClientV1.sol | 93 +++++++++++++++--- .../interfaces/IInterchainClientV1.sol | 97 +------------------ 2 files changed, 82 insertions(+), 108 deletions(-) diff --git a/packages/contracts-communication/contracts/InterchainClientV1.sol b/packages/contracts-communication/contracts/InterchainClientV1.sol index ee8539432e..80c5d22a48 100644 --- a/packages/contracts-communication/contracts/InterchainClientV1.sol +++ b/packages/contracts-communication/contracts/InterchainClientV1.sol @@ -51,7 +51,10 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli INTERCHAIN_DB = interchainDB; } - // @inheritdoc IInterchainClientV1 + /// @notice Allows the contract owner to set the address of the Guard module. + /// Note: batches marked as invalid by the Guard could not be used for message execution, + /// if the app opts in to use the Guard. + /// @param guard The address of the Guard module. function setDefaultGuard(address guard) external onlyOwner { if (guard == address(0)) { revert InterchainClientV1__GuardZeroAddress(); @@ -60,13 +63,32 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli emit DefaultGuardSet(guard); } - // @inheritdoc IInterchainClientV1 + /// @notice Sets the linked client for a specific chain ID. + /// Note: only Interchain Entries written by the linked client could be used for message execution. + /// @param chainId The chain ID for which the client is being set. + /// @param client The address of the client being linked. function setLinkedClient(uint64 chainId, bytes32 client) external onlyOwner { _linkedClient[chainId] = client; emit LinkedClientSet(chainId, client); } - // @inheritdoc IInterchainClientV1 + /// @notice Sends a message to another chain via the Interchain Communication Protocol. + /// @dev Charges a fee for the message, which is payable upon calling this function: + /// - Verification fees: paid to every module that verifies the message. + /// - Execution fee: paid to the executor that executes the message. + /// Note: while a specific execution service is specified to request the execution of the message, + /// any executor is able to execute the message on destination chain. + /// @param dstChainId The chain ID of the destination chain. + /// @param receiver The address of the receiver on the destination chain. + /// @param srcExecutionService The address of the execution service to use for the message. + /// @param srcModules The source modules involved in the message sending. + /// @param options Execution options for the message sent, encoded as bytes, + /// currently gas limit + native gas drop. + /// @param message The message to be sent. + /// @return desc The descriptor of the sent transaction: + /// - transactionId: the ID of the transaction that was sent. + /// - dbNonce: the database nonce of the batch containing the written entry for transaction. + /// - entryIndex: the index of the written entry for transaction within the batch. function interchainSend( uint64 dstChainId, bytes32 receiver, @@ -82,7 +104,7 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli return _interchainSend(dstChainId, receiver, srcExecutionService, srcModules, options, message); } - // @inheritdoc IInterchainClientV1 + /// @notice A thin wrapper around `interchainSend` that allows to specify the receiver address as an EVM address. function interchainSendEVM( uint64 dstChainId, address receiver, @@ -99,7 +121,15 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli return _interchainSend(dstChainId, receiverBytes32, srcExecutionService, srcModules, options, message); } - // @inheritdoc IInterchainClientV1 + /// @notice Executes a transaction that has been sent via the Interchain Communication Protocol. + /// Note: The transaction must be proven to be included in one of the InterchainDB batches. + /// Note: Transaction data includes the requested gas limit, but the executors could specify a different gas limit. + /// If the specified gas limit is lower than requested, the requested gas limit will be used. + /// Otherwise, the specified gas limit will be used. + /// This allows to execute the transactions with requested gas limit set too low. + /// @param gasLimit The gas limit to use for the execution. + /// @param transaction The transaction data. + /// @param proof The Merkle proof for transaction execution, fetched from the source chain. function interchainExecute( uint256 gasLimit, bytes calldata transaction, @@ -138,7 +168,11 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli ); } - /// @inheritdoc IInterchainClientV1 + /// @notice Writes the proof of execution for a transaction into the InterchainDB. + /// @dev Will revert if the transaction has not been executed. + /// @param transactionId The ID of the transaction to write the proof for. + /// @return dbNonce The database nonce of the batch containing the written proof for transaction. + /// @return entryIndex The index of the written proof for transaction within the batch. function writeExecutionProof(bytes32 transactionId) external returns (uint64 dbNonce, uint64 entryIndex) { address executor = _txExecutor[transactionId]; if (executor == address(0)) { @@ -151,7 +185,14 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - // @inheritdoc IInterchainClientV1 + /// @notice Determines if a transaction meets the criteria to be executed based on: + /// - If approved modules have verified the batch in the InterchainDB + /// - If the threshold of approved modules have been met + /// - If the optimistic window has passed for all modules + /// - If the Guard module (if opted in) has not submitted a batch that conflicts with the approved modules + /// @dev Will revert with a specific error message if the transaction is not executable. + /// @param encodedTx The encoded transaction to check for executable status. + /// @param proof The Merkle proof for the transaction, fetched from the source chain. function isExecutable(bytes calldata encodedTx, bytes32[] calldata proof) external view returns (bool) { InterchainTransaction memory icTx = _assertCorrectTransaction(encodedTx); // Check that options could be decoded @@ -161,7 +202,25 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli return true; } - // @inheritdoc IInterchainClientV1 + /// @notice Returns the readiness status of a transaction to be executed. + /// @dev Some of the possible statuses have additional arguments that are returned: + /// - Ready: the transaction is ready to be executed. + /// - AlreadyExecuted: the transaction has already been executed. + /// - `firstArg` is the transaction ID. + /// - BatchAwaitingResponses: not enough responses have been received for the transaction. + /// - `firstArg` is the number of responses received. + /// - `secondArg` is the number of responses required. + /// - BatchConflict: one of the modules have submitted a conflicting batch. + /// - `firstArg` is the address of the module. + /// - This is either one of the modules that the app trusts, or the Guard module used by the app. + /// - ReceiverNotICApp: the receiver is not an Interchain app. + /// - `firstArg` is the receiver address. + /// - ReceiverZeroRequiredResponses: the app config requires zero responses for the transaction. + /// - TxWrongDstChainId: the destination chain ID does not match the local chain ID. + /// - `firstArg` is the destination chain ID. + /// - UndeterminedRevert: the transaction will revert for another reason. + /// + /// Note: the arguments are abi-encoded bytes32 values (as their types could be different). // solhint-disable-next-line code-complexity function getTxReadinessV1( InterchainTransaction memory icTx, @@ -197,17 +256,22 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli } } - // @inheritdoc IInterchainClientV1 + /// @notice Returns the address of the executor for a transaction that has been sent to the local chain. function getExecutor(bytes calldata encodedTx) external view returns (address) { return _txExecutor[keccak256(encodedTx)]; } - // @inheritdoc IInterchainClientV1 + /// @notice Returns the address of the executor for a transaction that has been sent to the local chain. function getExecutorById(bytes32 transactionId) external view returns (address) { return _txExecutor[transactionId]; } - // @inheritdoc IInterchainClientV1 + /// @notice Returns the fee for sending an Interchain message. + /// @param dstChainId The chain ID of the destination chain. + /// @param srcExecutionService The address of the execution service to use for the message. + /// @param srcModules The source modules involved in the message sending. + /// @param options Execution options for the message sent, currently gas limit + native gas drop. + /// @param messageLen The length of the message being sent. function getInterchainFee( uint64 dstChainId, address srcExecutionService, @@ -232,7 +296,8 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli fee += IExecutionService(srcExecutionService).getExecutionFee(dstChainId, payloadSize, options); } - /// @inheritdoc IInterchainClientV1 + /// @notice Returns the address of the linked client (as bytes32) for a specific chain ID. + /// @dev Will return 0x0 if no client is linked for the chain ID. function getLinkedClient(uint64 chainId) external view returns (bytes32) { if (chainId == block.chainid) { revert InterchainClientV1__ChainIdNotRemote(chainId); @@ -240,7 +305,9 @@ contract InterchainClientV1 is Ownable, InterchainClientV1Events, IInterchainCli return _linkedClient[chainId]; } - /// @inheritdoc IInterchainClientV1 + /// @notice Returns the EVM address of the linked client for a specific chain ID. + /// @dev Will return 0x0 if no client is linked for the chain ID. + /// Will revert if the client is not an EVM client. function getLinkedClientEVM(uint64 chainId) external view returns (address linkedClientEVM) { if (chainId == block.chainid) { revert InterchainClientV1__ChainIdNotRemote(chainId); diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol b/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol index ac7e3eae97..4b5eb12453 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol @@ -33,38 +33,9 @@ interface IInterchainClientV1 { error InterchainClientV1__TxNotExecuted(bytes32 transactionId); error InterchainClientV1__TxVersionMismatch(uint16 txVersion, uint16 required); - /// @notice Allows the contract owner to set the address of the Guard module. - /// Note: batches marked as invalid by the Guard could not be used for message execution, - /// if the app opts in to use the Guard. - /// @param guard_ The address of the Guard module. function setDefaultGuard(address guard_) external; - - /** - * @notice Sets the linked client for a specific chain ID. - * @dev Stores the address of the linked client in a mapping with the chain ID as the key. - * @param chainId The chain ID for which the client is being set. - * @param client The address of the client being linked. - */ function setLinkedClient(uint64 chainId, bytes32 client) external; - /** - * @notice Sends a message to another chain via the Interchain Communication Protocol. - * @dev Charges a fee for the message, which is payable upon calling this function: - * - Verification fees: paid to every module that verifies the message. - * - Execution fee: paid to the executor that executes the message. - * Note: while a specific execution service is specified to request the execution of the message, - * any executor is able to execute the message on destination chain, earning the execution fee. - * @param dstChainId The chain ID of the destination chain. - * @param receiver The address of the receiver on the destination chain. - * @param srcExecutionService The address of the execution service to use for the message. - * @param srcModules The source modules involved in the message sending. - * @param options Execution options for the message sent, encoded as bytes, currently gas limit + native gas drop. - * @param message The message being sent. - * @return desc The descriptor of the sent transaction: - * - transactionId: the ID of the transaction that was sent. - * - dbNonce: the database nonce of the batch containing the written entry for transaction. - * - entryIndex: the index of the written entry for transaction within the batch. - */ function interchainSend( uint64 dstChainId, bytes32 receiver, @@ -76,7 +47,6 @@ interface IInterchainClientV1 { external payable returns (InterchainTxDescriptor memory desc); - function interchainSendEVM( uint64 dstChainId, address receiver, @@ -88,18 +58,6 @@ interface IInterchainClientV1 { external payable returns (InterchainTxDescriptor memory desc); - - /** - * @notice Executes a transaction that has been sent via the Interchain. - * @dev The transaction must have been previously sent and recorded. - * Transaction data includes the requested gas limit, but the executors could specify a different gas limit. - * If the specified gas limit is lower than requested, the requested gas limit will be used. - * Otherwise, the specified gas limit will be used. - * This allows to execute the transactions with requested gas limit set too low. - * @param gasLimit The gas limit to use for the execution. - * @param transaction The transaction data. - * @param proof The Merkle proof for transaction execution, fetched from the source chain. - */ function interchainExecute( uint256 gasLimit, bytes calldata transaction, @@ -107,45 +65,11 @@ interface IInterchainClientV1 { ) external payable; - - /// @notice Writes the proof of execution for a transaction into the InterchainDB. - /// @dev Will revert if the transaction has not been executed. - /// @param transactionId The ID of the transaction to write the proof for. - /// @return dbNonce The database nonce of the batch containing the written proof for transaction. - /// @return entryIndex The index of the written proof for transaction within the batch. function writeExecutionProof(bytes32 transactionId) external returns (uint64 dbNonce, uint64 entryIndex); - /** - * @notice Checks if a transaction is executable. - * @dev Determines if a transaction meets the criteria to be executed based on: - * - If approved modules have written to the InterchainDB - * - If the threshold of approved modules have been met - * - If the optimistic window has passed for all modules - * @param transaction The InterchainTransaction struct to be checked. - * @param proof The Merkle proof for transaction execution, fetched from the source chain. - * @return bool Returns true if the transaction is executable, false otherwise. - */ - function isExecutable(bytes calldata transaction, bytes32[] calldata proof) external view returns (bool); + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @notice Returns the readiness status of a transaction to be executed. - /// @dev Some of the possible statuses have additional arguments that are returned: - /// - Ready: the transaction is ready to be executed. - /// - AlreadyExecuted: the transaction has already been executed. - /// - `firstArg` is the transaction ID. - /// - BatchAwaitingResponses: not enough responses have been received for the transaction. - /// - `firstArg` is the number of responses received. - /// - `secondArg` is the number of responses required. - /// - BatchConflict: one of the modules have submitted a conflicting batch. - /// - `firstArg` is the address of the module. - /// - This is either one of the modules that the app trusts, or the Guard module used by the app. - /// - ReceiverNotICApp: the receiver is not an Interchain app. - /// - `firstArg` is the receiver address. - /// - ReceiverZeroRequiredResponses: the app config requires zero responses for the transaction. - /// - TxWrongDstChainId: the destination chain ID does not match the local chain ID. - /// - `firstArg` is the destination chain ID. - /// - UndeterminedRevert: the transaction will revert for another reason. - /// - /// Note: the arguments are abi-encoded bytes32 values (as their types could be different). + function isExecutable(bytes calldata transaction, bytes32[] calldata proof) external view returns (bool); function getTxReadinessV1( InterchainTransaction memory icTx, bytes32[] calldata proof @@ -153,13 +77,6 @@ interface IInterchainClientV1 { external view returns (TxReadiness status, bytes32 firstArg, bytes32 secondArg); - - /// @notice Returns the fee for sending an Interchain message. - /// @param dstChainId The chain ID of the destination chain. - /// @param srcExecutionService The address of the execution service to use for the message. - /// @param srcModules The source modules involved in the message sending. - /// @param options Execution options for the message sent, currently gas limit + native gas drop. - /// @param messageLen The length of the message being sent. function getInterchainFee( uint64 dstChainId, address srcExecutionService, @@ -171,18 +88,8 @@ interface IInterchainClientV1 { view returns (uint256); - /// @notice Returns the address of the executor for a transaction that has been sent to the local chain. function getExecutor(bytes calldata transaction) external view returns (address); - - /// @notice Returns the address of the executor for a transaction that has been sent to the local chain. function getExecutorById(bytes32 transactionId) external view returns (address); - - /// @notice Returns the address of the linked client (as bytes32) for a specific chain ID. - /// @dev Will return 0x0 if no client is linked for the chain ID. function getLinkedClient(uint64 chainId) external view returns (bytes32); - - /// @notice Returns the EVM address of the linked client for a specific chain ID. - /// @dev Will return 0x0 if no client is linked for the chain ID. - /// Will revert if the client is not an EVM client. function getLinkedClientEVM(uint64 chainId) external view returns (address); } From 3cd7130e3fa12fb1e97293b04111f766130c1521 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 4 May 2024 00:09:42 +0100 Subject: [PATCH 5/9] docs: InterchainDB --- .../contracts/InterchainDB.sol | 111 +++++++++++++++--- .../contracts/interfaces/IInterchainDB.sol | 97 --------------- 2 files changed, 95 insertions(+), 113 deletions(-) diff --git a/packages/contracts-communication/contracts/InterchainDB.sol b/packages/contracts-communication/contracts/InterchainDB.sol index e059beed8e..47797017d6 100644 --- a/packages/contracts-communication/contracts/InterchainDB.sol +++ b/packages/contracts-communication/contracts/InterchainDB.sol @@ -25,11 +25,15 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { bytes32 batchRoot; } + /// @notice The version of the Interchain DataBase. Must match the version of the batches. uint16 public constant DB_VERSION = 1; + /// @dev The entry values written on the local chain. bytes32[] internal _entryValues; + /// @dev The remote batches verified by the modules. mapping(address module => mapping(BatchKey batchKey => RemoteBatch batch)) internal _remoteBatches; + /// @dev Checks if the chain id is not the same as the local chain id. modifier onlyRemoteChainId(uint64 chainId) { if (chainId == block.chainid) { revert InterchainDB__ChainIdNotRemote(chainId); @@ -39,13 +43,27 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { // ═══════════════════════════════════════════════ WRITER-FACING ═══════════════════════════════════════════════════ - /// @inheritdoc IInterchainDB + /// @notice Write data to the Interchain DataBase as a new entry in the current batch. + /// Note: there are no guarantees that this entry will be available for reading on any of the remote chains. + /// Use `requestBatchVerification` to ensure that the entry is available for reading on the destination chain. + /// @param dataHash The hash of the data to be written to the Interchain DataBase as a new entry + /// @return dbNonce The database nonce of the batch containing the written entry + /// @return entryIndex The index of the written entry within the batch function writeEntry(bytes32 dataHash) external returns (uint64 dbNonce, uint64 entryIndex) { InterchainEntry memory entry = _writeEntry(dataHash); (dbNonce, entryIndex) = (entry.dbNonce, entry.entryIndex); } - /// @inheritdoc IInterchainDB + /// @notice Request the given Interchain Modules to verify an existing batch. + /// If the batch is not finalized, the module will verify it after finalization. + /// For the finalized batch the batch root is already available, and the module can verify it immediately. + /// Note: every module has a separate fee paid in the native gas token of the source chain, + /// and `msg.value` must be equal to the sum of all fees. + /// Note: this method is permissionless, and anyone can request verification for any batch. + /// @dev Will revert if the batch with the given nonce does not exist. + /// @param dstChainId The chain id of the destination chain + /// @param dbNonce The database nonce of the existing batch + /// @param srcModules The source chain addresses of the Interchain Modules to use for verification function requestBatchVerification( uint64 dstChainId, uint64 dbNonce, @@ -59,7 +77,15 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { _requestVerification(dstChainId, batch, srcModules); } - /// @inheritdoc IInterchainDB + /// @notice Write data to the Interchain DataBase as a new entry in the current batch. + /// Then request the Interchain Modules to verify the batch containing the written entry on the destination chain. + /// See `writeEntry` and `requestBatchVerification` for more details. + /// @dev Will revert if the empty array of modules is provided. + /// @param dstChainId The chain id of the destination chain + /// @param dataHash The hash of the data to be written to the Interchain DataBase as a new entry + /// @param srcModules The source chain addresses of the Interchain Modules to use for verification + /// @return dbNonce The database nonce of the batch containing the written entry + /// @return entryIndex The index of the written entry within the batch function writeEntryWithVerification( uint64 dstChainId, bytes32 dataHash, @@ -79,7 +105,13 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { // ═══════════════════════════════════════════════ MODULE-FACING ═══════════════════════════════════════════════════ - /// @inheritdoc IInterchainDB + /// @notice Allows the Interchain Module to verify the batch coming from the remote chain. + /// The module SHOULD verify the exact finalized batch from the remote chain. If the batch with a given nonce + /// is not finalized or does not exist, module CAN verify it with an empty root value. Once the batch is + /// finalized, the module SHOULD re-verify the batch with the correct root value. + /// Note: The DB will only accept the batch of the same version as the DB itself. + /// @dev Will revert if the batch with the same nonce but a different non-empty root is already verified. + /// @param versionedBatch The versioned Interchain Batch to verify function verifyRemoteBatch(bytes calldata versionedBatch) external { InterchainBatch memory batch = _assertCorrectBatch(versionedBatch); BatchKey batchKey = InterchainBatchLib.encodeBatchKey({srcChainId: batch.srcChainId, dbNonce: batch.dbNonce}); @@ -104,7 +136,11 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @inheritdoc IInterchainDB + /// @notice Returns the list of leafs of the finalized batch with the given nonce. + /// Note: the leafs are ordered by the index of the written entry in the current batch, + /// and the leafs value match the value of the written entry (srcWriter + dataHash hashed together). + /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. + /// @param dbNonce The database nonce of the finalized batch function getBatchLeafs(uint64 dbNonce) external view returns (bytes32[] memory leafs) { uint256 batchSize = getBatchSize(dbNonce); leafs = new bytes32[](batchSize); @@ -113,7 +149,15 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { } } - /// @inheritdoc IInterchainDB + /// @notice Returns the list of leafs of the finalized batch with the given nonce, + /// paginated by the given start and end indexes. The end index is exclusive. + /// Note: this is useful when the batch contains a large number of leafs, and calling `getBatchLeafs` + /// would result in a gas limit exceeded error. + /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. + /// Will revert if the provided range is invalid. + /// @param dbNonce The database nonce of the finalized batch + /// @param start The start index of the paginated leafs, inclusive + /// @param end The end index of the paginated leafs, exclusive function getBatchLeafsPaginated( uint64 dbNonce, uint64 start, @@ -133,26 +177,46 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { } } - /// @inheritdoc IInterchainDB + /// @notice Get the Merkle proof of inclusion for the entry with the given index + /// in the finalized batch with the given nonce. + /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. + /// Will revert if the entry with the given index does not exist within the batch. + /// @param dbNonce The database nonce of the finalized batch + /// @param entryIndex The index of the written entry within the batch + /// @return proof The Merkle proof of inclusion for the entry function getEntryProof(uint64 dbNonce, uint64 entryIndex) external view returns (bytes32[] memory proof) { // In "no batching" mode: the batch root is the same as the entry value, hence the proof is empty _assertEntryExists(dbNonce, entryIndex); return new bytes32[](0); } - /// @inheritdoc IInterchainDB + /// @notice Get the fee for writing data to the Interchain DataBase, and verifying it on the destination chain + /// using the provided Interchain Modules. + /// @dev Will revert if the empty array of modules is provided. + /// @param dstChainId The chain id of the destination chain + /// @param srcModules The source chain addresses of the Interchain Modules to use for verification function getInterchainFee(uint64 dstChainId, address[] calldata srcModules) external view returns (uint256 fee) { (, fee) = _getModuleFees(dstChainId, getDBNonce(), srcModules); } - /// @inheritdoc IInterchainDB + /// @notice Get the index of the next entry to be written to the database. + /// @return dbNonce The database nonce of the batch including the next entry + /// @return entryIndex The index of the next entry within that batch function getNextEntryIndex() external view returns (uint64 dbNonce, uint64 entryIndex) { // In "no batching" mode: entry index is 0, batch size is 1 dbNonce = getDBNonce(); entryIndex = 0; } - /// @inheritdoc IInterchainDB + /// @notice Check if the batch is verified by the Interchain Module on the destination chain. + /// - returned value `BATCH_UNVERIFIED` indicates that the module has not verified the batch. + /// - returned value `BATCH_CONFLICT` indicates that the module has verified a different batch root + /// for the same batch key. + /// @param dstModule The destination chain addresses of the Interchain Modules to use for verification + /// @param batch The Interchain Batch to check + /// @return moduleVerifiedAt The block timestamp at which the batch was verified by the module, + /// BATCH_UNVERIFIED if the module has not verified the batch, + /// BATCH_CONFLICT if the module has verified a different batch root for the same batch key. function checkBatchVerification( address dstModule, InterchainBatch memory batch @@ -172,7 +236,9 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { return remoteBatch.batchRoot == batch.batchRoot ? remoteBatch.verifiedAt : BATCH_CONFLICT; } - /// @inheritdoc IInterchainDB + /// @notice Get the versioned Interchain Batch with the given nonce. + /// Note: will return a batch with an empty root if the batch does not exist, or is not finalized. + /// @param dbNonce The database nonce of the batch function getVersionedBatch(uint64 dbNonce) external view returns (bytes memory versionedBatch) { InterchainBatch memory batch = getBatch(dbNonce); return VersionedPayloadLib.encodeVersionedPayload({ @@ -181,7 +247,9 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { }); } - /// @inheritdoc IInterchainDB + /// @notice Get the batch root containing the Interchain Entry with the given index. + /// @param entry The Interchain Entry to get the batch root for + /// @param proof The Merkle proof of inclusion for the entry in the batch function getBatchRoot(InterchainEntry memory entry, bytes32[] calldata proof) external pure returns (bytes32) { return BatchingV1Lib.getBatchRoot({ srcWriter: entry.srcWriter, @@ -191,14 +259,18 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { }); } - /// @inheritdoc IInterchainDB + /// @notice Returns the size of the finalized batch with the given nonce. + /// @dev Will return 0 for non-existent or non-finalized batches. + /// @param dbNonce The database nonce of the finalized batch function getBatchSize(uint64 dbNonce) public view returns (uint64) { // In "no batching" mode: the finalized batch size is 1, the pending batch size is 0 // We also return 0 for non-existent batches return dbNonce < getDBNonce() ? 1 : 0; } - /// @inheritdoc IInterchainDB + /// @notice Get the finalized Interchain Batch with the given nonce. + /// @dev Will return a batch with an empty root if the batch does not exist, or is not finalized. + /// @param dbNonce The database nonce of the finalized batch function getBatch(uint64 dbNonce) public view returns (InterchainBatch memory) { // In "no batching" mode: the batch root is the same as the entry hash. // For non-finalized or non-existent batches, the batch root is 0. @@ -206,13 +278,20 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB { return InterchainBatchLib.constructLocalBatch(dbNonce, batchRoot); } - /// @inheritdoc IInterchainDB + /// @notice Get the Interchain Entry's value written on the local chain with the given batch nonce and entry index. + /// Entry value is calculated as the hash of the writer address and the written data hash. + /// Note: the batch does not have to be finalized to fetch the entry value. + /// @dev Will revert if the batch with the given nonce does not exist, + /// or the entry with the given index does not exist within the batch. + /// @param dbNonce The database nonce of the existing batch + /// @param entryIndex The index of the written entry within the batch function getEntryValue(uint64 dbNonce, uint64 entryIndex) public view returns (bytes32) { _assertEntryExists(dbNonce, entryIndex); return _entryValues[dbNonce]; } - /// @inheritdoc IInterchainDB + /// @notice Get the nonce of the database, which is incremented every time a new batch is finalized. + /// This is the nonce of the current non-finalized batch. function getDBNonce() public view returns (uint64) { // We can do the unsafe cast here as writing more than 2^64 entries is practically impossible return uint64(_entryValues.length); diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol b/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol index ed309c2e15..3bc2a34c6e 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol @@ -13,24 +13,7 @@ interface IInterchainDB { error InterchainDB__FeeAmountBelowMin(uint256 feeAmount, uint256 minRequired); error InterchainDB__ModulesNotProvided(); - /// @notice Write data to the Interchain DataBase as a new entry in the current batch. - /// Note: there are no guarantees that this entry will be available for reading on any of the remote chains. - /// Use `requestBatchVerification` to ensure that the entry is available for reading on the destination chain. - /// @param dataHash The hash of the data to be written to the Interchain DataBase as a new entry - /// @return dbNonce The database nonce of the batch containing the written entry - /// @return entryIndex The index of the written entry within the batch function writeEntry(bytes32 dataHash) external returns (uint64 dbNonce, uint64 entryIndex); - - /// @notice Request the given Interchain Modules to verify an existing batch. - /// If the batch is not finalized, the module will verify it after finalization. - /// For the finalized batch the batch root is already available, and the module can verify it immediately. - /// Note: every module has a separate fee paid in the native gas token of the source chain, - /// and `msg.value` must be equal to the sum of all fees. - /// Note: this method is permissionless, and anyone can request verification for any batch. - /// @dev Will revert if the batch with the given nonce does not exist. - /// @param dstChainId The chain id of the destination chain - /// @param dbNonce The database nonce of the existing batch - /// @param srcModules The source chain addresses of the Interchain Modules to use for verification function requestBatchVerification( uint64 dstChainId, uint64 dbNonce, @@ -38,16 +21,6 @@ interface IInterchainDB { ) external payable; - - /// @notice Write data to the Interchain DataBase as a new entry in the current batch. - /// Then request the Interchain Modules to verify the batch containing the written entry on the destination chain. - /// See `writeEntry` and `requestBatchVerification` for more details. - /// @dev Will revert if the empty array of modules is provided. - /// @param dstChainId The chain id of the destination chain - /// @param dataHash The hash of the data to be written to the Interchain DataBase as a new entry - /// @param srcModules The source chain addresses of the Interchain Modules to use for verification - /// @return dbNonce The database nonce of the batch containing the written entry - /// @return entryIndex The index of the written entry within the batch function writeEntryWithVerification( uint64 dstChainId, bytes32 dataHash, @@ -57,40 +30,13 @@ interface IInterchainDB { payable returns (uint64 dbNonce, uint64 entryIndex); - /// @notice Allows the Interchain Module to verify the batch coming from the remote chain. - /// The module SHOULD verify the exact finalized batch from the remote chain. If the batch with a given nonce - /// is not finalized or does not exist, module CAN verify it with an empty root value. Once the batch is - /// finalized, the module SHOULD re-verify the batch with the correct root value. - /// Note: The DB will only accept the batch of the same version as the DB itself. - /// @dev Will revert if the batch with the same nonce but a different non-empty root is already verified. - /// @param versionedBatch The versioned Interchain Batch to verify function verifyRemoteBatch(bytes memory versionedBatch) external; // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @notice Get the fee for writing data to the Interchain DataBase, and verifying it on the destination chain - /// using the provided Interchain Modules. - /// @dev Will revert if the empty array of modules is provided. - /// @param dstChainId The chain id of the destination chain - /// @param srcModules The source chain addresses of the Interchain Modules to use for verification function getInterchainFee(uint64 dstChainId, address[] memory srcModules) external view returns (uint256); - /// @notice Returns the list of leafs of the finalized batch with the given nonce. - /// Note: the leafs are ordered by the index of the written entry in the current batch, - /// and the leafs value match the value of the written entry (srcWriter + dataHash hashed together). - /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. - /// @param dbNonce The database nonce of the finalized batch function getBatchLeafs(uint64 dbNonce) external view returns (bytes32[] memory); - - /// @notice Returns the list of leafs of the finalized batch with the given nonce, - /// paginated by the given start and end indexes. The end index is exclusive. - /// Note: this is useful when the batch contains a large number of leafs, and calling `getBatchLeafs` - /// would result in a gas limit exceeded error. - /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. - /// Will revert if the provided range is invalid. - /// @param dbNonce The database nonce of the finalized batch - /// @param start The start index of the paginated leafs, inclusive - /// @param end The end index of the paginated leafs, exclusive function getBatchLeafsPaginated( uint64 dbNonce, uint64 start, @@ -99,55 +45,16 @@ interface IInterchainDB { external view returns (bytes32[] memory); - - /// @notice Returns the size of the finalized batch with the given nonce. - /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. - /// @param dbNonce The database nonce of the finalized batch function getBatchSize(uint64 dbNonce) external view returns (uint64); - - /// @notice Get the finalized Interchain Batch with the given nonce. - /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. - /// @param dbNonce The database nonce of the finalized batch function getBatch(uint64 dbNonce) external view returns (InterchainBatch memory); - - /// @notice Get the versioned Interchain Batch with the given nonce. - /// Note: will return a batch with an empty root if the batch does not exist, or is not finalized. - /// @param dbNonce The database nonce of the batch function getVersionedBatch(uint64 dbNonce) external view returns (bytes memory); - /// @notice Get the Interchain Entry's value written on the local chain with the given batch nonce and entry index. - /// Entry value is calculated as the hash of the writer address and the written data hash. - /// Note: the batch does not have to be finalized to fetch the entry value. - /// @dev Will revert if the batch with the given nonce does not exist, - /// or the entry with the given index does not exist within the batch. - /// @param dbNonce The database nonce of the existing batch - /// @param entryIndex The index of the written entry within the batch function getEntryValue(uint64 dbNonce, uint64 entryIndex) external view returns (bytes32); - - /// @notice Get the Merkle proof of inclusion for the entry with the given index - /// in the finalized batch with the given nonce. - /// @dev Will revert if the batch with the given nonce does not exist, or is not finalized. - /// Will revert if the entry with the given index does not exist within the batch. - /// @param dbNonce The database nonce of the finalized batch - /// @param entryIndex The index of the written entry within the batch - /// @return proof The Merkle proof of inclusion for the entry function getEntryProof(uint64 dbNonce, uint64 entryIndex) external view returns (bytes32[] memory proof); - /// @notice Get the nonce of the database, which is incremented every time a new batch is finalized. - /// This is the nonce of the current non-finalized batch. function getDBNonce() external view returns (uint64); - - /// @notice Get the index of the next entry to be written to the database. - /// @return dbNonce The database nonce of the batch including the next entry - /// @return entryIndex The index of the next entry within that batch function getNextEntryIndex() external view returns (uint64 dbNonce, uint64 entryIndex); - /// @notice Check if the batch is verified by the Interchain Module on the destination chain. - /// Note: returned zero value indicates that the module has not verified the batch. - /// @param dstModule The destination chain addresses of the Interchain Modules to use for verification - /// @param batch The Interchain Batch to check - /// @return moduleVerifiedAt The block timestamp at which the batch was verified by the module, - /// or ZERO if the module has not verified the batch. function checkBatchVerification( address dstModule, InterchainBatch memory batch @@ -156,12 +63,8 @@ interface IInterchainDB { view returns (uint256 moduleVerifiedAt); - /// @notice Get the batch root containing the Interchain Entry with the given index. - /// @param entry The Interchain Entry to get the batch root for - /// @param proof The Merkle proof of inclusion for the entry in the batch function getBatchRoot(InterchainEntry memory entry, bytes32[] memory proof) external pure returns (bytes32); - /// @notice Get the version of the Interchain DataBase. // solhint-disable-next-line func-name-mixedcase function DB_VERSION() external pure returns (uint16); } From 0800dd5e2288f485e032fca02a8b1f370652bf2b Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 4 May 2024 00:15:35 +0100 Subject: [PATCH 6/9] docs: interchain modules --- .../interfaces/IInterchainModule.sol | 13 ----- .../contracts/interfaces/ISynapseModule.sol | 56 ------------------- .../contracts/modules/InterchainModule.sol | 16 +++++- .../contracts/modules/SynapseModule.sol | 47 +++++++++++----- 4 files changed, 46 insertions(+), 86 deletions(-) diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol b/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol index 16f840e9f6..502ef7f3e8 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol @@ -8,16 +8,6 @@ interface IInterchainModule { error InterchainModule__ChainIdNotRemote(uint64 chainId); error InterchainModule__FeeAmountBelowMin(uint256 feeAmount, uint256 minRequired); - /// @notice Request the verification of a batch from the Interchain DataBase by the module. - /// If the batch is not yet finalized, the verification on destination chain will be delayed until - /// the finalization is done and batch root is saved on the source chain. - /// Note: a fee is paid to the module for verification, and could be retrieved by using `getModuleFee`. - /// Note: this will eventually trigger `InterchainDB.verifyRemoteBatch(batch)` function on destination chain, - /// with no guarantee of ordering. - /// @dev Could be only called by the Interchain DataBase contract. - /// @param dstChainId The chain id of the destination chain - /// @param batchNonce The nonce of the batch on the source chain - /// @param versionedBatch The versioned batch to verify function requestBatchVerification( uint64 dstChainId, uint64 batchNonce, @@ -26,8 +16,5 @@ interface IInterchainModule { external payable; - /// @notice Get the Module fee for verifying a batch on the specified destination chain. - /// @param dstChainId The chain id of the destination chain - /// @param dbNonce The database nonce of the batch on the source chain function getModuleFee(uint64 dstChainId, uint64 dbNonce) external view returns (uint256); } diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseModule.sol b/packages/contracts-communication/contracts/interfaces/ISynapseModule.sol index 7749b1a513..52d7b9701d 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseModule.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseModule.sol @@ -8,80 +8,24 @@ interface ISynapseModule is IInterchainModule { error SynapseModule__GasOracleZeroAddress(); error SynapseModule__FeeRecipientZeroAddress(); - // ═══════════════════════════════════════════════ 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 Adds a list of new verifiers to the module. - /// @dev Could be only called by the owner. Will revert if any of the verifiers is already added. - /// @param verifiers The list of addresses of the verifiers to add function addVerifiers(address[] calldata verifiers) 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 Removes a list of verifiers from the module. - /// @dev Could be only called by the owner. Will revert if any of the verifiers is not added. - /// @param verifiers The list of addresses of the verifiers to remove function removeVerifiers(address[] calldata verifiers) 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 feeRecipient The address of the fee collector function setFeeRecipient(address feeRecipient) external; - - /// @notice Sets the fraction of the accumulated fees to be paid to caller of `claimFees`. - /// This encourages rational actors to call the function as soon as claim fee is higher than the gas cost. - /// @dev Could be only called by the owner. Could not exceed 1%. - /// @param claimerFraction The fraction of the fees to be paid to the claimer (100% = 1e18) function setClaimerFraction(uint256 claimerFraction) 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; - - /// @notice Sets the estimated gas limit for verifying a batch on the given chain. - /// @dev Could be only called by the owner. - /// @param chainId The chain ID for which to set the gas limit - /// @param gasLimit The new gas limit function setVerifyGasLimit(uint64 chainId, uint256 gasLimit) external; - // ══════════════════════════════════════════════ PERMISSIONLESS ═══════════════════════════════════════════════════ - - /// @notice Verifies a batch from the remote chain using a set of verifier signatures. - /// If the threshold is met, the batch 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 encodedBatch The encoded batch to verify - /// @param signatures Signatures used to verify the batch, concatenated function verifyRemoteBatch(bytes calldata encodedBatch, bytes calldata signatures) external; // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @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); - - /// @notice Returns the estimated gas limit for verifying a batch on the given chain. - /// Note: this defaults to DEFAULT_VERIFY_GAS_LIMIT if not set. function getVerifyGasLimit(uint64 chainId) external view returns (uint256); } diff --git a/packages/contracts-communication/contracts/modules/InterchainModule.sol b/packages/contracts-communication/contracts/modules/InterchainModule.sol index 6f44a768ab..9dad8d8157 100644 --- a/packages/contracts-communication/contracts/modules/InterchainModule.sol +++ b/packages/contracts-communication/contracts/modules/InterchainModule.sol @@ -15,13 +15,23 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa abstract contract InterchainModule is InterchainModuleEvents, IInterchainModule { using VersionedPayloadLib for bytes; + /// @notice The address of the Interchain DataBase contract: used for verifying the batches. address public immutable INTERCHAIN_DB; constructor(address interchainDB) { INTERCHAIN_DB = interchainDB; } - /// @inheritdoc IInterchainModule + /// @notice Request the verification of a batch from the Interchain DataBase by the module. + /// If the batch is not yet finalized, the verification on destination chain will be delayed until + /// the finalization is done and batch root is saved on the source chain. + /// Note: a fee is paid to the module for verification, and could be retrieved by using `getModuleFee`. + /// Note: this will eventually trigger `InterchainDB.verifyRemoteBatch(batch)` function on destination chain, + /// with no guarantee of ordering. + /// @dev Could be only called by the Interchain DataBase contract. + /// @param dstChainId The chain id of the destination chain + /// @param batchNonce The nonce of the batch on the source chain + /// @param versionedBatch The versioned batch to verify function requestBatchVerification( uint64 dstChainId, uint64 batchNonce, @@ -47,7 +57,9 @@ abstract contract InterchainModule is InterchainModuleEvents, IInterchainModule emit BatchVerificationRequested(dstChainId, encodedBatch, ethSignedBatchHash); } - /// @inheritdoc IInterchainModule + /// @notice Get the Module fee for verifying a batch on the specified destination chain. + /// @param dstChainId The chain id of the destination chain + /// @param dbNonce The database nonce of the batch on the source chain function getModuleFee(uint64 dstChainId, uint64 dbNonce) external view returns (uint256) { return _getModuleFee(dstChainId, dbNonce); } diff --git a/packages/contracts-communication/contracts/modules/SynapseModule.sol b/packages/contracts-communication/contracts/modules/SynapseModule.sol index e58e471bc5..c624ddb054 100644 --- a/packages/contracts-communication/contracts/modules/SynapseModule.sol +++ b/packages/contracts-communication/contracts/modules/SynapseModule.sol @@ -32,7 +32,7 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul /// @dev Recipient of the fees collected by the module. address internal _feeRecipient; - /// @inheritdoc ISynapseModule + /// @notice Address of the gas oracle used for estimating the verification fees. address public gasOracle; constructor(address interchainDB, address owner_) InterchainModule(interchainDB) Ownable(owner_) { @@ -41,12 +41,14 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul // ═══════════════════════════════════════════════ PERMISSIONED ════════════════════════════════════════════════════ - /// @inheritdoc ISynapseModule + /// @notice Adds a new verifier to the module. + /// @dev Could be only called by the owner. Will revert if the verifier is already added. function addVerifier(address verifier) external onlyOwner { _addVerifier(verifier); } - /// @inheritdoc ISynapseModule + /// @notice Adds a list of new verifiers to the module. + /// @dev Could be only called by the owner. Will revert if any of the verifiers is already added. function addVerifiers(address[] calldata verifiers) external onlyOwner { uint256 length = verifiers.length; for (uint256 i = 0; i < length; ++i) { @@ -54,12 +56,14 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul } } - /// @inheritdoc ISynapseModule + /// @notice Removes a verifier from the module. + /// @dev Could be only called by the owner. Will revert if the verifier is not added. function removeVerifier(address verifier) external onlyOwner { _removeVerifier(verifier); } - /// @inheritdoc ISynapseModule + /// @notice Removes a list of verifiers from the module. + /// @dev Could be only called by the owner. Will revert if any of the verifiers is not added. function removeVerifiers(address[] calldata verifiers) external onlyOwner { uint256 length = verifiers.length; for (uint256 i = 0; i < length; ++i) { @@ -67,13 +71,15 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul } } - /// @inheritdoc ISynapseModule + /// @notice Sets the threshold of the module. + /// @dev Could be only called by the owner. Will revert if the threshold is zero. function setThreshold(uint256 threshold) external onlyOwner { _verifiers.modifyThreshold(threshold); emit ThresholdSet(threshold); } - /// @inheritdoc ISynapseModule + /// @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. function setFeeRecipient(address feeRecipient) external onlyOwner { if (feeRecipient == address(0)) { revert SynapseModule__FeeRecipientZeroAddress(); @@ -82,7 +88,9 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul emit FeeRecipientSet(feeRecipient); } - /// @inheritdoc ISynapseModule + /// @notice Sets the fraction of the accumulated fees to be paid to caller of `claimFees`. + /// This encourages rational actors to call the function as soon as claim fee is higher than the gas cost. + /// @dev Could be only called by the owner. Could not exceed 1% (1e16). function setClaimerFraction(uint256 claimerFraction) external onlyOwner { if (claimerFraction > MAX_CLAIMER_FRACTION) { revert ClaimableFees__ClaimerFractionAboveMax(claimerFraction, MAX_CLAIMER_FRACTION); @@ -91,7 +99,8 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul emit ClaimerFractionSet(claimerFraction); } - /// @inheritdoc ISynapseModule + /// @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. function setGasOracle(address gasOracle_) external onlyOwner { if (gasOracle_.code.length == 0) { revert SynapseModule__GasOracleNotContract(gasOracle_); @@ -100,7 +109,10 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul emit GasOracleSet(gasOracle_); } - /// @inheritdoc ISynapseModule + /// @notice Sets the estimated gas limit for verifying a batch on the given chain. + /// @dev Could be only called by the owner. + /// @param chainId The chain ID for which to set the gas limit + /// @param gasLimit The new gas limit for the verification on the specified chain function setVerifyGasLimit(uint64 chainId, uint256 gasLimit) external onlyOwner { _verifyGasLimit[chainId] = gasLimit; emit VerifyGasLimitSet(chainId, gasLimit); @@ -108,7 +120,11 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul // ══════════════════════════════════════════════ PERMISSIONLESS ═══════════════════════════════════════════════════ - /// @inheritdoc ISynapseModule + /// @notice Verifies a batch from the remote chain using a set of verifier signatures. + /// If the threshold is met, the batch 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 encodedBatch The encoded batch to verify + /// @param signatures Signatures used to verify the batch, concatenated function verifyRemoteBatch(bytes calldata encodedBatch, bytes calldata signatures) external { bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(keccak256(encodedBatch)); _verifiers.verifySignedHash(ethSignedHash, signatures); @@ -117,22 +133,23 @@ contract SynapseModule is InterchainModule, ClaimableFees, Ownable, SynapseModul // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ - /// @inheritdoc ISynapseModule + /// @notice Returns the list of verifiers for the module. function getVerifiers() external view returns (address[] memory) { return _verifiers.getSigners(); } - /// @inheritdoc ISynapseModule + /// @notice Checks if the given account is a verifier for the module. function isVerifier(address account) external view returns (bool) { return _verifiers.isSigner(account); } - /// @inheritdoc ISynapseModule + /// @notice Gets the threshold of the module. This is the minimum number of signatures required for verification. function getThreshold() public view returns (uint256) { return _verifiers.getThreshold(); } - /// @inheritdoc ISynapseModule + /// @notice Returns the estimated gas limit for verifying a batch on the given chain. + /// Note: this defaults to DEFAULT_VERIFY_GAS_LIMIT if not set. function getVerifyGasLimit(uint64 chainId) public view override returns (uint256 gasLimit) { gasLimit = _verifyGasLimit[chainId]; if (gasLimit == 0) { From b42b4f615b110a762704953511edfe51eb01abc2 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Sat, 4 May 2024 00:17:25 +0100 Subject: [PATCH 7/9] docs: MessageBus --- .../contracts/legacy/MessageBus.sol | 27 +++++++++++++---- .../legacy/interfaces/IMessageBus.sol | 29 ------------------- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/packages/contracts-communication/contracts/legacy/MessageBus.sol b/packages/contracts-communication/contracts/legacy/MessageBus.sol index ec04bc63b6..4db4c4747a 100644 --- a/packages/contracts-communication/contracts/legacy/MessageBus.sol +++ b/packages/contracts-communication/contracts/legacy/MessageBus.sol @@ -22,7 +22,13 @@ contract MessageBus is ICAppV1, MessageBusEvents, IMessageBus { constructor(address admin) ICAppV1(admin) {} - /// @inheritdoc IMessageBus + /// @notice Sends a message to a receiving contract address on another chain. + /// Sender must make sure that the message is unique and not a duplicate message. + /// @dev Legacy MessageBus only supports V1 of the options format, which specifies only the gas limit. + /// @param receiver The bytes32 address of the destination contract to be called + /// @param dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains + /// @param message The arbitrary payload to pass to the destination chain receiver + /// @param options Versioned struct used to instruct relayer on how to proceed with gas limits function sendMessage( bytes32 receiver, uint256 dstChainId, @@ -68,24 +74,35 @@ contract MessageBus is ICAppV1, MessageBusEvents, IMessageBus { }); } - /// @inheritdoc IMessageBus + /// @notice Allows the Interchain Governor to set the gas buffer for sending the interchain messages. + /// Note: The gas buffer is added to the gas limit requested by the sending app to cover the gas usage + /// of the MessageBus contract on the destination chain. function setGasBuffer(uint64 gasBuffer_) external onlyRole(IC_GOVERNOR_ROLE) { gasBuffer = gasBuffer_; emit GasBufferSet(gasBuffer_); } - /// @inheritdoc IMessageBus + /// @notice Allows the Interchain Governor to set the message length in bytes to be used for fee estimation. + /// This does not affect the sendMessage function, but only the fee estimation. function setMessageLengthEstimate(uint256 length) external onlyRole(IC_GOVERNOR_ROLE) { messageLengthEstimate = length; emit MessageLengthEstimateSet(length); } - /// @inheritdoc IMessageBus + /// @notice Returns srcGasToken fee to charge in wei for the cross-chain message based on the gas limit. + /// @dev This function is using the preset message length to estimate the gas fee. This should cover most cases, + /// if the message length is lower than the preset value. For more accurate fee estimation, use estimateFeeExact. + /// @param dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains + /// @param options Versioned struct used to instruct relayer on how to proceed with gas limits function estimateFee(uint256 dstChainId, bytes calldata options) external view returns (uint256) { return estimateFeeExact(dstChainId, options, messageLengthEstimate); } - /// @inheritdoc IMessageBus + /// @notice Returns srcGasToken fee to charge in wei for the cross-chain message based on the message length + /// and the gas limit. + /// @param dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains + /// @param options Versioned struct used to instruct relayer on how to proceed with gas limits + /// @param messageLen The length of the message to be sent in bytes function estimateFeeExact( uint256 dstChainId, bytes calldata options, diff --git a/packages/contracts-communication/contracts/legacy/interfaces/IMessageBus.sol b/packages/contracts-communication/contracts/legacy/interfaces/IMessageBus.sol index aeaffb45a4..564e6894e2 100644 --- a/packages/contracts-communication/contracts/legacy/interfaces/IMessageBus.sol +++ b/packages/contracts-communication/contracts/legacy/interfaces/IMessageBus.sol @@ -4,13 +4,6 @@ pragma solidity ^0.8.0; interface IMessageBus { error MessageBus__ReceiverNotEVM(bytes32 receiver); - /// @notice Sends a message to a receiving contract address on another chain. - /// Sender must make sure that the message is unique and not a duplicate message. - /// @dev Legacy MessageBus only supports V1 of the options format, which specifies only the gas limit. - /// @param receiver The bytes32 address of the destination contract to be called - /// @param dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains - /// @param message The arbitrary payload to pass to the destination chain receiver - /// @param options Versioned struct used to instruct relayer on how to proceed with gas limits function sendMessage( bytes32 receiver, uint256 dstChainId, @@ -19,31 +12,11 @@ interface IMessageBus { ) external payable; - - /// @notice Allows the Interchain Governor to set the gas buffer for sending the interchain messages. - /// Note: The gas buffer is added to the gas limit requested by the sending app to cover the gas usage - /// of the MessageBus contract on the destination chain. function setGasBuffer(uint64 gasBuffer_) external; - - /// @notice Allows the Interchain Governor to set the message length in bytes to be used for fee estimation. - /// This does not affect the sendMessage function, but only the fee estimation. function setMessageLengthEstimate(uint256 length) external; - /// @notice Returns the preset message length in bytes used for fee estimation. function messageLengthEstimate() external view returns (uint256); - - /// @notice Returns srcGasToken fee to charge in wei for the cross-chain message based on the gas limit. - /// @dev This function is using the preset message length to estimate the gas fee. This should cover most cases, - /// if the message length is lower than the preset value. For more accurate fee estimation, use estimateFeeExact. - /// @param dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains - /// @param options Versioned struct used to instruct relayer on how to proceed with gas limits function estimateFee(uint256 dstChainId, bytes memory options) external view returns (uint256); - - /// @notice Returns srcGasToken fee to charge in wei for the cross-chain message based on the message length - /// and the gas limit. - /// @param dstChainId The destination chain ID - typically, standard EVM chain ID, but differs on nonEVM chains - /// @param options Versioned struct used to instruct relayer on how to proceed with gas limits - /// @param messageLen The length of the message to be sent in bytes function estimateFeeExact( uint256 dstChainId, bytes memory options, @@ -52,7 +25,5 @@ interface IMessageBus { external view returns (uint256); - - /// @notice Returns the nonce of MessageBus: the amount of sent messages so far. function nonce() external view returns (uint64); } From b948176368ce734daa80b7b96b7d0d3b0e72aa8a Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Wed, 8 May 2024 12:09:05 +0100 Subject: [PATCH 8/9] style: make interfaces easier to read --- .../contracts/interfaces/IClaimableFees.sol | 2 ++ .../contracts/interfaces/IExecutionService.sol | 3 +++ .../contracts/interfaces/IGasOracle.sol | 3 +++ .../contracts/interfaces/IInterchainApp.sol | 2 ++ .../contracts/interfaces/IInterchainClientV1.sol | 3 +++ .../contracts/interfaces/IInterchainDB.sol | 3 +++ .../contracts/interfaces/IInterchainModule.sol | 2 ++ .../contracts/interfaces/ISynapseExecutionServiceV1.sol | 2 ++ .../contracts/interfaces/ISynapseGasOracle.sol | 2 ++ .../contracts/interfaces/ISynapseGasOracleV1.sol | 2 ++ 10 files changed, 24 insertions(+) diff --git a/packages/contracts-communication/contracts/interfaces/IClaimableFees.sol b/packages/contracts-communication/contracts/interfaces/IClaimableFees.sol index 0251c322b2..17672d5615 100644 --- a/packages/contracts-communication/contracts/interfaces/IClaimableFees.sol +++ b/packages/contracts-communication/contracts/interfaces/IClaimableFees.sol @@ -8,6 +8,8 @@ interface IClaimableFees { function claimFees() external; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function getClaimableAmount() external view returns (uint256); function getClaimerFraction() external view returns (uint256); function getClaimerReward() external view returns (uint256); diff --git a/packages/contracts-communication/contracts/interfaces/IExecutionService.sol b/packages/contracts-communication/contracts/interfaces/IExecutionService.sol index 56fc97673f..804ab72d6d 100644 --- a/packages/contracts-communication/contracts/interfaces/IExecutionService.sol +++ b/packages/contracts-communication/contracts/interfaces/IExecutionService.sol @@ -11,7 +11,10 @@ interface IExecutionService { external payable; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function executorEOA() external view returns (address); + function getExecutionFee( uint64 dstChainId, uint256 txPayloadSize, diff --git a/packages/contracts-communication/contracts/interfaces/IGasOracle.sol b/packages/contracts-communication/contracts/interfaces/IGasOracle.sol index 332d6f3aab..ea40c4505b 100644 --- a/packages/contracts-communication/contracts/interfaces/IGasOracle.sol +++ b/packages/contracts-communication/contracts/interfaces/IGasOracle.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.0; interface IGasOracle { + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function convertRemoteValueToLocalUnits(uint64 remoteChainId, uint256 value) external view returns (uint256); function estimateTxCostInLocalUnits( @@ -12,6 +14,7 @@ interface IGasOracle { external view returns (uint256); + function estimateTxCostInRemoteUnits( uint64 remoteChainId, uint256 gasLimit, diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol b/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol index 6b702b375c..27983c6ef3 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainApp.sol @@ -13,5 +13,7 @@ interface IInterchainApp { external payable; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function getReceivingConfig() external view returns (bytes memory appConfig, address[] memory modules); } diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol b/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol index 4b5eb12453..1c2aba1cee 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol @@ -47,6 +47,7 @@ interface IInterchainClientV1 { external payable returns (InterchainTxDescriptor memory desc); + function interchainSendEVM( uint64 dstChainId, address receiver, @@ -58,6 +59,7 @@ interface IInterchainClientV1 { external payable returns (InterchainTxDescriptor memory desc); + function interchainExecute( uint256 gasLimit, bytes calldata transaction, @@ -65,6 +67,7 @@ interface IInterchainClientV1 { ) external payable; + function writeExecutionProof(bytes32 transactionId) external returns (uint64 dbNonce, uint64 entryIndex); // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol b/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol index 3bc2a34c6e..bf7dca27be 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainDB.sol @@ -14,6 +14,7 @@ interface IInterchainDB { error InterchainDB__ModulesNotProvided(); function writeEntry(bytes32 dataHash) external returns (uint64 dbNonce, uint64 entryIndex); + function requestBatchVerification( uint64 dstChainId, uint64 dbNonce, @@ -21,6 +22,7 @@ interface IInterchainDB { ) external payable; + function writeEntryWithVerification( uint64 dstChainId, bytes32 dataHash, @@ -45,6 +47,7 @@ interface IInterchainDB { external view returns (bytes32[] memory); + function getBatchSize(uint64 dbNonce) external view returns (uint64); function getBatch(uint64 dbNonce) external view returns (InterchainBatch memory); function getVersionedBatch(uint64 dbNonce) external view returns (bytes memory); diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol b/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol index 502ef7f3e8..fd12643cc9 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainModule.sol @@ -16,5 +16,7 @@ interface IInterchainModule { external payable; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function getModuleFee(uint64 dstChainId, uint64 dbNonce) external view returns (uint256); } diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol b/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol index b6b47e7468..0374e07485 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseExecutionServiceV1.sol @@ -15,6 +15,8 @@ interface ISynapseExecutionServiceV1 is IExecutionService { function setGasOracle(address gasOracle_) external; function setGlobalMarkup(uint256 globalMarkup_) external; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function gasOracle() external view returns (address); function globalMarkup() external view returns (uint256); } diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol index 45782242cc..7b43b312b9 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracle.sol @@ -6,5 +6,7 @@ import {IGasOracle} from "./IGasOracle.sol"; interface ISynapseGasOracle is IGasOracle { function receiveRemoteGasData(uint64 srcChainId, bytes calldata data) external; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function getLocalGasData() external view returns (bytes memory); } diff --git a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol index c66019c89a..5a403c030e 100644 --- a/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol +++ b/packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol @@ -24,6 +24,8 @@ interface ISynapseGasOracleV1 is ISynapseGasOracle { function setRemoteGasPrice(uint64 chainId, uint256 gasPrice) external; function setRemoteNativePrice(uint64 chainId, uint256 nativePrice) external; + // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════ + function getLocalNativePrice() external view returns (uint256); function getRemoteGasData(uint64 chainId) external view returns (RemoteGasData memory); } From 4c2822e8875c4c299d06ca86981a0067cdb0a723 Mon Sep 17 00:00:00 2001 From: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com> Date: Wed, 8 May 2024 12:13:47 +0100 Subject: [PATCH 9/9] chore: fix linter warnings --- .../contracts/execution/SynapseExecutionServiceV1.sol | 2 +- .../contracts/oracles/SynapseGasOracleV1.sol | 1 - .../contracts-communication/test/InterchainDB.Src.t.sol | 4 ++-- .../test/apps/InterchainAppV1.Messaging.t.sol | 6 +++--- .../contracts-communication/test/apps/InterchainAppV1.t.sol | 6 ++++-- .../contracts-communication/test/libs/AppConfigLib.t.sol | 6 +++--- packages/contracts-communication/test/libs/OptionsLib.t.sol | 6 +++--- .../test/modules/SynapseModule.Management.t.sol | 4 +++- 8 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol b/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol index 0556e097f8..207bf88044 100644 --- a/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol +++ b/packages/contracts-communication/contracts/execution/SynapseExecutionServiceV1.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; import {SynapseExecutionServiceEvents} from "../events/SynapseExecutionServiceEvents.sol"; import {ClaimableFees} from "../fees/ClaimableFees.sol"; -import {IExecutionService, ISynapseExecutionServiceV1} from "../interfaces/ISynapseExecutionServiceV1.sol"; +import {ISynapseExecutionServiceV1} from "../interfaces/ISynapseExecutionServiceV1.sol"; import {IGasOracle} from "../interfaces/IGasOracle.sol"; import {OptionsLib, OptionsV1} from "../libs/Options.sol"; import {VersionedPayloadLib} from "../libs/VersionedPayload.sol"; diff --git a/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol b/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol index fdc1ba8a25..8ce8141982 100644 --- a/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol +++ b/packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.20; import {SynapseGasOracleV1Events} from "../events/SynapseGasOracleV1Events.sol"; -import {ISynapseGasOracle, IGasOracle} from "../interfaces/ISynapseGasOracle.sol"; import {ISynapseGasOracleV1} from "../interfaces/ISynapseGasOracleV1.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; diff --git a/packages/contracts-communication/test/InterchainDB.Src.t.sol b/packages/contracts-communication/test/InterchainDB.Src.t.sol index 3fefdb5280..324c6e377b 100644 --- a/packages/contracts-communication/test/InterchainDB.Src.t.sol +++ b/packages/contracts-communication/test/InterchainDB.Src.t.sol @@ -113,8 +113,8 @@ contract InterchainDBSourceTest is Test, InterchainDBEvents { } function getModuleCalldata(InterchainBatch memory batch) internal view returns (bytes memory) { - bytes memory versionedBatch = getVersionedBatch(batch); - return abi.encodeCall(IInterchainModule.requestBatchVerification, (DST_CHAIN_ID, batch.dbNonce, versionedBatch)); + bytes memory vBatch = getVersionedBatch(batch); + return abi.encodeCall(IInterchainModule.requestBatchVerification, (DST_CHAIN_ID, batch.dbNonce, vBatch)); } function addressToBytes32(address addr) internal pure returns (bytes32) { diff --git a/packages/contracts-communication/test/apps/InterchainAppV1.Messaging.t.sol b/packages/contracts-communication/test/apps/InterchainAppV1.Messaging.t.sol index 2bec88f46c..51fd1197c9 100644 --- a/packages/contracts-communication/test/apps/InterchainAppV1.Messaging.t.sol +++ b/packages/contracts-communication/test/apps/InterchainAppV1.Messaging.t.sol @@ -182,7 +182,7 @@ abstract contract InterchainAppV1MessagingTest is InterchainAppV1Test { function test_sendInterchainMessage_revert_BalanceBelowMin() public { deal(address(appHarness), MOCK_IC_FEE - 1); - expectRevertBalanceBelowMin({actual: MOCK_IC_FEE - 1, required: MOCK_IC_FEE}); + expectRevertBalanceBelowMin({balance: MOCK_IC_FEE - 1, minValue: MOCK_IC_FEE}); appHarness.exposed__sendInterchainMessage({ dstChainId: REMOTE_CHAIN_ID, receiver: linkedAppMockBytes32, @@ -246,7 +246,7 @@ abstract contract InterchainAppV1MessagingTest is InterchainAppV1Test { function test_sendInterchainMessageEVM_revert_BalanceBelowMin() public { deal(address(appHarness), MOCK_IC_FEE - 1); - expectRevertBalanceBelowMin({actual: MOCK_IC_FEE - 1, required: MOCK_IC_FEE}); + expectRevertBalanceBelowMin({balance: MOCK_IC_FEE - 1, minValue: MOCK_IC_FEE}); appHarness.exposed__sendInterchainMessageEVM({ dstChainId: REMOTE_CHAIN_ID, receiver: linkedAppMock, @@ -308,7 +308,7 @@ abstract contract InterchainAppV1MessagingTest is InterchainAppV1Test { function test_sendToLinkedApp_revert_BalanceBelowMin() public { deal(address(appHarness), MOCK_IC_FEE - 1); - expectRevertBalanceBelowMin({actual: MOCK_IC_FEE - 1, required: MOCK_IC_FEE}); + expectRevertBalanceBelowMin({balance: MOCK_IC_FEE - 1, minValue: MOCK_IC_FEE}); appHarness.exposed__sendToLinkedApp({ dstChainId: REMOTE_CHAIN_ID, messageFee: MOCK_IC_FEE, diff --git a/packages/contracts-communication/test/apps/InterchainAppV1.t.sol b/packages/contracts-communication/test/apps/InterchainAppV1.t.sol index 1d27c1ed9c..e3149ed72a 100644 --- a/packages/contracts-communication/test/apps/InterchainAppV1.t.sol +++ b/packages/contracts-communication/test/apps/InterchainAppV1.t.sol @@ -127,8 +127,10 @@ abstract contract InterchainAppV1Test is Test, AbstractICAppEvents, InterchainAp ); } - function expectRevertBalanceBelowMin(uint256 actual, uint256 required) internal { - vm.expectRevert(abi.encodeWithSelector(AbstractICApp.InterchainApp__BalanceBelowMin.selector, actual, required)); + function expectRevertBalanceBelowMin(uint256 balance, uint256 minValue) internal { + vm.expectRevert( + abi.encodeWithSelector(AbstractICApp.InterchainApp__BalanceBelowMin.selector, balance, minValue) + ); } function expectRevertInterchainClientAlreadyAdded(address client) internal { diff --git a/packages/contracts-communication/test/libs/AppConfigLib.t.sol b/packages/contracts-communication/test/libs/AppConfigLib.t.sol index bb7c8aba09..803e6c5668 100644 --- a/packages/contracts-communication/test/libs/AppConfigLib.t.sol +++ b/packages/contracts-communication/test/libs/AppConfigLib.t.sol @@ -43,9 +43,9 @@ contract AppConfigLibTest is Test { function test_decodeAppConfigV1_revertLowerVersion() public { AppConfigV1 memory appConfig = AppConfigV1(3, 100, 0, address(0)); - uint16 VersionInvalid = AppConfigLib.APP_CONFIG_V1 - 1; - bytes memory encoded = VersionedPayloadLib.encodeVersionedPayload(VersionInvalid, abi.encode(appConfig)); - vm.expectRevert(abi.encodeWithSelector(AppConfigLib.AppConfigLib__VersionInvalid.selector, VersionInvalid)); + uint16 versionInvalid = AppConfigLib.APP_CONFIG_V1 - 1; + bytes memory encoded = VersionedPayloadLib.encodeVersionedPayload(versionInvalid, abi.encode(appConfig)); + vm.expectRevert(abi.encodeWithSelector(AppConfigLib.AppConfigLib__VersionInvalid.selector, versionInvalid)); libHarness.decodeAppConfigV1(encoded); } } diff --git a/packages/contracts-communication/test/libs/OptionsLib.t.sol b/packages/contracts-communication/test/libs/OptionsLib.t.sol index 12eb18cc94..28f8ec59a0 100644 --- a/packages/contracts-communication/test/libs/OptionsLib.t.sol +++ b/packages/contracts-communication/test/libs/OptionsLib.t.sol @@ -39,9 +39,9 @@ contract OptionsLibTest is Test { function test_decodeOptionsV1_revertLowerVersion() public { OptionsV1 memory options = OptionsV1(200_000, 100_000); - uint16 VersionInvalid = OptionsLib.OPTIONS_V1 - 1; - bytes memory encoded = VersionedPayloadLib.encodeVersionedPayload(VersionInvalid, abi.encode(options)); - vm.expectRevert(abi.encodeWithSelector(OptionsLib.OptionsLib__VersionInvalid.selector, VersionInvalid)); + uint16 versionInvalid = OptionsLib.OPTIONS_V1 - 1; + bytes memory encoded = VersionedPayloadLib.encodeVersionedPayload(versionInvalid, abi.encode(options)); + vm.expectRevert(abi.encodeWithSelector(OptionsLib.OptionsLib__VersionInvalid.selector, versionInvalid)); libHarness.decodeOptionsV1(encoded); } } diff --git a/packages/contracts-communication/test/modules/SynapseModule.Management.t.sol b/packages/contracts-communication/test/modules/SynapseModule.Management.t.sol index c8cfa9b348..f34273d8a4 100644 --- a/packages/contracts-communication/test/modules/SynapseModule.Management.t.sol +++ b/packages/contracts-communication/test/modules/SynapseModule.Management.t.sol @@ -326,7 +326,9 @@ contract SynapseModuleManagementTest is Test, ClaimableFeesEvents, SynapseModule } function test_setGasOracle_revert_zeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ISynapseModule.SynapseModule__GasOracleNotContract.selector, address(0))); + bytes memory revertData = + abi.encodeWithSelector(ISynapseModule.SynapseModule__GasOracleNotContract.selector, address(0)); + vm.expectRevert(revertData); vm.prank(owner); module.setGasOracle(address(0)); }