487 lines
15 KiB
Solidity
487 lines
15 KiB
Solidity
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
pragma solidity ^0.8.13;
|
|
|
|
import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
|
|
|
|
// Maximum Swap In/Out Ratio - 0.3
|
|
// https://balancer.gitbook.io/balancer/core-concepts/protocol/limitations#v2-limits
|
|
uint256 constant RESERVE_LIMIT_FACTOR = 4;
|
|
uint256 constant SWAP_DEADLINE_SEC = 1000;
|
|
|
|
contract BalancerV2SwapAdapter is ISwapAdapter {
|
|
IVault immutable vault;
|
|
|
|
constructor(address payable vault_) {
|
|
vault = IVault(vault_);
|
|
}
|
|
|
|
/// @notice Calculate the price of the buy token in terms of the sell token.
|
|
/// @dev The resulting price is not scaled by the token decimals.
|
|
/// Also this function is not 'view' because Balancer V2 simulates the swap
|
|
/// and then returns the amount diff in revert data.
|
|
/// @param poolId The ID of the trading pool.
|
|
/// @param sellToken The token being sold.
|
|
/// @param buyToken The token being bought.
|
|
/// @param sellAmount The amount of tokens being sold.
|
|
/// @return calculatedPrice The price of the buy token in terms of the sell
|
|
/// as a Fraction struct.
|
|
function priceSingle(
|
|
bytes32 poolId,
|
|
IERC20 sellToken,
|
|
IERC20 buyToken,
|
|
uint256 sellAmount
|
|
) public returns (Fraction memory calculatedPrice) {
|
|
IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1);
|
|
swapSteps[0] = IVault.BatchSwapStep({
|
|
poolId: poolId,
|
|
assetInIndex: 0,
|
|
assetOutIndex: 1,
|
|
amount: sellAmount,
|
|
userData: ""
|
|
});
|
|
address[] memory assets = new address[](2);
|
|
assets[0] = address(sellToken);
|
|
assets[1] = address(buyToken);
|
|
IVault.FundManagement memory funds = IVault.FundManagement({
|
|
sender: msg.sender,
|
|
fromInternalBalance: false,
|
|
recipient: payable(msg.sender),
|
|
toInternalBalance: false
|
|
});
|
|
|
|
// assetDeltas correspond to the assets array
|
|
int256[] memory assetDeltas = new int256[](2);
|
|
assetDeltas = vault.queryBatchSwap(
|
|
IVault.SwapKind.GIVEN_IN, swapSteps, assets, funds
|
|
);
|
|
// assetDeltas[1] is the amount of tokens sent from the vault (i.e.
|
|
// bought), so the sign is negative, which means the sign should be
|
|
// flipped to get the price.
|
|
calculatedPrice =
|
|
Fraction(uint256(-assetDeltas[1]), uint256(assetDeltas[0]));
|
|
}
|
|
|
|
function getSellAmount(
|
|
bytes32 poolId,
|
|
IERC20 sellToken,
|
|
IERC20 buyToken,
|
|
uint256 buyAmount
|
|
) public returns (uint256 sellAmount) {
|
|
IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1);
|
|
swapSteps[0] = IVault.BatchSwapStep({
|
|
poolId: poolId,
|
|
assetInIndex: 0,
|
|
assetOutIndex: 1,
|
|
amount: buyAmount,
|
|
userData: ""
|
|
});
|
|
address[] memory assets = new address[](2);
|
|
assets[0] = address(sellToken);
|
|
assets[1] = address(buyToken);
|
|
IVault.FundManagement memory funds = IVault.FundManagement({
|
|
sender: msg.sender,
|
|
fromInternalBalance: false,
|
|
recipient: payable(msg.sender),
|
|
toInternalBalance: false
|
|
});
|
|
|
|
// assetDeltas correspond to the assets array
|
|
int256[] memory assetDeltas = new int256[](2);
|
|
assetDeltas = vault.queryBatchSwap(
|
|
IVault.SwapKind.GIVEN_OUT, swapSteps, assets, funds
|
|
);
|
|
|
|
sellAmount = uint256(assetDeltas[0]);
|
|
}
|
|
|
|
function priceBatch(
|
|
bytes32 poolId,
|
|
IERC20 sellToken,
|
|
IERC20 buyToken,
|
|
uint256[] memory specifiedAmounts
|
|
) external returns (Fraction[] memory calculatedPrices) {
|
|
for (uint256 i = 0; i < specifiedAmounts.length; i++) {
|
|
calculatedPrices[i] =
|
|
priceSingle(poolId, sellToken, buyToken, specifiedAmounts[i]);
|
|
}
|
|
}
|
|
|
|
function price(bytes32, IERC20, IERC20, uint256[] memory)
|
|
external
|
|
pure
|
|
override
|
|
returns (Fraction[] memory)
|
|
{
|
|
revert NotImplemented("BalancerV2SwapAdapter.price");
|
|
}
|
|
|
|
function swap(
|
|
bytes32 poolId,
|
|
IERC20 sellToken,
|
|
IERC20 buyToken,
|
|
OrderSide side,
|
|
uint256 specifiedAmount
|
|
) external override returns (Trade memory trade) {
|
|
uint256 sellAmount;
|
|
IVault.SwapKind kind;
|
|
uint256 limit; // TODO set this slippage limit properly
|
|
if (side == OrderSide.Sell) {
|
|
kind = IVault.SwapKind.GIVEN_IN;
|
|
sellAmount = specifiedAmount;
|
|
limit = 0;
|
|
} else {
|
|
kind = IVault.SwapKind.GIVEN_OUT;
|
|
sellAmount =
|
|
getSellAmount(poolId, sellToken, buyToken, specifiedAmount);
|
|
limit = type(uint256).max;
|
|
}
|
|
|
|
sellToken.transferFrom(msg.sender, address(this), sellAmount);
|
|
sellToken.approve(address(vault), sellAmount);
|
|
|
|
uint256 gasBefore = gasleft();
|
|
trade.calculatedAmount = vault.swap(
|
|
IVault.SingleSwap({
|
|
poolId: poolId,
|
|
kind: kind,
|
|
assetIn: address(sellToken),
|
|
assetOut: address(buyToken),
|
|
amount: specifiedAmount,
|
|
userData: ""
|
|
}),
|
|
IVault.FundManagement({
|
|
sender: address(this),
|
|
fromInternalBalance: false,
|
|
recipient: msg.sender,
|
|
toInternalBalance: false
|
|
}),
|
|
limit,
|
|
block.timestamp + SWAP_DEADLINE_SEC
|
|
);
|
|
trade.gasUsed = gasBefore - gasleft();
|
|
trade.price = priceSingle(poolId, sellToken, buyToken, specifiedAmount);
|
|
}
|
|
|
|
function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken)
|
|
external
|
|
view
|
|
override
|
|
returns (uint256[] memory limits)
|
|
{
|
|
limits = new uint256[](2);
|
|
(IERC20[] memory tokens, uint256[] memory balances,) =
|
|
vault.getPoolTokens(poolId);
|
|
|
|
for (uint256 i = 0; i < tokens.length; i++) {
|
|
if (tokens[i] == sellToken) {
|
|
limits[0] = balances[i] / RESERVE_LIMIT_FACTOR;
|
|
}
|
|
if (tokens[i] == buyToken) {
|
|
limits[1] = balances[i] / RESERVE_LIMIT_FACTOR;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getCapabilities(bytes32, IERC20, IERC20)
|
|
external
|
|
pure
|
|
override
|
|
returns (Capability[] memory capabilities)
|
|
{
|
|
capabilities = new Capability[](2);
|
|
capabilities[0] = Capability.SellOrder;
|
|
capabilities[1] = Capability.BuyOrder;
|
|
}
|
|
|
|
function getTokens(bytes32 poolId)
|
|
external
|
|
view
|
|
override
|
|
returns (IERC20[] memory tokens)
|
|
{
|
|
(tokens,,) = vault.getPoolTokens(poolId);
|
|
}
|
|
|
|
/// @dev Balancer V2 does not support enumerating pools, they have to be
|
|
/// indexed off-chain.
|
|
function getPoolIds(uint256, uint256)
|
|
external
|
|
pure
|
|
override
|
|
returns (bytes32[] memory)
|
|
{
|
|
revert NotImplemented("BalancerV2SwapAdapter.getPoolIds");
|
|
}
|
|
}
|
|
|
|
interface IVault {
|
|
struct BatchSwapStep {
|
|
bytes32 poolId;
|
|
uint256 assetInIndex;
|
|
uint256 assetOutIndex;
|
|
uint256 amount;
|
|
bytes userData;
|
|
}
|
|
|
|
struct FundManagement {
|
|
address sender;
|
|
bool fromInternalBalance;
|
|
address recipient;
|
|
bool toInternalBalance;
|
|
}
|
|
|
|
struct ExitPoolRequest {
|
|
address[] assets;
|
|
uint256[] minAmountsOut;
|
|
bytes userData;
|
|
bool toInternalBalance;
|
|
}
|
|
|
|
struct JoinPoolRequest {
|
|
address[] assets;
|
|
uint256[] maxAmountsIn;
|
|
bytes userData;
|
|
bool fromInternalBalance;
|
|
}
|
|
|
|
struct PoolBalanceOp {
|
|
SwapKind kind;
|
|
bytes32 poolId;
|
|
address token;
|
|
uint256 amount;
|
|
}
|
|
|
|
struct UserBalanceOp {
|
|
SwapKind kind;
|
|
address asset;
|
|
uint256 amount;
|
|
address sender;
|
|
address recipient;
|
|
}
|
|
|
|
struct SingleSwap {
|
|
bytes32 poolId;
|
|
SwapKind kind;
|
|
address assetIn;
|
|
address assetOut;
|
|
uint256 amount;
|
|
bytes userData;
|
|
}
|
|
|
|
event AuthorizerChanged(address indexed newAuthorizer);
|
|
event ExternalBalanceTransfer(
|
|
address indexed token,
|
|
address indexed sender,
|
|
address recipient,
|
|
uint256 amount
|
|
);
|
|
event FlashLoan(
|
|
address indexed recipient,
|
|
address indexed token,
|
|
uint256 amount,
|
|
uint256 feeAmount
|
|
);
|
|
event InternalBalanceChanged(
|
|
address indexed user, address indexed token, int256 delta
|
|
);
|
|
event PausedStateChanged(bool paused);
|
|
event PoolBalanceChanged(
|
|
bytes32 indexed poolId,
|
|
address indexed liquidityProvider,
|
|
address[] tokens,
|
|
int256[] deltas,
|
|
uint256[] protocolFeeAmounts
|
|
);
|
|
event PoolBalanceManaged(
|
|
bytes32 indexed poolId,
|
|
address indexed assetManager,
|
|
address indexed token,
|
|
int256 cashDelta,
|
|
int256 managedDelta
|
|
);
|
|
event PoolRegistered(
|
|
bytes32 indexed poolId,
|
|
address indexed poolAddress,
|
|
uint8 specialization
|
|
);
|
|
event RelayerApprovalChanged(
|
|
address indexed relayer, address indexed sender, bool approved
|
|
);
|
|
event Swap(
|
|
bytes32 indexed poolId,
|
|
address indexed tokenIn,
|
|
address indexed tokenOut,
|
|
uint256 amountIn,
|
|
uint256 amountOut
|
|
);
|
|
event TokensDeregistered(bytes32 indexed poolId, address[] tokens);
|
|
event TokensRegistered(
|
|
bytes32 indexed poolId, address[] tokens, address[] assetManagers
|
|
);
|
|
|
|
function WETH() external view returns (address);
|
|
|
|
function batchSwap(
|
|
SwapKind kind,
|
|
BatchSwapStep[] memory swaps,
|
|
address[] memory assets,
|
|
FundManagement memory funds,
|
|
int256[] memory limits,
|
|
uint256 deadline
|
|
) external payable returns (int256[] memory assetDeltas);
|
|
|
|
function deregisterTokens(bytes32 poolId, address[] memory tokens)
|
|
external;
|
|
|
|
function exitPool(
|
|
bytes32 poolId,
|
|
address sender,
|
|
address recipient,
|
|
ExitPoolRequest memory request
|
|
) external;
|
|
|
|
function flashLoan(
|
|
address recipient,
|
|
address[] memory tokens,
|
|
uint256[] memory amounts,
|
|
bytes memory userData
|
|
) external;
|
|
|
|
function getActionId(bytes4 selector) external view returns (bytes32);
|
|
|
|
function getAuthorizer() external view returns (address);
|
|
|
|
function getDomainSeparator() external view returns (bytes32);
|
|
|
|
function getInternalBalance(address user, address[] memory tokens)
|
|
external
|
|
view
|
|
returns (uint256[] memory balances);
|
|
|
|
function getNextNonce(address user) external view returns (uint256);
|
|
|
|
function getPausedState()
|
|
external
|
|
view
|
|
returns (
|
|
bool paused,
|
|
uint256 pauseWindowEndTime,
|
|
uint256 bufferPeriodEndTime
|
|
);
|
|
|
|
function getPool(bytes32 poolId) external view returns (address, uint8);
|
|
|
|
function getPoolTokenInfo(bytes32 poolId, address token)
|
|
external
|
|
view
|
|
returns (
|
|
uint256 cash,
|
|
uint256 managed,
|
|
uint256 lastChangeBlock,
|
|
address assetManager
|
|
);
|
|
|
|
function getProtocolFeesCollector() external view returns (address);
|
|
|
|
function hasApprovedRelayer(address user, address relayer)
|
|
external
|
|
view
|
|
returns (bool);
|
|
|
|
function joinPool(
|
|
bytes32 poolId,
|
|
address sender,
|
|
address recipient,
|
|
JoinPoolRequest memory request
|
|
) external payable;
|
|
|
|
function managePoolBalance(PoolBalanceOp[] memory ops) external;
|
|
|
|
function manageUserBalance(UserBalanceOp[] memory ops) external payable;
|
|
|
|
/**
|
|
* @dev Simulates a call to `batchSwap`, returning an array of Vault asset
|
|
* deltas. Calls to `swap` cannot be
|
|
* simulated directly, but an equivalent `batchSwap` call can and will yield
|
|
* the exact same result.
|
|
*
|
|
* Each element in the array corresponds to the asset at the same index, and
|
|
* indicates the number of tokens (or ETH)
|
|
* the Vault would take from the sender (if positive) or send to the
|
|
* recipient (if negative). The arguments it
|
|
* receives are the same that an equivalent `batchSwap` call would receive.
|
|
*
|
|
* Unlike `batchSwap`, this function performs no checks on the sender or
|
|
* recipient field in the `funds` struct.
|
|
* This makes it suitable to be called by off-chain applications via
|
|
* eth_call without needing to hold tokens,
|
|
* approve them for the Vault, or even know a user's address.
|
|
*
|
|
* Note that this function is not 'view' (due to implementation details):
|
|
* the client code must explicitly execute
|
|
* eth_call instead of eth_sendTransaction.
|
|
*/
|
|
function queryBatchSwap(
|
|
SwapKind kind,
|
|
BatchSwapStep[] memory swaps,
|
|
address[] memory assets,
|
|
FundManagement memory funds
|
|
) external returns (int256[] memory);
|
|
|
|
function registerPool(uint8 specialization) external returns (bytes32);
|
|
|
|
function registerTokens(
|
|
bytes32 poolId,
|
|
address[] memory tokens,
|
|
address[] memory assetManagers
|
|
) external;
|
|
|
|
function setAuthorizer(address newAuthorizer) external;
|
|
|
|
function setPaused(bool paused) external;
|
|
|
|
function setRelayerApproval(address sender, address relayer, bool approved)
|
|
external;
|
|
|
|
/**
|
|
* @dev Performs a swap with a single Pool.
|
|
*
|
|
* If the swap is 'given in' (the number of tokens to send to the Pool is
|
|
* known), it returns the amount of tokens
|
|
* taken from the Pool, which must be greater than or equal to `limit`.
|
|
*
|
|
* If the swap is 'given out' (the number of tokens to take from the Pool is
|
|
* known), it returns the amount of tokens
|
|
* sent to the Pool, which must be less than or equal to `limit`.
|
|
*
|
|
* Internal Balance usage and the recipient are determined by the `funds`
|
|
* struct.
|
|
*
|
|
* Emits a `Swap` event.
|
|
*/
|
|
function swap(
|
|
SingleSwap memory singleSwap,
|
|
FundManagement memory funds,
|
|
uint256 limit,
|
|
uint256 deadline
|
|
) external payable returns (uint256);
|
|
|
|
receive() external payable;
|
|
|
|
function getPoolTokens(bytes32 poolId)
|
|
external
|
|
view
|
|
returns (
|
|
IERC20[] memory tokens,
|
|
uint256[] memory balances,
|
|
uint256 lastChangeBlock
|
|
);
|
|
|
|
enum SwapKind {
|
|
/// The number of tokens to send to the Pool is known
|
|
GIVEN_IN,
|
|
/// The number of tokens to take from the Pool is known
|
|
GIVEN_OUT
|
|
}
|
|
}
|