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

swapper changes #257

Merged
merged 12 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
60 changes: 60 additions & 0 deletions contracts/mock/MockVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./MockWeightedPool.sol";

contract MockVault {

MockWeightedPool public _pool;
IERC20[] public _tokens;
uint256 public constant LIQUIDITY_AMOUNT = 1e18;

constructor(IERC20[] memory tokens, address owner) {
_tokens = tokens;
_pool = new MockWeightedPool(IVault(address(this)), owner);
}

function getPoolTokens(bytes32 poolId)
external
view
returns (
IERC20[] memory tokens,
uint256[] memory balances,
uint256 lastChangeBlock
) {
return (_tokens, balances, lastChangeBlock);
}

function joinPool(
bytes32 poolId,
address sender,
address recipient,
JoinPoolRequest memory request
) external payable {
_pool.mint(recipient, LIQUIDITY_AMOUNT);
}

struct JoinPoolRequest {
IAsset[] assets;
uint256[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}

function exitPool(
bytes32 poolId,
address sender,
address payable recipient,
ExitPoolRequest memory request
) external {
_pool.burnFrom(sender, LIQUIDITY_AMOUNT);
}

struct ExitPoolRequest {
IAsset[] assets;
uint256[] minAmountsOut;
bytes userData;
bool toInternalBalance;
}
}
58 changes: 58 additions & 0 deletions contracts/mock/MockWeightedPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

import "./MockERC20.sol";
import "../pcv/balancer/IVault.sol";

contract MockWeightedPool is MockERC20 {

uint256 public _startTime;
uint256 public _endTime;
uint256[] public _endWeights;

IVault public immutable getVault;
bytes32 public constant getPoolId = bytes32(uint256(1));
address public immutable getOwner;

bool public getSwapEnabled;
bool public getPaused;
uint256 public getSwapFeePercentage;

constructor(IVault vault, address owner) {
getOwner = owner;
getVault = vault;
}

function getGradualWeightUpdateParams()
external
view
returns (
uint256 startTime,
uint256 endTime,
uint256[] memory endWeights
) {
return (_startTime, _endTime, _endWeights);
}

function setSwapEnabled(bool swapEnabled) external {
getSwapEnabled = swapEnabled;
}

function updateWeightsGradually(
uint256 startTime,
uint256 endTime,
uint256[] memory endWeights
) external {
_startTime = startTime;
_endTime = endTime;
_endWeights = endWeights;
}

function setSwapFeePercentage(uint256 swapFeePercentage) external {
getSwapFeePercentage = swapFeePercentage;
}

function setPaused(bool paused) external {
getPaused = paused;
}
}
129 changes: 80 additions & 49 deletions contracts/pcv/balancer/BalancerLBPSwapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,26 @@ import "./IVault.sol";
import "../../utils/Timed.sol";
import "../../refs/OracleRef.sol";
import "../IPCVSwapper.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title BalancerLBPSwapper
/// @author Fei Protocol
/// @notice an auction contract which cyclically sells one token for another using Balancer LBP
contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPoolManager {
using Decimal for Decimal.D256;
using SafeERC20 for IERC20;

// ------------- Events -------------

event WithdrawERC20(
address indexed _caller,
address indexed _token,
address indexed _to,
uint256 _amount
);

event ExitPool();

event MinTokenSpentUpdate(uint256 oldMinTokenSpentBalance, uint256 newMinTokenSpentBalance);

// ------------- Balancer State -------------
Expand Down Expand Up @@ -87,8 +99,6 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo
Timed(_frequency)
WeightedBalancerPoolManager()
{
_initTimed();

// tokenSpent and tokenReceived are immutable
tokenSpent = _tokenSpent;
tokenReceived = _tokenReceived;
Expand All @@ -108,6 +118,7 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo
*/
function init(IWeightedPool _pool) external {
require(address(pool) == address(0), "BalancerLBPSwapper: initialized");
_initTimed();

pool = _pool;
IVault _vault = _pool.getVault();
Expand Down Expand Up @@ -178,7 +189,6 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo
require(lastChangeBlock < block.number, "BalancerLBPSwapper: pool changed this block");

uint256 bptTotal = pool.totalSupply();
uint256 bptBalance = pool.balanceOf(address(this));

// Balancer locks a small amount of bptTotal after init, so 0 bpt means pool needs initializing
if (bptTotal == 0) {
Expand All @@ -188,24 +198,8 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo
require(swapEndTime() < block.timestamp, "BalancerLBPSwapper: weight update in progress");

// 1. Withdraw existing LP tokens (if currently held)
if (bptBalance != 0) {
IVault.ExitPoolRequest memory exitRequest;

// Uses encoding for exact BPT IN withdrawal using all held BPT
bytes memory userData = abi.encode(IWeightedPool.ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT, bptBalance);

exitRequest.assets = assets;
exitRequest.minAmountsOut = new uint256[](2); // 0 min
exitRequest.userData = userData;
exitRequest.toInternalBalance = false; // use external balances to be able to transfer out tokenReceived
_exitPool();

vault.exitPool(
pid,
address(this),
payable(address(this)),
exitRequest
);
}
// 2. Reset weights to 99:1
// Using current block time triggers immediate weight reset
_updateWeightsGradually(
Expand All @@ -217,36 +211,47 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo

// 3. Provide new liquidity
uint256 spentTokenBalance = IERC20(tokenSpent).balanceOf(address(this));
if (spentTokenBalance > minTokenSpentBalance) {
// uses exact tokens in encoding for deposit, supplying both tokens
// will use some of the previously withdrawn tokenReceived to seed the 1% required for new auction
uint256[] memory amountsIn = _getTokensIn(spentTokenBalance);
bytes memory userData = abi.encode(IWeightedPool.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, 0);

IVault.JoinPoolRequest memory joinRequest;
joinRequest.assets = assets;
joinRequest.maxAmountsIn = amountsIn;
joinRequest.userData = userData;
joinRequest.fromInternalBalance = false; // uses external balances because tokens are held by contract

vault.joinPool(
pid,
address(this),
payable(address(this)),
joinRequest
);

// 4. Kick off new auction ending after `duration` seconds
_updateWeightsGradually(
pool,
block.timestamp,
block.timestamp + duration,
endWeights
);
_initTimed(); // reset timer
}
require(spentTokenBalance > minTokenSpentBalance, "BalancerLBPSwapper: not enough for new swap");

// uses exact tokens in encoding for deposit, supplying both tokens
// will use some of the previously withdrawn tokenReceived to seed the 1% required for new auction
uint256[] memory amountsIn = _getTokensIn(spentTokenBalance);
bytes memory userData = abi.encode(IWeightedPool.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, 0);

IVault.JoinPoolRequest memory joinRequest;
joinRequest.assets = assets;
joinRequest.maxAmountsIn = amountsIn;
joinRequest.userData = userData;
joinRequest.fromInternalBalance = false; // uses external balances because tokens are held by contract

vault.joinPool(pid, address(this), payable(address(this)), joinRequest);

// 4. Kick off new auction ending after `duration` seconds
_updateWeightsGradually(pool, block.timestamp, block.timestamp + duration, endWeights);
_initTimed(); // reset timer
// 5. Send remaining tokenReceived to target
IERC20(tokenReceived).transfer(tokenReceivingAddress, IERC20(tokenReceived).balanceOf(address(this)));
_transferAll(tokenReceived, tokenReceivingAddress);
}

/// @notice redeeem all assets from LP pool
/// @param to destination for withdrawn tokens
function exitPool(address to) external onlyPCVController {
_exitPool();
_transferAll(tokenSpent, to);
_transferAll(tokenReceived, to);
}

/// @notice withdraw ERC20 from the contract
/// @param token address of the ERC20 to send
/// @param to address destination of the ERC20
/// @param amount quantity of ERC20 to send
function withdrawERC20(
address token,
address to,
uint256 amount
) public onlyPCVController {
IERC20(token).safeTransfer(to, amount);
emit WithdrawERC20(msg.sender, token, to, amount);
}

/// @notice returns when the next auction ends
Expand Down Expand Up @@ -281,6 +286,30 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo
return (tokens, _getTokensIn(spentTokenBalance));
}


function _exitPool() internal {
uint256 bptBalance = pool.balanceOf(address(this));
if (bptBalance != 0) {
IVault.ExitPoolRequest memory exitRequest;

// Uses encoding for exact BPT IN withdrawal using all held BPT
bytes memory userData = abi.encode(IWeightedPool.ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT, bptBalance);

exitRequest.assets = assets;
exitRequest.minAmountsOut = new uint256[](2); // 0 min
exitRequest.userData = userData;
exitRequest.toInternalBalance = false; // use external balances to be able to transfer out tokenReceived

vault.exitPool(pid, address(this), payable(address(this)), exitRequest);
emit ExitPool();
}
}

function _transferAll(address token, address to) internal {
IERC20 _token = IERC20(token);
_token.safeTransfer(to, _token.balanceOf(address(this)));
}

function _setReceivingAddress(address newTokenReceivingAddress) internal {
require(newTokenReceivingAddress != address(0), "BalancerLBPSwapper: zero address");
address oldTokenReceivingAddress = tokenReceivingAddress;
Expand Down Expand Up @@ -317,6 +346,8 @@ contract BalancerLBPSwapper is IPCVSwapper, OracleRef, Timed, WeightedBalancerPo
endWeights
);
_initTimed();

_transferAll(tokenReceived, tokenReceivingAddress);
}

function _getTokensIn(uint256 spentTokenBalance) internal view returns(uint256[] memory amountsIn) {
Expand Down
Loading