Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BeefySwapper v2 #253

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 157 additions & 56 deletions contracts/BIFI/infra/BeefySwapper.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;
pragma solidity 0.8.23;

import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import { IBeefyOracle } from "../interfaces/oracle/IBeefyOracle.sol";
import { BytesLib } from "../utils/BytesLib.sol";
import {BytesLib} from "../utils/BytesLib.sol";
import {IBeefyOracle} from "../interfaces/oracle/IBeefyOracle.sol";
import {IBeefySwapper} from "../interfaces/beefy/IBeefySwapper.sol";
import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

/// @title Beefy Swapper
/// @author Beefy, @kexley
/// @notice Centralized swapper
contract BeefySwapper is OwnableUpgradeable {
contract BeefySwapper is OwnableUpgradeable, IBeefySwapper {
using SafeERC20Upgradeable for IERC20MetadataUpgradeable;
using BytesLib for bytes;

Expand All @@ -35,25 +35,15 @@ contract BeefySwapper is OwnableUpgradeable {
/// @param minAmountOut Minimum amount required from the swap
error SlippageExceeded(uint256 amountOut, uint256 minAmountOut);

/// @dev Stored data for a swap
/// @param router Target address that will handle the swap
/// @param data Payload of a template swap between the two tokens
/// @param amountIndex Location in the data byte string where the amount should be overwritten
/// @param minIndex Location in the data byte string where the min amount to swap should be
/// overwritten
/// @param minAmountSign Represents the sign of the min amount to be included in the swap, any
/// negative value will encode a negative min amount (required for Balancer)
struct SwapInfo {
address router;
bytes data;
uint256 amountIndex;
uint256 minIndex;
int8 minAmountSign;
}
/// @dev At least minLength tokens should be included in the route
error RouteTooShort(uint256 minLength);

/// @notice Stored swap info for a token
/// @inheritdoc IBeefySwapper
mapping(address => mapping(address => SwapInfo)) public swapInfo;

/// @inheritdoc IBeefySwapper
mapping(address => mapping(address => address[])) public swapRoute;

/// @notice Oracle used to calculate the minimum output of a swap
IBeefyOracle public oracle;

Expand All @@ -80,6 +70,12 @@ contract BeefySwapper is OwnableUpgradeable {
/// @param swapInfo Struct of stored swap information for the pair of tokens
event SetSwapInfo(address indexed fromToken, address indexed toToken, SwapInfo swapInfo);

/// @notice Set new swap route between two tokens
/// @param fromToken Address of the source token
/// @param toToken Address of the destination token
/// @param route Array of tokens to route through
event SetSwapRoute(address indexed fromToken, address indexed toToken, address[] route);

/// @notice Set a new oracle
/// @param oracle New oracle address
event SetOracle(address oracle);
Expand All @@ -98,13 +94,7 @@ contract BeefySwapper is OwnableUpgradeable {
slippage = _slippage;
}

/// @notice Swap between two tokens with slippage calculated using the oracle
/// @dev Caller must have already approved this contract to spend the _fromToken. After the
/// swap the _toToken token is sent directly to the caller
/// @param _fromToken Token to swap from
/// @param _toToken Token to swap to
/// @param _amountIn Amount of _fromToken to use in the swap
/// @return amountOut Amount of _toToken returned to the caller
/// @inheritdoc IBeefySwapper
function swap(
address _fromToken,
address _toToken,
Expand All @@ -114,14 +104,7 @@ contract BeefySwapper is OwnableUpgradeable {
amountOut = _swap(_fromToken, _toToken, _amountIn, minAmountOut);
}

/// @notice Swap between two tokens with slippage provided by the caller
/// @dev Caller must have already approved this contract to spend the _fromToken. After the
/// swap the _toToken token is sent directly to the caller
/// @param _fromToken Token to swap from
/// @param _toToken Token to swap to
/// @param _amountIn Amount of _fromToken to use in the swap
/// @param _minAmountOut Minimum amount of _toToken that is acceptable to be returned to caller
/// @return amountOut Amount of _toToken returned to the caller
/// @inheritdoc IBeefySwapper
function swap(
address _fromToken,
address _toToken,
Expand All @@ -131,23 +114,49 @@ contract BeefySwapper is OwnableUpgradeable {
amountOut = _swap(_fromToken, _toToken, _amountIn, _minAmountOut);
}

/// @notice Get the amount out from a simulated swap with slippage and non-fresh prices
/// @param _fromToken Token to swap from
/// @param _toToken Token to swap to
/// @param _amountIn Amount of _fromToken to use in the swap
/// @return amountOut Amount of _toTokens returned from the swap
/// @inheritdoc IBeefySwapper
function swap(
address[] calldata _route,
uint256 _amountIn
) external returns (uint256 amountOut) {
uint256 minAmountOut = _getAmountOut(_route[0], _route[_route.length - 1], _amountIn);
amountOut = _swap(_route, _amountIn, minAmountOut);
}

/// @inheritdoc IBeefySwapper
function swap(
address[] calldata _route,
uint256 _amountIn,
uint256 _minAmountOut
) external returns (uint256 amountOut) {
amountOut = _swap(_route, _amountIn, _minAmountOut);
}

/// @inheritdoc IBeefySwapper
function getAmountOut(
address _fromToken,
address _toToken,
uint256 _amountIn
) external view returns (uint256 amountOut) {
(uint256 fromPrice, uint256 toPrice) =
(oracle.getPrice(_fromToken), oracle.getPrice(_toToken));
(uint256 fromPrice, uint256 toPrice) =
(oracle.getPrice(_fromToken), oracle.getPrice(_toToken));
uint8 decimals0 = IERC20MetadataUpgradeable(_fromToken).decimals();
uint8 decimals1 = IERC20MetadataUpgradeable(_toToken).decimals();
amountOut = _calculateAmountOut(_amountIn, fromPrice, toPrice, decimals0, decimals1);
}

/// @inheritdoc IBeefySwapper
function hasSwap(
address _fromToken,
address _toToken
) external view returns (bool) {
if (_haveSwapData(_fromToken, _toToken)) {
return true;
}

return swapRoute[_fromToken][_toToken].length > 0;
}

/// @dev Use the oracle to get prices for both _fromToken and _toToken and calculate the
/// estimated output reduced by the slippage
/// @param _fromToken Token to swap from
Expand All @@ -168,6 +177,7 @@ contract BeefySwapper is OwnableUpgradeable {

/// @dev _fromToken is pulled into this contract from the caller, swap is executed according to
/// the stored data, resulting _toTokens are sent to the caller
/// Will attempt direct swap first, if no data is stored for the pair, will attempt to swap via stored route
/// @param _fromToken Token to swap from
/// @param _toToken Token to swap to
/// @param _amountIn Amount of _fromToken to use in the swap
Expand All @@ -179,12 +189,68 @@ contract BeefySwapper is OwnableUpgradeable {
uint256 _amountIn,
uint256 _minAmountOut
) private returns (uint256 amountOut) {
IERC20MetadataUpgradeable(_fromToken).safeTransferFrom(msg.sender, address(this), _amountIn);
_executeSwap(_fromToken, _toToken, _amountIn, _minAmountOut);
amountOut = IERC20MetadataUpgradeable(_toToken).balanceOf(address(this));
if (amountOut < _minAmountOut) revert SlippageExceeded(amountOut, _minAmountOut);
IERC20MetadataUpgradeable(_toToken).safeTransfer(msg.sender, amountOut);
emit Swap(msg.sender, _fromToken, _toToken, _amountIn, amountOut);
if (!_haveSwapData(_fromToken, _toToken)) {
address[] memory route = swapRoute[_fromToken][_toToken];
if (route.length == 0) {
revert NoSwapData(_fromToken, _toToken);
}
return _swap(route, _amountIn, _minAmountOut);
}

{
IERC20MetadataUpgradeable(_fromToken).safeTransferFrom(msg.sender, address(this), _amountIn);
amountOut = IERC20MetadataUpgradeable(_fromToken).balanceOf(address(this));

_executeSwap(_fromToken, _toToken, amountOut, _minAmountOut);
amountOut = IERC20MetadataUpgradeable(_toToken).balanceOf(address(this));

if (amountOut < _minAmountOut) {
revert SlippageExceeded(amountOut, _minAmountOut);
}

IERC20MetadataUpgradeable(_toToken).safeTransfer(msg.sender, amountOut);
emit Swap(msg.sender, _fromToken, _toToken, _amountIn, amountOut);
}
}

/// @dev _route[0] is pulled into this contract from the caller, swap is executed according to
/// the stored data, resulting _route[_route.length-1] are sent to the caller
/// @param _route List of tokens to swap via
/// @param _amountIn Amount of _route[0] to use in the swap
/// @param _minAmountOut Minimum amount of _route[_route.length-1] that is acceptable to be returned to caller
/// @return amountOut Amount of _route[_route.length-1] returned to the caller
function _swap(
address[] memory _route,
uint256 _amountIn,
uint256 _minAmountOut
) private returns (uint256 amountOut) {
uint256 routeLength = _route.length;
if (routeLength < 2) {
revert RouteTooShort(2);
}
uint256 lastIndex = routeLength - 1;
address fromToken = _route[0];
address toToken = _route[lastIndex];

IERC20MetadataUpgradeable(fromToken).safeTransferFrom(msg.sender, address(this), _amountIn);
amountOut = IERC20MetadataUpgradeable(fromToken).balanceOf(address(this));

uint256 nextIndex;
address nextToken;
for (uint i; i < lastIndex;) {
nextIndex = i + 1;
nextToken = _route[nextIndex];
_executeSwap(_route[i], nextToken, amountOut, nextIndex == lastIndex ? _minAmountOut : 0);
amountOut = IERC20MetadataUpgradeable(nextToken).balanceOf(address(this));
unchecked {++i;}
}

if (amountOut < _minAmountOut) {
revert SlippageExceeded(amountOut, _minAmountOut);
}

IERC20MetadataUpgradeable(toToken).safeTransfer(msg.sender, amountOut);
emit Swap(msg.sender, fromToken, toToken, _amountIn, amountOut);
}

/// @dev Fetch the stored swap info for the route between the two tokens, insert the encoded
Expand All @@ -201,22 +267,29 @@ contract BeefySwapper is OwnableUpgradeable {
) private {
SwapInfo memory swapData = swapInfo[_fromToken][_toToken];
address router = swapData.router;
if (router == address(0)) revert NoSwapData(_fromToken, _toToken);
bytes memory data = swapData.data;

data = _insertData(data, swapData.amountIndex, abi.encode(_amountIn));

bytes memory minAmountData = swapData.minAmountSign >= 0
? abi.encode(_minAmountOut)
: abi.encode(-int256(_minAmountOut));
: abi.encode(- int256(_minAmountOut));

data = _insertData(data, swapData.minIndex, minAmountData);

IERC20MetadataUpgradeable(_fromToken).forceApprove(router, type(uint256).max);
(bool success,) = router.call(data);
if (!success) revert SwapFailed(router, data);
}

/// @dev Helper function to check if there is swap data defined for a token pair
/// @param _fromToken Token to swap from
/// @param _toToken Token to swap to
/// @return haveData Whether there is swap data defined
function _haveSwapData(address _fromToken, address _toToken) private view returns (bool haveData) {
haveData = swapInfo[_fromToken][_toToken].router != address(0);
}

/// @dev Helper function to insert data to an in-memory bytes string
/// @param _data Template swap payload with blank spaces to overwrite
/// @param _index Start location in the data byte string where the _newData should overwrite
Expand Down Expand Up @@ -298,10 +371,38 @@ contract BeefySwapper is OwnableUpgradeable {
for (uint i; i < tokenLength;) {
swapInfo[_fromTokens[i]][_toTokens[i]] = _swapInfos[i];
emit SetSwapInfo(_fromTokens[i], _toTokens[i], _swapInfos[i]);
unchecked { ++i; }
unchecked {++i;}
}
}

/// @notice Owner function to set the stored swap route between two tokens
/// @dev Oracle for input and output tokens, and swap data for each token pair must be valid
/// @param _route Array of tokens to route through
function setSwapRoute(address[] calldata _route) external onlyOwner {
uint256 routeLength = _route.length;
if (routeLength < 3) {
revert RouteTooShort(3);
}

address fromToken = _route[0];
uint256 lastIndex = routeLength - 1;
address toToken = _route[lastIndex];

// check if we have functioning oracle for first and last tokens
_getFreshPrice(fromToken, toToken);

// check we can swap between each pair of tokens in the route
for (uint i; i < lastIndex;) {
if (!_haveSwapData(_route[i], _route[i + 1])) {
revert NoSwapData(_route[i], _route[i + 1]);
}
unchecked {++i;}
}

swapRoute[fromToken][toToken] = _route;
emit SetSwapRoute(fromToken, toToken, _route);
}

/// @notice Owner function to set the oracle used to calculate the minimum outputs
/// @dev No validation checks
/// @param _oracle Address of the new oracle
Expand Down
Loading
Loading