Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { IOFT, OFTCoreUpgradeable } from "./OFTCoreUpgradeable.sol";
import { SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";

import { ArbNativeTokenManager } from "./precompiles/ArbNativeTokenManager.sol";

abstract contract MintBurnNativeOFTAdapterUpgradeable is OFTCoreUpgradeable {
ArbNativeTokenManager public immutable arbNativeTokenManager = ArbNativeTokenManager(address(0x73));

error IncorrectMessageValue(uint256 provided, uint256 required);
error CreditFailed(address to, uint256 amountLD, bytes revertData);

/**
* @param _localDecimals The decimals of the native on the local chain (this chain). 18 on ETH.
* @param _lzEndpoint The LayerZero endpoint address.
*/
constructor(
uint8 _localDecimals,
address _lzEndpoint,
) OFTCoreUpgradeable(_localDecimals, _lzEndpoint) {}

function __MintBurnNativeOFTAdapter_init(address _delegate) internal onlyInitializing {
__OFTCore_init(_delegate);
}

function __MintBurnNativeOFTAdapter_init_unchained() internal onlyInitializing {}

function token() external pure returns (address) {
return address(0);
}

/**
* @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
* @return requiresApproval Needs approval of the underlying token implementation.
*
* @dev In the case of default OFTAdapter, approval is required.
* @dev In non-default OFTAdapter contracts with something like mint and burn privileges, it would NOT need approval.
*/
function approvalRequired() external pure virtual returns (bool) {
return false;
}

/**
* @dev Burns tokens from the sender's specified balance, ie. pull method.
* @param _amountLD The amount of tokens to send in local decimals.
* @param _minAmountLD The minimum amount to send in local decimals.
* @param _dstEid The destination chain ID.
* @return amountSentLD The amount sent in local decimals.
* @return amountReceivedLD The amount received in local decimals on the remote.
*
* @dev msg.sender will need to approve this _amountLD of tokens to be locked inside of the contract.
* @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, ie. 1 token in, 1 token out.
* IF the 'innerToken' applies something like a transfer fee, the default will NOT work...
* a pre/post balance check will need to be done to calculate the amountReceivedLD.
*/
function _debit(
address /*_from*/,
uint256 _amountLD,
uint256 _minAmountLD,
uint32 _dstEid
) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
(amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);

arbNativeTokenManager.burnNativeToken(amountSentLD);
}

/**
* @dev Credits tokens to the specified address.
* @param _to The address to credit the tokens to.
* @param _amountLD The amount of tokens to credit in local decimals.
* @dev _srcEid The source chain ID.
* @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.
*
* @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, ie. 1 token in, 1 token out.
* IF the 'innerToken' applies something like a transfer fee, the default will NOT work...
* a pre/post balance check will need to be done to calculate the amountReceivedLD.
*/
function _credit(
address _to,
uint256 _amountLD,
uint32 /*_srcEid*/
) internal virtual override returns (uint256 amountReceivedLD) {
arbNativeTokenManager.mintNativeToken(_amountLD);

// @dev Transfer tokens to the recipient.
(bool success, bytes memory data) = payable(_to).call{value: _amountLD}("");
if (!success) {
revert CreditFailed(_to, _amountLD, data);
}

// @dev In the case of NON-default NativeOFTAdapter, the amountLD MIGHT not be == amountReceivedLD.
return _amountLD;
}

function send(
SendParam calldata _sendParam,
MessagingFee calldata _fee,
address _refundAddress
) external payable override returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
// @dev Ensure the native funds in msg.value are exactly enough to cover the fees and amount to send (with dust removed).
// @dev This will revert if the _sendParam.amountLD contains any dust
uint256 requiredMsgValue = _fee.nativeFee + _removeDust(_sendParam.amountLD);
if (msg.value != requiredMsgValue) {
revert IncorrectMessageValue(msg.value, requiredMsgValue);
}

// @dev Applies the token transfers regarding this send() operation.
// - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.
// - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.
(uint256 amountSentLD, uint256 amountReceivedLD) = _debit(
msg.sender,
_sendParam.amountLD,
_sendParam.minAmountLD,
_sendParam.dstEid
);

// @dev Builds the options and OFT message to quote in the endpoint.
(bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);

// @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.
msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
// @dev Formulate the OFT receipt.
oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);

emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);
}

/**
* @dev Overridden to be empty as this assertion is done higher up on the overriden send() function.
* @param _nativeFee The native fee to be paid.
* @return nativeFee The amount of native currency paid.
*/
function _payNative(uint256 _nativeFee) internal pure override returns (uint256 nativeFee) {
return _nativeFee;
}

function oftVersion() external pure virtual override returns (bytes4 interfaceId, uint64 version) {
return (type(IOFT).interfaceId, 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2021-2025, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE.md
// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.4.21 <0.9.0;

/**
* @title Enables minting and burning native tokens.
* @notice Authorized callers are added/removed through ArbOwner precompile.
* Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000073.
* Available in ArbOS version 41
*/
interface ArbNativeTokenManager {
/**
* @notice In case the caller is authorized,
* mints some amount of the native gas token for this chain to the caller.
*/
function mintNativeToken(
uint256 amount
) external;

/**
* @notice In case the caller is authorized,
* burns some amount of the native gas token for this chain from the caller.
*/
function burnNativeToken(
uint256 amount
) external;
}
Loading