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

Scroll native minting #27

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .vscode/settings.json

This file was deleted.

15 changes: 15 additions & 0 deletions audit/ScrollNativeMintingAuditReport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# [NM-0217] Scroll Native Minting Request

**File(s)**: [L1ScrollReceiverETHUpgradeable.sol](https://github.com/etherfi-protocol/weETH-cross-chain/blob/aa5fd7687686c67febe7f07c3f68da798ef3fd41/contracts/NativeMinting/ReceiverContracts/L1ScrollReceiverETHUpgradeable.sol), [L2OPStackSyncPoolETHUpgradeable.sol](https://github.com/etherfi-protocol/weETH-cross-chain/blob/8467b3903c71790c08f183bcbe8224bfb1c6b0b2/contracts/NativeMinting/L2SyncPoolContracts/L2OPStackSyncPoolETHUpgradeable.sol), [L2ScrollSyncPoolETHUpgradeable](https://github.com/etherfi-protocol/weETH-cross-chain/blob/b953a0260deef2f70ba556ff064d45b21d9bc894/contracts/NativeMinting/L2SyncPoolContracts/L2ScrollSyncPoolETHUpgradeable.sol#L108)

### Summary

This PR extends the cross chain functionality that allows users to natively mint weETH without the need to swap through a DEX by adding support for the Scroll blockchain. In order to add this feature, the smart contracts required slight customizations of the already existing code to integrate with Scroll.

---

### Review conclusions

After reviewing the updated code, we don't see any clear risk on the changes that were implemented. The code seems to work as expected.

---

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@ import {Constants} from "../../libraries/Constants.sol";
import {IL1Receiver} from "../../../interfaces/IL1Receiver.sol";

/**
* @title L2 Mode Sync Pool for ETH
* @dev A sync pool that only supports ETH on Mode L2
* @title L2 OP Stack Sync Pool for ETH
* @dev A sync pool that only supports ETH on OP Stack L2s
* This contract allows to send ETH from L2 to L1 during the sync process
*/
contract L2ModeSyncPoolETHUpgradeable is
L2BaseSyncPoolUpgradeable,
BaseMessengerUpgradeable,
BaseReceiverUpgradeable
{
error L2ModeSyncPoolETH__OnlyETH();
contract L2OPStackSyncPoolETHUpgradeable is L2BaseSyncPoolUpgradeable, BaseMessengerUpgradeable, BaseReceiverUpgradeable {

event DepositWithReferral(address indexed sender, uint256 amount, address referral);

error L2OPStackSyncPoolETH__OnlyETH();

/**
* @dev Constructor for L2 Mode Sync Pool for ETH
* @dev Constructor for L2 OP Stack Sync Pool for ETH
* @param endpoint Address of the LayerZero endpoint
*/
constructor(address endpoint) L2BaseSyncPoolUpgradeable(endpoint) {}
constructor(address endpoint) L2BaseSyncPoolUpgradeable(endpoint) {
_disableInitializers();
}

/**
* @dev Initialize the contract
Expand Down Expand Up @@ -62,7 +63,7 @@ contract L2ModeSyncPoolETHUpgradeable is
* @param amountIn The amount of tokens
*/
function _receiveTokenIn(address tokenIn, uint256 amountIn) internal virtual override {
if (tokenIn != Constants.ETH_ADDRESS) revert L2ModeSyncPoolETH__OnlyETH();
if (tokenIn != Constants.ETH_ADDRESS) revert L2OPStackSyncPoolETH__OnlyETH();

super._receiveTokenIn(tokenIn, amountIn);
}
Expand All @@ -89,7 +90,7 @@ contract L2ModeSyncPoolETHUpgradeable is
MessagingFee calldata fee
) internal virtual override returns (MessagingReceipt memory) {
if (l1TokenIn != Constants.ETH_ADDRESS || l2TokenIn != Constants.ETH_ADDRESS) {
revert L2ModeSyncPoolETH__OnlyETH();
revert L2OPStackSyncPoolETH__OnlyETH();
}

address receiver = getReceiver();
Expand All @@ -107,4 +108,17 @@ contract L2ModeSyncPoolETHUpgradeable is

return receipt;
}

/**
* @dev Deposit function with referral event
*/
function deposit(
address tokenIn,
uint256 amountIn,
uint256 minAmountOut,
address referral
) public payable returns (uint256 amountOut) {
emit DepositWithReferral(msg.sender, msg.value, referral);
return super.deposit(tokenIn, amountIn, minAmountOut);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {
MessagingFee,
MessagingReceipt
} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";

import {BaseMessengerUpgradeable} from "../LayerZeroBaseContracts/BaseMessengerUpgradeable.sol";
import {BaseReceiverUpgradeable} from "../LayerZeroBaseContracts/BaseReceiverUpgradeable.sol";
import {L2BaseSyncPoolUpgradeable} from "../LayerZeroBaseContracts/L2BaseSyncPoolUpgradeable.sol";
import {IL2ScrollMessenger} from "../../../interfaces/IL2ScrollMessenger.sol";
import {Constants} from "../../libraries/Constants.sol";
import {IL1Receiver} from "../../../interfaces/IL1Receiver.sol";

/**
* @title L2 Scroll Stack Sync Pool for ETH
* @dev A sync pool that only supports ETH on Scroll Stack L2s
* This contract allows to send ETH from L2 to L1 during the sync process
*/
contract L2ScrollSyncPoolETHUpgradeable is L2BaseSyncPoolUpgradeable, BaseMessengerUpgradeable, BaseReceiverUpgradeable {

event DepositWithReferral(address indexed sender, uint256 amount, address referral);

error L2ScrollStackSyncPoolETH__OnlyETH();

/**
* @dev Constructor for L2 Scroll Stack Sync Pool for ETH
* @param endpoint Address of the LayerZero endpoint
*/
constructor(address endpoint) L2BaseSyncPoolUpgradeable(endpoint) {
_disableInitializers();
}

/**
* @dev Initialize the contract
* @param l2ExchangeRateProvider Address of the exchange rate provider
* @param rateLimiter Address of the rate limiter
* @param tokenOut Address of the token to mint on Layer 2
* @param dstEid Destination endpoint ID (most of the time, the Layer 1 endpoint ID)
* @param messenger Address of the messenger contract (most of the time, the L2 native bridge address)
* @param receiver Address of the receiver contract (most of the time, the L1 receiver contract)
* @param delegate Address of the owner
*/
function initialize(
address l2ExchangeRateProvider,
address rateLimiter,
address tokenOut,
uint32 dstEid,
address messenger,
address receiver,
address delegate
) external virtual initializer {

seongyun-ko marked this conversation as resolved.
Show resolved Hide resolved
__L2BaseSyncPool_init(l2ExchangeRateProvider, rateLimiter, tokenOut, dstEid, delegate);
__BaseMessenger_init(messenger);
__BaseReceiver_init(receiver);
__Ownable_init(delegate);
}

/**
* @dev Only allows ETH to be received
* @param tokenIn The token address
* @param amountIn The amount of tokens
*/
function _receiveTokenIn(address tokenIn, uint256 amountIn) internal virtual override {
if (tokenIn != Constants.ETH_ADDRESS) revert L2ScrollStackSyncPoolETH__OnlyETH();

super._receiveTokenIn(tokenIn, amountIn);
}

/**
* @dev Internal function to sync tokens to L1
* This will send an additional message to the messenger contract after the LZ message
* This message will contain the ETH that the LZ message anticipates to receive
* @param dstEid Destination endpoint ID
* @param l1TokenIn Address of the token on Layer 1
* @param amountIn Amount of tokens deposited on Layer 2
* @param amountOut Amount of tokens minted on Layer 2
* @param extraOptions Extra options for the messaging protocol
* @param fee Messaging fee
* @return receipt Messaging receipt
*/
function _sync(
uint32 dstEid,
address l2TokenIn,
address l1TokenIn,
uint256 amountIn,
uint256 amountOut,
bytes calldata extraOptions,
MessagingFee calldata fee
) internal virtual override returns (MessagingReceipt memory) {
if (l1TokenIn != Constants.ETH_ADDRESS || l2TokenIn != Constants.ETH_ADDRESS) {
revert L2ScrollStackSyncPoolETH__OnlyETH();
}

address receiver = getReceiver();
address messenger = getMessenger();

uint32 originEid = endpoint.eid();

MessagingReceipt memory receipt =
super._sync(dstEid, l2TokenIn, l1TokenIn, amountIn, amountOut, extraOptions, fee);

bytes memory data = abi.encode(originEid, receipt.guid, l1TokenIn, amountIn, amountOut);
bytes memory message = abi.encodeCall(IL1Receiver.onMessageReceived, data);

IL2ScrollMessenger(messenger).sendMessage{value: amountIn}(receiver, amountIn, message, 0);

return receipt;
}

/**
* @dev Deposit function with referral event
*/
function deposit(
address tokenIn,
uint256 amountIn,
uint256 minAmountOut,
address referral
) public payable returns (uint256 amountOut) {
emit DepositWithReferral(msg.sender, msg.value, referral);
return super.deposit(tokenIn, amountIn, minAmountOut);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {L1BaseReceiverUpgradeable} from "../LayerZeroBaseContracts/L1BaseReceiverUpgradeable.sol";
import {IL1ScrollMessenger} from "../../../interfaces/IL1ScrollMessenger.sol";
import {Constants} from "../../libraries/Constants.sol";

/**
* @title L1 Scroll Receiver ETH
* @notice L1 receiver contract for ETH
* @dev This contract receives messages from the scroll L2 messenger and forwards them to the L1 sync pool
* It only supports ETH
*/
contract L1ScrollReceiverETHUpgradeable is L1BaseReceiverUpgradeable {
error L1ScrollReceiverETH__OnlyETH();

constructor() {
_disableInitializers();
}

/**
* @dev Initializer for L1 Mode Receiver ETH
* @param l1SyncPool Address of the L1 sync pool
* @param messenger Address of the messenger contract
* @param owner Address of the owner
*/
function initialize(address l1SyncPool, address messenger, address owner) external initializer {
__Ownable_init(owner);
__L1BaseReceiver_init(l1SyncPool, messenger);
}

/**
* @dev Function to receive messages from the L2 messenger
* @param message The message received from the L2 messenger
*/
function onMessageReceived(bytes calldata message) external payable virtual override {
(uint32 originEid, bytes32 guid, address tokenIn, uint256 amountIn, uint256 amountOut) =
abi.decode(message, (uint32, bytes32, address, uint256, uint256));

if (tokenIn != Constants.ETH_ADDRESS) revert L1ScrollReceiverETH__OnlyETH();

address sender = IL1ScrollMessenger(getMessenger()).xDomainMessageSender();

_forwardToL1SyncPool(
originEid, bytes32(uint256(uint160(sender))), guid, tokenIn, amountIn, amountOut, msg.value
);
}
}
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fs_permissions = [{ access = "read-write", path = "./"}]

optimizer = true
optimizer_runs = 200
solc_version = "0.8.20"
via_ir = true
solc_version = "0.8.24"


# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
35 changes: 35 additions & 0 deletions interfaces/IL2ScrollMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.16;

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

interface IL2ScrollMessenger is IScrollMessenger {
/**********
* Events *
**********/

/// @notice Emitted when the maximum number of times each message can fail in L2 is updated.
/// @param oldMaxFailedExecutionTimes The old maximum number of times each message can fail in L2.
/// @param newMaxFailedExecutionTimes The new maximum number of times each message can fail in L2.
event UpdateMaxFailedExecutionTimes(uint256 oldMaxFailedExecutionTimes, uint256 newMaxFailedExecutionTimes);

/*****************************
* Public Mutating Functions *
*****************************/

/// @notice execute L1 => L2 message
/// @dev Make sure this is only called by privileged accounts.
/// @param from The address of the sender of the message.
/// @param to The address of the recipient of the message.
/// @param value The msg.value passed to the message call.
/// @param nonce The nonce of the message to avoid replay attack.
/// @param message The content of the message.
function relayMessage(
address from,
address to,
uint256 value,
uint256 nonce,
bytes calldata message
) external;
}
13 changes: 8 additions & 5 deletions scripts/AdapterMigration/01_DeployUpgradeableAdapter.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.so
import "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol";
import "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol";
import "@layerzerolabs/lz-evm-oapp-v2/contracts-upgradeable/oapp/interfaces/IOAppOptionsType3.sol";
// import "@layerzerolabs/lz-evm-oapp-v2/contracts-upgradeable/oapp/interfaces/IOAppOptionsType3.sol";
import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";

import "../../contracts/EtherfiOFTAdapterUpgradeable.sol";
import "../../utils/Constants.sol";
import "../../contracts/EtherFiOFTAdapterUpgradeable.sol";
import "../../utils/L2Constants.sol";


import "../../utils/LayerZeroHelpers.sol";
contract DeployUpgradeableOFTAdapter is Script, Constants, LayerZeroHelpers {

contract DeployUpgradeableOFTAdapter is Script, L2Constants, LayerZeroHelpers {
using OptionsBuilder for bytes;

EnforcedOptionParam[] public enforcedOptions;
Expand Down Expand Up @@ -60,7 +63,7 @@ contract DeployUpgradeableOFTAdapter is Script, Constants, LayerZeroHelpers {
for (uint256 i = 0; i < L2s.length; i++) {
_appendEnforcedOptions(L2s[i].L2_EID);
}
adapter.setEnforcedOptions(enforcedOptions);
IOAppOptionsType3(adapterProxy).setEnforcedOptions(enforcedOptions);

console.log("Transfering ownership to the gnosis...");
adapter.setDelegate(L1_CONTRACT_CONTROLLER);
Expand Down
4 changes: 2 additions & 2 deletions scripts/AdapterMigration/02_DeployMigrationOFT.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { EnforcedOptionParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oap
import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "../../contracts/MigrationOFT.sol";
import "../../utils/Constants.sol";
import "../../utils/L2Constants.sol";
import "../../utils/LayerZeroHelpers.sol";


contract DeployMigrationOFT is Script, Constants, LayerZeroHelpers {
contract DeployMigrationOFT is Script, L2Constants, LayerZeroHelpers {
using OptionsBuilder for bytes;

address public migrationOFTAddress;
Expand Down
2 changes: 1 addition & 1 deletion scripts/AdapterMigration/03_MigrationTransactions.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManage
import "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/utils/RateLimiter.sol";

import "../../contracts/MintableOFTUpgradeable.sol";
import "../../utils/Constants.sol";
import "../../utils/L2Constants.sol";
import "../../utils/LayerZeroHelpers.sol";

contract GenerationMigrationTransactions is Script, Constants, LayerZeroHelpers {
Expand Down
Loading