From 3afb7f9281d9090838b79106810299436e551a4e Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 6 Dec 2024 18:23:41 +0100 Subject: [PATCH 01/12] feat: Add Linea network support --- lib/solidity-utils | 2 +- .../adapters/linea/ILineaAdapter.sol | 34 ++++++ src/contracts/adapters/linea/LineaAdapter.sol | 103 ++++++++++++++++++ .../linea/interfaces/IMessageService.sol | 20 ++++ src/contracts/libs/Errors.sol | 1 + 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/contracts/adapters/linea/ILineaAdapter.sol create mode 100644 src/contracts/adapters/linea/LineaAdapter.sol create mode 100644 src/contracts/adapters/linea/interfaces/IMessageService.sol diff --git a/lib/solidity-utils b/lib/solidity-utils index 5d728d5d..6f72233c 160000 --- a/lib/solidity-utils +++ b/lib/solidity-utils @@ -1 +1 @@ -Subproject commit 5d728d5d4c43508b46223fffbae94ee16f61f977 +Subproject commit 6f72233c112ed0df5b6c3e02a2a3d04a56e3b2bc diff --git a/src/contracts/adapters/linea/ILineaAdapter.sol b/src/contracts/adapters/linea/ILineaAdapter.sol new file mode 100644 index 00000000..bb19d1cd --- /dev/null +++ b/src/contracts/adapters/linea/ILineaAdapter.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title ILineaAdapter + * @author BGD Labs + * @notice interface containing the events, objects and method definitions used in the Linea bridge adapter + */ +interface ILineaAdapter { + /** + * @notice method to get the Linea message service address + * @return address of the Linea message service + */ + function LINEA_MESSAGE_SERVICE() external view returns (address); + + /** + * @notice method to know if a destination chain is supported by adapter + * @return flag indicating if the destination chain is supported by the adapter + */ + function isDestinationChainIdSupported(uint256 chainId) external view returns (bool); + + /** + * @notice method called by Linea message service with the bridged message + * @param message bytes containing the bridged information + */ + function receiveMessage(bytes memory message) external; + + /** + * @notice method to get the origin chain id + * @return id of the chain where the messages originate. + * @dev this method is needed as Optimism does not pass the origin chain + */ + function getOriginChainId() external view returns (uint256); +} diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol new file mode 100644 index 00000000..064c5ef1 --- /dev/null +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {IMessageService} from './interfaces/IMessageService.sol'; +import {BaseAdapter, IBaseAdapter} from '../BaseAdapter.sol'; +import {ChainIds} from 'solidity-utils/contracts/utils/ChainHelpers.sol'; +import {Errors} from '../../libs/Errors.sol'; +import {ILineaAdapter} from './ILineaAdapter.sol'; +import {SafeCast} from 'solidity-utils/contracts/oz-common/SafeCast.sol'; + +/** + * @title LineaAdapter + * @author BGD Labs + * @notice Optimism bridge adapter. Used to send and receive messages cross chain between Ethereum and Optimism + * @dev it uses the eth balance of CrossChainController contract to pay for message bridging as the method to bridge + is called via delegate call + * @dev note that this adapter is can only be used for the communication path ETHEREUM -> LINEA + */ +contract LineaAdapter is ILineaAdapter, BaseAdapter { + /// @inheritdoc ILineaAdapter + address public immutable LINEA_MESSAGE_SERVICE; + + /** + * @notice only calls from the set ovm are accepted. + */ + modifier onlyLineaMessageService() { + require(msg.sender == address(LINEA_MESSAGE_SERVICE), Errors.CALLER_NOT_LINEA_MESSAGE_SERVICE); + _; + } + + /** + * @param crossChainController address of the cross chain controller that will use this bridge adapter + * @param lineaMessageService Linea entry point address + * @param providerGasLimit base gas limit used by the bridge adapter + * @param adapterName string indicating the adapter name + * @param trustedRemotes list of remote configurations to set as trusted + */ + constructor( + address crossChainController, + address lineaMessageService, + uint256 providerGasLimit, + string memory adapterName, + TrustedRemotesConfig[] memory trustedRemotes + ) BaseAdapter(crossChainController, providerGasLimit, adapterName, trustedRemotes) { + LINEA_MESSAGE_SERVICE = lineaMessageService; + } + + /// @inheritdoc IBaseAdapter + function forwardMessage( + address receiver, + uint256 executionGasLimit, + uint256 destinationChainId, + bytes calldata message + ) external virtual returns (address, uint256) { + require( + isDestinationChainIdSupported(destinationChainId), + Errors.DESTINATION_CHAIN_ID_NOT_SUPPORTED + ); + require(receiver != address(0), Errors.RECEIVER_NOT_SET); + + uint256 cost = 0; // TODO: find how to calculate cost + require(cost <= address(this).balance, Errors.NOT_ENOUGH_VALUE_TO_PAY_BRIDGE_FEES); + + IMessageService(LINEA_MESSAGE_SERVICE).sendMessage{value: cost}( + receiver, + _fee, + abi.encodeWithSelector(ILineaAdapter.receiveMessage.selector, message) + ); + return (LINEA_MESSAGE_SERVICE, 0); + } + + /// @inheritdoc ILineaAdapter + function ovmReceive(bytes calldata message) external onlyLineaMessageService { + uint256 originChainId = getOriginChainId(); + address srcAddress = IMessageService(LINEA_MESSAGE_SERVICE).sender(); + require( + _trustedRemotes[originChainId] == srcAddress && srcAddress != address(0), + Errors.REMOTE_NOT_TRUSTED + ); + + _registerReceivedMessage(message, originChainId); + } + + /// @inheritdoc ILineaAdapter + function getOriginChainId() public pure virtual returns (uint256) { + return ChainIds.ETHEREUM; + } + + /// @inheritdoc ILineaAdapter + function isDestinationChainIdSupported(uint256 chainId) public view virtual returns (bool) { + return chainId == ChainIds.LINEA; + } + + /// @inheritdoc IBaseAdapter + function nativeToInfraChainId(uint256 nativeChainId) public pure override returns (uint256) { + return nativeChainId; + } + + /// @inheritdoc IBaseAdapter + function infraToNativeChainId(uint256 infraChainId) public pure override returns (uint256) { + return infraChainId; + } +} diff --git a/src/contracts/adapters/linea/interfaces/IMessageService.sol b/src/contracts/adapters/linea/interfaces/IMessageService.sol new file mode 100644 index 00000000..97285424 --- /dev/null +++ b/src/contracts/adapters/linea/interfaces/IMessageService.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// Modified from: https://docs.linea.build/get-started/concepts/message-service#interface-imessageservicesol +pragma solidity ^0.8.0; + +interface IMessageService { + /** + * @notice Sends a message for transporting from the given chain. + * @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain. + * @param _to The destination address on the destination chain. + * @param _fee The message service fee on the origin chain. + * @param _calldata The calldata used by the destination message service to call the destination contract. + */ + function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable; + + /** + * @notice Returns the original sender of the message on the origin layer. + * @return The original sender of the message on the origin layer. + */ + function sender() external view returns (address); +} diff --git a/src/contracts/libs/Errors.sol b/src/contracts/libs/Errors.sol index 7ca6c1c9..040f57da 100644 --- a/src/contracts/libs/Errors.sol +++ b/src/contracts/libs/Errors.sol @@ -51,4 +51,5 @@ library Errors { string public constant CALLER_NOT_WORMHOLE_RELAYER = '42'; // caller must be the Wormhole relayer string public constant ZK_SYNC_BRIDGE_HUB_CANT_BE_ADDRESS_0 = '43'; // ZkSync Bridgehub can not be address 0 string public constant CL_GAS_PRICE_ORACLE_CANT_BE_ADDRESS_0 = '44'; // ChainLink gas price oracle can not be address 0 + string public constant CALLER_NOT_LINEA_MESSAGE_SERVICE = '45'; // caller must be the Linea message service } From 5ef84a047712c91b9b959e0a8367bd245cd91251 Mon Sep 17 00:00:00 2001 From: sendra Date: Mon, 9 Dec 2024 12:14:38 +0100 Subject: [PATCH 02/12] fix: added linea adapter deploy scripts --- scripts/Adapters/DeployLineaAdapter.sol | 55 ++++++++++++++++++++ scripts/contract_extensions/LineaAdapter.sol | 41 +++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 scripts/Adapters/DeployLineaAdapter.sol create mode 100644 scripts/contract_extensions/LineaAdapter.sol diff --git a/scripts/Adapters/DeployLineaAdapter.sol b/scripts/Adapters/DeployLineaAdapter.sol new file mode 100644 index 00000000..9dfcdc63 --- /dev/null +++ b/scripts/Adapters/DeployLineaAdapter.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import './BaseAdapterScript.sol'; +import {LineaAdapter, ILineaAdapter} from '../../src/contracts/adapters/linea/LineaAdapter.sol'; +import {LineaAdapterTestnet} from '../contract_extensions/LineaAdapter.sol'; + +library LineaAdapterDeploymentHelper { + struct LineaAdapterArgs { + BaseAdapterArgs baseArgs; + address lineaMessageService; + } + + function getAdapterCode(LineaAdapterArgs memory lineaArgs) internal pure returns (bytes memory) { + bytes memory creationCode = lineaArgs.baseArgs.isTestnet + ? type(LineaAdapterTestnet).creationCode + : type(LineaAdapter).creationCode; + + return + abi.encodePacked( + creationCode, + abi.encode( + lineaArgs.baseArgs.crossChainController, + lineaArgs.lineaMessageService, + lineaArgs.baseArgs.providerGasLimit, + lineaArgs.baseArgs.trustedRemotes + ) + ); + } +} + +abstract contract BaseDeployLineaAdapter is BaseAdapterScript { + function LINEA_MESSAGE_SERVICE() internal view virtual returns (address) { + return address(0); + } + + function PROVIDER_GAS_LIMIT() internal view virtual override returns (uint256) { + return 150_000; + } + + function _getAdapterByteCode( + BaseAdapterArgs memory baseArgs + ) internal view override returns (bytes memory) { + require(baseArgs.trustedRemotes.length == 1, 'Linea adapter can only have one remote'); + require(LINEA_MESSAGE_SERVICE() != address(0), 'Linea message service can not be 0'); + + return + LineaAdapterDeploymentHelper.getAdapterCode( + LineaAdapterDeploymentHelper.LineaAdapterArgs({ + baseArgs: baseArgs, + inbox: LINEA_MESSAGE_SERVICE() + }) + ); + } +} diff --git a/scripts/contract_extensions/LineaAdapter.sol b/scripts/contract_extensions/LineaAdapter.sol new file mode 100644 index 00000000..cff10195 --- /dev/null +++ b/scripts/contract_extensions/LineaAdapter.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.8; + +import {TestNetChainIds} from 'solidity-utils/contracts/utils/ChainHelpers.sol'; +import {ILineaAdapter, LineaAdapter} from '../../src/contracts/adapters/linea/LineaAdapter.sol'; + +/** + * @title LineaAdapterTestnet + * @author BGD Labs + */ +contract LineaAdapterTestnet is LineaAdapter { + /** + * @param crossChainController address of the cross chain controller that will use this bridge adapter + * @param lineaMessageService linea entry point address + * @param trustedRemotes list of remote configurations to set as trusted + */ + constructor( + address crossChainController, + address lineaMessageService, + uint256 providerGasLimit, + TrustedRemotesConfig[] memory trustedRemotes + ) + LineaAdapter( + crossChainController, + lineaMessageService, + providerGasLimit, + trustedRemotes, + 'Linea native adapter' + ) + {} + + /// @inheritdoc ILineaAdapter + function isDestinationChainIdSupported(uint256 chainId) public pure override returns (bool) { + return chainId == TestNetChainIds.LINEA_SEPOLIA; + } + + /// @inheritdoc ILineaAdapter + function getOriginChainId() public pure override returns (uint256) { + return TestNetChainIds.ETHEREUM_SEPOLIA; + } +} From df0bd2e13f05c9a6743508c110586056d8b01af6 Mon Sep 17 00:00:00 2001 From: sendra Date: Tue, 10 Dec 2024 13:39:26 +0100 Subject: [PATCH 03/12] fix: set fee to 0 --- src/contracts/adapters/linea/LineaAdapter.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol index 064c5ef1..beaae03f 100644 --- a/src/contracts/adapters/linea/LineaAdapter.sol +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -58,12 +58,11 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { ); require(receiver != address(0), Errors.RECEIVER_NOT_SET); - uint256 cost = 0; // TODO: find how to calculate cost - require(cost <= address(this).balance, Errors.NOT_ENOUGH_VALUE_TO_PAY_BRIDGE_FEES); - - IMessageService(LINEA_MESSAGE_SERVICE).sendMessage{value: cost}( + // @dev we set _fee to 0 because for now we will do the claim manually. Until an automated way of getting the + // price is implemented by Linea + IMessageService(LINEA_MESSAGE_SERVICE).sendMessage( receiver, - _fee, + 0, abi.encodeWithSelector(ILineaAdapter.receiveMessage.selector, message) ); return (LINEA_MESSAGE_SERVICE, 0); From a8acb2370d089fe0cc9fa055c241764e4833c91f Mon Sep 17 00:00:00 2001 From: sendra Date: Tue, 10 Dec 2024 15:35:26 +0100 Subject: [PATCH 04/12] fix: added unit tests --- scripts/Adapters/DeployLineaAdapter.sol | 2 +- scripts/contract_extensions/LineaAdapter.sol | 10 +- src/contracts/adapters/linea/LineaAdapter.sol | 9 +- src/contracts/libs/Errors.sol | 1 + tests/adapters/LineaAdapter.t.sol | 275 ++++++++++++++++++ 5 files changed, 282 insertions(+), 15 deletions(-) create mode 100644 tests/adapters/LineaAdapter.t.sol diff --git a/scripts/Adapters/DeployLineaAdapter.sol b/scripts/Adapters/DeployLineaAdapter.sol index 9dfcdc63..0ba4ca90 100644 --- a/scripts/Adapters/DeployLineaAdapter.sol +++ b/scripts/Adapters/DeployLineaAdapter.sol @@ -48,7 +48,7 @@ abstract contract BaseDeployLineaAdapter is BaseAdapterScript { LineaAdapterDeploymentHelper.getAdapterCode( LineaAdapterDeploymentHelper.LineaAdapterArgs({ baseArgs: baseArgs, - inbox: LINEA_MESSAGE_SERVICE() + lineaMessageService: LINEA_MESSAGE_SERVICE() }) ); } diff --git a/scripts/contract_extensions/LineaAdapter.sol b/scripts/contract_extensions/LineaAdapter.sol index cff10195..a4a23b32 100644 --- a/scripts/contract_extensions/LineaAdapter.sol +++ b/scripts/contract_extensions/LineaAdapter.sol @@ -19,15 +19,7 @@ contract LineaAdapterTestnet is LineaAdapter { address lineaMessageService, uint256 providerGasLimit, TrustedRemotesConfig[] memory trustedRemotes - ) - LineaAdapter( - crossChainController, - lineaMessageService, - providerGasLimit, - trustedRemotes, - 'Linea native adapter' - ) - {} + ) LineaAdapter(crossChainController, lineaMessageService, providerGasLimit, trustedRemotes) {} /// @inheritdoc ILineaAdapter function isDestinationChainIdSupported(uint256 chainId) public pure override returns (bool) { diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol index beaae03f..b3ef9c1d 100644 --- a/src/contracts/adapters/linea/LineaAdapter.sol +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -32,23 +32,22 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { * @param crossChainController address of the cross chain controller that will use this bridge adapter * @param lineaMessageService Linea entry point address * @param providerGasLimit base gas limit used by the bridge adapter - * @param adapterName string indicating the adapter name * @param trustedRemotes list of remote configurations to set as trusted */ constructor( address crossChainController, address lineaMessageService, uint256 providerGasLimit, - string memory adapterName, TrustedRemotesConfig[] memory trustedRemotes - ) BaseAdapter(crossChainController, providerGasLimit, adapterName, trustedRemotes) { + ) BaseAdapter(crossChainController, providerGasLimit, 'Linea native adapter', trustedRemotes) { + require(lineaMessageService != address(0), Errors.LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0); LINEA_MESSAGE_SERVICE = lineaMessageService; } /// @inheritdoc IBaseAdapter function forwardMessage( address receiver, - uint256 executionGasLimit, + uint256, uint256 destinationChainId, bytes calldata message ) external virtual returns (address, uint256) { @@ -69,7 +68,7 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { } /// @inheritdoc ILineaAdapter - function ovmReceive(bytes calldata message) external onlyLineaMessageService { + function receiveMessage(bytes calldata message) external onlyLineaMessageService { uint256 originChainId = getOriginChainId(); address srcAddress = IMessageService(LINEA_MESSAGE_SERVICE).sender(); require( diff --git a/src/contracts/libs/Errors.sol b/src/contracts/libs/Errors.sol index 040f57da..a0ec2adb 100644 --- a/src/contracts/libs/Errors.sol +++ b/src/contracts/libs/Errors.sol @@ -52,4 +52,5 @@ library Errors { string public constant ZK_SYNC_BRIDGE_HUB_CANT_BE_ADDRESS_0 = '43'; // ZkSync Bridgehub can not be address 0 string public constant CL_GAS_PRICE_ORACLE_CANT_BE_ADDRESS_0 = '44'; // ChainLink gas price oracle can not be address 0 string public constant CALLER_NOT_LINEA_MESSAGE_SERVICE = '45'; // caller must be the Linea message service + string public constant LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0 = '46'; // Linea message service can not be address 0 } diff --git a/tests/adapters/LineaAdapter.t.sol b/tests/adapters/LineaAdapter.t.sol new file mode 100644 index 00000000..d03bc513 --- /dev/null +++ b/tests/adapters/LineaAdapter.t.sol @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {LineaAdapter, IBaseAdapter, ILineaAdapter, IMessageService} from '../../src/contracts/adapters/linea/LineaAdapter.sol'; +import {ICrossChainReceiver} from '../../src/contracts/interfaces/ICrossChainReceiver.sol'; +import {ChainIds} from 'solidity-utils/contracts/utils/ChainHelpers.sol'; +import {Errors} from '../../src/contracts/libs/Errors.sol'; +import {BaseAdapterTest} from './BaseAdapterTest.sol'; + +contract LineaAdapterTest is BaseAdapterTest { + LineaAdapter internal lineaAdapter; + event SetTrustedRemote(uint256 indexed originChainId, address indexed originForwarder); + + modifier setLineaAdapter( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + uint256 originChainId + ) { + vm.assume(crossChainController != tx.origin); // zkVM doesn't support mocking tx.origin + vm.assume(baseGasLimit < 1 ether); + _assumeSafeAddress(crossChainController); + _assumeSafeAddress(lineaMessageService); + vm.assume(originForwarder != address(0)); + vm.assume(originChainId > 0); + + IBaseAdapter.TrustedRemotesConfig memory originConfig = IBaseAdapter.TrustedRemotesConfig({ + originForwarder: originForwarder, + originChainId: originChainId + }); + IBaseAdapter.TrustedRemotesConfig[] + memory originConfigs = new IBaseAdapter.TrustedRemotesConfig[](1); + originConfigs[0] = originConfig; + + lineaAdapter = new LineaAdapter( + crossChainController, + lineaMessageService, + baseGasLimit, + originConfigs + ); + _; + } + + struct Params { + address lineaMessageService; + address receiver; + uint256 dstGasLimit; + address caller; + } + + function setUp() public {} + + function testWrongLineaMessageSender( + address crossChainController, + uint256 baseGasLimit, + address originForwarder, + uint256 originChainId + ) public { + vm.assume(crossChainController != address(0)); + vm.assume(originForwarder != address(0)); + vm.assume(originChainId > 0); + + IBaseAdapter.TrustedRemotesConfig memory originConfig = IBaseAdapter.TrustedRemotesConfig({ + originForwarder: originForwarder, + originChainId: originChainId + }); + IBaseAdapter.TrustedRemotesConfig[] + memory originConfigs = new IBaseAdapter.TrustedRemotesConfig[](1); + originConfigs[0] = originConfig; + vm.expectRevert(bytes(Errors.LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0)); + new LineaAdapter(crossChainController, address(0), baseGasLimit, originConfigs); + } + + function testInitialize( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + uint256 originChainId + ) + public + setLineaAdapter( + crossChainController, + lineaMessageService, + originForwarder, + baseGasLimit, + originChainId + ) + { + assertEq(lineaAdapter.getTrustedRemoteByChainId(originChainId), originForwarder); + } + + function testForwardMessage( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + uint256 originChainId + ) + public + setLineaAdapter( + crossChainController, + lineaMessageService, + originForwarder, + baseGasLimit, + originChainId + ) + { + _testForwardMessage( + Params({ + lineaMessageService: lineaMessageService, + receiver: address(135961), + dstGasLimit: 12, + caller: address(12354) + }) + ); + } + + function testForwardMessageWhenChainNotSupported( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + uint256 originChainId, + uint256 dstGasLimit, + address receiver, + bytes memory message + ) + public + setLineaAdapter( + crossChainController, + lineaMessageService, + originForwarder, + baseGasLimit, + originChainId + ) + { + vm.assume(receiver != address(0)); + + vm.expectRevert(bytes(Errors.DESTINATION_CHAIN_ID_NOT_SUPPORTED)); + lineaAdapter.forwardMessage(receiver, dstGasLimit, 11, message); + } + + function testForwardMessageWhenWrongReceiver( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + uint256 originChainId, + uint256 dstGasLimit, + bytes memory message + ) + public + setLineaAdapter( + crossChainController, + lineaMessageService, + originForwarder, + baseGasLimit, + originChainId + ) + { + vm.expectRevert(bytes(Errors.RECEIVER_NOT_SET)); + lineaAdapter.forwardMessage(address(0), dstGasLimit, ChainIds.LINEA, message); + } + + function testReceive( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + bytes memory message + ) + public + setLineaAdapter(crossChainController, lineaMessageService, originForwarder, baseGasLimit, 1) + { + hoax(lineaMessageService); + + vm.mockCall( + lineaMessageService, + abi.encodeWithSelector(IMessageService.sender.selector), + abi.encode(originForwarder) + ); + vm.mockCall( + crossChainController, + abi.encodeWithSelector(ICrossChainReceiver.receiveCrossChainMessage.selector), + abi.encode() + ); + vm.expectCall( + crossChainController, + 0, + abi.encodeWithSelector(ICrossChainReceiver.receiveCrossChainMessage.selector, message, 1) + ); + lineaAdapter.receiveMessage(message); + } + + function testReceiveWhenRemoteNotTrusted( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + bytes memory message, + address remote + ) + public + setLineaAdapter(crossChainController, lineaMessageService, originForwarder, baseGasLimit, 1) + { + vm.assume(remote != originForwarder); + hoax(lineaMessageService); + + vm.mockCall( + lineaMessageService, + abi.encodeWithSelector(IMessageService.sender.selector), + abi.encode(remote) + ); + vm.expectRevert(bytes(Errors.REMOTE_NOT_TRUSTED)); + + lineaAdapter.receiveMessage(message); + } + + function testReceiveWhenIncorrectOriginChainId( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + bytes memory message, + uint256 originChainId + ) + public + setLineaAdapter( + crossChainController, + lineaMessageService, + originForwarder, + baseGasLimit, + originChainId + ) + { + vm.assume(originChainId != 1); + hoax(lineaMessageService); + + vm.mockCall( + lineaMessageService, + abi.encodeWithSelector(IMessageService.sender.selector), + abi.encode(originForwarder) + ); + vm.expectRevert(bytes(Errors.REMOTE_NOT_TRUSTED)); + + lineaAdapter.receiveMessage(message); + } + + function _testForwardMessage(Params memory params) internal { + bytes memory message = abi.encode('test message'); + + hoax(params.caller, 10 ether); + + vm.mockCall( + params.lineaMessageService, + abi.encodeWithSelector(IMessageService.sendMessage.selector), + abi.encode() + ); + (bool success, bytes memory returnData) = address(lineaAdapter).delegatecall( + abi.encodeWithSelector( + IBaseAdapter.forwardMessage.selector, + params.receiver, + params.dstGasLimit, + ChainIds.LINEA, + message + ) + ); + vm.clearMockedCalls(); + + assertEq(success, true); + assertEq(returnData, abi.encode(params.lineaMessageService, 0)); + } +} From e8bd84eb98908f17f5343f711de3b912fe590915 Mon Sep 17 00:00:00 2001 From: sendra Date: Tue, 10 Dec 2024 18:05:59 +0100 Subject: [PATCH 05/12] fix: use struct as contructor params, fix natspec --- scripts/contract_extensions/LineaAdapter.sol | 11 ++++- .../adapters/linea/ILineaAdapter.sol | 18 +++++++- src/contracts/adapters/linea/LineaAdapter.sol | 45 +++++++++++-------- tests/adapters/LineaAdapter.t.sol | 19 +++++--- 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/scripts/contract_extensions/LineaAdapter.sol b/scripts/contract_extensions/LineaAdapter.sol index a4a23b32..7eb4bbf6 100644 --- a/scripts/contract_extensions/LineaAdapter.sol +++ b/scripts/contract_extensions/LineaAdapter.sol @@ -19,7 +19,16 @@ contract LineaAdapterTestnet is LineaAdapter { address lineaMessageService, uint256 providerGasLimit, TrustedRemotesConfig[] memory trustedRemotes - ) LineaAdapter(crossChainController, lineaMessageService, providerGasLimit, trustedRemotes) {} + ) + LineaAdapter( + ILineaAdapter.LineaParams({ + crossChainController: crossChainController, + lineaMessageService: lineaMessageService, + providerGasLimit: providerGasLimit, + trustedRemotes: trustedRemotes + }) + ) + {} /// @inheritdoc ILineaAdapter function isDestinationChainIdSupported(uint256 chainId) public pure override returns (bool) { diff --git a/src/contracts/adapters/linea/ILineaAdapter.sol b/src/contracts/adapters/linea/ILineaAdapter.sol index bb19d1cd..c72eba89 100644 --- a/src/contracts/adapters/linea/ILineaAdapter.sol +++ b/src/contracts/adapters/linea/ILineaAdapter.sol @@ -1,12 +1,28 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {IBaseAdapter} from '../IBaseAdapter.sol'; + /** * @title ILineaAdapter * @author BGD Labs * @notice interface containing the events, objects and method definitions used in the Linea bridge adapter */ -interface ILineaAdapter { +interface ILineaAdapter is IBaseAdapter { + /** + * @notice struct used to pass parameters to the Linea constructor + * @param crossChainController address of the cross chain controller that will use this bridge adapter + * @param lineaMessageService Linea entry point address + * @param providerGasLimit base gas limit used by the bridge adapter + * @param trustedRemotes list of remote configurations to set as trusted + */ + struct LineaParams { + address crossChainController; + address lineaMessageService; + uint256 providerGasLimit; + TrustedRemotesConfig[] trustedRemotes; + } + /** * @notice method to get the Linea message service address * @return address of the Linea message service diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol index b3ef9c1d..7a89a76b 100644 --- a/src/contracts/adapters/linea/LineaAdapter.sol +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -2,26 +2,27 @@ pragma solidity ^0.8.0; import {IMessageService} from './interfaces/IMessageService.sol'; -import {BaseAdapter, IBaseAdapter} from '../BaseAdapter.sol'; +import {BaseAdapter} from '../BaseAdapter.sol'; import {ChainIds} from 'solidity-utils/contracts/utils/ChainHelpers.sol'; import {Errors} from '../../libs/Errors.sol'; -import {ILineaAdapter} from './ILineaAdapter.sol'; +import {ILineaAdapter, IBaseAdapter} from './ILineaAdapter.sol'; import {SafeCast} from 'solidity-utils/contracts/oz-common/SafeCast.sol'; /** * @title LineaAdapter * @author BGD Labs - * @notice Optimism bridge adapter. Used to send and receive messages cross chain between Ethereum and Optimism + * @notice Linea bridge adapter. Used to send and receive messages cross chain between Ethereum and Linea * @dev it uses the eth balance of CrossChainController contract to pay for message bridging as the method to bridge is called via delegate call - * @dev note that this adapter is can only be used for the communication path ETHEREUM -> LINEA + * @dev note that this adapter can only be used for the communication path ETHEREUM -> LINEA + * @dev documentation regarding the Linea bridge can be found here: https://docs.linea.build/get-started/concepts/message-service#technical-reference */ contract LineaAdapter is ILineaAdapter, BaseAdapter { /// @inheritdoc ILineaAdapter address public immutable LINEA_MESSAGE_SERVICE; /** - * @notice only calls from the set ovm are accepted. + * @notice only calls from the set message service are accepted. */ modifier onlyLineaMessageService() { require(msg.sender == address(LINEA_MESSAGE_SERVICE), Errors.CALLER_NOT_LINEA_MESSAGE_SERVICE); @@ -29,19 +30,23 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { } /** - * @param crossChainController address of the cross chain controller that will use this bridge adapter - * @param lineaMessageService Linea entry point address - * @param providerGasLimit base gas limit used by the bridge adapter - * @param trustedRemotes list of remote configurations to set as trusted + * @param params object containing the necessary parameters to initialize the contract */ constructor( - address crossChainController, - address lineaMessageService, - uint256 providerGasLimit, - TrustedRemotesConfig[] memory trustedRemotes - ) BaseAdapter(crossChainController, providerGasLimit, 'Linea native adapter', trustedRemotes) { - require(lineaMessageService != address(0), Errors.LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0); - LINEA_MESSAGE_SERVICE = lineaMessageService; + LineaParams memory params + ) + BaseAdapter( + params.crossChainController, + params.providerGasLimit, + 'Linea native adapter', + params.trustedRemotes + ) + { + require( + params.lineaMessageService != address(0), + Errors.LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0 + ); + LINEA_MESSAGE_SERVICE = params.lineaMessageService; } /// @inheritdoc IBaseAdapter @@ -90,12 +95,16 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { } /// @inheritdoc IBaseAdapter - function nativeToInfraChainId(uint256 nativeChainId) public pure override returns (uint256) { + function nativeToInfraChainId( + uint256 nativeChainId + ) public pure override(BaseAdapter, IBaseAdapter) returns (uint256) { return nativeChainId; } /// @inheritdoc IBaseAdapter - function infraToNativeChainId(uint256 infraChainId) public pure override returns (uint256) { + function infraToNativeChainId( + uint256 infraChainId + ) public pure override(BaseAdapter, IBaseAdapter) returns (uint256) { return infraChainId; } } diff --git a/tests/adapters/LineaAdapter.t.sol b/tests/adapters/LineaAdapter.t.sol index d03bc513..f093d7c9 100644 --- a/tests/adapters/LineaAdapter.t.sol +++ b/tests/adapters/LineaAdapter.t.sol @@ -34,10 +34,12 @@ contract LineaAdapterTest is BaseAdapterTest { originConfigs[0] = originConfig; lineaAdapter = new LineaAdapter( - crossChainController, - lineaMessageService, - baseGasLimit, - originConfigs + ILineaAdapter.LineaParams({ + crossChainController: crossChainController, + lineaMessageService: lineaMessageService, + providerGasLimit: baseGasLimit, + trustedRemotes: originConfigs + }) ); _; } @@ -69,7 +71,14 @@ contract LineaAdapterTest is BaseAdapterTest { memory originConfigs = new IBaseAdapter.TrustedRemotesConfig[](1); originConfigs[0] = originConfig; vm.expectRevert(bytes(Errors.LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0)); - new LineaAdapter(crossChainController, address(0), baseGasLimit, originConfigs); + new LineaAdapter( + ILineaAdapter.LineaParams({ + crossChainController: crossChainController, + lineaMessageService: address(0), + providerGasLimit: baseGasLimit, + trustedRemotes: originConfigs + }) + ); } function testInitialize( From 5a09e93c424b55e7df3af081fc7896adef433f25 Mon Sep 17 00:00:00 2001 From: sendra Date: Tue, 10 Dec 2024 18:10:31 +0100 Subject: [PATCH 06/12] fix: updated script --- scripts/Adapters/DeployLineaAdapter.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/Adapters/DeployLineaAdapter.sol b/scripts/Adapters/DeployLineaAdapter.sol index 0ba4ca90..262e0c5d 100644 --- a/scripts/Adapters/DeployLineaAdapter.sol +++ b/scripts/Adapters/DeployLineaAdapter.sol @@ -20,10 +20,12 @@ library LineaAdapterDeploymentHelper { abi.encodePacked( creationCode, abi.encode( - lineaArgs.baseArgs.crossChainController, - lineaArgs.lineaMessageService, - lineaArgs.baseArgs.providerGasLimit, - lineaArgs.baseArgs.trustedRemotes + ILineaAdapter.LineaParams({ + crossChainController: lineaArgs.baseArgs.crossChainController, + lineaMessageService: lineaArgs.lineaMessageService, + providerGasLimit: lineaArgs.baseArgs.providerGasLimit, + trustedRemotes: lineaArgs.baseArgs.trustedRemotes + }) ) ); } From ccd596c60cc7cdd044de34516f4008561e5ee36d Mon Sep 17 00:00:00 2001 From: sendra Date: Tue, 10 Dec 2024 19:18:01 +0100 Subject: [PATCH 07/12] fix: linea adapter testnet extension --- scripts/contract_extensions/LineaAdapter.sol | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/scripts/contract_extensions/LineaAdapter.sol b/scripts/contract_extensions/LineaAdapter.sol index 7eb4bbf6..8af90193 100644 --- a/scripts/contract_extensions/LineaAdapter.sol +++ b/scripts/contract_extensions/LineaAdapter.sol @@ -10,25 +10,9 @@ import {ILineaAdapter, LineaAdapter} from '../../src/contracts/adapters/linea/Li */ contract LineaAdapterTestnet is LineaAdapter { /** - * @param crossChainController address of the cross chain controller that will use this bridge adapter - * @param lineaMessageService linea entry point address - * @param trustedRemotes list of remote configurations to set as trusted + * @param params object containing the necessary parameters to initialize the contract */ - constructor( - address crossChainController, - address lineaMessageService, - uint256 providerGasLimit, - TrustedRemotesConfig[] memory trustedRemotes - ) - LineaAdapter( - ILineaAdapter.LineaParams({ - crossChainController: crossChainController, - lineaMessageService: lineaMessageService, - providerGasLimit: providerGasLimit, - trustedRemotes: trustedRemotes - }) - ) - {} + constructor(ILineaAdapter.LineaParams memory params) LineaAdapter(params) {} /// @inheritdoc ILineaAdapter function isDestinationChainIdSupported(uint256 chainId) public pure override returns (bool) { From a419d68c9d8be81076d4cfc2cb3937bea4ae6982 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 11 Dec 2024 12:25:15 +0100 Subject: [PATCH 08/12] fix: added readme for manual message claiming --- src/contracts/adapters/linea/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/contracts/adapters/linea/README.md diff --git a/src/contracts/adapters/linea/README.md b/src/contracts/adapters/linea/README.md new file mode 100644 index 00000000..e49ed17a --- /dev/null +++ b/src/contracts/adapters/linea/README.md @@ -0,0 +1,26 @@ +# Manual Message Claiming + +To manually claim a message sent from origin (Ethereum) on the Linea network follow these steps: + +1. Get the tx hash where the message was sent on the origin network (Ethereum). This is to get all the information +needed on the following steps. Once you have the tx hash, go to the block explorer, input the tx hash, and then go to events. +Once you are on the events page of the tx, locate the event `MessageSent` +2. Go to the online event decode page: https://tools.deth.net/event-decoder and input the event topics from step 1, and the data. +Add this code (event abi) to the ABI text box: +``` +event MessageSent( + address indexed _from, + address indexed _to, + uint256 _fee, + uint256 _value, + uint256 _nonce, + bytes _calldata, + bytes32 indexed _messageHash + ); +``` +3. Go to the Linea block explorer: https://lineascan.build/ and add the Linea MessageService contract: +- mainnet: [0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec](https://sepolia.lineascan.build/address/0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec#writeProxyContract) +- sepolia: [0x971e727e956690b9957be6d51Ec16E73AcAC83A7](https://sepolia.lineascan.build/address/0x971e727e956690b9957be6d51Ec16E73AcAC83A7#writeProxyContract) +Once there go to write as proxy contract, and fill the information from step 2 to the method `claimMessage` and execute the tx. +Take into account that the topics, calldata and nonce must be in Hex format. + From e4ad1d1523efafe6dc20589e72bcfb8fdf9b30b5 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 11 Dec 2024 16:50:39 +0100 Subject: [PATCH 09/12] fix: added hardcoded fee --- src/contracts/adapters/linea/LineaAdapter.sol | 9 +++++-- tests/adapters/LineaAdapter.t.sol | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol index 7a89a76b..2f488175 100644 --- a/src/contracts/adapters/linea/LineaAdapter.sol +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -21,6 +21,8 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { /// @inheritdoc ILineaAdapter address public immutable LINEA_MESSAGE_SERVICE; + uint256 public constant L2_FEE = 0.003 ether; + /** * @notice only calls from the set message service are accepted. */ @@ -62,9 +64,12 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { ); require(receiver != address(0), Errors.RECEIVER_NOT_SET); + + require(address(this).balance >= L2_FEE, Errors.NOT_ENOUGH_VALUE_TO_PAY_BRIDGE_FEES); + // @dev we set _fee to 0 because for now we will do the claim manually. Until an automated way of getting the // price is implemented by Linea - IMessageService(LINEA_MESSAGE_SERVICE).sendMessage( + IMessageService(LINEA_MESSAGE_SERVICE).sendMessage{value: L2_FEE}( receiver, 0, abi.encodeWithSelector(ILineaAdapter.receiveMessage.selector, message) @@ -90,7 +95,7 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { } /// @inheritdoc ILineaAdapter - function isDestinationChainIdSupported(uint256 chainId) public view virtual returns (bool) { + function isDestinationChainIdSupported(uint256 chainId) public pure virtual returns (bool) { return chainId == ChainIds.LINEA; } diff --git a/tests/adapters/LineaAdapter.t.sol b/tests/adapters/LineaAdapter.t.sol index f093d7c9..096b6685 100644 --- a/tests/adapters/LineaAdapter.t.sol +++ b/tests/adapters/LineaAdapter.t.sol @@ -173,6 +173,30 @@ contract LineaAdapterTest is BaseAdapterTest { lineaAdapter.forwardMessage(address(0), dstGasLimit, ChainIds.LINEA, message); } + function testForwardMessageWhenNoValue( + address crossChainController, + address lineaMessageService, + address originForwarder, + uint256 baseGasLimit, + uint256 originChainId, + uint256 dstGasLimit, + address receiver, + bytes memory message + ) + public + setLineaAdapter( + crossChainController, + lineaMessageService, + originForwarder, + baseGasLimit, + originChainId + ) + { + vm.assume(receiver != address(0)); + vm.expectRevert(bytes(Errors.NOT_ENOUGH_VALUE_TO_PAY_BRIDGE_FEES)); + lineaAdapter.forwardMessage(receiver, dstGasLimit, ChainIds.LINEA, message); + } + function testReceive( address crossChainController, address lineaMessageService, From 3a57b0b980ff951ccda22abf8ea7778335b79193 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 11 Dec 2024 16:53:35 +0100 Subject: [PATCH 10/12] fix: adjusted fee --- src/contracts/adapters/linea/LineaAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol index 2f488175..6567ed7b 100644 --- a/src/contracts/adapters/linea/LineaAdapter.sol +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -21,7 +21,7 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { /// @inheritdoc ILineaAdapter address public immutable LINEA_MESSAGE_SERVICE; - uint256 public constant L2_FEE = 0.003 ether; + uint256 public constant L2_FEE = 0.002 ether; /** * @notice only calls from the set message service are accepted. From 239475f03956173abb5e09df31ed748f996c5944 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 11 Dec 2024 17:13:39 +0100 Subject: [PATCH 11/12] fix: send fee also on function arg --- src/contracts/adapters/linea/LineaAdapter.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/contracts/adapters/linea/LineaAdapter.sol b/src/contracts/adapters/linea/LineaAdapter.sol index 6567ed7b..95707806 100644 --- a/src/contracts/adapters/linea/LineaAdapter.sol +++ b/src/contracts/adapters/linea/LineaAdapter.sol @@ -67,11 +67,11 @@ contract LineaAdapter is ILineaAdapter, BaseAdapter { require(address(this).balance >= L2_FEE, Errors.NOT_ENOUGH_VALUE_TO_PAY_BRIDGE_FEES); - // @dev we set _fee to 0 because for now we will do the claim manually. Until an automated way of getting the - // price is implemented by Linea + // @dev we set _fee to hardcoded L2_FEE to overpay to ensure automatic claiming. If by some case it is not enough then + // we will do the claim manually. Until an automated way of getting the price is implemented by Linea IMessageService(LINEA_MESSAGE_SERVICE).sendMessage{value: L2_FEE}( receiver, - 0, + L2_FEE, abi.encodeWithSelector(ILineaAdapter.receiveMessage.selector, message) ); return (LINEA_MESSAGE_SERVICE, 0); From d2beabf84110006199dd416196ae27a4ab04352b Mon Sep 17 00:00:00 2001 From: sendra Date: Thu, 12 Dec 2024 15:57:28 +0100 Subject: [PATCH 12/12] fix: correct link to explorer --- src/contracts/adapters/linea/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/adapters/linea/README.md b/src/contracts/adapters/linea/README.md index e49ed17a..72930d22 100644 --- a/src/contracts/adapters/linea/README.md +++ b/src/contracts/adapters/linea/README.md @@ -19,7 +19,7 @@ event MessageSent( ); ``` 3. Go to the Linea block explorer: https://lineascan.build/ and add the Linea MessageService contract: -- mainnet: [0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec](https://sepolia.lineascan.build/address/0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec#writeProxyContract) +- mainnet: [0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec](https://lineascan.build/address/0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec#writeProxyContract) - sepolia: [0x971e727e956690b9957be6d51Ec16E73AcAC83A7](https://sepolia.lineascan.build/address/0x971e727e956690b9957be6d51Ec16E73AcAC83A7#writeProxyContract) Once there go to write as proxy contract, and fill the information from step 2 to the method `claimMessage` and execute the tx. Take into account that the topics, calldata and nonce must be in Hex format.