// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IExecutor} from "@interfaces/IExecutor.sol";
import {TransferManager} from "../TransferManager.sol";
import {ETH_ADDRESS} from "../../lib/NativeETH.sol";
error EtherfiExecutor__InvalidDataLength();
error EtherfiExecutor__InvalidDirection();
interface IEtherfiRedemptionManager {
function redeemEEth(
uint256 eEthAmount,
address receiver,
address outputToken
) external;
}
interface IEtherfiLiquidityPool {
function deposit() external payable returns (uint256);
}
interface IWeETH {
function wrap(uint256 _eETHAmount) external returns (uint256);
function unwrap(uint256 _weETHAmount) external returns (uint256);
}
enum EtherfiDirection {
EethToEth,
EthToEeth,
EethToWeeth,
WeethToEeth
}
contract EtherfiExecutor is IExecutor {
using SafeERC20 for IERC20;
address public immutable ethAddress;
address public immutable eethAddress;
address public immutable liquidityPoolAddress;
address public immutable weethAddress;
address public immutable redemptionManagerAddress;
constructor(
address _ethAddress,
address _eethAddress,
address _liquidityPoolAddress,
address _weethAddress,
address _redemptionManagerAddress
) {
require(
_ethAddress != address(0), "EtherfiExecutor: ethAddress is zero"
);
require(
_eethAddress != address(0), "EtherfiExecutor: eethAddress is zero"
);
require(
_liquidityPoolAddress != address(0),
"EtherfiExecutor: liquidityPoolAddress is zero"
);
require(
_weethAddress != address(0), "EtherfiExecutor: weethAddress is zero"
);
require(
_redemptionManagerAddress != address(0),
"EtherfiExecutor: redemptionManagerAddress is zero"
);
ethAddress = _ethAddress;
eethAddress = _eethAddress;
liquidityPoolAddress = _liquidityPoolAddress;
weethAddress = _weethAddress;
redemptionManagerAddress = _redemptionManagerAddress;
}
// slither-disable-next-line locked-ether
function swap(uint256 amountIn, bytes calldata data, address receiver)
external
payable
{
EtherfiDirection direction;
direction = _decodeData(data);
if (direction == EtherfiDirection.EethToEth) {
// eETH is share-based and rounds down on amount conversions;
// cap redeem amount to current balance to avoid 1-wei dust reverts.
uint256 redeemAmount = IERC20(eethAddress).balanceOf(address(this));
if (redeemAmount > amountIn) {
redeemAmount = amountIn;
}
IEtherfiRedemptionManager(redemptionManagerAddress)
.redeemEEth(redeemAmount, receiver, ethAddress);
} else if (direction == EtherfiDirection.EthToEeth) {
// slither-disable-next-line arbitrary-send-eth,unused-return
IEtherfiLiquidityPool(liquidityPoolAddress)
.deposit{value: amountIn}();
} else if (direction == EtherfiDirection.EethToWeeth) {
// slither-disable-next-line unused-return
IWeETH(weethAddress).wrap(amountIn);
} else if (direction == EtherfiDirection.WeethToEeth) {
// slither-disable-next-line unused-return
IWeETH(weethAddress).unwrap(amountIn);
} else {
revert EtherfiExecutor__InvalidDirection();
}
}
function getTransferData(bytes calldata data)
external
view
returns (
TransferManager.TransferType transferType,
address receiver,
address tokenIn,
address tokenOut,
bool outputToRouter
)
{
EtherfiDirection direction = _decodeData(data);
if (direction == EtherfiDirection.EthToEeth) {
tokenIn = ETH_ADDRESS;
transferType = TransferManager.TransferType.TransferNativeInExecutor;
tokenOut = eethAddress;
outputToRouter = true;
} else if (direction == EtherfiDirection.EethToEth) {
transferType = TransferManager.TransferType.ProtocolWillDebit;
receiver = redemptionManagerAddress;
tokenIn = eethAddress;
tokenOut = ETH_ADDRESS;
outputToRouter = false;
} else if (direction == EtherfiDirection.EethToWeeth) {
transferType = TransferManager.TransferType.ProtocolWillDebit;
receiver = weethAddress;
tokenIn = eethAddress;
tokenOut = weethAddress;
outputToRouter = true;
} else if (direction == EtherfiDirection.WeethToEeth) {
transferType = TransferManager.TransferType.ProtocolWillDebit;
receiver = msg.sender;
tokenIn = weethAddress;
tokenOut = eethAddress;
outputToRouter = true;
} else {
revert EtherfiExecutor__InvalidDirection();
}
}
function fundsExpectedAddress(
bytes calldata /* data */
)
external
view
returns (address)
{
return msg.sender;
}
function _decodeData(bytes calldata data)
internal
pure
returns (EtherfiDirection direction)
{
if (data.length != 1) {
revert EtherfiExecutor__InvalidDataLength();
}
direction = EtherfiDirection(uint8(data[0]));
}
}