Skip to content

Commit

Permalink
Merge pull request #94 from bgd-labs/feat/linea
Browse files Browse the repository at this point in the history
feat: Add Linea network support
  • Loading branch information
sendra authored Jan 7, 2025
2 parents 75ad342 + d2beabf commit d4b0849
Show file tree
Hide file tree
Showing 8 changed files with 604 additions and 0 deletions.
57 changes: 57 additions & 0 deletions scripts/Adapters/DeployLineaAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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(
ILineaAdapter.LineaParams({
crossChainController: lineaArgs.baseArgs.crossChainController,
lineaMessageService: lineaArgs.lineaMessageService,
providerGasLimit: lineaArgs.baseArgs.providerGasLimit,
trustedRemotes: 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,
lineaMessageService: LINEA_MESSAGE_SERVICE()
})
);
}
}
26 changes: 26 additions & 0 deletions scripts/contract_extensions/LineaAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 params object containing the necessary parameters to initialize the contract
*/
constructor(ILineaAdapter.LineaParams memory params) LineaAdapter(params) {}

/// @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;
}
}
50 changes: 50 additions & 0 deletions src/contracts/adapters/linea/ILineaAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 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
*/
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);
}
115 changes: 115 additions & 0 deletions src/contracts/adapters/linea/LineaAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {IMessageService} from './interfaces/IMessageService.sol';
import {BaseAdapter} from '../BaseAdapter.sol';
import {ChainIds} from 'solidity-utils/contracts/utils/ChainHelpers.sol';
import {Errors} from '../../libs/Errors.sol';
import {ILineaAdapter, IBaseAdapter} from './ILineaAdapter.sol';
import {SafeCast} from 'solidity-utils/contracts/oz-common/SafeCast.sol';

/**
* @title LineaAdapter
* @author BGD Labs
* @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 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;

uint256 public constant L2_FEE = 0.002 ether;

/**
* @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);
_;
}

/**
* @param params object containing the necessary parameters to initialize the contract
*/
constructor(
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
function forwardMessage(
address receiver,
uint256,
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);


require(address(this).balance >= L2_FEE, Errors.NOT_ENOUGH_VALUE_TO_PAY_BRIDGE_FEES);

// @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,
L2_FEE,
abi.encodeWithSelector(ILineaAdapter.receiveMessage.selector, message)
);
return (LINEA_MESSAGE_SERVICE, 0);
}

/// @inheritdoc ILineaAdapter
function receiveMessage(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 pure virtual returns (bool) {
return chainId == ChainIds.LINEA;
}

/// @inheritdoc IBaseAdapter
function nativeToInfraChainId(
uint256 nativeChainId
) public pure override(BaseAdapter, IBaseAdapter) returns (uint256) {
return nativeChainId;
}

/// @inheritdoc IBaseAdapter
function infraToNativeChainId(
uint256 infraChainId
) public pure override(BaseAdapter, IBaseAdapter) returns (uint256) {
return infraChainId;
}
}
26 changes: 26 additions & 0 deletions src/contracts/adapters/linea/README.md
Original file line number Diff line number Diff line change
@@ -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://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.

20 changes: 20 additions & 0 deletions src/contracts/adapters/linea/interfaces/IMessageService.sol
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 2 additions & 0 deletions src/contracts/libs/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@ 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
string public constant LINEA_MESSAGE_SERVICE_CANT_BE_ADDRESS_0 = '46'; // Linea message service can not be address 0
}
Loading

0 comments on commit d4b0849

Please sign in to comment.