From 8ab91c0bb0573adaa1f48abc01dc7f1fb9396eba Mon Sep 17 00:00:00 2001 From: ravinagill15 Date: Thu, 26 Jun 2025 15:33:00 -0700 Subject: [PATCH 1/2] mint/burn native oft adapter in devtools --- .../MintBurnNativeOFTAdapterUpgradeable.sol | 145 ++++++++++++++++++ .../oft/precompiles/ArbNativeTokenManager.sol | 29 ++++ 2 files changed, 174 insertions(+) create mode 100644 packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol create mode 100644 packages/oft-evm-upgradeable/contracts/oft/precompiles/ArbNativeTokenManager.sol diff --git a/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol b/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol new file mode 100644 index 0000000000..df5f25cdba --- /dev/null +++ b/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: UNLICENSED + +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. + * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. + */ + constructor( + uint8 _localDecimals, + address _lzEndpoint, + address _delegate + ) 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); + } +} \ No newline at end of file diff --git a/packages/oft-evm-upgradeable/contracts/oft/precompiles/ArbNativeTokenManager.sol b/packages/oft-evm-upgradeable/contracts/oft/precompiles/ArbNativeTokenManager.sol new file mode 100644 index 0000000000..64ca1d0135 --- /dev/null +++ b/packages/oft-evm-upgradeable/contracts/oft/precompiles/ArbNativeTokenManager.sol @@ -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; +} \ No newline at end of file From 7008927f9b95c602421638de3750e45ccdac3a62 Mon Sep 17 00:00:00 2001 From: ravinagill15 Date: Thu, 26 Jun 2025 15:35:38 -0700 Subject: [PATCH 2/2] added license, removed unused parameter --- .../contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol b/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol index df5f25cdba..579932d170 100644 --- a/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol +++ b/packages/oft-evm-upgradeable/contracts/oft/MintBurnNativeOFTAdapterUpgradeable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; @@ -16,12 +16,10 @@ abstract contract MintBurnNativeOFTAdapterUpgradeable is OFTCoreUpgradeable { /** * @param _localDecimals The decimals of the native on the local chain (this chain). 18 on ETH. * @param _lzEndpoint The LayerZero endpoint address. - * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. */ constructor( uint8 _localDecimals, address _lzEndpoint, - address _delegate ) OFTCoreUpgradeable(_localDecimals, _lzEndpoint) {} function __MintBurnNativeOFTAdapter_init(address _delegate) internal onlyInitializing {