diff --git a/packages/oft-evm-upgradeable/contracts/oft/NativeOFTAdapterUpgradeable.sol b/packages/oft-evm-upgradeable/contracts/oft/NativeOFTAdapterUpgradeable.sol new file mode 100644 index 0000000000..8804e8c327 --- /dev/null +++ b/packages/oft-evm-upgradeable/contracts/oft/NativeOFTAdapterUpgradeable.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +import { MessagingFee, MessagingReceipt, OFTFeeDetail, OFTLimit, OFTReceipt, SendParam } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; +import { IOFT, OFTCoreUpgradeable } from "./OFTCoreUpgradeable.sol"; + +/** + * + * @title NativeOFTAdapterUpgradeable + * @dev NativeOFTAdapterUpgradeable is a contract that adapts native currency to the OFT functionality. + * + * @dev WARNING: ONLY 1 of these should exist for a given global mesh, + * unless you make a NON-default implementation of OFT, which needs to be done very carefully. + * @dev WARNING: The default NativeOFTAdapterUpgradeable implementation assumes LOSSLESS transfers, ie. 1 native in, 1 native out. + */ +abstract contract NativeOFTAdapterUpgradeable is OFTCoreUpgradeable { + + 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) {} + + /** + * @dev Initializes the NativeOFTAdapter with the provided delegate. + * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. + * + * @dev The delegate typically should be set as the owner of the contract. + * @dev Ownable is not initialized here on purpose. It should be initialized in the child contract to + * accommodate the different version of Ownable. + */ + function __NativeOFTAdapter_init(address _delegate) internal onlyInitializing { + __OFTCore_init(_delegate); + } + + function __NativeOFTAdapter_init_unchained() internal onlyInitializing {} + + /** + * @dev Returns the address of the native token + * @return The address of the native token. + */ + function token() public pure returns (address) { + return address(0); + } + + /** + * @notice Indicates whether the OFT contract requires approval of the 'token()' to send. + * @return bool indicating whether approval of underlying token implementation is required. + * + * @dev In the case of default NativeOFTAdapter, approval is not required. + */ + function approvalRequired() external pure virtual returns (bool) { + return false; + } + + /** + * @dev Executes the send operation while ensuring the correct amount of native is sent. + * @param _sendParam The parameters for the send operation. + * @param _fee The calculated fee for the send() operation. + * - nativeFee: The native fee. + * - lzTokenFee: The lzToken fee. + * @param _refundAddress The address to receive any excess funds. + * @return msgReceipt The receipt for the send operation. + * @return oftReceipt The OFT receipt information. + * + * @dev MessagingReceipt: LayerZero msg receipt + * - guid: The unique identifier for the sent message. + * - nonce: The nonce of the sent message. + * - fee: The LayerZero fee incurred for the message. + */ + function send( + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) public payable virtual 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 Locks native sent by the sender as msg.value + * @dev _from The address to debit. + * @param _amountLD The amount of native 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. + */ + function _debit( + address /*_from*/, + uint256 _amountLD, + uint256 _minAmountLD, + uint32 _dstEid + ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { + // @dev Native funds sent with msg.value are locked into this contract higher up on the overridden send() function + (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); + } + + /** + * @dev Credits native to the specified address. + * @param _to The address to credit the native to. + * @param _amountLD The amount of native to credit. + * @dev _srcEid The source chain ID. + * @return amountReceivedLD The amount of native ACTUALLY received. + */ + function _credit( + address _to, + uint256 _amountLD, + uint32 /*_srcEid*/ + ) internal virtual override returns (uint256 amountReceivedLD) { + // @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; + } + + /** + * @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; + } + + /** + * @notice Provides the fee breakdown and settings data for an OFT. Unused in the default implementation. + * @param _sendParam The parameters for the send operation. + * @return oftLimit The OFT limit information. + * @return oftFeeDetails The details of OFT fees. + * @return oftReceipt The OFT receipt information. + */ + function quoteOFT( + SendParam calldata _sendParam + ) + external + view + virtual + override + returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt) + { + oftLimit = OFTLimit(0, type(uint256).max); + + // Unused in the default implementation; reserved for future complex fee details. + oftFeeDetails = new OFTFeeDetail[](0); + + // @dev This is the same as the send() operation, but without the actual send. + // - amountSentLD is the amount in local decimals that would be sent from the sender. + // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance. + // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does. + (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView( + _sendParam.amountLD, + _sendParam.minAmountLD, + _sendParam.dstEid + ); + oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD); + } +} diff --git a/packages/oft-evm-upgradeable/test/OFT.t.sol b/packages/oft-evm-upgradeable/test/OFT.t.sol index 328b0ad601..a15c541015 100644 --- a/packages/oft-evm-upgradeable/test/OFT.t.sol +++ b/packages/oft-evm-upgradeable/test/OFT.t.sol @@ -6,6 +6,8 @@ import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/Opti import { OFTUpgradeableMock } from "./mocks/OFTUpgradeableMock.sol"; import { MessagingFee, MessagingReceipt } from "../contracts/oft/OFTCoreUpgradeable.sol"; import { OFTAdapterUpgradeableMock } from "./mocks/OFTAdapterUpgradeableMock.sol"; +import { NativeOFTAdapterUpgradeableMock } from "./mocks/NativeOFTAdapterUpgradeableMock.sol"; +import { NativeOFTAdapterUpgradeable } from "../contracts/oft/NativeOFTAdapterUpgradeable.sol"; import { ERC20Mock } from "./mocks/ERC20Mock.sol"; import { OFTComposerMock } from "./mocks/OFTComposerMock.sol"; import { OFTInspectorMock, IOAppMsgInspector } from "./mocks/OFTInspectorMock.sol"; @@ -20,36 +22,43 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Met import "forge-std/console.sol"; import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { NativeOFTAdapterUpgradeableMockCodec } from "./lib/NativeOFTAdapterUpgradeableMockCodec.sol"; contract OFTTest is TestHelperOz5 { using OptionsBuilder for bytes; + using NativeOFTAdapterUpgradeableMockCodec for NativeOFTAdapterUpgradeable; uint32 aEid = 1; uint32 bEid = 2; uint32 cEid = 3; + uint32 dEid = 4; OFTUpgradeableMock aOFT; OFTUpgradeableMock bOFT; OFTAdapterUpgradeableMock cOFTAdapter; + NativeOFTAdapterUpgradeableMock dNativeOFTAdapter; ERC20Mock cERC20Mock; OFTInspectorMock oAppInspector; - address public userA = address(0x1); - address public userB = address(0x2); - address public userC = address(0x3); + address public userA = makeAddr("userA"); + address public userB = makeAddr("userB"); + address public userC = makeAddr("userC"); + address public userD = makeAddr("userD"); uint256 public initialBalance = 100 ether; + uint256 public initialNativeBalance = 1000 ether; address public proxyAdmin = makeAddr("proxyAdmin"); function setUp() public virtual override { - vm.deal(userA, 1000 ether); - vm.deal(userB, 1000 ether); - vm.deal(userC, 1000 ether); + vm.deal(userA, initialNativeBalance); + vm.deal(userB, initialNativeBalance); + vm.deal(userC, initialNativeBalance); + vm.deal(userD, initialNativeBalance); super.setUp(); - setUpEndpoints(3, LibraryType.UltraLightNode); + setUpEndpoints(4, LibraryType.UltraLightNode); aOFT = OFTUpgradeableMock( _deployContractAndProxy( @@ -76,11 +85,20 @@ contract OFTTest is TestHelperOz5 { ) ); + dNativeOFTAdapter = NativeOFTAdapterUpgradeableMock( + _deployContractAndProxy( + type(NativeOFTAdapterUpgradeableMock).creationCode, + abi.encode(18, address(endpoints[dEid])), + abi.encodeWithSelector(NativeOFTAdapterUpgradeableMock.initialize.selector, address(this)) + ) + ); + // config and wire the ofts - address[] memory ofts = new address[](3); + address[] memory ofts = new address[](4); ofts[0] = address(aOFT); ofts[1] = address(bOFT); ofts[2] = address(cOFTAdapter); + ofts[3] = address(dNativeOFTAdapter); this.wireOApps(ofts); // mint tokens @@ -108,10 +126,11 @@ contract OFTTest is TestHelperOz5 { return address(new TransparentUpgradeableProxy(addr, proxyAdmin, _initializeArgs)); } - function test_constructor() public view { + function test_constructor() public virtual { assertEq(aOFT.owner(), address(this)); assertEq(bOFT.owner(), address(this)); assertEq(cOFTAdapter.owner(), address(this)); + assertEq(dNativeOFTAdapter.owner(), address(this)); assertEq(aOFT.balanceOf(userA), initialBalance); assertEq(bOFT.balanceOf(userB), initialBalance); @@ -120,9 +139,12 @@ contract OFTTest is TestHelperOz5 { assertEq(aOFT.token(), address(aOFT)); assertEq(bOFT.token(), address(bOFT)); assertEq(cOFTAdapter.token(), address(cERC20Mock)); + assertEq(dNativeOFTAdapter.token(), address(0)); + + assertEq(dNativeOFTAdapter.approvalRequired(), false); } - function test_oftVersion() public view { + function test_oftVersion() public { (bytes4 interfaceId, ) = aOFT.oftVersion(); bytes4 expectedId = 0x02e49c2c; assertEq(interfaceId, expectedId); @@ -209,7 +231,7 @@ contract OFTTest is TestHelperOz5 { assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test } - function test_oft_compose_codec() public view { + function test_oft_compose_codec() public { uint64 nonce = 1; uint32 srcEid = 2; uint256 amountCreditLD = 3; @@ -268,12 +290,12 @@ contract OFTTest is TestHelperOz5 { aOFT.debit(amountToSendLD, minAmountToCreditLD, dstEid); } - function test_toLD() public view { + function test_toLD() public { uint64 amountSD = 1000; assertEq(amountSD * aOFT.decimalConversionRate(), aOFT.toLD(uint64(amountSD))); } - function test_toSD() public view { + function test_toSD() public { uint256 amountLD = 1000000; assertEq(amountLD / aOFT.decimalConversionRate(), aOFT.toSD(amountLD)); } @@ -366,7 +388,7 @@ contract OFTTest is TestHelperOz5 { composeMsg = OFTMsgCodec.composeMsg(message); } - function test_oft_build_msg() public view { + function test_oft_build_msg() public { uint32 dstEid = bEid; bytes32 to = addressToBytes32(userA); uint256 amountToSendLD = 1.23456789 ether; @@ -399,7 +421,7 @@ contract OFTTest is TestHelperOz5 { assertEq(composeMsg_, expectedComposeMsg); } - function test_oft_build_msg_no_compose_msg() public view { + function test_oft_build_msg_no_compose_msg() public { uint32 dstEid = bEid; bytes32 to = addressToBytes32(userA); uint256 amountToSendLD = 1.23456789 ether; @@ -505,7 +527,7 @@ contract OFTTest is TestHelperOz5 { assertEq(combinedOptions, expectedOptions); } - function test_combine_options_no_enforced_options() public view { + function test_combine_options_no_enforced_options() public { uint32 eid = 1; uint16 msgType = 1; @@ -555,4 +577,142 @@ contract OFTTest is TestHelperOz5 { vm.expectRevert(abi.encodeWithSelector(IOAppMsgInspector.InspectionFailed.selector, message, extraOptions)); (message, ) = aOFT.buildMsgAndOptions(sendParam, amountToCreditLD); } + + function test_native_oft_adapter_debit() public virtual { + uint256 amountToSendLD = 1 ether; + uint256 minAmountToCreditLD = 1 ether; + uint32 dstEid = dEid; + + vm.prank(userD); + vm.expectRevert( + abi.encodeWithSelector(IOFT.SlippageExceeded.selector, amountToSendLD, minAmountToCreditLD + 1) + ); + dNativeOFTAdapter.debitView(amountToSendLD, minAmountToCreditLD + 1, dstEid); + + vm.prank(userD); + (uint256 amountDebitedLD, uint256 amountToCreditLD) = dNativeOFTAdapter.debit( + amountToSendLD, + minAmountToCreditLD, + dstEid + ); + + assertEq(amountDebitedLD, amountToSendLD); + assertEq(amountToCreditLD, amountToSendLD); + } + + function test_native_oft_adapter_credit() public virtual { + uint256 amountToCreditLD = 1 ether; + uint32 srcEid = dEid; + + // simulate userD already having deposited native to the adapter + vm.deal(address(dNativeOFTAdapter), amountToCreditLD); + + uint256 amountReceived = dNativeOFTAdapter.credit(userB, amountToCreditLD, srcEid); + + assertEq(userB.balance, initialNativeBalance + amountReceived); + assertEq(address(dNativeOFTAdapter).balance, 0); + } + + function test_native_oft_adapter_send() public virtual { + assertEq(userD.balance, initialNativeBalance); + assertEq(address(dNativeOFTAdapter).balance, 0); + + uint256 amountToSendLD = 1 ether; + uint32 dstEid = bEid; + + SendParam memory sendParam = SendParam( + dstEid, + addressToBytes32(userB), + amountToSendLD, + amountToSendLD, + OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0), + "", + "" + ); + + MessagingFee memory fee = dNativeOFTAdapter.quoteSend(sendParam, false); + uint256 correctMsgValue = fee.nativeFee + sendParam.amountLD; + + // expect sending wrapped native to fail if the amount to be sent is not provided in msg.value + vm.prank(userD); + vm.expectRevert( + abi.encodeWithSelector(NativeOFTAdapterUpgradeable.IncorrectMessageValue.selector, fee.nativeFee, correctMsgValue) + ); + dNativeOFTAdapter.send{ value: fee.nativeFee }(sendParam, fee, userD); + + // expect sending wrapped native to succeed if the amount to be sent and the fee are both included in msg.value + vm.prank(userD); + dNativeOFTAdapter.send{ value: correctMsgValue }(sendParam, fee, userD); + + assertEq(userD.balance, initialNativeBalance - correctMsgValue); + assertEq(address(dNativeOFTAdapter).balance, amountToSendLD); + + // expect sending wrapped native to fail if extra msg.value is provided + // i.e msg.value > amount to be sent (with dust removed) + fee + uint256 extraMsgValue = correctMsgValue + 1; + vm.prank(userD); + vm.expectRevert( + abi.encodeWithSelector(NativeOFTAdapterUpgradeable.IncorrectMessageValue.selector, extraMsgValue, correctMsgValue) + ); + dNativeOFTAdapter.send{ value: extraMsgValue }(sendParam, fee, userD); + } + + function test_native_oft_adapter_send_compose_msg() public virtual { + uint256 amountToSend = 1 ether; + + OFTComposerMock composer = new OFTComposerMock(); + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorLzComposeOption(0, 500000, 0); + bytes memory composeMsg = hex"1234"; + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(address(composer)), + amountToSend, + amountToSend, + options, + composeMsg, + "" + ); + MessagingFee memory fee = dNativeOFTAdapter.quoteSend(sendParam, false); + + assertEq(userD.balance, initialNativeBalance); + assertEq(address(dNativeOFTAdapter).balance, 0); + assertEq(bOFT.balanceOf(address(composer)), 0); + + uint256 msgValue = fee.nativeFee + dNativeOFTAdapter.removeDust(amountToSend); + + vm.prank(userD); + (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = dNativeOFTAdapter.send{ value: msgValue }( + sendParam, + fee, + payable(address(this)) + ); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + // lzCompose params + address from_ = address(bOFT); + bytes memory options_ = options; + bytes32 guid_ = msgReceipt.guid; + address to_ = address(composer); + bytes memory composerMsg_ = OFTComposeMsgCodec.encode( + msgReceipt.nonce, + dEid, + oftReceipt.amountReceivedLD, + abi.encodePacked(addressToBytes32(userD), composeMsg) + ); + this.lzCompose(bEid, from_, options_, guid_, to_, composerMsg_); + + assertEq(userD.balance, initialNativeBalance - msgValue); + assertEq(address(dNativeOFTAdapter).balance, amountToSend); + assertEq(bOFT.balanceOf(address(composer)), amountToSend); + + assertEq(composer.from(), from_); + assertEq(composer.guid(), guid_); + assertEq(composer.message(), composerMsg_); + assertEq(composer.executor(), address(this)); + assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test + } } diff --git a/packages/oft-evm-upgradeable/test/OFTFeeUpgradeable.t.sol b/packages/oft-evm-upgradeable/test/OFTFeeUpgradeable.t.sol index 301694b28b..2bc3612f0e 100644 --- a/packages/oft-evm-upgradeable/test/OFTFeeUpgradeable.t.sol +++ b/packages/oft-evm-upgradeable/test/OFTFeeUpgradeable.t.sol @@ -356,4 +356,44 @@ contract OFTFeeTest is OFTTest { assertEq(cERC20Mock.balanceOf(address(cOFTAdapter)), amountDebitedLD - expectedFee); } } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // @dev Override native adapter tests to skip them in OFTFeeTest (fee tests don't need native adapter functionality) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + function test_constructor() public virtual override { + assertEq(aOFT.owner(), address(this)); + assertEq(bOFT.owner(), address(this)); + assertEq(cOFTAdapter.owner(), address(this)); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + assertEq(IERC20(cOFTAdapter.token()).balanceOf(userC), initialBalance); + + assertEq(aOFT.token(), address(aOFT)); + assertEq(bOFT.token(), address(bOFT)); + assertEq(cOFTAdapter.token(), address(cERC20Mock)); + + // Skip native adapter assertions in fee test suite + } + + function test_native_oft_adapter_debit() public virtual override { + // Skip native adapter tests in fee test suite + vm.skip(true); + } + + function test_native_oft_adapter_credit() public virtual override { + // Skip native adapter tests in fee test suite + vm.skip(true); + } + + function test_native_oft_adapter_send() public virtual override { + // Skip native adapter tests in fee test suite + vm.skip(true); + } + + function test_native_oft_adapter_send_compose_msg() public virtual override { + // Skip native adapter tests in fee test suite + vm.skip(true); + } } \ No newline at end of file diff --git a/packages/oft-evm-upgradeable/test/lib/NativeOFTAdapterUpgradeableMockCodec.sol b/packages/oft-evm-upgradeable/test/lib/NativeOFTAdapterUpgradeableMockCodec.sol new file mode 100644 index 0000000000..3541abf55b --- /dev/null +++ b/packages/oft-evm-upgradeable/test/lib/NativeOFTAdapterUpgradeableMockCodec.sol @@ -0,0 +1,15 @@ +// SPDX-LICENSE-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +import { NativeOFTAdapterUpgradeable } from "../../contracts/oft/NativeOFTAdapterUpgradeable.sol"; +import { NativeOFTAdapterUpgradeableMock } from "../mocks/NativeOFTAdapterUpgradeableMock.sol"; + +// @title NativeOFTAdapterUpgradeableMockCodec +// @notice Codec to convert NativeOFTAdapterUpgradeable to NativeOFTAdapterUpgradeableMock in a consistent, readable manner. +// @dev For testing purposes only. +library NativeOFTAdapterUpgradeableMockCodec { + function asNativeOFTAdapterUpgradeableMock(NativeOFTAdapterUpgradeable _oft) internal pure returns (NativeOFTAdapterUpgradeableMock) { + return NativeOFTAdapterUpgradeableMock(payable(address(_oft))); + } +} \ No newline at end of file diff --git a/packages/oft-evm-upgradeable/test/mocks/NativeOFTAdapterUpgradeableMock.sol b/packages/oft-evm-upgradeable/test/mocks/NativeOFTAdapterUpgradeableMock.sol new file mode 100644 index 0000000000..38a76508e8 --- /dev/null +++ b/packages/oft-evm-upgradeable/test/mocks/NativeOFTAdapterUpgradeableMock.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { NativeOFTAdapterUpgradeable } from "../../contracts/oft/NativeOFTAdapterUpgradeable.sol"; + +contract NativeOFTAdapterUpgradeableMock is NativeOFTAdapterUpgradeable { + constructor( + uint8 _localDecimals, + address _lzEndpoint + ) NativeOFTAdapterUpgradeable(_localDecimals, _lzEndpoint, address(0)) {} + + function initialize(address _delegate) external initializer { + __NativeOFTAdapter_init(_delegate); + __Ownable_init(_delegate); + } + + // @dev expose internal functions for testing purposes + function debit( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debit(msg.sender, _amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function debitView( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) { + return _credit(_to, _amountToCreditLD, _srcEid); + } + + function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) { + return _removeDust(_amountLD); + } +}