Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: module data passing #2205

Merged
merged 11 commits into from
Mar 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ abstract contract SynapseModuleEvents {

event ClaimFeeFractionChanged(uint256 claimFeeFraction);
event FeesClaimed(address feeCollector, uint256 collectedFees, address claimer, uint256 claimerFee);

event GasDataSent(uint256 dstChainId, bytes data);
event GasDataReceived(uint256 srcChainId, bytes data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
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(uint256 srcChainId, bytes calldata data) external;

/// @notice Gets the gas data for the local chain.
function getLocalGasData() external view returns (bytes memory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface ISynapseModule is IInterchainModule {
error SynapseModule__ClaimFeeFractionExceedsMax(uint256 claimFeeFraction);
error SynapseModule__FeeCollectorNotSet();
error SynapseModule__GasOracleNotContract(address gasOracle);
error SynapseModule__GasOracleNotSet();
error SynapseModule__NoFeesToClaim();

// ═══════════════════════════════════════════════ PERMISSIONED ════════════════════════════════════════════════════
Expand Down
32 changes: 32 additions & 0 deletions packages/contracts-communication/contracts/libs/ModuleEntry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

library ModuleEntryLib {
/// @notice Encodes the InterchainEntry and the auxiliary module data into a single bytes array
/// @param entry The InterchainEntry to encode
/// @param moduleData The auxiliary module data to encode
function encodeModuleEntry(
InterchainEntry memory entry,
bytes memory moduleData
)
internal
pure
returns (bytes memory)
{
return abi.encode(entry, moduleData);
}

/// @notice Decodes the bytes array into the InterchainEntry and the auxiliary module data
/// @param encodedModuleEntry The bytes array to decode
/// @return entry The decoded InterchainEntry
/// @return moduleData The decoded auxiliary module data
function decodeModuleEntry(bytes memory encodedModuleEntry)
internal
pure
returns (InterchainEntry memory entry, bytes memory moduleData)
{
return abi.decode(encodedModuleEntry, (InterchainEntry, bytes));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {InterchainModuleEvents} from "../events/InterchainModuleEvents.sol";
import {IInterchainDB} from "../interfaces/IInterchainDB.sol";
import {IInterchainModule} from "../interfaces/IInterchainModule.sol";

import {InterchainEntry} from "../libs/InterchainEntry.sol";
import {InterchainEntry, ModuleEntryLib} from "../libs/ModuleEntry.sol";

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

Expand All @@ -32,7 +32,8 @@ abstract contract InterchainModule is InterchainModuleEvents, IInterchainModule
if (msg.value < requiredFee) {
revert InterchainModule__InsufficientFee({actual: msg.value, required: requiredFee});
}
bytes memory encodedEntry = abi.encode(entry);
bytes memory moduleData = _fillModuleData(destChainId, entry.dbNonce);
bytes memory encodedEntry = ModuleEntryLib.encodeModuleEntry(entry, moduleData);
bytes32 ethSignedEntryHash = MessageHashUtils.toEthSignedMessageHash(keccak256(encodedEntry));
_requestVerification(destChainId, encodedEntry);
emit VerificationRequested(destChainId, encodedEntry, ethSignedEntryHash);
Expand All @@ -46,20 +47,27 @@ abstract contract InterchainModule is InterchainModuleEvents, IInterchainModule
/// @dev Should be called once the Module has verified the entry and needs to signal this
/// to the InterchainDB.
function _verifyEntry(bytes memory encodedEntry) internal {
InterchainEntry memory entry = abi.decode(encodedEntry, (InterchainEntry));
(InterchainEntry memory entry, bytes memory moduleData) = ModuleEntryLib.decodeModuleEntry(encodedEntry);
if (entry.srcChainId == block.chainid) {
revert InterchainModule__SameChainId();
}
IInterchainDB(INTERCHAIN_DB).verifyEntry(entry);
_receiveModuleData(entry.srcChainId, entry.dbNonce, moduleData);
emit EntryVerified(
entry.srcChainId, encodedEntry, MessageHashUtils.toEthSignedMessageHash(keccak256(encodedEntry))
);
}

// solhint-disable no-empty-blocks
/// @dev Internal logic to request the verification of an entry on the destination chain.
// solhint-disable-next-line no-empty-blocks
function _requestVerification(uint256 destChainId, bytes memory encodedEntry) internal virtual {}

/// @dev Internal logic to fill the module data for the specified destination chain.
function _fillModuleData(uint256 destChainId, uint256 dbNonce) internal virtual returns (bytes memory) {}

/// @dev Internal logic to handle the auxiliary module data relayed from the remote chain.
function _receiveModuleData(uint256 srcChainId, uint256 dbNonce, bytes memory moduleData) internal virtual {}

/// @dev Internal logic to get the module fee for verifying an entry on the specified destination chain.
function _getModuleFee(uint256 destChainId) internal view virtual returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.20;
import {InterchainModule} from "./InterchainModule.sol";

import {SynapseModuleEvents} from "../events/SynapseModuleEvents.sol";
import {IGasOracle} from "../interfaces/IGasOracle.sol";
import {ISynapseGasOracle} from "../interfaces/ISynapseGasOracle.sol";
import {ISynapseModule} from "../interfaces/ISynapseModule.sol";

import {ThresholdECDSA} from "../libs/ThresholdECDSA.sol";
Expand All @@ -26,6 +26,10 @@ contract SynapseModule is InterchainModule, Ownable, SynapseModuleEvents, ISynap
uint256 internal _claimFeeFraction;
/// @dev Gas limit for the verifyEntry function on the remote chain.
mapping(uint256 chainId => uint256 gasLimit) internal _verifyGasLimit;
/// @dev Hash of the last gas data sent to the remote chain.
mapping(uint256 chainId => bytes32 gasDataHash) internal _lastGasDataHash;
/// @dev Nonce of the last gas data received from the remote chain.
mapping(uint256 chainid => uint256 gasDataNonce) internal _lastGasDataNonce;
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

/// @inheritdoc ISynapseModule
address public feeCollector;
Expand Down Expand Up @@ -173,6 +177,45 @@ contract SynapseModule is InterchainModule, Ownable, SynapseModuleEvents, ISynap
emit VerifierRemoved(verifier);
}

/// @dev Internal logic to fill the module data for the specified destination chain.
function _fillModuleData(
uint256 destChainId,
uint256 dbNonce
)
internal
override
returns (bytes memory moduleData)
{
moduleData = _getSynapseGasOracle().getLocalGasData();
// Exit early if data is empty
if (moduleData.length == 0) {
return moduleData;
}
bytes32 dataHash = keccak256(moduleData);
// Don't send the same data twice
if (dataHash == _lastGasDataHash[destChainId]) {
moduleData = "";
} else {
_lastGasDataHash[destChainId] = dataHash;
emit GasDataSent(destChainId, moduleData);
}
}

/// @dev Internal logic to handle the auxiliary module data relayed from the remote chain.
function _receiveModuleData(uint256 srcChainId, uint256 dbNonce, bytes memory moduleData) internal override {
// Exit early if data is empty
if (moduleData.length == 0) {
return;
}
// Don't process outdated data
uint256 lastNonce = _lastGasDataNonce[srcChainId];
if (lastNonce == 0 || lastNonce < dbNonce) {
_lastGasDataNonce[srcChainId] = dbNonce;
_getSynapseGasOracle().receiveRemoteGasData(srcChainId, moduleData);
emit GasDataReceived(srcChainId, moduleData);
}
}

// ══════════════════════════════════════════════ INTERNAL VIEWS ═══════════════════════════════════════════════════

/// @dev Internal logic to get the module fee for verifying an entry on the specified destination chain.
Expand All @@ -183,10 +226,18 @@ contract SynapseModule is InterchainModule, Ownable, SynapseModuleEvents, ISynap
// entry is 32 (length) + 32*4 (fields) = 160
// signatures: 32 (length) + 65*threshold (padded up to be a multiple of 32 bytes)
// Total formula is: 4 + 32 (entry offset) + 32 (signatures offset) + 160 + 32
return IGasOracle(gasOracle).estimateTxCostInLocalUnits({
return _getSynapseGasOracle().estimateTxCostInLocalUnits({
remoteChainId: destChainId,
gasLimit: getVerifyGasLimit(destChainId),
calldataSize: 292 + 64 * getThreshold()
});
}

/// @dev Internal logic to get the Synapse Gas Oracle. Reverts if the gas oracle is not set.
function _getSynapseGasOracle() internal view returns (ISynapseGasOracle synapseGasOracle) {
synapseGasOracle = ISynapseGasOracle(gasOracle);
if (address(synapseGasOracle) == address(0)) {
revert SynapseModule__GasOracleNotSet();
}
}
}
103 changes: 0 additions & 103 deletions packages/contracts-communication/script/MessagingBase.s.sol

This file was deleted.

2 changes: 1 addition & 1 deletion packages/contracts-communication/script/testnet-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ yarn fsr script/deploy/DeploySynapseModule.s.sol "$chainName" "$walletName" "$@"
yarn fsr-str script/deploy/DeployWithMsgSender.s.sol "$chainName" "$walletName" "ExecutionFees" "$@"
yarn fsr-str script/deploy/DeployWithMsgSender.s.sol "$chainName" "$walletName" "ExecutionService" "$@"

yarn fsr-str script/deploy/DeployNoArgs.s.sol "$chainName" "$walletName" "GasOracleMock" "$@"
yarn fsr-str script/deploy/DeployNoArgs.s.sol "$chainName" "$walletName" "SynapseGasOracleMock" "$@"
6 changes: 3 additions & 3 deletions packages/contracts-communication/test/ExecutionService.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ pragma solidity 0.8.20;

import {ExecutionService, ExecutionServiceEvents, IExecutionService} from "../contracts/ExecutionService.sol";
import {Test} from "forge-std/Test.sol";
import {GasOracleMock} from "./mocks/GasOracleMock.sol";
import {SynapseGasOracleMock} from "./mocks/SynapseGasOracleMock.sol";

contract ExecutionServiceTest is ExecutionServiceEvents, Test {
ExecutionService public executionService;
GasOracleMock public gasOracle;
SynapseGasOracleMock public gasOracle;

address icClient = address(0x123);
address executorEOA = address(0x456);
address owner = makeAddr("Owner");

function setUp() public {
gasOracle = new GasOracleMock();
gasOracle = new SynapseGasOracleMock();
executionService = new ExecutionService(address(this));
executionService.setInterchainClient(icClient);
executionService.setExecutorEOA(executorEOA);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {InterchainEntry, ModuleEntryLib} from "../../contracts/libs/ModuleEntry.sol";

contract ModuleEntryLibHarness {
function encodeModuleEntry(
InterchainEntry memory entry,
bytes memory moduleData
)
external
pure
returns (bytes memory)
{
return ModuleEntryLib.encodeModuleEntry(entry, moduleData);
}

function decodeModuleEntry(bytes memory encodedModuleEntry)
external
pure
returns (InterchainEntry memory, bytes memory)
{
return ModuleEntryLib.decodeModuleEntry(encodedModuleEntry);
}
}
29 changes: 29 additions & 0 deletions packages/contracts-communication/test/libs/ModuleEntryLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {InterchainEntry, ModuleEntryLibHarness} from "../harnesses/ModuleEntryLibHarness.sol";

import {Test} from "forge-std/Test.sol";

// solhint-disable func-name-mixedcase
contract ModuleEntryLibTest is Test {
ModuleEntryLibHarness public libHarness;

function setUp() public {
libHarness = new ModuleEntryLibHarness();
}

function assertEq(InterchainEntry memory actual, InterchainEntry memory expected) public {
assertEq(actual.srcChainId, expected.srcChainId, "!srcChainId");
assertEq(actual.dbNonce, expected.dbNonce, "!dbNonce");
assertEq(actual.srcWriter, expected.srcWriter, "!srcWriter");
assertEq(actual.dataHash, expected.dataHash, "!dataHash");
}

function test_roundTrip(InterchainEntry memory entry, bytes memory moduleData) public {
bytes memory encoded = libHarness.encodeModuleEntry(entry, moduleData);
(InterchainEntry memory decodedEntry, bytes memory decodedModuleData) = libHarness.decodeModuleEntry(encoded);
assertEq(decodedEntry, entry);
assertEq(decodedModuleData, moduleData);
}
}
Loading
Loading