diff --git a/src/contracts/MockReceiver.sol b/src/contracts/MockReceiver.sol index 0791ae4..6cf7e78 100644 --- a/src/contracts/MockReceiver.sol +++ b/src/contracts/MockReceiver.sol @@ -1,12 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + import { IOAppCore } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol"; import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol"; import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { OptionsBuilder } from "@fraxfinance/layerzero-v2-upgradeable/oapp/contracts/oapp/libs/OptionsBuilder.sol"; +import { SendParam, MessagingFee, IOFT } from "@fraxfinance/layerzero-v2-upgradeable/oapp/contracts/oft/interfaces/IOFT.sol"; + import { FraxtalL2 } from "src/contracts/chain-constants/FraxtalL2.sol"; interface ICurve { @@ -24,23 +28,23 @@ contract MockReceiver is IOAppComposer { address public immutable endpoint; - address public lzFrax = 0x80Eede496655FB9047dd39d9f418d5483ED600df; - address public lzFraxCurve = 0x53f8F4e154F68C2D29a0D06BD50f82bCf1bd95dB; + address public fraxOft = 0x80Eede496655FB9047dd39d9f418d5483ED600df; + address public fraxCurve = 0x53f8F4e154F68C2D29a0D06BD50f82bCf1bd95dB; - address public lzSFrax = 0x5Bff88cA1442c2496f7E475E9e7786383Bc070c0; - address public lzSFraxCurve = 0xd2866eF5A94E741Ec8EDE5FF8e3A1f9C59c5e298; + address public sFraxOft = 0x5Bff88cA1442c2496f7E475E9e7786383Bc070c0; + address public sFraxCurve = 0xd2866eF5A94E741Ec8EDE5FF8e3A1f9C59c5e298; - address public lzFrxEth = 0x43eDD7f3831b08FE70B7555ddD373C8bF65a9050; - address public lzFrxEthCurve = 0x50842664DfBD876249D0113671d72dB168FBE4d0; + address public frxEthOft = 0x43eDD7f3831b08FE70B7555ddD373C8bF65a9050; + address public frxEthCurve = 0x50842664DfBD876249D0113671d72dB168FBE4d0; - address public lzSFrxEth = 0x3Ec3849C33291a9eF4c5dB86De593EB4A37fDe45; - address public lzSFrxEthCurve = 0xe5F61df936d50302962d5B914537Ff3cB63b3526; + address public sFrxEthOft = 0x3Ec3849C33291a9eF4c5dB86De593EB4A37fDe45; + address public sFrxEthCurve = 0xe5F61df936d50302962d5B914537Ff3cB63b3526; - address public lzFxs = 0x64445f0aecC51E94aD52d8AC56b7190e764E561a; - address public lzFxsCurve = 0xBc383485068Ffd275D7262Bef65005eE7a5A1870; + address public fxsOft = 0x64445f0aecC51E94aD52d8AC56b7190e764E561a; + address public fxsCurve = 0xBc383485068Ffd275D7262Bef65005eE7a5A1870; - address public lzFpi = 0x90581eCa9469D8D7F5D3B60f4715027aDFCf7927; - address public lzFpiCurve = 0x7FaA69f8fEbe38bBfFbAE3252DE7D1491F0c6157; + address public fpiOft = 0x90581eCa9469D8D7F5D3B60f4715027aDFCf7927; + address public fpiCurve = 0x7FaA69f8fEbe38bBfFbAE3252DE7D1491F0c6157; /// @dev Initializes the contract. /// @param _endpoint LayerZero Endpoint address @@ -50,6 +54,37 @@ contract MockReceiver is IOAppComposer { receive() external payable {} + /// @dev Using the _oApp address (as provided by the endpoint), return the respective tokens + /// @dev ie. a send of FRAX would have the _oApp address of the FRAX OFT + /// @return nToken "Native token" (pre-compiled proxy address) + /// @return curve (Address of curve.fi pool for nToken/lzToken) + function _getRespectiveTokens(address _oApp) internal view returns ( + address nToken, + address curve + ) { + if (_oApp == fraxOft) { + nToken = FraxtalL2.FRAX; + curve = fraxCurve; + } else if (_oApp == sFraxOft) { + nToken = FraxtalL2.SFRAX; + curve = sFraxCurve; + } else if (_oApp == frxEthOft) { + nToken = FraxtalL2.WFRXETH; + curve = frxEthCurve; + } else if (_oApp == sFrxEthOft) { + nToken = FraxtalL2.SFRXETH; + curve = sFrxEthCurve; + } else if (_oApp == fxsOft) { + nToken = FraxtalL2.FXS; + curve = fxsCurve; + } else if (_oApp == fpiOft) { + nToken = FraxtalL2.FPI; + curve = fpiCurve; + } else { + revert InvalidOApp(); + } + } + /// @notice Handles incoming composed messages from LayerZero. /// @dev Decodes the message payload to perform a token swap. /// This method expects the encoded compose message to contain the swap amount and recipient address. @@ -67,36 +102,7 @@ contract MockReceiver is IOAppComposer { ) external payable override { require(msg.sender == endpoint, "!endpoint"); - address nToken; // "native" token - address lzToken; // "LayerZero" token - address curve; // curve.fi pool - if (_oApp == lzFrax) { - nToken = FraxtalL2.FRAX; - lzToken = lzFrax; - curve = lzFraxCurve; - } else if (_oApp == lzSFrax) { - nToken = FraxtalL2.SFRAX; - lzToken = lzSFrax; - curve = lzSFraxCurve; - } else if (_oApp == lzFrxEth) { - nToken = FraxtalL2.WFRXETH; - lzToken = lzFrxEth; - curve = lzFrxEthCurve; - } else if (_oApp == lzSFrxEth) { - nToken = FraxtalL2.SFRXETH; - lzToken = lzSFrxEth; - curve = lzSFrxEthCurve; - } else if (_oApp == lzFxs) { - nToken = FraxtalL2.FXS; - lzToken = lzFxs; - curve = lzFxsCurve; - } else if (_oApp == lzFpi) { - nToken = FraxtalL2.FPI; - lzToken = lzFpi; - curve = lzFpiCurve; - } else { - revert InvalidOApp(); - } + (address nToken, address curve) = _getRespectiveTokens(_oApp); // Extract the composed message from the delivered message using the MsgCodec (address recipient, uint256 amountOutMin) = abi.decode( @@ -105,10 +111,10 @@ contract MockReceiver is IOAppComposer { ); uint256 amount = OFTComposeMsgCodec.amountLD(_message); - IERC20(lzToken).approve(curve, amount); - try ICurve(curve).exchange({ i: int128(1), j: int128(0), _dx: amount, _min_dy: amountOutMin }) returns ( - uint256 amountOut - ) { + // try swap + IERC20(_oApp).approve(curve, amount); + try ICurve(curve).exchange({i: int128(1), j: int128(0), _dx: amount, _min_dy: amountOutMin }) returns (uint256 amountOut) + { if (nToken == FraxtalL2.WFRXETH) { // unwrap then send IWETH(nToken).withdraw(amountOut); @@ -120,8 +126,83 @@ contract MockReceiver is IOAppComposer { } } catch { // reset approval - swap failed - IERC20(lzToken).approve(curve, 0); - IERC20(lzToken).transfer(recipient, amount); + IERC20(_oApp).approve(curve, 0); + + // send non-converted OFT to recipient + IERC20(_oApp).transfer(recipient, amount); } } + + /// @notice swap native token on curve and send OFT to another chain + /// @param _oApp Address of the upgradeable OFT + /// @param _dstEid Destination EID + /// @param _to Bytes32 representation of recipient ( ie. for EVM: bytes32(uint256(uint160(addr))) ) + /// @param _amount Amount of OFT to send + /// @param _amountOutMin Minimum amount allowed to receive from the Curve.fi swap + /// @param _amountLDMin Minimum amount allowed to receive from the LZ send + function swapAndSend( + address _oApp, + uint32 _dstEid, + bytes32 _to, + uint256 _amount, + uint256 _amountOutMin, + uint256 _amountLDMin + ) external { + ( + address nToken, + address curve + ) = _getRespectiveTokens(_oApp); + + // transfer from sender to here + IERC20(nToken).transferFrom(msg.sender, address(this), _amount); + + // Swap + IERC20(nToken).approve(curve, _amount); + uint256 amountOut = ICurve(curve).exchange({i: int128(0), j: int128(1), _dx: _amount, _min_dy: _amountOutMin }); + + // Send OFT to destination chain + _send({ + _oApp: _oApp, + _dstEid: _dstEid, + _to: _to, + _amountLD: amountOut, + _amountLDMin: _amountLDMin + }); + } + + function _swap( + bool _isNToken, + address _token, + address _curve, + uint256 _amount, + uint256 _amountOutMin + ) internal returns (uint256) { + // approval first + IERC20(_token).approve(_curve, _amount); + + // swap + (int128 i, int128 j) = _isNToken ? (int128(0), int128(1)) : (int128(1), int128(0)); + return ICurve(_curve).exchange({ i: i, j: j, _dx: _amount, _min_dy: _amountOutMin}); + } + + function _send( + address _oApp, + uint32 _dstEid, + bytes32 _to, + uint256 _amountLD, + uint256 _amountLDMin + ) internal { + bytes memory options = OptionsBuilder.newOptions(); + SendParam memory sendParam = SendParam({ + dstEid: _dstEid, + to: _to, + amountLD: _amountLD, + minAmountLD: _amountLDMin, + extraOptions: options, + composeMsg: "", + oftCmd: "" + }); + MessagingFee memory fee = IOFT(_oApp).quoteSend(sendParam, false); + IOFT(_oApp).send{ value: fee.nativeFee }(sendParam, fee, payable(msg.sender)); + } }