Spectra finance diff
555 lines
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
pragma solidity 0.8.20;
import {Math} from "openzeppelin-math/Math.sol";
import {Math} from "openzeppelin-math/Math.sol";
import {IERC20Permit} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20Permit} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC3156FlashBorrower} from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC3156FlashBorrower} from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC3156FlashLender} from "openzeppelin-contracts/interfaces/IERC3156FlashLender.sol";
import {IERC3156FlashLender} from "openzeppelin-contracts/interfaces/IERC3156FlashLender.sol";
import {IERC4626} from "openzeppelin-contracts/interfaces/IERC4626.sol";
import {IERC4626} from "openzeppelin-contracts/interfaces/IERC4626.sol";
import {AccessManagedUpgradeable} from "openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import {AccessManagedUpgradeable} from "openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import {SafeERC20, IERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {SafeERC20, IERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Commands} from "./Commands.sol";
import {Commands} from "./Commands.sol";
import {Constants} from "./Constants.sol";
import {Constants} from "./Constants.sol";
import {CurvePoolUtil} from "../libraries/CurvePoolUtil.sol";
import {CurvePoolUtil} from "../libraries/CurvePoolUtil.sol";
import {RayMath} from "../libraries/RayMath.sol";
import {RayMath} from "../libraries/RayMath.sol";
import {ICurvePool} from "../interfaces/ICurvePool.sol";
import {ICurvePool} from "../interfaces/ICurvePool.sol";
import {IPrincipalToken} from "src/interfaces/IPrincipalToken.sol";
import {IPrincipalToken} from "src/interfaces/IPrincipalToken.sol";
import {IRegistry} from "../interfaces/IRegistry.sol";
import {IRegistry} from "../interfaces/IRegistry.sol";
import {ISpectra4626Wrapper} from "../interfaces/ISpectra4626Wrapper.sol";
import {RouterUtil} from "./util/RouterUtil.sol";
import {RouterUtil} from "./util/RouterUtil.sol";
abstract contract Dispatcher is AccessManagedUpgradeable {
abstract contract Dispatcher is AccessManagedUpgradeable {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
using Math for uint256;
using Math for uint256;
using RayMath for uint256;
using RayMath for uint256;
error InvalidCommandType(uint256 commandType);
error InvalidCommandType(uint256 commandType);
error MinimumBalanceNotReached(
error MinimumBalanceNotReached(
address token,
address token,
address owner,
address owner,
uint256 minimumBalance,
uint256 minimumBalance,
uint256 actualBalance
uint256 actualBalance
);
);
error InvalidFlashloanLender(address lender);
error InvalidFlashloanLender(address lender);
error InvalidTokenIndex(uint256 i, uint256 j);
error InvalidTokenIndex(uint256 i, uint256 j);
error AddressError();
error AddressError();
error AmountError();
error CallFailed();
error PermitFailed();
error PermitFailed();
error MaxInvolvedTokensExceeded();
error MaxInvolvedTokensExceeded();
error BalanceUnderflow();
error BalanceUnderflow();
// used for tracking balance changes in _previewRate
// used for tracking balance changes in _previewRate
struct TokenBalance {
struct TokenBalance {
address token;
address token;
uint256 balance;
uint256 balance;
}
}
/** @dev registry of the protocol */
/** @dev registry of the protocol */
address public immutable registry;
address public immutable registry;
/** @dev used during a router execution to track the initiator of the execution */
/** @dev used during a router execution to track the initiator of the execution */
address internal msgSender;
address internal msgSender;
/** @dev used during a flashloan execution to track the lender address */
/** @dev used during a flashloan execution to track the lender address */
address internal flashloanLender;
address internal flashloanLender;
/** @notice Router Util contract */
/** @notice Router Util contract */
address public routerUtil;
address public routerUtil;
constructor(address _registry) {
constructor(address _registry) {
if (_registry == address(0)) {
if (_registry == address(0)) {
revert AddressError();
revert AddressError();
}
}
registry = _registry;
registry = _registry;
}
}
function initializeDispatcher(
function initializeDispatcher(
address _routerUtil,
address _routerUtil,
address _initialAuthority
address _initialAuthority
) internal initializer {
) internal initializer {
if (_routerUtil == address(0)) {
if (_routerUtil == address(0)) {
revert AddressError();
revert AddressError();
}
}
routerUtil = _routerUtil;
routerUtil = _routerUtil;
__AccessManaged_init(_initialAuthority);
__AccessManaged_init(_initialAuthority);
}
}
/**
/**
* @dev Setter for the router utility contract
* @param _newRouterUtil the new address of the router utility contract
*/
function setRouterUtil(address _newRouterUtil) external restricted {
if (_newRouterUtil == address(0)) {
revert AddressError();
}
if (_newRouterUtil == routerUtil) {
return;
}
routerUtil = _newRouterUtil;
}
/**
* @dev Executes a single command along with its encoded input data
* @dev Executes a single command along with its encoded input data
* @param _commandType encoded representation of the command
* @param _commandType The encoded representation of the command
* @param _inputs calldata carrying the arguments to the functions that should be called
* @param _inputs The encoded arguments for the specified command
*/
*/
function _dispatch(bytes1 _commandType, bytes calldata _inputs) internal {
function _dispatch(bytes1 _commandType, bytes calldata _inputs) internal {
uint256 command = uint8(_commandType & Commands.COMMAND_TYPE_MASK);
uint256 command = uint8(_commandType & Commands.COMMAND_TYPE_MASK);
if (command == Commands.TRANSFER_FROM) {
if (command == Commands.TRANSFER_FROM) {
(address token, uint256 value) = abi.decode(_inputs, (address, uint256));
(address token, uint256 value) = abi.decode(_inputs, (address, uint256));
IERC20(token).safeTransferFrom(msgSender, address(this), value);
IERC20(token).safeTransferFrom(msgSender, address(this), value);
} else if (command == Commands.TRANSFER_FROM_WITH_PERMIT) {
} else if (command == Commands.TRANSFER_FROM_WITH_PERMIT) {
(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) = abi
(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) = abi
.decode(_inputs, (address, uint256, uint256, uint8, bytes32, bytes32));
.decode(_inputs, (address, uint256, uint256, uint8, bytes32, bytes32));
try IERC20Permit(token).permit(msgSender, address(this), value, deadline, v, r, s) {
try IERC20Permit(token).permit(msgSender, address(this), value, deadline, v, r, s) {
// Permit executed successfully, proceed
// Permit executed successfully, proceed
} catch {
} catch {
// Check allowance to see if permit was already executed
// Check allowance to see if permit was already executed
uint256 allowance = IERC20(token).allowance(msgSender, address(this));
uint256 allowance = IERC20(token).allowance(msgSender, address(this));
if (allowance < value) {
if (allowance < value) {
revert PermitFailed();
revert PermitFailed();
}
}
}
}
IERC20(token).safeTransferFrom(msgSender, address(this), value);
IERC20(token).safeTransferFrom(msgSender, address(this), value);
} else if (command == Commands.TRANSFER) {
} else if (command == Commands.TRANSFER) {
(address token, address recipient, uint256 value) = abi.decode(
(address token, address recipient, uint256 value) = abi.decode(
_inputs,
_inputs,
(address, address, uint256)
(address, address, uint256)
);
);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
value = _resolveTokenValue(token, value);
value = _resolveTokenValue(token, value);
if (value != 0) {
if (value != 0) {
IERC20(token).safeTransfer(recipient, value);
IERC20(token).safeTransfer(recipient, value);
}
}
} else if (command == Commands.CURVE_SWAP) {
} else if (command == Commands.CURVE_SWAP) {
(
(
address pool,
address pool,
uint256 i,
uint256 i,
uint256 j,
uint256 j,
uint256 amountIn,
uint256 amountIn,
uint256 minAmountOut,
uint256 minAmountOut,
address recipient
address recipient
) = abi.decode(_inputs, (address, uint256, uint256, uint256, uint256, address));
) = abi.decode(_inputs, (address, uint256, uint256, uint256, uint256, address));
// pool.coins(i) is the token to be swapped
// pool.coins(i) is the token to be swapped
address token = ICurvePool(pool).coins(i);
address token = ICurvePool(pool).coins(i);
amountIn = _resolveTokenValue(token, amountIn);
amountIn = _resolveTokenValue(token, amountIn);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
IERC20(token).forceApprove(pool, amountIn);
IERC20(token).forceApprove(pool, amountIn);
ICurvePool(pool).exchange(
ICurvePool(pool).exchange(
i,
i,
j,
j,
amountIn,
amountIn,
minAmountOut,
minAmountOut,
false, // Do not use ETH
false, // Do not use ETH
recipient
recipient
);
);
IERC20(token).forceApprove(pool, 0);
IERC20(token).forceApprove(pool, 0);
} else if (command == Commands.WRAP_VAULT_IN_4626_ADAPTER) {
(
address wrapper,
uint256 vaultShares,
address recipient,
uint256 minWrapperShares
) = abi.decode(_inputs, (address, uint256, address, uint256));
address vault = ISpectra4626Wrapper(wrapper).vaultShare();
recipient = _resolveAddress(recipient);
vaultShares = _resolveTokenValue(vault, vaultShares);
IERC20(vault).forceApprove(wrapper, vaultShares);
ISpectra4626Wrapper(wrapper).wrap(vaultShares, recipient, minWrapperShares);
IERC20(vault).forceApprove(wrapper, 0);
} else if (command == Commands.UNWRAP_VAULT_FROM_4626_ADAPTER) {
(
address wrapper,
uint256 wrapperShares,
address recipient,
uint256 minVaultShares
) = abi.decode(_inputs, (address, uint256, address, uint256));
recipient = _resolveAddress(recipient);
wrapperShares = _resolveTokenValue(wrapper, wrapperShares);
ISpectra4626Wrapper(wrapper).unwrap(
wrapperShares,
recipient,
address(this),
minVaultShares
);
} else if (command == Commands.DEPOSIT_ASSET_IN_IBT) {
} else if (command == Commands.DEPOSIT_ASSET_IN_IBT) {
(address ibt, uint256 assets, address recipient) = abi.decode(
(address ibt, uint256 assets, address recipient) = abi.decode(
_inputs,
_inputs,
(address, uint256, address)
(address, uint256, address)
);
);
address asset = IERC4626(ibt).asset();
address asset = IERC4626(ibt).asset();
assets = _resolveTokenValue(asset, assets);
assets = _resolveTokenValue(asset, assets);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
IERC20(asset).forceApprove(ibt, assets);
IERC20(asset).forceApprove(ibt, assets);
IERC4626(ibt).deposit(assets, recipient);
IERC4626(ibt).deposit(assets, recipient);
IERC20(asset).forceApprove(ibt, 0);
IERC20(asset).forceApprove(ibt, 0);
} else if (command == Commands.DEPOSIT_ASSET_IN_PT) {
} else if (command == Commands.DEPOSIT_ASSET_IN_PT) {
(
(
address pt,
address pt,
uint256 assets,
uint256 assets,
address ptRecipient,
address ptRecipient,
address ytRecipient,
address ytRecipient,
uint256 minShares
uint256 minShares
) = abi.decode(_inputs, (address, uint256, address, address, uint256));
) = abi.decode(_inputs, (address, uint256, address, address, uint256));
address asset = IPrincipalToken(pt).underlying();
address asset = IPrincipalToken(pt).underlying();
assets = _resolveTokenValue(asset, assets);
assets = _resolveTokenValue(asset, assets);
ptRecipient = _resolveAddress(ptRecipient);
ptRecipient = _resolveAddress(ptRecipient);
ytRecipient = _resolveAddress(ytRecipient);
ytRecipient = _resolveAddress(ytRecipient);
bool isRegisteredPT = IRegistry(registry).isRegisteredPT(pt);
bool isRegisteredPT = IRegistry(registry).isRegisteredPT(pt);
if (isRegisteredPT) {
if (isRegisteredPT) {
_ensureApproved(asset, pt, assets);
_ensureApproved(asset, pt, assets);
} else {
} else {
IERC20(asset).forceApprove(pt, assets);
IERC20(asset).forceApprove(pt, assets);
}
}
IPrincipalToken(pt).deposit(assets, ptRecipient, ytRecipient, minShares);
IPrincipalToken(pt).deposit(assets, ptRecipient, ytRecipient, minShares);
if (!isRegisteredPT) {
if (!isRegisteredPT) {
IERC20(asset).forceApprove(pt, 0);
IERC20(asset).forceApprove(pt, 0);
}
}
} else if (command == Commands.DEPOSIT_IBT_IN_PT) {
} else if (command == Commands.DEPOSIT_IBT_IN_PT) {
(
(
address pt,
address pt,
uint256 ibts,
uint256 ibts,
address ptRecipient,
address ptRecipient,
address ytRecipient,
address ytRecipient,
uint256 minShares
uint256 minShares
) = abi.decode(_inputs, (address, uint256, address, address, uint256));
) = abi.decode(_inputs, (address, uint256, address, address, uint256));
address ibt = IPrincipalToken(pt).getIBT();
address ibt = IPrincipalToken(pt).getIBT();
ibts = _resolveTokenValue(ibt, ibts);
ibts = _resolveTokenValue(ibt, ibts);
ptRecipient = _resolveAddress(ptRecipient);
ptRecipient = _resolveAddress(ptRecipient);
ytRecipient = _resolveAddress(ytRecipient);
ytRecipient = _resolveAddress(ytRecipient);
bool isRegisteredPT = IRegistry(registry).isRegisteredPT(pt);
bool isRegisteredPT = IRegistry(registry).isRegisteredPT(pt);
if (isRegisteredPT) {
if (isRegisteredPT) {
_ensureApproved(ibt, pt, ibts);
_ensureApproved(ibt, pt, ibts);
} else {
} else {
IERC20(ibt).forceApprove(pt, ibts);
IERC20(ibt).forceApprove(pt, ibts);
}
}
IPrincipalToken(pt).depositIBT(ibts, ptRecipient, ytRecipient, minShares);
IPrincipalToken(pt).depositIBT(ibts, ptRecipient, ytRecipient, minShares);
if (!isRegisteredPT) {
if (!isRegisteredPT) {
IERC20(ibt).forceApprove(pt, 0);
IERC20(ibt).forceApprove(pt, 0);
}
}
} else if (command == Commands.REDEEM_IBT_FOR_ASSET) {
} else if (command == Commands.REDEEM_IBT_FOR_ASSET) {
(address ibt, uint256 shares, address recipient) = abi.decode(
(address ibt, uint256 shares, address recipient) = abi.decode(
_inputs,
_inputs,
(address, uint256, address)
(address, uint256, address)
);
);
shares = _resolveTokenValue(ibt, shares);
shares = _resolveTokenValue(ibt, shares);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
IERC4626(ibt).redeem(shares, recipient, address(this));
IERC4626(ibt).redeem(shares, recipient, address(this));
} else if (command == Commands.REDEEM_PT_FOR_ASSET) {
} else if (command == Commands.REDEEM_PT_FOR_ASSET) {
(address pt, uint256 shares, address recipient, uint256 minAssets) = abi.decode(
(address pt, uint256 shares, address recipient, uint256 minAssets) = abi.decode(
_inputs,
_inputs,
(address, uint256, address, uint256)
(address, uint256, address, uint256)
);
);
shares = _resolveTokenValue(pt, shares);
shares = _resolveTokenValue(pt, shares);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
IPrincipalToken(pt).redeem(shares, recipient, address(this), minAssets);
IPrincipalToken(pt).redeem(shares, recipient, address(this), minAssets);
} else if (command == Commands.REDEEM_PT_FOR_IBT) {
} else if (command == Commands.REDEEM_PT_FOR_IBT) {
(address pt, uint256 shares, address recipient, uint256 minIbts) = abi.decode(
(address pt, uint256 shares, address recipient, uint256 minIbts) = abi.decode(
_inputs,
_inputs,
(address, uint256, address, uint256)
(address, uint256, address, uint256)
);
);
shares = _resolveTokenValue(pt, shares);
shares = _resolveTokenValue(pt, shares);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
IPrincipalToken(pt).redeemForIBT(shares, recipient, address(this), minIbts);
IPrincipalToken(pt).redeemForIBT(shares, recipient, address(this), minIbts);
} else if (command == Commands.FLASH_LOAN) {
} else if (command == Commands.FLASH_LOAN) {
(address lender, address token, uint256 amount, bytes memory data) = abi.decode(
(address lender, address token, uint256 amount, bytes memory data) = abi.decode(
_inputs,
_inputs,
(address, address, uint256, bytes)
(address, address, uint256, bytes)
);
);
if (!IRegistry(registry).isRegisteredPT(lender)) {
if (!IRegistry(registry).isRegisteredPT(lender)) {
revert InvalidFlashloanLender(lender);
revert InvalidFlashloanLender(lender);
}
}
flashloanLender = lender;
flashloanLender = lender;
IERC3156FlashLender(lender).flashLoan(
IERC3156FlashLender(lender).flashLoan(
IERC3156FlashBorrower(address(this)),
IERC3156FlashBorrower(address(this)),
token,
token,
amount,
amount,
data
data
);
);
flashloanLender = address(0);
flashloanLender = address(0);
} else if (command == Commands.CURVE_SPLIT_IBT_LIQUIDITY) {
} else if (command == Commands.CURVE_SPLIT_IBT_LIQUIDITY) {
(
(
address pool,
address pool,
uint256 ibts,
uint256 ibts,
address recipient,
address recipient,
address ytRecipient,
address ytRecipient,
uint256 minPTShares
uint256 minPTShares
) = abi.decode(_inputs, (address, uint256, address, address, uint256));
) = abi.decode(_inputs, (address, uint256, address, address, uint256));
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
ytRecipient = _resolveAddress(ytRecipient);
ytRecipient = _resolveAddress(ytRecipient);
address ibt = ICurvePool(pool).coins(0);
address ibt = ICurvePool(pool).coins(0);
address pt = ICurvePool(pool).coins(1);
address pt = ICurvePool(pool).coins(1);
ibts = _resolveTokenValue(ibt, ibts);
ibts = _resolveTokenValue(ibt, ibts);
uint256 ibtToDepositInPT = CurvePoolUtil.calcIBTsToTokenizeForCurvePool(ibts, pool, pt);
uint256 ibtToDepositInPT = CurvePoolUtil.calcIBTsToTokenizeForCurvePool(ibts, pool, pt);
if (ibtToDepositInPT != 0) {
if (ibtToDepositInPT != 0) {
bool isRegisteredPT = IRegistry(registry).isRegisteredPT(pt);
bool isRegisteredPT = IRegistry(registry).isRegisteredPT(pt);
if (isRegisteredPT) {
if (isRegisteredPT) {
_ensureApproved(ibt, pt, ibtToDepositInPT);
_ensureApproved(ibt, pt, ibtToDepositInPT);
} else {
} else {
IERC20(ibt).forceApprove(pt, ibtToDepositInPT);
IERC20(ibt).forceApprove(pt, ibtToDepositInPT);
}
}
IPrincipalToken(pt).depositIBT(
IPrincipalToken(pt).depositIBT(
ibtToDepositInPT,
ibtToDepositInPT,
recipient,
recipient,
ytRecipient,
ytRecipient,
minPTShares
minPTShares
);
);
if (!isRegisteredPT) {
if (!isRegisteredPT) {
IERC20(ibt).forceApprove(pt, 0);
IERC20(ibt).forceApprove(pt, 0);
}
}
}
}
if (recipient != address(this) && (ibts - ibtToDepositInPT) != 0) {
if (recipient != address(this) && (ibts - ibtToDepositInPT) != 0) {
IERC20(ibt).safeTransfer(recipient, ibts - ibtToDepositInPT);
IERC20(ibt).safeTransfer(recipient, ibts - ibtToDepositInPT);
}
}
} else if (command == Commands.CURVE_ADD_LIQUIDITY) {
} else if (command == Commands.CURVE_ADD_LIQUIDITY) {
(
(
address pool,
address pool,
uint256[2] memory amounts,
uint256[2] memory amounts,
uint256 min_mint_amount,
uint256 min_mint_amount,
address recipient
address recipient
) = abi.decode(_inputs, (address, uint256[2], uint256, address));
) = abi.decode(_inputs, (address, uint256[2], uint256, address));
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
address ibt = ICurvePool(pool).coins(0);
address ibt = ICurvePool(pool).coins(0);
address pt = ICurvePool(pool).coins(1);
address pt = ICurvePool(pool).coins(1);
amounts[0] = _resolveTokenValue(ibt, amounts[0]);
amounts[0] = _resolveTokenValue(ibt, amounts[0]);
amounts[1] = _resolveTokenValue(pt, amounts[1]);
amounts[1] = _resolveTokenValue(pt, amounts[1]);
IERC20(ibt).forceApprove(pool, amounts[0]);
IERC20(ibt).forceApprove(pool, amounts[0]);
IERC20(pt).forceApprove(pool, amounts[1]);
IERC20(pt).forceApprove(pool, amounts[1]);
ICurvePool(pool).add_liquidity(amounts, min_mint_amount, false, recipient);
ICurvePool(pool).add_liquidity(amounts, min_mint_amount, false, recipient);
IERC20(ibt).forceApprove(pool, 0);
IERC20(ibt).forceApprove(pool, 0);
IERC20(pt).forceApprove(pool, 0);
IERC20(pt).forceApprove(pool, 0);
} else if (command == Commands.CURVE_REMOVE_LIQUIDITY) {
} else if (command == Commands.CURVE_REMOVE_LIQUIDITY) {
(address pool, uint256 lps, uint256[2] memory min_amounts, address recipient) = abi
(address pool, uint256 lps, uint256[2] memory min_amounts, address recipient) = abi
.decode(_inputs, (address, uint256, uint256[2], address));
.decode(_inputs, (address, uint256, uint256[2], address));
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
address lpToken = ICurvePool(pool).token();
address lpToken = ICurvePool(pool).token();
lps = _resolveTokenValue(lpToken, lps);
lps = _resolveTokenValue(lpToken, lps);
ICurvePool(pool).remove_liquidity(lps, min_amounts, false, recipient);
ICurvePool(pool).remove_liquidity(lps, min_amounts, false, recipient);
} else if (command == Commands.CURVE_REMOVE_LIQUIDITY_ONE_COIN) {
} else if (command == Commands.CURVE_REMOVE_LIQUIDITY_ONE_COIN) {
(address pool, uint256 lps, uint256 i, uint256 min_amount, address recipient) = abi
(address pool, uint256 lps, uint256 i, uint256 min_amount, address recipient) = abi
.decode(_inputs, (address, uint256, uint256, uint256, address));
.decode(_inputs, (address, uint256, uint256, uint256, address));
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
address lpToken = ICurvePool(pool).token();
address lpToken = ICurvePool(pool).token();
lps = _resolveTokenValue(lpToken, lps);
lps = _resolveTokenValue(lpToken, lps);
ICurvePool(pool).remove_liquidity_one_coin(lps, i, min_amount, false, recipient);
ICurvePool(pool).remove_liquidity_one_coin(lps, i, min_amount, false, recipient);
} else if (command == Commands.KYBER_SWAP) {
(
address kyberRouter,
address tokenIn,
uint256 amountIn,
address tokenOut,
,
bytes memory targetData
) = abi.decode(_inputs, (address, address, uint256, address, uint256, bytes));
if (tokenOut == Constants.ETH) {
revert AddressError();
}
if (tokenIn == Constants.ETH) {
if (msg.value != amountIn) {
revert AmountError();
}
(bool success, ) = kyberRouter.call{value: msg.value}(targetData);
if (!success) {
revert CallFailed();
}
} else {
amountIn = _resolveTokenValue(tokenIn, amountIn);
IERC20(tokenIn).forceApprove(kyberRouter, amountIn);
(bool success, ) = kyberRouter.call(targetData);
if (!success) {
revert CallFailed();
}
IERC20(tokenIn).forceApprove(kyberRouter, 0);
}
} else if (command == Commands.ASSERT_MIN_BALANCE) {
} else if (command == Commands.ASSERT_MIN_BALANCE) {
(address token, address owner, uint256 minValue) = abi.decode(
(address token, address owner, uint256 minValue) = abi.decode(
_inputs,
_inputs,
(address, address, uint256)
(address, address, uint256)
);
);
owner = _resolveAddress(owner);
owner = _resolveAddress(owner);
uint256 balance = IERC20(token).balanceOf(owner);
uint256 balance = IERC20(token).balanceOf(owner);
if (balance < minValue) {
if (balance < minValue) {
revert MinimumBalanceNotReached(token, owner, minValue, balance);
revert MinimumBalanceNotReached(token, owner, minValue, balance);
}
}
} else {
} else {
revert InvalidCommandType(command);
revert InvalidCommandType(command);
}
}
}
}
/**
/**
* @dev Returns either the input token value as is, or replaced with its corresponding behaviour in Constants.sol
* @dev Returns either the input token value as is, or replaced with its corresponding behaviour in Constants.sol
* @param _token address of the token
* @param _token The address of the token
* @param _value token amount
* @param _value The token amount
* @return The amount stored previously if current amount used for detecting contract balance, else current value
* @return The amount stored previously if current amount used for detecting contract balance, else current value
*/
*/
function _resolveTokenValue(address _token, uint256 _value) internal view returns (uint256) {
function _resolveTokenValue(address _token, uint256 _value) internal view returns (uint256) {
if (_value == Constants.CONTRACT_BALANCE) {
if (_value == Constants.CONTRACT_BALANCE) {
return IERC20(_token).balanceOf(address(this));
return IERC20(_token).balanceOf(address(this));
} else {
} else {
return _value;
return _value;
}
}
}
}
/**
/**
* @dev Returns either the input address as is, or replaced with its corresponding behaviour in Constants.sol
* @dev Returns either the input address as is, or replaced with its corresponding behaviour in Constants.sol
* @param _input input address
* @param _input The input address
* @return address corresponding to input
* @return The address corresponding to input
*/
*/
function _resolveAddress(address _input) internal view returns (address) {
function _resolveAddress(address _input) internal view returns (address) {
if (_input == Constants.ADDRESS_THIS) {
if (_input == Constants.ADDRESS_THIS) {
return address(this);
return address(this);
} else if (_input == Constants.MSG_SENDER) {
} else if (_input == Constants.MSG_SENDER) {
return msgSender;
return msgSender;
} else {
} else {
return _input;
return _input;
}
}
}
}
/**
/**
* @dev Checks the allowance of a token and approves the spender if necessary
* @dev Checks the allowance of a token and approves the spender if necessary
* @param _token address of the token to be approved
* @param _token address of the token to be approved
* @param _spender address of the spender
* @param _spender address of the spender
* @param _value token amount
* @param _value token amount
*/
*/
function _ensureApproved(address _token, address _spender, uint256 _value) internal {
function _ensureApproved(address _token, address _spender, uint256 _value) internal {
uint256 allowance = IERC20(_token).allowance(address(this), _spender);
uint256 allowance = IERC20(_token).allowance(address(this), _spender);
if (allowance < _value) {
if (allowance < _value) {
// This approval will only be executed the first time to save gas for subsequent operations
// This approval will only be executed the first time to save gas for subsequent operations
IERC20(_token).forceApprove(_spender, type(uint256).max);
IERC20(_token).forceApprove(_spender, type(uint256).max);
}
}
}
}
/**
/**
* Simulates the execution of batched commands.
* @dev Simulates the execution of a command and returns the expected resulting rate
* @param _commandType Type of command to be executed.
* @param _commandType The encoded representation of the command
* @param _inputs Calldata for the commands.
* @param _inputs The encoded arguments for the specified command
* @param _spot If true, the preview uses the spot exchange rate. Otherwise, includes price impact and curve pool fees.
* @param _spot If set to true, spot exchange rate is used for swaps. Additionally for all commands,
* @param _balances Array of balances to track balances changes during this preview.
* input amounts are disregarded, and one unit of the token of interest is used instead.
* @return The preview of the rate and token amount in 27 decimals precision.
* If set to false, the function includes price impact and curve pool fees for swaps.
* @param _balances Array of balances to track balances changes during this preview
* @return The preview rate value, which represents the amount of output token obtained for each wei
* of input token, multiplied by 1 ray unit.
*/
*/
function _dispatchPreviewRate(
function _dispatchPreviewRate(
bytes1 _commandType,
bytes1 _commandType,
bytes calldata _inputs,
bytes calldata _inputs,
bool _spot,
bool _spot,
TokenBalance[] memory _balances
TokenBalance[] memory _balances
) internal view returns (uint256) {
) internal view returns (uint256) {
uint256 command = uint8(_commandType & Commands.COMMAND_TYPE_MASK);
uint256 command = uint8(_commandType & Commands.COMMAND_TYPE_MASK);
if (command == Commands.TRANSFER_FROM || command == Commands.TRANSFER_FROM_WITH_PERMIT) {
if (command == Commands.TRANSFER_FROM || command == Commands.TRANSFER_FROM_WITH_PERMIT) {
// Does not affect the rate, but amount is now set as the input value
if (!_spot) {
if (!_spot) {
(address token, uint256 value) = abi.decode(_inputs, (address, uint256));
(address token, uint256 value) = abi.decode(_inputs, (address, uint256));
_increasePreviewTokenValue(value, token, _balances);
_increasePreviewTokenValue(value, token, _balances);
}
}
return RayMath.RAY_UNIT;
return RayMath.RAY_UNIT;
} else if (command == Commands.TRANSFER) {
} else if (command == Commands.TRANSFER) {
if (!_spot) {
if (!_spot) {
(address token, address recipient, uint256 value) = abi.decode(
(address token, address recipient, uint256 value) = abi.decode(
_inputs,
_inputs,
(address, address, uint256)
(address, address, uint256)
);
);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
if (recipient != address(this)) {
if (recipient != address(this)) {
_decreasePreviewTokenValue(value, token, _balances);
_decreasePreviewTokenValue(value, token, _balances);
}
}
}
}
return RayMath.RAY_UNIT;
return RayMath.RAY_UNIT;
}
} else if (command == Commands.CURVE_SWAP) {
// Does not affect the amount
else if (command == Commands.CURVE_SWAP) {
(address pool, uint256 i, uint256 j, uint256 amountIn, , address recipient) = abi
(address pool, uint256 i, uint256 j, uint256 amountIn, , address recipient) = abi
.decode(_inputs, (address, uint256, uint256, uint256, uint256, address));
.decode(_inputs, (address, uint256, uint256, uint256, uint256, address));
uint256 exchangeRate;
uint256 exchangeRate;
if (_spot) {
if (_spot) {
// rate : spotExchangeRate * (ibtUnit / curveUnit) * rayUnit / ibtUnit
exchangeRate = RouterUtil(routerUtil).spotExchangeRate(pool, i, j).toRay(
exchangeRate = RouterUtil(routerUtil).spotExchangeRate(pool, i, j).toRay(
CurvePoolUtil.CURVE_DECIMALS
CurvePoolUtil.CURVE_DECIMALS
);
);
} else {
} else {
amountIn = _decreasePreviewTokenValue(
amountIn = _decreasePreviewTokenValue(
amountIn,
amountIn,
ICurvePool(pool).coins(i),
ICurvePool(pool).coins(i),
_balances
_balances
);
);
uint256 dy = ICurvePool(pool).get_dy(i, j, amountIn);
uint256 dy = ICurvePool(pool).get_dy(i, j, amountIn);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
if (recipient == address(this)) {
_increasePreviewTokenValue(dy, ICurvePool(pool).coins(j), _balances);
_increasePreviewTokenValue(dy, ICurvePool(pool).coins(j), _balances);
}
}
// rate : dy * rayUnit / amountIn
exchangeRate = dy.mulDiv(RayMath.RAY_UNIT, amountIn);
exchangeRate = dy.mulDiv(RayMath.RAY_UNIT, amountIn);
}
}
return exchangeRate;
return exchangeRate;
} else if (command == Commands.WRAP_VAULT_IN_4626_ADAPTER) {
(address wrapper, uint256 vaultShares, address recipient) = abi.decode(
_inputs,
(address, uint256, address)
);
address vault = ISpectra4626Wrapper(wrapper).vaultShare();
if (_spot) {
vaultShares = RouterUtil(routerUtil).getUnit(vault);
} else {
vaultShares = _decreasePreviewTokenValue(vaultShares, vault, _balances);
}
uint256 _expectedWrapperShares = ISpectra4626Wrapper(wrapper).previewWrap(vaultShares);
Text moved with changes from lines 478-495 (89.9% similarity)
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
_increasePreviewTokenValue(_expectedWrapperShares, wrapper, _balances);
}
// rate : expectedWrapperShares * rayUnit / vaultShares
return _expectedWrapperShares.mulDiv(RayMath.RAY_UNIT, vaultShares);
} else if (command == Commands.UNWRAP_VAULT_FROM_4626_ADAPTER) {
(address wrapper, uint256 wrapperShares, address recipient) = abi.decode(
_inputs,
(address, uint256, address)
);
if (_spot) {
wrapperShares = RouterUtil(routerUtil).getUnit(wrapper);
} else {
wrapperShares = _decreasePreviewTokenValue(wrapperShares, wrapper, _balances);
}
uint256 _expectedVaultShares = ISpectra4626Wrapper(wrapper).previewUnwrap(
wrapperShares
);
Text moved with changes from lines 515-524 (92.5% similarity)
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
_increasePreviewTokenValue(
_expectedVaultShares,
ISpectra4626Wrapper(wrapper).vaultShare(),
_balances
);
}
// rate : expectedVaultShares * rayUnit / wrapperShares
return _expectedVaultShares.mulDiv(RayMath.RAY_UNIT, wrapperShares);
} else if (command == Commands.DEPOSIT_ASSET_IN_IBT) {
} else if (command == Commands.DEPOSIT_ASSET_IN_IBT) {
(address ibt, uint256 assets, address recipient) = abi.decode(
(address ibt, uint256 assets, address recipient) = abi.decode(
_inputs,
_inputs,
(address, uint256, address)
(address, uint256, address)
);
);
address asset = IERC4626(ibt).asset();
if (_spot) {
if (_spot) {
assets = RouterUtil(routerUtil).getUnit(ibt);
assets = RouterUtil(routerUtil).getUnit(asset);
} else {
} else {
assets = _decreasePreviewTokenValue(assets, IERC4626(ibt).asset(), _balances);
assets = _decreasePreviewTokenValue(assets, asset, _balances);
}
}
uint256 _expectedShares = IERC4626(ibt).previewDeposit(assets);
uint256 _expectedShares = IERC4626(ibt).previewDeposit(assets);
recipient = _resolveAddress(recipient);
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
if (recipient == address(this)) {
_increasePreviewTokenValue(_expectedShares, ibt, _balances);
_increasePreviewTokenValue(_expectedShares, ibt, _balances);
}
}
// rate : shares * rayUnit / assets
// rate : shares * rayUnit / assets
return _expectedShares.mulDiv(RayMath.RAY_UNIT, assets);
return _expectedShares.mulDiv(RayMath.RAY_UNIT, assets);
} else if (command == Commands.DEPOSIT_ASSET_IN_PT) {
} else if (command == Commands.DEPOSIT_ASSET_IN_PT) {
(address pt, uint256 assets, address ptRecipient, address ytRecipient) = abi.decode(
(address pt, uint256 assets, address ptRecipient, address ytRecipient) = abi.decode(
_inputs,
_inputs,
(address, uint256, address, address)
(address, uint256, address, address)
);
);
if (_spot) {
if (_spot) {
assets = RouterUtil(routerUtil).getUnderlyingUnit(pt);
assets = RouterUtil(routerUtil).getPTUnderlyingUnit(pt);
} else {
} else {
assets = _decreasePreviewTokenValue(
assets = _decreasePreviewTokenValue(
assets,
assets,
IPrincipalToken(pt).underlying(),
IPrincipalToken(pt).underlying(),
_balances
_balances
);
);
}
}
uint256 _expectedShares = IPrincipalToken(pt).previewDeposit(assets);
uint256 _expectedShares = IPrincipalToken(pt).previewDeposit(assets);
ptRecipient = _resolveAddress(ptRecipient);
ptRecipient = _resolveAddress(ptRecipient);
if (ptRecipient == address(this)) {
if (ptRecipient == address(this)) {
_increasePreviewTokenValue(_expectedShares, pt, _balances);
_increasePreviewTokenValue(_expectedShares, pt, _balances);
}
}
ytRecipient = _resolveAddress(ytRecipient);
ytRecipient = _resolveAddress(ytRecipient);
if (ytRecipient == address(this)) {
if (ytRecipient == address(t
_increasePreviewTokenValue(_expectedShares, IPrincipalToken(pt).getYT(), _balances);
}
// rate : shares * rayUnit / assets
return _expectedShares.mulDiv(RayMath.RAY_UNIT, assets);
} else if (command == Commands.DEPOSIT_IBT_IN_PT) {
(address pt, uint256 ibts, address ptRecipient, address ytRecipient) = abi.decode(
_inputs,
(address, uint256, address, address)
);
if (_spot) {
ibts = RouterUtil(routerUtil).getUnit(pt);
} else {
ibts = _decreasePreviewTokenValue(ibts, IPrincipalToken(pt).getIBT(), _balances);
}
uint256 _expectedShares = IPrincipalToken(pt).previewDepositIBT(ibts);
ptRecipient = _resolveAddress(ptRecipient);
if (ptRecipient == address(this)) {
_increasePreviewTokenValue(_expectedShares, pt, _balances);
}
Text moved with changes to lines 481-498 (89.9% similarity)
ytRecipient = _resolveAddress(ytRecipient);
if (ytRecipient == address(this)) {
_increasePreviewTokenValue(_expectedShares, IPrincipalToken(pt).getYT(), _balances);
}
// rate : shares * rayUnit / ibts
return _expectedShares.mulDiv(RayMath.RAY_UNIT, ibts);
} else if (command == Commands.REDEEM_IBT_FOR_ASSET) {
(address ibt, uint256 shares, address recipient) = abi.decode(
_inputs,
(address, uint256, address)
);
if (_spot) {
shares = RouterUtil(routerUtil).getUnit(ibt);
} else {
shares = _decreasePreviewTokenValue(shares, ibt, _balances);
}
uint256 _expectedAssets = IERC4626(ibt).previewRedeem(shares);
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
_increasePreviewTokenValue(_expectedAssets, IERC4626(ibt).asset(), _balances);
}
// rate : assets * rayUnit / shares
return _expectedAssets.mulDiv(RayMath.RAY_UNIT, shares);
} else if (command == Commands.REDEEM_PT_FOR_ASSET) {
(address pt, uint256 shares, address recipient) = abi.decode(
_inputs,
(address, uint256, address)
);
if (_spot) {
shares = RouterUtil(routerUtil).getUnit(pt);
} else {
shares = _decreasePreviewTokenValue(shares, pt, _balances);
if (block.timestamp < IPrincipalToken(pt).maturity()) {
_decreasePreviewTokenValue(shares, IPrincipalToken(pt).getYT(), _balances);
}
}
uint256 _expectedAssets = IPrincipalToken(pt).previewRedeem(shares);
Text moved with changes to lines 500-509 (92.5% similarity)
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
_increasePreviewTokenValue(
_expectedAssets,
IPrincipalToken(pt).underlying(),
_balances
);
}
// rate : assets * rayUnit / shares
return _expectedAssets.mulDiv(RayMath.RAY_UNIT, shares);
} else if (command == Commands.REDEEM_PT_FOR_IBT) {
(address pt, uint256 shares, address recipient) = abi.decode(
_inputs,
(address, uint256, address)
);
if (_spot) {
shares = RouterUtil(routerUtil).getUnit(pt);
} else {
shares = _decreasePreviewTokenValue(shares, pt, _balances);
if (block.timestamp < IPrincipalToken(pt).maturity()) {
_decreasePreviewTokenValue(shares, IPrincipalToken(pt).getYT(), _balances);
}
}
uint256 _expectedIBTs = IPrincipalToken(pt).previewRedeemForIBT(shares);
recipient = _resolveAddress(recipient);
if (recipient == address(this)) {
_increasePreviewTokenValue(_expectedIBTs, IPrincipalToken(pt).getIBT(), _balances);
}
// rate : ibts * rayUnit / shares
return _expectedIBTs.mulDiv(RayMath.RAY_UNIT, shares);
} else if (command == Commands.ASSERT_MIN_BALANCE) {
return (RayMath.RAY_UNIT);
} else {
revert InvalidCommandType(command);
}
}
/**
* @dev Decrease balance for given token by given value in provided balances array.
* @param _value value to subtract from token balance
* @p