diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d60c67b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "solidity.packageDefaultDependenciesContractsDirectory": "src", + "solidity.packageDefaultDependenciesDirectory": "lib", + "solidity.compileUsingRemoteVersion": "v0.8.23", + "[solidity]": { + "editor.defaultFormatter": "JuanBlanco.solidity" + }, + "solidity.formatter": "forge" +} \ No newline at end of file diff --git a/lib/LayerZero-v2 b/lib/LayerZero-v2 index c321320..ccfd0d3 160000 --- a/lib/LayerZero-v2 +++ b/lib/LayerZero-v2 @@ -1 +1 @@ -Subproject commit c3213200dfe8fabbf7d92c685590d34e6e70da43 +Subproject commit ccfd0d38f83ca8103b14ab9ca77f32e0419510ff diff --git a/lib/oapp-upgradeable/OAppCoreUpgradeable.sol b/lib/oapp-upgradeable/oapp/OAppCoreUpgradeable.sol similarity index 100% rename from lib/oapp-upgradeable/OAppCoreUpgradeable.sol rename to lib/oapp-upgradeable/oapp/OAppCoreUpgradeable.sol diff --git a/lib/oapp-upgradeable/OAppReceiverUpgradeable.sol b/lib/oapp-upgradeable/oapp/OAppReceiverUpgradeable.sol similarity index 100% rename from lib/oapp-upgradeable/OAppReceiverUpgradeable.sol rename to lib/oapp-upgradeable/oapp/OAppReceiverUpgradeable.sol diff --git a/lib/oapp-upgradeable/OAppSenderUpgradeable.sol b/lib/oapp-upgradeable/oapp/OAppSenderUpgradeable.sol similarity index 100% rename from lib/oapp-upgradeable/OAppSenderUpgradeable.sol rename to lib/oapp-upgradeable/oapp/OAppSenderUpgradeable.sol diff --git a/lib/oapp-upgradeable/OAppUpgradeable.sol b/lib/oapp-upgradeable/oapp/OAppUpgradeable.sol similarity index 100% rename from lib/oapp-upgradeable/OAppUpgradeable.sol rename to lib/oapp-upgradeable/oapp/OAppUpgradeable.sol diff --git a/lib/oapp-upgradeable/oapp/libs/OAppOptionsType3Upgradeable.sol b/lib/oapp-upgradeable/oapp/libs/OAppOptionsType3Upgradeable.sol new file mode 100644 index 0000000..9efe27b --- /dev/null +++ b/lib/oapp-upgradeable/oapp/libs/OAppOptionsType3Upgradeable.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppOptionsType3.sol"; + +/** + * @title OAppOptionsType3Upgradeable + * @dev Abstract contract implementing the OAppOptionsType3 interface with type 3 options. + */ +abstract contract OAppOptionsType3Upgradeable is IOAppOptionsType3, OwnableUpgradeable { + uint16 internal constant OPTION_TYPE_3 = 3; + + // @dev The "msgType" should be defined in the child contract. + mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions; + + /** + * @dev Sets the enforced options for specific endpoint and message type combinations. + * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options. + * + * @dev Only the owner/admin of the OApp can call this function. + * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc. + * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. + * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay + * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose(). + */ + function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner { + for (uint256 i = 0; i < _enforcedOptions.length; i++) { + // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining. + _assertOptionsType3(_enforcedOptions[i].options); + enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options; + } + + emit EnforcedOptionSet(_enforcedOptions); + } + + /** + * @notice Combines options for a given endpoint and message type. + * @param _eid The endpoint ID. + * @param _msgType The OAPP message type. + * @param _extraOptions Additional options passed by the caller. + * @return options The combination of caller specified options AND enforced options. + * + * @dev If there is an enforced lzReceive option: + * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether} + * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function. + * @dev This presence of duplicated options is handled off-chain in the verifier/executor. + */ + function combineOptions( + uint32 _eid, + uint16 _msgType, + bytes calldata _extraOptions + ) public view virtual returns (bytes memory) { + bytes memory enforced = enforcedOptions[_eid][_msgType]; + + // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options. + if (enforced.length == 0) return _extraOptions; + + // No caller options, return enforced + if (_extraOptions.length == 0) return enforced; + + // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined. + if (_extraOptions.length >= 2) { + _assertOptionsType3(_extraOptions); + // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced. + return bytes.concat(enforced, _extraOptions[2:]); + } + + // No valid set of options was found. + revert InvalidOptions(_extraOptions); + } + + /** + * @dev Internal function to assert that options are of type 3. + * @param _options The options to be checked. + */ + function _assertOptionsType3(bytes calldata _options) internal pure virtual { + uint16 optionsType = uint16(bytes2(_options[0:2])); + if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options); + } +} diff --git a/lib/oapp-upgradeable/oft/OFTCoreUpgradeable.sol b/lib/oapp-upgradeable/oft/OFTCoreUpgradeable.sol new file mode 100644 index 0000000..a33e990 --- /dev/null +++ b/lib/oapp-upgradeable/oft/OFTCoreUpgradeable.sol @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/oapp/OAppUpgradeable.sol"; + +import { OAppOptionsType3Upgradeable } from "@zodomo/oapp-upgradeable/oapp/libs/OAppOptionsType3Upgradeable.sol"; + +import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol"; + +import { OAppPreCrimeSimulatorUpgradeable } from "../precrime/OAppPreCrimeSimulatorUpgradeable.sol"; + +import { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol"; +import { OFTMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTMsgCodec.sol"; +import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol"; + +/** + * @title OFTCore + * @dev Abstract contract for the OftChain (OFT) token. + */ +abstract contract OFTCoreUpgradeable is IOFT, OAppUpgradeable, OAppPreCrimeSimulatorUpgradeable, OAppOptionsType3Upgradeable { + using OFTMsgCodec for bytes; + using OFTMsgCodec for bytes32; + + // @notice Provides a conversion rate when swapping between denominations of SD and LD + // - shareDecimals == SD == shared Decimals + // - localDecimals == LD == local decimals + // @dev Considers that tokens have different decimal amounts on various chains. + // @dev eg. + // For a token + // - locally with 4 decimals --> 1.2345 => uint(12345) + // - remotely with 2 decimals --> 1.23 => uint(123) + // - The conversion rate would be 10 ** (4 - 2) = 100 + // @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote, + // you can only display 1.23 -> uint(123). + // @dev To preserve the dust that would otherwise be lost on that conversion, + // we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh + uint256 public decimalConversionRate; + + // @notice Msg types that are used to identify the various OFT operations. + // @dev This can be extended in child contracts for non-default oft operations + // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol. + uint16 public constant SEND = 1; + uint16 public constant SEND_AND_CALL = 2; + + // Address of an optional contract to inspect both 'message' and 'options' + address public msgInspector; + + /** + * @dev Initializer. + * @param _localDecimals The decimals of the token on the local chain (this chain). + * @param _endpoint The address of the LayerZero endpoint. + * @param _owner The address of the OFT owner. + */ + function _initializeOFTCore(uint8 _localDecimals, address _endpoint, address _owner) + internal + virtual + onlyInitializing + { + _initializeOApp(_endpoint, _owner); + if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals(); + decimalConversionRate = 10 ** (_localDecimals - sharedDecimals()); + } + + /** + * @dev Retrieves the shared decimals of the OFT. + * @return The shared decimals of the OFT. + * + * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap + * Lowest common decimal denominator between chains. + * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64). + * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller. + * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615 + */ + function sharedDecimals() public pure virtual returns (uint8) { + return 6; + } + + /** + * @dev Sets the message inspector address for the OFT. + * @param _msgInspector The address of the message inspector. + * + * @dev This is an optional contract that can be used to inspect both 'message' and 'options'. + * @dev Set it to address(0) to disable it, or set it to a contract address to enable it. + */ + function setMsgInspector(address _msgInspector) public virtual onlyOwner { + msgInspector = _msgInspector; + emit MsgInspectorSet(_msgInspector); + } + + /** + * @notice Provides a quote for OFT-related operations. + * @param _sendParam The parameters for the send operation. + * @dev _oftCmd The OFT command to be executed. + * @return oftLimit The OFT limit information. + * @return oftFeeDetails The details of OFT fees. + * @return oftReceipt The OFT receipt information. + */ + function quoteOFT( + SendParam calldata _sendParam, + bytes calldata /*_oftCmd*/ // @dev unused in the default implementation. + ) + external + view + virtual + returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt) + { + uint256 minAmountLD = 0; // Unused in the default implementation. + uint256 maxAmountLD = type(uint64).max; // Unused in the default implementation. + oftLimit = OFTLimit(minAmountLD, maxAmountLD); + + // 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. + // - amountToDebitLD is the amount in local decimals that was be debited from the sender. + // - amountToCreditLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance. + // @dev The amount credited does NOT always equal the amount the user actually receives. + // HOWEVER, In the default implementation it is. + (uint256 amountToDebitLD, uint256 amountToCreditLD) = _debitView( + _sendParam.amountToSendLD, + _sendParam.minAmountToCreditLD, + _sendParam.dstEid + ); + oftReceipt = OFTReceipt(amountToDebitLD, amountToCreditLD); + } + + /** + * @notice Provides a quote for the send() operation. + * @param _sendParam The parameters for the send() operation. + * @param _extraOptions Additional options supplied by the caller to be used in the LayerZero message. + * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token. + * @param _composeMsg The composed message for the send() operation. + * @dev _oftCmd The OFT command to be executed. + * @return msgFee The calculated LayerZero messaging fee from the send() operation. + * + * @dev MessagingFee: LayerZero msg fee + * - nativeFee: The native fee. + * - lzTokenFee: The lzToken fee. + */ + function quoteSend( + SendParam calldata _sendParam, + bytes calldata _extraOptions, + bool _payInLzToken, + bytes calldata _composeMsg, + bytes calldata /*_oftCmd*/ // @dev unused in the default implementation. + ) external view virtual returns (MessagingFee memory msgFee) { + // @dev mock the amount to credit, this is the same operation used in the send(). + // The quote is as similar as possible to the actual send() operation. + (, uint256 amountToCreditLD) = _debitView( + _sendParam.amountToSendLD, + _sendParam.minAmountToCreditLD, + _sendParam.dstEid + ); + + // @dev Builds the options and OFT message to quote in the endpoint. + (bytes memory message, bytes memory options) = _buildMsgAndOptions( + _sendParam, + _extraOptions, + _composeMsg, + amountToCreditLD + ); + + // @dev Calculates the LayerZero fee for the send() operation. + return _quote(_sendParam.dstEid, message, options, _payInLzToken); + } + + /** + * @dev Executes the send operation. + * @param _sendParam The parameters for the send operation. + * @param _extraOptions Additional options 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. + * @param _composeMsg The composed message for the send() operation. + * @dev _oftCmd The OFT command to be executed. + * @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, + bytes calldata _extraOptions, + MessagingFee calldata _fee, + address _refundAddress, + bytes calldata _composeMsg, + bytes calldata /*_oftCmd*/ // @dev unused in the default implementation. + ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) { + // @dev Applies the token transfers regarding this send() operation. + // - amountDebitedLD is the amount in local decimals that was ACTUALLY debited from the sender. + // - amountToCreditLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance. + (uint256 amountDebitedLD, uint256 amountToCreditLD) = _debit( + _sendParam.amountToSendLD, + _sendParam.minAmountToCreditLD, + _sendParam.dstEid + ); + + // @dev Builds the options and OFT message to quote in the endpoint. + (bytes memory message, bytes memory options) = _buildMsgAndOptions( + _sendParam, + _extraOptions, + _composeMsg, + amountToCreditLD + ); + + // @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(amountDebitedLD, amountToCreditLD); + + emit OFTSent(msgReceipt.guid, msg.sender, amountDebitedLD, amountToCreditLD, _composeMsg); + } + + /** + * @dev Internal function to build the message and options. + * @param _sendParam The parameters for the send() operation. + * @param _extraOptions Additional options for the send() operation. + * @param _composeMsg The composed message for the send() operation. + * @param _amountToCreditLD The amount to credit in local decimals. + * @return message The encoded message. + * @return options The encoded options. + */ + function _buildMsgAndOptions( + SendParam calldata _sendParam, + bytes calldata _extraOptions, + bytes calldata _composeMsg, + uint256 _amountToCreditLD + ) internal view virtual returns (bytes memory message, bytes memory options) { + bool hasCompose; + // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is. + (message, hasCompose) = OFTMsgCodec.encode( + _sendParam.to, + _toSD(_amountToCreditLD), + // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote. + // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01' + _composeMsg + ); + // @dev Change the msg type depending if its composed or not. + uint16 msgType = hasCompose ? SEND_AND_CALL : SEND; + // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3. + options = combineOptions(_sendParam.dstEid, msgType, _extraOptions); + + // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector. + // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean + if (msgInspector != address(0)) IOAppMsgInspector(msgInspector).inspect(message, options); + } + + /** + * @dev Internal function to handle the receive on the LayerZero endpoint. + * @param _origin The origin information. + * - srcEid: The source chain endpoint ID. + * - sender: The sender address from the src chain. + * - nonce: The nonce of the LayerZero message. + * @param _guid The unique identifier for the received LayerZero message. + * @param _message The encoded message. + * @dev _executor The address of the executor. + * @dev _extraData Additional data. + */ + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address /*_executor*/, // @dev unused in the default implementation. + bytes calldata /*_extraData*/ // @dev unused in the default implementation. + ) internal virtual override { + // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm) + // Thus everything is bytes32() encoded in flight. + address toAddress = _message.sendTo().bytes32ToAddress(); + // @dev Convert the amount to credit into local decimals. + uint256 amountToCreditLD = _toLD(_message.amountSD()); + // @dev Credit the amount to the recipient and return the ACTUAL amount the recipient received in local decimals + uint256 amountReceivedLD = _credit(toAddress, amountToCreditLD, _origin.srcEid); + + if (_message.isComposed()) { + // @dev Proprietary composeMsg format for the OFT. + bytes memory composeMsg = OFTComposeMsgCodec.encode( + _origin.nonce, + _origin.srcEid, + amountReceivedLD, + _message.composeMsg() + ); + + // @dev Stores the lzCompose payload that will be executed in a separate tx. + // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains. + // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed. + // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive. + // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0. + endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg); + } + + emit OFTReceived(_guid, toAddress, amountToCreditLD, amountReceivedLD); + } + + /** + * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive. + * @param _origin The origin information. + * - srcEid: The source chain endpoint ID. + * - sender: The sender address from the src chain. + * - nonce: The nonce of the LayerZero message. + * @param _guid The unique identifier for the received LayerZero message. + * @param _message The LayerZero message. + * @param _executor The address of the off-chain executor. + * @param _extraData Arbitrary data passed by the msg executor. + * + * @dev Enables the preCrime simulator to mock sending lzReceive() messages, + * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver. + */ + function _lzReceiveSimulate( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) internal virtual override { + _lzReceive(_origin, _guid, _message, _executor, _extraData); + } + + /** + * @dev Check if the peer is considered 'trusted' by the OApp. + * @param _eid The endpoint ID to check. + * @param _peer The peer to check. + * @return Whether the peer passed is considered 'trusted' by the OApp. + * + * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source. + */ + function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) { + return peers[_eid] == _peer; + } + + /** + * @dev Internal function to remove dust from the given local decimal amount. + * @param _amountLD The amount in local decimals. + * @return amountLD The amount after removing dust. + * + * @dev Prevents the loss of dust when moving amounts between chains with different decimals. + * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100). + */ + function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) { + return (_amountLD / decimalConversionRate) * decimalConversionRate; + } + + /** + * @dev Internal function to convert an amount from shared decimals into local decimals. + * @param _amountSD The amount in shared decimals. + * @return amountLD The amount in local decimals. + */ + function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) { + return _amountSD * decimalConversionRate; + } + + /** + * @dev Internal function to convert an amount from local decimals into shared decimals. + * @param _amountLD The amount in local decimals. + * @return amountSD The amount in shared decimals. + */ + function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) { + return uint64(_amountLD / decimalConversionRate); + } + + /** + * @dev Internal function to mock the amount mutation from a OFT debit() operation. + * @param _amountToSendLD The amount to send in local decimals. + * @param _minAmountToCreditLD The minimum amount to credit in local decimals. + * @dev _dstEid The destination endpoint ID. + * @return amountToDebitLD The amount to ACTUALLY debit, in local decimals. + * @return amountToCreditLD The amount to credit on the remote chain, in local decimals. + * + * @dev This is where things like fees would be calculated and deducted from the amount to credit on the remote. + */ + function _debitView( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 /*_dstEid*/ + ) internal view virtual returns (uint256 amountToDebitLD, uint256 amountToCreditLD) { + // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token. + amountToDebitLD = _removeDust(_amountToSendLD); + // @dev The amount to credit is the same as the amount to debit in the default implementation. + amountToCreditLD = amountToDebitLD; + + // @dev Check for slippage. + if (amountToCreditLD < _minAmountToCreditLD) { + revert SlippageExceeded(amountToCreditLD, _minAmountToCreditLD); + } + } + + /** + * @dev Internal function to perform a debit operation. + * @param _amountToSendLD The amount to send in local decimals. + * @param _minAmountToCreditLD The minimum amount to credit in local decimals. + * @param _dstEid The destination endpoint ID. + * @return amountDebitedLD The amount ACTUALLY debited in local decimals. + * @return amountToCreditLD The amount to credit in local decimals on the remote. + */ + function _debit( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) internal virtual returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + // @dev Caller can indicate it wants to use push vs. pull method by passing an _amountToSendLD of 0. + if (_amountToSendLD > 0) { + // @dev Pull the tokens from the caller. + (amountDebitedLD, amountToCreditLD) = _debitSender(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } else { + // @dev Caller has pushed tokens. + (amountDebitedLD, amountToCreditLD) = _debitThis(_minAmountToCreditLD, _dstEid); + } + } + + /** + * @dev Internal function to perform a debit operation for this chain. + * @param _amountToSendLD The amount to send in local decimals. + * @param _minAmountToCreditLD The minimum amount to credit in local decimals. + * @param _dstEid The destination endpoint ID. + * @return amountDebitedLD The amount ACTUALLY debited in local decimals. + * @return amountToCreditLD The amount to credit in local decimals. + * + * @dev Defined here but are intended to be override depending on the OFT implementation. + * @dev This is used when the OFT pulls the tokens from the caller. + * ie. A user sends has approved the OFT to spend on its behalf. + */ + function _debitSender( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) internal virtual returns (uint256 amountDebitedLD, uint256 amountToCreditLD); + + /** + * @dev Internal function to perform a debit operation for this chain. + * @param _minAmountToCreditLD The minimum amount to credit in local decimals. + * @param _dstEid The destination endpoint ID. + * @return amountDebitedLD The amount ACTUALLY debited in local decimals. + * @return amountToCreditLD The amount to credit in local decimals. + * + * @dev Defined here but are intended to be override depending on the OFT implementation. + * @dev This is used when the OFT is the recipient of a push operation. + * ie. A user sends tokens direct to the OFT contract address. + */ + function _debitThis( + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) internal virtual returns (uint256 amountDebitedLD, uint256 amountToCreditLD); + + /** + * @dev Internal function to perform a credit operation. + * @param _to The address to credit. + * @param _amountToCreditLD The amount to credit in local decimals. + * @param _srcEid The source endpoint ID. + * @return amountReceivedLD The amount ACTUALLY received in local decimals. + * + * @dev Defined here but are intended to be override depending on the OFT implementation. + * @dev Depending on OFT implementation the _amountToCreditLD could differ from the amountReceivedLD. + */ + function _credit( + address _to, + uint256 _amountToCreditLD, + uint32 _srcEid + ) internal virtual returns (uint256 amountReceivedLD); +} diff --git a/lib/oapp-upgradeable/oft/OFTUpgradeable.sol b/lib/oapp-upgradeable/oft/OFTUpgradeable.sol new file mode 100644 index 0000000..9e12e19 --- /dev/null +++ b/lib/oapp-upgradeable/oft/OFTUpgradeable.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +import {OFTCoreUpgradeable} from "./OFTCoreUpgradeable.sol"; + +/** + * @title OFT Contract + * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract. + */ +contract OFTUpgradeable is OFTCoreUpgradeable, ERC20Upgradeable { + /** + * @dev Initializer for the OFT contract. + * @param _name The name of the OFT. + * @param _symbol The symbol of the OFT. + * @param _lzEndpoint The LayerZero endpoint address. + * @param _owner The owner of the contract. + */ + function _initializeOFT(string memory _name, string memory _symbol, address _lzEndpoint, address _owner) + internal + virtual + onlyInitializing + { + __ERC20_init(_name, _symbol); + _initializeOFTCore(decimals(), _lzEndpoint, _owner); + } + + /** + * @dev Retrieves the OFT contract version. + * @return major The major version. + * @return minor The minor version. + * + * @dev major version: Indicates a cross-chain compatible msg encoding with other OFTs. + * @dev minor version: Indicates a version within the local chains context. eg. OFTAdapter vs. OFT + * @dev For example, if a new feature is added to the OFT contract, the minor version will be incremented. + * @dev If a new feature is added to the OFT cross-chain msg encoding, the major version will be incremented. + * ie. localOFT version(1,1) CAN send messages to remoteOFT version(1,2) + */ + function oftVersion() external pure returns (uint64 major, uint64 minor) { + return (1, 1); + } + + /** + * @dev Retrieves the address of the underlying ERC20 implementation. + * @return The address of the OFT token. + * + * @dev In the case of OFT, address(this) and erc20 are the same contract. + */ + function token() external view returns (address) { + return address(this); + } + + /** + * @dev Burns tokens from the sender's specified balance. + * @param _amountToSendLD The amount of tokens to send in local decimals. + * @param _minAmountToCreditLD The minimum amount to credit in local decimals. + * @param _dstEid The destination chain ID. + * @return amountDebitedLD The amount of tokens ACTUALLY debited in local decimals. + * @return amountToCreditLD The amount of tokens to credit in local decimals. + */ + function _debitSender(uint256 _amountToSendLD, uint256 _minAmountToCreditLD, uint32 _dstEid) + internal + virtual + override + returns (uint256 amountDebitedLD, uint256 amountToCreditLD) + { + (amountDebitedLD, amountToCreditLD) = _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + + // @dev In NON-default OFT, amountDebited could be 100, with a 10% fee, the credited amount is 90, + // therefore amountDebited CAN differ from amountToCredit. + + // @dev Default OFT burns on src. + _burn(msg.sender, amountDebitedLD); + } + + /** + * @dev Burns tokens that have been sent into this contract. + * @param _minAmountToReceiveLD The minimum amount to receive in local decimals. + * @param _dstEid The destination chain ID. + * @return amountDebitedLD The amount of tokens ACTUALLY debited in local decimals. + * @return amountToCreditLD The amount of tokens to credit in local decimals. + */ + function _debitThis(uint256 _minAmountToReceiveLD, uint32 _dstEid) + internal + virtual + override + returns (uint256 amountDebitedLD, uint256 amountToCreditLD) + { + // @dev This is the push method, where at any point in the transaction, the OFT receives tokens and they can be sent by the caller. + // @dev This SHOULD be done atomically, otherwise any caller can spend tokens that are owned by the contract. + // @dev In the NON-default case where fees are stored in the contract, there should be a value reserved via a global state. + // eg. balanceOf(address(this)) - accruedFees; + (amountDebitedLD, amountToCreditLD) = _debitView(balanceOf(address(this)), _minAmountToReceiveLD, _dstEid); + + // @dev Default OFT burns on src. + _burn(address(this), amountDebitedLD); + + // @dev When sending tokens direct to the OFT contract, + // there is NOT a default mechanism to capture the dust that MIGHT get left in the contract. + // If you want to refund this dust, will need to add another function to return it. + } + + /** + * @dev Credits tokens to the specified address. + * @param _to The address to credit the tokens to. + * @param _amountToCreditLD 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. + */ + function _credit(address _to, uint256 _amountToCreditLD, uint32 /*_srcEid*/ ) + internal + virtual + override + returns (uint256 amountReceivedLD) + { + // @dev Default OFT mints on dst. + _mint(_to, _amountToCreditLD); + // @dev In the case of NON-default OFT, the amountToCreditLD MIGHT not == amountReceivedLD. + return _amountToCreditLD; + } +} diff --git a/lib/oapp-upgradeable/precrime/OAppPreCrimeSimulatorUpgradeable.sol b/lib/oapp-upgradeable/precrime/OAppPreCrimeSimulatorUpgradeable.sol new file mode 100644 index 0000000..e24b7e9 --- /dev/null +++ b/lib/oapp-upgradeable/precrime/OAppPreCrimeSimulatorUpgradeable.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { IPreCrime } from "@layerzerolabs/lz-evm-oapp-v2/contracts/precrime/interfaces/IPreCrime.sol"; +import { IOAppPreCrimeSimulator, InboundPacket, Origin } from "@layerzerolabs/lz-evm-oapp-v2/contracts/precrime/interfaces/IOAppPreCrimeSimulator.sol"; + +/** + * @title OAppPreCrimeSimulator + * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp. + */ +abstract contract OAppPreCrimeSimulatorUpgradeable is IOAppPreCrimeSimulator, OwnableUpgradeable { + // The address of the preCrime implementation. + address public preCrime; + + /** + * @dev Retrieves the address of the OApp contract. + * @return The address of the OApp contract. + * + * @dev The simulator contract is the base contract for the OApp by default. + * @dev If the simulator is a separate contract, override this function. + */ + function oApp() external view virtual returns (address) { + return address(this); + } + + /** + * @dev Sets the preCrime contract address. + * @param _preCrime The address of the preCrime contract. + */ + function setPreCrime(address _preCrime) public virtual onlyOwner { + preCrime = _preCrime; + emit PreCrimeSet(_preCrime); + } + + /** + * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results. + * @param _packets An array of InboundPacket objects representing received packets to be delivered. + * + * @dev WARNING: MUST revert at the end with the simulation results. + * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function, + * WITHOUT actually executing them. + */ + function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual { + for (uint256 i = 0; i < _packets.length; i++) { + InboundPacket calldata packet = _packets[i]; + + // Ignore packets that are not from trusted peers. + if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue; + + // @dev Because a verifier is calling this function, it doesnt have access to executor params: + // - address _executor + // - bytes calldata _extraData + // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive(). + // They are instead stubbed to default values, address(0) and bytes("") + // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit, + // which would cause the revert to be ignored. + this.lzReceiveSimulate{ value: packet.value }( + packet.origin, + packet.guid, + packet.message, + packet.executor, + packet.extraData + ); + } + + // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult(). + revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult()); + } + + /** + * @dev Is effectively an internal function because msg.sender must be address(this). + * Allows resetting the call stack for 'internal' calls. + * @param _origin The origin information containing the source endpoint and sender address. + * - srcEid: The source chain endpoint ID. + * - sender: The sender address on the src chain. + * - nonce: The nonce of the message. + * @param _guid The unique identifier of the packet. + * @param _message The message payload of the packet. + * @param _executor The executor address for the packet. + * @param _extraData Additional data for the packet. + */ + function lzReceiveSimulate( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) external payable virtual { + // @dev Ensure ONLY can be called 'internally'. + if (msg.sender != address(this)) revert OnlySelf(); + _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData); + } + + /** + * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive. + * @param _origin The origin information. + * - srcEid: The source chain endpoint ID. + * - sender: The sender address from the src chain. + * - nonce: The nonce of the LayerZero message. + * @param _guid The GUID of the LayerZero message. + * @param _message The LayerZero message. + * @param _executor The address of the off-chain executor. + * @param _extraData Arbitrary data passed by the msg executor. + * + * @dev Enables the preCrime simulator to mock sending lzReceive() messages, + * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver. + */ + function _lzReceiveSimulate( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) internal virtual; + + /** + * @dev checks if the specified peer is considered 'trusted' by the OApp. + * @param _eid The endpoint Id to check. + * @param _peer The peer to check. + * @return Whether the peer passed is considered 'trusted' by the OApp. + */ + function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool); +} diff --git a/src/Counter.sol b/src/Counter.sol index 07b3d93..ca52c65 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/OAppUpgradeable.sol"; +import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/oapp/OAppUpgradeable.sol"; contract Counter is OAppUpgradeable, UUPSUpgradeable { bytes public constant MESSAGE = ""; diff --git a/src/OFT.sol b/src/OFT.sol new file mode 100644 index 0000000..e8e359f --- /dev/null +++ b/src/OFT.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OFTUpgradeable} from "@zodomo/oapp-upgradeable/oft/OFTUpgradeable.sol"; + +contract OFT is OFTUpgradeable, UUPSUpgradeable { + constructor() { + _disableInitializers(); + } + + function initialize(string memory _name, string memory _symbol, address _lzEndpoint, address _owner) + public + initializer + { + _initializeOFT(_name, _symbol, _lzEndpoint, _owner); + } + + /* ========== UUPS ========== */ + //solhint-disable-next-line no-empty-blocks + function _authorizeUpgrade(address) internal override onlyOwner {} + + function getImplementation() external view returns (address) { + return _getImplementation(); + } +} diff --git a/src/OFTMock.sol b/src/OFTMock.sol new file mode 100644 index 0000000..625fb33 --- /dev/null +++ b/src/OFTMock.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {OFT} from "./OFT.sol"; +import {SendParam} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol"; + +contract OFTMock is OFT { + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } + + // @dev expose internal functions for testing purposes + function debit(uint256 _amountToSendLD, uint256 _minAmountToCreditLD, uint32 _dstEid) + public + returns (uint256 amountDebitedLD, uint256 amountToCreditLD) + { + return _debit(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function debitView(uint256 _amountToSendLD, uint256 _minAmountToCreditLD, uint32 _dstEid) + public + view + returns (uint256 amountDebitedLD, uint256 amountToCreditLD) + { + return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) { + return _removeDust(_amountLD); + } + + function toLD(uint64 _amountSD) public view returns (uint256 amountLD) { + return _toLD(_amountSD); + } + + function toSD(uint256 _amountLD) public view returns (uint64 amountSD) { + return _toSD(_amountLD); + } + + function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) { + return _credit(_to, _amountToCreditLD, _srcEid); + } + + function buildMsgAndOptions( + SendParam calldata _sendParam, + bytes calldata _extraOptions, + bytes calldata _composeMsg, + uint256 _amountToCreditLD + ) public view returns (bytes memory message, bytes memory options) { + return _buildMsgAndOptions(_sendParam, _extraOptions, _composeMsg, _amountToCreditLD); + } +} diff --git a/src/OmniCounter.sol b/src/OmniCounter.sol index 0af7c49..68c161b 100644 --- a/src/OmniCounter.sol +++ b/src/OmniCounter.sol @@ -8,7 +8,7 @@ import { MessagingReceipt } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/OAppUpgradeable.sol"; +import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/oapp/OAppUpgradeable.sol"; import {ILayerZeroComposer} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol"; library MsgCodec { diff --git a/test/OFT.t.sol b/test/OFT.t.sol new file mode 100644 index 0000000..dc76325 --- /dev/null +++ b/test/OFT.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Test, console2} from "forge-std/Test.sol"; + +import {OptionsBuilder} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol"; + +import {OFTMock} from "./mocks/OFTMock.sol"; +import {MessagingFee, MessagingReceipt} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol"; +import {IOFT, SendParam, OFTReceipt} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol"; + +import {OFT} from "../src/OFT.sol"; +import {UUPSProxy} from "../src/UUPSProxy.sol"; +import {ProxyTestHelper} from "./utils/ProxyTestHelper.sol"; + +contract OFTTest is ProxyTestHelper { + using OptionsBuilder for bytes; + + uint32 aEid = 1; + uint32 bEid = 2; + uint32 cEid = 3; + + OFTMock aOFT; + OFTMock bOFT; + + address public userA = address(0x1); + address public userB = address(0x2); + address public userC = address(0x3); + uint256 public initialBalance = 100 ether; + + function setUp() public virtual override { + vm.deal(userA, 1000 ether); + vm.deal(userB, 1000 ether); + vm.deal(userC, 1000 ether); + + super.setUp(); + + setUpEndpoints(3, LibraryType.UltraLightNode); + + aOFT = OFTMock( + _deployOAppProxyGeneralized( + type(OFTMock).creationCode, + abi.encodeWithSelector(OFT.initialize.selector, "aOFT", "aOFT", address(endpoints[aEid]), address(this)) + ) + ); + + bOFT = OFTMock( + _deployOAppProxyGeneralized( + type(OFTMock).creationCode, + abi.encodeWithSelector(OFT.initialize.selector, "bOFT", "bOFT", address(endpoints[bEid]), address(this)) + ) + ); + + // config and wire the ofts + address[] memory ofts = new address[](2); + ofts[0] = address(aOFT); + ofts[1] = address(bOFT); + this.wireOApps(ofts); + + // mint tokens + aOFT.mint(userA, initialBalance); + bOFT.mint(userB, initialBalance); + } + + function test_initializer() public { + assertEq(aOFT.owner(), address(this)); + assertEq(bOFT.owner(), address(this)); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + assertEq(aOFT.token(), address(aOFT)); + assertEq(bOFT.token(), address(bOFT)); + } + + function test_send_oft() public { + uint256 tokensToSend = 1 ether; + SendParam memory sendParam = SendParam(bEid, addressToBytes32(userB), tokensToSend, tokensToSend); + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + MessagingFee memory fee = aOFT.quoteSend(sendParam, options, false, "", ""); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + vm.prank(userA); + aOFT.send{value: fee.nativeFee}(sendParam, options, fee, payable(address(this)), "", ""); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); + } + + function _deployOAppProxy(address _endpoint, address _owner, address implementationAddress) + internal + override + returns (address proxyAddress) + {} +} diff --git a/test/OmniCounter.t.sol b/test/OmniCounter.t.sol index dadd2ea..a9a222a 100644 --- a/test/OmniCounter.t.sol +++ b/test/OmniCounter.t.sol @@ -73,12 +73,10 @@ contract OmniCounterTest is ProxyTestHelper { function test_nativeDrop_increment() public { uint256 balanceBefore = address(bCounter).balance; - bytes memory options = OptionsBuilder - .newOptions() - .addExecutorLzReceiveOption(200000, 0) + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0) .addExecutorNativeDropOption(1 gwei, addressToBytes32(address(bCounter))); - (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.VANILLA_TYPE, options); - aCounter.increment{ value: nativeFee }(bEid, MsgCodec.VANILLA_TYPE, options); + (uint256 nativeFee,) = aCounter.quote(bEid, MsgCodec.VANILLA_TYPE, options); + aCounter.increment{value: nativeFee}(bEid, MsgCodec.VANILLA_TYPE, options); // verify packet to bCounter manually verifyPackets(bEid, addressToBytes32(address(bCounter))); @@ -142,13 +140,10 @@ contract OmniCounterTest is ProxyTestHelper { uint256 countBBefore = bCounter.count(); uint256 composedCountBBefore = bCounter.composedCount(); - bytes memory options = OptionsBuilder - .newOptions() - .addExecutorLzReceiveOption(200000, 0) + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0) .addExecutorLzComposeOption(0, 10000000, 10000000); - (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.COMPOSED_ABA_TYPE, options); - console2.log("bEid is", bEid); - aCounter.increment{ value: nativeFee }(bEid, MsgCodec.COMPOSED_ABA_TYPE, options); + (uint256 nativeFee,) = aCounter.quote(bEid, MsgCodec.COMPOSED_ABA_TYPE, options); + aCounter.increment{value: nativeFee}(bEid, MsgCodec.COMPOSED_ABA_TYPE, options); verifyPackets(bEid, addressToBytes32(address(bCounter)), 0, address(bCounter)); assertEq(bCounter.count(), countBBefore + 1, "increment B1 assertion failure"); diff --git a/test/mocks/Counter2.sol b/test/mocks/Counter2.sol index 6125f21..221ac2c 100644 --- a/test/mocks/Counter2.sol +++ b/test/mocks/Counter2.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/OAppUpgradeable.sol"; +import {OAppUpgradeable, MessagingFee, Origin} from "@zodomo/oapp-upgradeable/oapp/OAppUpgradeable.sol"; contract Counter2 is OAppUpgradeable, UUPSUpgradeable { bytes public constant MESSAGE = ""; diff --git a/test/mocks/OFTMock.sol b/test/mocks/OFTMock.sol new file mode 100644 index 0000000..fd30d4d --- /dev/null +++ b/test/mocks/OFTMock.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {OFT} from "../../src/OFT.sol"; +import {SendParam} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol"; + +contract OFTMock is OFT { + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } + + // @dev expose internal functions for testing purposes + function debit(uint256 _amountToSendLD, uint256 _minAmountToCreditLD, uint32 _dstEid) + public + returns (uint256 amountDebitedLD, uint256 amountToCreditLD) + { + return _debit(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function debitView(uint256 _amountToSendLD, uint256 _minAmountToCreditLD, uint32 _dstEid) + public + view + returns (uint256 amountDebitedLD, uint256 amountToCreditLD) + { + return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) { + return _removeDust(_amountLD); + } + + function toLD(uint64 _amountSD) public view returns (uint256 amountLD) { + return _toLD(_amountSD); + } + + function toSD(uint256 _amountLD) public view returns (uint64 amountSD) { + return _toSD(_amountLD); + } + + function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) { + return _credit(_to, _amountToCreditLD, _srcEid); + } + + function buildMsgAndOptions( + SendParam calldata _sendParam, + bytes calldata _extraOptions, + bytes calldata _composeMsg, + uint256 _amountToCreditLD + ) public view returns (bytes memory message, bytes memory options) { + return _buildMsgAndOptions(_sendParam, _extraOptions, _composeMsg, _amountToCreditLD); + } +} diff --git a/test/utils/ProxyTestHelper.sol b/test/utils/ProxyTestHelper.sol index 2a47265..04bee06 100644 --- a/test/utils/ProxyTestHelper.sol +++ b/test/utils/ProxyTestHelper.sol @@ -40,4 +40,19 @@ abstract contract ProxyTestHelper is TestHelper { internal virtual returns (address proxyAddress); + + function _deployOAppProxyGeneralized(bytes memory _oappBytecode, bytes memory _deletegateCallData) + internal + returns (address proxyAddress) + { + address implementationAddress = address(0); + + assembly { + implementationAddress := create(0, add(_oappBytecode, 0x20), mload(_oappBytecode)) + if iszero(extcodesize(implementationAddress)) { revert(0, 0) } + } + + UUPSProxy proxy = new UUPSProxy(implementationAddress, _deletegateCallData); + proxyAddress = address(proxy); + } }