diff --git a/packages/contracts-communication/contracts/InterchainClientV1.sol b/packages/contracts-communication/contracts/InterchainClientV1.sol index 777c9de340..22ff5e6621 100644 --- a/packages/contracts-communication/contracts/InterchainClientV1.sol +++ b/packages/contracts-communication/contracts/InterchainClientV1.sol @@ -1,17 +1,17 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IInterchainDB} from "./interfaces/IInterchainDB.sol"; - +import {IExecutionFees} from "./interfaces/IExecutionFees.sol"; +import {IExecutionService} from "./interfaces/IExecutionService.sol"; import {IInterchainApp} from "./interfaces/IInterchainApp.sol"; - -import {InterchainEntry} from "./libs/InterchainEntry.sol"; - import {IInterchainClientV1} from "./interfaces/IInterchainClientV1.sol"; +import {IInterchainDB} from "./interfaces/IInterchainDB.sol"; +import {InterchainEntry} from "./libs/InterchainEntry.sol"; +import {OptionsLib, OptionsV1} from "./libs/Options.sol"; import {TypeCasts} from "./libs/TypeCasts.sol"; -import {OptionsLib, OptionsV1} from "./libs/Options.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /** * @title InterchainClientV1 @@ -22,24 +22,29 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 { uint64 public clientNonce; address public interchainDB; + address public executionFees; mapping(bytes32 => bool) public executedTransactions; // Chain ID => Bytes32 Address of src clients mapping(uint256 => bytes32) public linkedClients; - // TODO: Add permissioning + constructor() Ownable(msg.sender) {} + // @inheritdoc IInterchainClientV1 - function setLinkedClient(uint256 chainId, bytes32 client) public onlyOwner { - linkedClients[chainId] = client; + function setExecutionFees(address executionFees_) public onlyOwner { + executionFees = executionFees_; } - constructor() Ownable(msg.sender) {} - // @inheritdoc IInterchainClientV1 function setInterchainDB(address _interchainDB) public onlyOwner { interchainDB = _interchainDB; } + // @inheritdoc IInterchainClientV1 + function setLinkedClient(uint256 chainId, bytes32 client) public onlyOwner { + linkedClients[chainId] = client; + } + /** * @notice Emitted when an interchain transaction is sent. */ @@ -103,8 +108,9 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 { // TODO: Calculate Gas Pricing per module and charge fees // @inheritdoc IInterchainClientV1 function interchainSend( - bytes32 receiver, uint256 dstChainId, + bytes32 receiver, + address srcExecutionService, bytes calldata message, bytes calldata options, address[] calldata srcModules @@ -112,7 +118,9 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 { public payable { - uint256 totalModuleFees = msg.value; + uint256 verificationFees = IInterchainDB(interchainDB).getInterchainFee(dstChainId, srcModules); + // TODO: should check msg.value >= totalModuleFees + uint256 executionFee = msg.value - verificationFees; InterchainTransaction memory icTx = InterchainTransaction({ srcSender: TypeCasts.addressToBytes32(msg.sender), @@ -131,11 +139,18 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 { ); icTx.transactionId = transactionId; - uint256 dbWriterNonce = IInterchainDB(interchainDB).writeEntryWithVerification{value: totalModuleFees}( + icTx.dbWriterNonce = IInterchainDB(interchainDB).writeEntryWithVerification{value: verificationFees}( icTx.dstChainId, icTx.transactionId, srcModules ); - icTx.dbWriterNonce = dbWriterNonce; - + IExecutionService(srcExecutionService).requestExecution({ + dstChainId: dstChainId, + // TODO: this should be encodedTx.length + txPayloadSize: message.length, + transactionId: transactionId, + executionFee: executionFee, + options: options + }); + IExecutionFees(executionFees).addExecutionFee{value: executionFee}(dstChainId, transactionId); emit InterchainTransactionSent( icTx.srcSender, icTx.srcChainId, diff --git a/packages/contracts-communication/contracts/interfaces/IExecutor.sol b/packages/contracts-communication/contracts/interfaces/IExecutionService.sol similarity index 98% rename from packages/contracts-communication/contracts/interfaces/IExecutor.sol rename to packages/contracts-communication/contracts/interfaces/IExecutionService.sol index 003512f2ff..619086b5bd 100644 --- a/packages/contracts-communication/contracts/interfaces/IExecutor.sol +++ b/packages/contracts-communication/contracts/interfaces/IExecutionService.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface IExecutor { +interface IExecutionService { /// @notice Request the execution of an Interchain Transaction on a remote chain. /// 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: diff --git a/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol b/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol index 1197dbe809..1427bcee81 100644 --- a/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol +++ b/packages/contracts-communication/contracts/interfaces/IInterchainClientV1.sol @@ -2,6 +2,13 @@ pragma solidity ^0.8.0; interface IInterchainClientV1 { + /** + * @notice Sets the address of the ExecutionFees contract. + * @dev Only callable by the contract owner or an authorized account. + * @param executionFees_ The address of the ExecutionFees contract. + */ + function setExecutionFees(address executionFees_) 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. @@ -19,16 +26,22 @@ interface 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. - * @param receiver The address of the receiver on the destination chain. + * @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 message The message being sent. * @param options Execution options for the message sent, encoded as bytes, currently primarily gas limit + native gas drop. * @param srcModules The source modules involved in the message sending. */ function interchainSend( - bytes32 receiver, uint256 dstChainId, + bytes32 receiver, + address srcExecutionService, bytes calldata message, bytes calldata options, address[] calldata srcModules diff --git a/packages/contracts-communication/test/InterchainClientV1Test.t.sol b/packages/contracts-communication/test/InterchainClientV1Test.t.sol index a9d0d19451..f6371faa3c 100644 --- a/packages/contracts-communication/test/InterchainClientV1Test.t.sol +++ b/packages/contracts-communication/test/InterchainClientV1Test.t.sol @@ -10,12 +10,17 @@ import {TypeCasts} from "../contracts/libs/TypeCasts.sol"; import {InterchainClientV1Harness} from "./harnesses/InterchainClientV1Harness.sol"; +import {ExecutionFeesMock} from "./mocks/ExecutionFeesMock.sol"; +import {ExecutionServiceMock} from "./mocks/ExecutionServiceMock.sol"; import {InterchainAppMock} from "./mocks/InterchainAppMock.sol"; import {InterchainModuleMock} from "./mocks/InterchainModuleMock.sol"; import {Test} from "forge-std/Test.sol"; contract InterchainClientV1Test is Test { + ExecutionFeesMock executionFees; + ExecutionServiceMock executionService; + InterchainClientV1Harness icClient; InterchainDB icDB; InterchainAppMock icApp; @@ -31,9 +36,12 @@ contract InterchainClientV1Test is Test { function setUp() public { vm.startPrank(contractOwner); + executionFees = new ExecutionFeesMock(); + executionService = new ExecutionServiceMock(); icClient = new InterchainClientV1Harness(); icDB = new InterchainDB(); icClient.setInterchainDB(address(icDB)); + icClient.setExecutionFees(address(executionFees)); icModule = new InterchainModuleMock(); icApp = new InterchainAppMock(); @@ -110,7 +118,9 @@ contract InterchainClientV1Test is Test { bytes32 transactionID = keccak256( abi.encode(TypeCasts.addressToBytes32(msg.sender), block.chainid, receiver, DST_CHAIN_ID, message, nonce) ); - icClient.interchainSend{value: totalModuleFees}(receiver, DST_CHAIN_ID, message, options, srcModules); + icClient.interchainSend{value: totalModuleFees}( + DST_CHAIN_ID, receiver, address(executionService), message, options, srcModules + ); // TODO: should check the transaction ID? transactionID; } diff --git a/packages/contracts-communication/test/mocks/ExecutionFeesMock.sol b/packages/contracts-communication/test/mocks/ExecutionFeesMock.sol new file mode 100644 index 0000000000..0c22f5c7a3 --- /dev/null +++ b/packages/contracts-communication/test/mocks/ExecutionFeesMock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {IExecutionFees} from "../../contracts/interfaces/IExecutionFees.sol"; + +contract ExecutionFeesMock is IExecutionFees { + function addExecutionFee(uint256 dstChainId, bytes32 transactionId) external payable {} + + function recordExecutor(uint256 dstChainId, bytes32 transactionId, address executor) external {} + + function claimExecutionFees() external {} + + function getAccumulatedRewards(address executor) external view returns (uint256 accumulated) {} + + function getUnclaimedRewards(address executor) external view returns (uint256 unclaimed) {} +} diff --git a/packages/contracts-communication/test/mocks/ExecutionServiceMock.sol b/packages/contracts-communication/test/mocks/ExecutionServiceMock.sol new file mode 100644 index 0000000000..711fdfdd9f --- /dev/null +++ b/packages/contracts-communication/test/mocks/ExecutionServiceMock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {IExecutionService} from "../../contracts/interfaces/IExecutionService.sol"; + +contract ExecutionServiceMock is IExecutionService { + function requestExecution( + uint256 dstChainId, + uint256 txPayloadSize, + bytes32 transactionId, + uint256 executionFee, + bytes memory options + ) + external + {} + + function getExecutionFee( + uint256 dstChainId, + uint256 txPayloadSize, + bytes memory options + ) + external + view + returns (uint256) + {} +}