From 86cb1ffbeed3406f501be88d63f3421003804d7f Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Thu, 21 Oct 2021 10:32:40 -0700 Subject: [PATCH 01/54] masterchef mod --- brownie-config.yml | 2 +- contracts/Strategy.sol | 321 +++++++++++++------------ interfaces/MasterChef.sol | 113 +++++++++ scripts/pid.py | 22 ++ tests/conftest.py | 152 ++++-------- tests/test_clone.py | 31 --- tests/test_migration.py | 6 +- tests/test_operation.py | 84 ++++--- tests/util.py | 16 +- yearn-strategy/tests/conftest.py | 6 +- yearn-strategy/tests/test_operation.py | 16 +- 11 files changed, 412 insertions(+), 357 deletions(-) create mode 100644 interfaces/MasterChef.sol create mode 100644 scripts/pid.py delete mode 100644 tests/test_clone.py diff --git a/brownie-config.yml b/brownie-config.yml index d386fdb..376f6b9 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -1,7 +1,7 @@ # use Ganache's forked mainnet mode as the default network # NOTE: You don't *have* to do this, but it is often helpful for testing networks: - default: mainnet-fork + default: ftm-mainnet-fork # automatically fetch contract sources from Etherscan autofetch_sources: True diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 2e6a671..b61e793 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -11,10 +11,7 @@ import {SafeERC20, SafeMath, IERC20, Address} from "@openzeppelin/contracts/toke import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Math} from "@openzeppelin/contracts/math/Math.sol"; import "../interfaces/BalancerV2.sol"; - -interface IName { - function name() external view returns (string memory); -} +import "../interfaces/MasterChef.sol"; contract Strategy is BaseStrategy { using SafeERC20 for IERC20; @@ -24,14 +21,21 @@ contract Strategy is BaseStrategy { IERC20 internal constant weth = IERC20(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); IBalancerVault public balancerVault; IBalancerPool public bpt; - IERC20[] public rewardTokens; + IERC20 public rewardToken; IAsset[] internal assets; - SwapSteps[] internal swapSteps; + SwapSteps internal swapSteps; uint256[] internal minAmountsOut; bytes32 public balancerPoolId; uint8 public numTokens; uint8 public tokenIndex; + // masterchef + IBeethovenxMasterChef internal masterChef; + IAsset[] internal stakeAssets; + IBalancerPool internal stakeBpt; + uint internal stakeTokenIndex; + uint internal stakePercentage; + struct SwapSteps { bytes32[] poolIds; IAsset[] assets; @@ -51,48 +55,22 @@ contract Strategy is BaseStrategy { uint256 public maxSingleDeposit; uint256 public minDepositPeriod; // seconds uint256 public lastDepositTime; + uint256 internal masterChefPoolId; + uint256 internal masterChefStakePoolId; uint256 internal constant basisOne = 10000; - bool internal isOriginal = true; constructor( address _vault, address _balancerVault, address _balancerPool, + address _masterChef, uint256 _maxSlippageIn, uint256 _maxSlippageOut, uint256 _maxSingleDeposit, - uint256 _minDepositPeriod) + uint256 _minDepositPeriod, + uint256 _masterChefPoolId) public BaseStrategy(_vault){ - _initializeStrat(_vault, _balancerVault, _balancerPool, _maxSlippageIn, _maxSlippageOut, _maxSingleDeposit, _minDepositPeriod); - } - - function initialize( - address _vault, - address _strategist, - address _rewards, - address _keeper, - address _balancerVault, - address _balancerPool, - uint256 _maxSlippageIn, - uint256 _maxSlippageOut, - uint256 _maxSingleDeposit, - uint256 _minDepositPeriod - ) external { - _initialize(_vault, _strategist, _rewards, _keeper); - _initializeStrat(_vault, _balancerVault, _balancerPool, _maxSlippageIn, _maxSlippageOut, _maxSingleDeposit, _minDepositPeriod); - } - - function _initializeStrat( - address _vault, - address _balancerVault, - address _balancerPool, - uint256 _maxSlippageIn, - uint256 _maxSlippageOut, - uint256 _maxSingleDeposit, - uint256 _minDepositPeriod) - internal { - require(address(bpt) == address(0x0), "Strategy already initialized!"); - healthCheck = address(0xDDCea799fF1699e98EDF118e0629A974Df7DF012); // health.ychad.eth + // healthCheck = address(0xDDCea799fF1699e98EDF118e0629A974Df7DF012); bpt = IBalancerPool(_balancerPool); balancerPoolId = bpt.getPoolId(); balancerVault = IBalancerVault(_balancerVault); @@ -113,45 +91,14 @@ contract Strategy is BaseStrategy { maxSingleDeposit = _maxSingleDeposit.mul(10 ** uint256(ERC20(address(want)).decimals())); minAmountsOut = new uint256[](numTokens); minDepositPeriod = _minDepositPeriod; + masterChefPoolId = _masterChefPoolId; + masterChef = IBeethovenxMasterChef(_masterChef); + require(masterChef.lpTokens(masterChefPoolId) == address(bpt), "Wrong MasterChef Pool!"); want.safeApprove(address(balancerVault), max); + bpt.approve(address(masterChef), max); } - event Cloned(address indexed clone); - - function clone( - address _vault, - address _strategist, - address _rewards, - address _keeper, - address _balancerVault, - address _balancerPool, - uint256 _maxSlippageIn, - uint256 _maxSlippageOut, - uint256 _maxSingleDeposit, - uint256 _minDepositPeriod - ) external returns (address payable newStrategy) { - require(isOriginal); - - bytes20 addressBytes = bytes20(address(this)); - - assembly { - // EIP-1167 bytecode - let clone_code := mload(0x40) - mstore(clone_code, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) - mstore(add(clone_code, 0x14), addressBytes) - mstore(add(clone_code, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) - newStrategy := create(0, clone_code, 0x37) - } - - Strategy(newStrategy).initialize( - _vault, _strategist, _rewards, _keeper, _balancerVault, _balancerPool, _maxSlippageIn, _maxSlippageOut, _maxSingleDeposit, _minDepositPeriod - ); - - emit Cloned(newStrategy); - } - - // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ function name() external view override returns (string memory) { @@ -170,8 +117,17 @@ contract Strategy is BaseStrategy { uint256 beforeWant = balanceOfWant(); - // 2 forms of profit. Incentivized rewards (BAL+other) and pool fees (want) collectTradingFees(); + // claim beets + claimRewards(); + + // calc amount beets to stake + uint256 toStake = balanceOfReward().mul(stakePercentage).div(basisOne); + // unstake all staked beets + unstake(); + // stake pre-calc amount of beets for higher apy + stake(toStake); + // sell the % not staking sellRewards(); uint256 afterWant = balanceOfWant(); @@ -186,30 +142,34 @@ contract Strategy is BaseStrategy { } } + event Debug(string msg, uint val); + function adjustPosition(uint256 _debtOutstanding) internal override { if (now - lastDepositTime < minDepositPeriod) { return; } + // put want into lp then put want-lp into masterchef uint256 pooledBefore = balanceOfPooled(); - uint256[] memory maxAmountsIn = new uint256[](numTokens); uint256 amountIn = Math.min(maxSingleDeposit, balanceOfWant()); - maxAmountsIn[tokenIndex] = amountIn; - - if (maxAmountsIn[tokenIndex] > 0) { - uint256[] memory amountsIn = new uint256[](numTokens); - amountsIn[tokenIndex] = amountIn; - bytes memory userData = abi.encode(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, 0); - IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest(assets, maxAmountsIn, userData, false); - balancerVault.joinPool(balancerPoolId, address(this), address(this), request); + emit Debug("amountIn", amountIn); + if (joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { + // put all want-lp into masterchef + masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); uint256 pooledDelta = balanceOfPooled().sub(pooledBefore); + emit Debug("pooledDelta", pooledDelta); uint256 joinSlipped = amountIn > pooledDelta ? amountIn.sub(pooledDelta) : 0; uint256 maxLoss = amountIn.mul(maxSlippageIn).div(basisOne); - require(joinSlipped <= maxLoss, "Exceeded maxSlippageIn!"); lastDepositTime = now; } + + + // claim all beets + claimRewards(); + // and stake all + stakeAllRewards(); } function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss){ @@ -222,7 +182,12 @@ contract Strategy is BaseStrategy { if (_amountNeeded > looseAmount) { uint256 toExitAmount = _amountNeeded.sub(looseAmount); - _sellBptForExactToken(toExitAmount); + // withdraw all bpt out of masterchef + masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); + // sell some bpt + exitPoolExactToken(toExitAmount); + // put remaining bpt back into masterchef + masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); _liquidatedAmount = Math.min(balanceOfWant(), _amountNeeded); _loss = _amountNeeded.sub(_liquidatedAmount); @@ -235,6 +200,9 @@ contract Strategy is BaseStrategy { function liquidateAllPositions() internal override returns (uint256 liquidated) { uint eta = estimatedTotalAssets(); + // withdraw all bpt out of masterchef + masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); + // sell all bpt uint256 bpts = balanceOfBpt(); if (bpts > 0) { // exit entire position for single token. Could revert due to single exit limit enforced by balancer @@ -245,17 +213,16 @@ contract Strategy is BaseStrategy { liquidated = balanceOfWant(); _enforceSlippageOut(eta, liquidated); + return liquidated; } function prepareMigration(address _newStrategy) internal override { - bpt.transfer(_newStrategy, balanceOfBpt()); - for (uint i = 0; i < rewardTokens.length; i++) { - IERC20 token = rewardTokens[i]; - uint256 balance = token.balanceOf(address(this)); - if (balance > 0) { - token.transfer(_newStrategy, balance); - } + masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(_newStrategy)); + masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef(), address(_newStrategy)); + uint256 rewards = balanceOfReward(); + if (rewards > 0) { + rewardToken.transfer(_newStrategy, rewards); } } @@ -267,51 +234,38 @@ contract Strategy is BaseStrategy { return now.sub(lastDepositTime) > minDepositPeriod && balanceOfWant() > 0; } - function harvestTrigger(uint256 callCostInWei) public view override returns (bool){ - bool hasRewards; - for (uint8 i = 0; i < rewardTokens.length; i++) { - ERC20 rewardToken = ERC20(address(rewardTokens[i])); + // HELPERS // - uint decReward = rewardToken.decimals(); - uint decWant = ERC20(address(want)).decimals(); - if (rewardToken.balanceOf(address(this)) > 10 ** (decReward > decWant ? decReward.sub(decWant) : 0)) { - hasRewards = true; - break; - } - } - return super.harvestTrigger(callCostInWei) && hasRewards; + // claim all beets rewards from masterchef + function claimRewards() internal { + masterChef.harvest(masterChefPoolId, address(this)); + masterChef.harvest(masterChefStakePoolId, address(this)); } - - // HELPERS // function sellRewards() internal { - for (uint8 i = 0; i < rewardTokens.length; i++) { - ERC20 rewardToken = ERC20(address(rewardTokens[i])); - uint256 amount = rewardToken.balanceOf(address(this)); - - uint decReward = rewardToken.decimals(); - uint decWant = ERC20(address(want)).decimals(); - - if (amount > 10 ** (decReward > decWant ? decReward.sub(decWant) : 0)) { - uint length = swapSteps[i].poolIds.length; - IBalancerVault.BatchSwapStep[] memory steps = new IBalancerVault.BatchSwapStep[](length); - int[] memory limits = new int[](length + 1); - limits[0] = int(amount); - for (uint j = 0; j < length; j++) { - steps[j] = IBalancerVault.BatchSwapStep(swapSteps[i].poolIds[j], - j, - j + 1, - j == 0 ? amount : 0, - abi.encode(0) - ); - } - balancerVault.batchSwap(IBalancerVault.SwapKind.GIVEN_IN, - steps, - swapSteps[i].assets, - IBalancerVault.FundManagement(address(this), false, address(this), false), - limits, - now + 10); + uint256 amount = balanceOfReward(); + uint decReward = ERC20(address(rewardToken)).decimals(); + uint decWant = ERC20(address(want)).decimals(); + + if (amount > 10 ** (decReward > decWant ? decReward.sub(decWant) : 0)) { + uint length = swapSteps.poolIds.length; + IBalancerVault.BatchSwapStep[] memory steps = new IBalancerVault.BatchSwapStep[](length); + int[] memory limits = new int[](length + 1); + limits[0] = int(amount); + for (uint j = 0; j < length; j++) { + steps[j] = IBalancerVault.BatchSwapStep(swapSteps.poolIds[j], + j, + j + 1, + j == 0 ? amount : 0, + abi.encode(0) + ); } + balancerVault.batchSwap(IBalancerVault.SwapKind.GIVEN_IN, + steps, + swapSteps.assets, + IBalancerVault.FundManagement(address(this), false, address(this), false), + limits, + now + 10); } } @@ -320,7 +274,7 @@ contract Strategy is BaseStrategy { uint256 debt = vault.strategies(address(this)).totalDebt; if (total > debt) { uint256 profit = total.sub(debt); - _sellBptForExactToken(profit); + exitPoolExactToken(profit); } } @@ -332,15 +286,27 @@ contract Strategy is BaseStrategy { return bpt.balanceOf(address(this)); } - function balanceOfReward(uint256 index) public view returns (uint256 _amount){ - return rewardTokens[index].balanceOf(address(this)); + function balanceOfBptInMasterChef() public view returns (uint256 _amount){ + (_amount,) = masterChef.userInfo(masterChefPoolId, address(this)); + } + + function balanceOfStakeBptInMasterChef() public view returns (uint256 _amount){ + (_amount,) = masterChef.userInfo(masterChefStakePoolId, address(this)); + } + + function balanceOfReward() public view returns (uint256 _amount){ + return rewardToken.balanceOf(address(this)); + } + + function balanceOfPendingReward() public view returns (uint256 _amount){ + return masterChef.pendingBeets(masterChefStakePoolId, address(this)).add(masterChef.pendingBeets(masterChefPoolId, address(this))); } function balanceOfPooled() public view returns (uint256 _amount){ uint256 totalWantPooled; - (IERC20[] memory tokens,uint256[] memory totalBalances,uint256 lastChangeBlock) = balancerVault.getPoolTokens(balancerPoolId); + (IERC20[] memory tokens, uint256[] memory totalBalances, uint256 lastChangeBlock) = balancerVault.getPoolTokens(balancerPoolId); for (uint8 i = 0; i < numTokens; i++) { - uint256 tokenPooled = totalBalances[i].mul(balanceOfBpt()).div(bpt.totalSupply()); + uint256 tokenPooled = totalBalances[i].mul(balanceOfBpt().add(balanceOfBptInMasterChef())).div(bpt.totalSupply()); if (tokenPooled > 0) { IERC20 token = tokens[i]; if (token != want) { @@ -367,7 +333,7 @@ contract Strategy is BaseStrategy { ); } - function _sellBptForExactToken(uint256 _amountTokenOut) internal { + function exitPoolExactToken(uint256 _amountTokenOut) internal { uint256[] memory amountsOut = new uint256[](numTokens); amountsOut[tokenIndex] = _amountTokenOut; bytes memory userData = abi.encode(IBalancerVault.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, balanceOfBpt()); @@ -375,25 +341,22 @@ contract Strategy is BaseStrategy { balancerVault.exitPool(balancerPoolId, address(this), address(this), request); } - // for partnership rewards like Lido or airdrops - function whitelistRewards(address _rewardToken, SwapSteps memory _steps) public onlyVaultManagers { - IERC20 token = IERC20(_rewardToken); - token.approve(address(balancerVault), max); - rewardTokens.push(token); - swapSteps.push(_steps); - } - - function delistAllRewards() public onlyVaultManagers { - for (uint i = 0; i < rewardTokens.length; i++) { - rewardTokens[i].approve(address(balancerVault), 0); + function joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ + uint256[] memory maxAmountsIn = new uint256[](_numTokens); + maxAmountsIn[_tokenIndex] = _amountIn; + if (_amountIn > 0) { + bytes memory userData = abi.encode(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, 0); + IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest(_assets, maxAmountsIn, userData, false); + balancerVault.joinPool(_poolId, address(this), address(this), request); + return true; } - IERC20[] memory noRewardTokens; - rewardTokens = noRewardTokens; - delete swapSteps; + return false; } - function numRewards() public view returns (uint256 _num){ - return rewardTokens.length; + function whitelistReward(address _rewardToken, SwapSteps memory _steps) public onlyVaultManagers { + rewardToken = IERC20(_rewardToken); + rewardToken.approve(address(balancerVault), max); + swapSteps = _steps; } function setParams(uint256 _maxSlippageIn, uint256 _maxSlippageOut, uint256 _maxSingleDeposit, uint256 _minDepositPeriod) public onlyVaultManagers { @@ -415,9 +378,57 @@ contract Strategy is BaseStrategy { require(exitSlipped <= maxLoss, "Exceeded maxSlippageOut!"); } - function getSwapSteps() public view returns (SwapSteps[] memory){ + function getSwapSteps() public view returns (SwapSteps memory){ return swapSteps; } + function setMasterChef(address _masterChef) public onlyVaultManagers { + bpt.approve(address(masterChef), 0); + masterChef = IBeethovenxMasterChef(_masterChef); + bpt.approve(address(masterChef), max); + } + + // stake all beets + function stakeAllRewards() internal { + stake(balanceOfReward()); + } + + // stake beets into beets-lp, then beets-lp into masterchef + function stake(uint256 _amount) internal { + if (joinPool(_amount, stakeAssets, stakeAssets.length, stakeTokenIndex, stakeBpt.getPoolId())) { + masterChef.deposit(masterChefStakePoolId, stakeBpt.balanceOf(address(this)), address(this)); + } + } + + // unstake all beets-lp from masterchef, single sided withdraw all beets from beets-lp + function unstake() internal { + uint256 bpts = balanceOfStakeBptInMasterChef(); + masterChef.withdrawAndHarvest(masterChefStakePoolId, bpts, address(this)); + + uint256[] memory minAmtsOut = new uint256[](stakeAssets.length); + if (bpts > 0) { + // exit all beets from beets pool. Don't care about slippage since it's rewards + bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, bpts, stakeTokenIndex); + IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(stakeAssets, minAmtsOut, userData, false); + balancerVault.exitPool(stakeBpt.getPoolId(), address(this), address(this), request); + } + } + + function setStakeParams( + uint256 _stakePercentageBips, + IAsset[] memory _stakeAssets, + address _stakePool, + uint256 _stakeTokenIndex, + uint256 _masterChefStakePoolId + ) public onlyVaultManagers { + stakePercentage = _stakePercentageBips; + stakeAssets = _stakeAssets; + masterChefStakePoolId = _masterChefStakePoolId; + stakeBpt = IBalancerPool(_stakePool); + stakeBpt.approve(address(masterChef), max); + stakeTokenIndex = _stakeTokenIndex; + } + receive() external payable {} } + diff --git a/interfaces/MasterChef.sol b/interfaces/MasterChef.sol new file mode 100644 index 0000000..2bd96c6 --- /dev/null +++ b/interfaces/MasterChef.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.6.0 <0.7.0; +pragma experimental ABIEncoderV2; + +import {SafeERC20, SafeMath, IERC20, Address} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +interface IBeethovenxMasterChef { + /* + This master chef is based on SUSHI's version with some adjustments: + - Upgrade to pragma 0.8.7 + - therefore remove usage of SafeMath (built in overflow check for solidity > 8) + - Merge sushi's master chef V1 & V2 (no usage of dummy pool) + - remove withdraw function (without harvest) => requires the rewardDebt to be an signed int instead of uint which requires a lot of casting and has no real usecase for us + - no dev emissions, but treasury emissions instead + - treasury percentage is subtracted from emissions instead of added on top + - update of emission rate with upper limit of 6 BEETS/block + - more require checks in general + */ + + struct UserInfo { + uint256 amount; // How many LP tokens the user has provided. + uint256 rewardDebt; // Reward debt. See explanation below. + // + // We do some fancy math here. Basically, any point in time, the amount of BEETS + // entitled to a user but is pending to be distributed is: + // + // pending reward = (user.amount * pool.accBeetsPerShare) - user.rewardDebt + // + // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: + // 1. The pool's `accBeetsPerShare` (and `lastRewardBlock`) gets updated. + // 2. User receives the pending reward sent to his/her address. + // 3. User's `amount` gets updated. + // 4. User's `rewardDebt` gets updated. + } + // Info of each pool. + struct PoolInfo { + // we have a fixed number of BEETS tokens released per block, each pool gets his fraction based on the allocPoint + uint256 allocPoint; // How many allocation points assigned to this pool. the fraction BEETS to distribute per block. + uint256 lastRewardBlock; // Last block number that BEETS distribution occurs. + uint256 accBeetsPerShare; // Accumulated BEETS per LP share. this is multiplied by ACC_BEETS_PRECISION for more exact results (rounding errors) + } + + function poolLength() external view returns (uint256); + + // Add a new lp to the pool. Can only be called by the owner. + function add(uint256 _allocPoint, IERC20 _lpToken, IRewarder _rewarder) external; + // Update the given pool's BEETS allocation point. Can only be called by the owner. + /// @param _pid The index of the pool. See `poolInfo`. + /// @param _allocPoint New AP of the pool. + /// @param _rewarder Address of the rewarder delegate. + /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. + function set(uint256 _pid, uint256 _allocPoint, IRewarder _rewarder, bool overwrite) external; + + // View function to see pending BEETS on frontend. + function pendingBeets(uint256 _pid, address _user) external view returns (uint256 pending); + + /// @notice Update reward variables for all pools. Be careful of gas spending! + /// @param pids Pool IDs of all to be updated. Make sure to update all active pools. + function massUpdatePools(uint256[] calldata pids) external; + + // Update reward variables of the given pool to be up-to-date. + function updatePool(uint256 _pid) external returns (PoolInfo memory pool); + + // Deposit LP tokens to MasterChef for BEETS allocation. + function deposit(uint256 _pid, uint256 _amount, address _to) external; + + function harvestAll(uint256[] calldata _pids, address _to) external; + + /// @notice Harvest proceeds for transaction sender to `_to`. + /// @param _pid The index of the pool. See `poolInfo`. + /// @param _to Receiver of BEETS rewards. + function harvest(uint256 _pid, address _to) external; + + /// @notice Withdraw LP tokens from MCV and harvest proceeds for transaction sender to `_to`. + /// @param _pid The index of the pool. See `poolInfo`. + /// @param _amount LP token amount to withdraw. + /// @param _to Receiver of the LP tokens and BEETS rewards. + function withdrawAndHarvest( + uint256 _pid, + uint256 _amount, + address _to + ) external; + + // Withdraw without caring about rewards. EMERGENCY ONLY. + function emergencyWithdraw(uint256 _pid, address _to) external; + + // Safe BEETS transfer function, just in case if rounding error causes pool to not have enough BEETS. + function safeBeetsTransfer(address _to, uint256 _amount) external; + // Update treasury address by the owner. + function treasury(address _treasuryAddress) external; + + function updateEmissionRate(uint256 _beetsPerBlock) external; + + function userInfo(uint256 _pid, address _user) external view returns (uint256 _amountLP, uint256 _rewardDebt); + + function lpTokens(uint256) external view returns (address _lp); +} + +interface IRewarder { + function onBeetsReward( + uint256 pid, + address user, + address recipient, + uint256 beetsAmount, + uint256 newLpAmount + ) external; + + function pendingTokens( + uint256 pid, + address user, + uint256 beetsAmount + ) external view returns (IERC20[] memory, uint256[] memory); +} diff --git a/scripts/pid.py b/scripts/pid.py new file mode 100644 index 0000000..49e0849 --- /dev/null +++ b/scripts/pid.py @@ -0,0 +1,22 @@ +from brownie import Contract +import time + +# 0 Symphony Nr 10. Opus 138. +# 1 The Grand Orchestra +# 2 Steady Beets +# 3 FTM Sonata +# 4 The E Major +# 5 The B Major +# 6 The Classic Trio +# 7 Dance of the Degens +# 8 Fantom of the Opera +# 9 The Fidelio Duetto +# 10 MIM-USDC-USDT Stable Pool +# 11 Guqin qi + +def main(): + masterChef = Contract("0x8166994d9ebBe5829EC86Bd81258149B87faCfd3") + for i in range(masterChef.poolLength()): + pool = Contract(masterChef.lpTokens(i)) + print(f'{i} {pool.name()}') + time.sleep(3) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 04d3d64..8ca00bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,100 +46,57 @@ def keeper(accounts): @pytest.fixture def token(): # 0x6B175474E89094C44Da98b954EedeAC495271d0F DAI - # 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 USDC - # 0xdAC17F958D2ee523a2206206994597C13D831ec7 USDT - # 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 wSTETH - # 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 WETH - token_address = "0x6B175474E89094C44Da98b954EedeAC495271d0F" - yield Contract(token_address) - - -@pytest.fixture -def token2(): - # 0x6B175474E89094C44Da98b954EedeAC495271d0F DAI - # 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 USDC - # 0xdAC17F958D2ee523a2206206994597C13D831ec7 USDT - # 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 wSTETH - # 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 WETH - token_address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + # 0x049d68029688eAbF473097a2fC38ef61633A3C7A fUSDT + # 0x04068DA6C83AFCFA0e13ba15A6696662335D5B75 USDC + token_address = "0x82f0B8B456c1A451378467398982d4834b6829c1" yield Contract(token_address) @pytest.fixture def token_whale(accounts): - # 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643 DAI - # 0x0A59649758aa4d66E25f08Dd01271e891fe52199 USDC - # 0xA929022c9107643515F5c777cE9a910F0D1e490C USDT - # 0xba12222222228d8ba445958a75a0704d566bf2c8 wSTETH - # 0x2F0b23f53734252Bda2277357e97e1517d6B042A WETH - return accounts.at("0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643", force=True) + # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM + # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 fUSDT + # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 USDC + return accounts.at("0x2dd7C9371965472E5A5fD28fbE165007c61439E1", force=True) @pytest.fixture def amount(accounts, token, user, token_whale): - amount = 10_000_000 * 10 ** token.decimals() + amount = 1_000_000 * 10 ** token.decimals() # In order to get some funds for the token you are about to use, token.transfer(user, amount, {"from": token_whale}) yield amount @pytest.fixture -def amount2(accounts, token2, user): - amount = 1_000_000 * 10 ** token2.decimals() - # In order to get some funds for the token you are about to use, - # 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643 DAI - # 0x0A59649758aa4d66E25f08Dd01271e891fe52199 USDC - # 0xA929022c9107643515F5c777cE9a910F0D1e490C USDT - reserve = accounts.at("0x0A59649758aa4d66E25f08Dd01271e891fe52199", force=True) - token2.transfer(user, amount, {"from": reserve}) - yield amount - - -@pytest.fixture -def weth(): - token_address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +def wftm(): + token_address = "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" yield Contract(token_address) @pytest.fixture -def bal(): - token_address = "0xba100000625a3754423978a60c9317c58a424e3D" +def beets(): + token_address = "0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e" yield Contract(token_address) @pytest.fixture -def bal_whale(accounts): - yield accounts.at("0xBA12222222228d8Ba445958a75a0704d566BF2C8", force=True) - - -@pytest.fixture -def ldo(): - token_address = "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32" +def usdc(): + token_address = "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75" yield Contract(token_address) @pytest.fixture -def ldo_whale(accounts): - yield accounts.at("0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", force=True) - - -@pytest.fixture -def weth_amout(user, weth): - weth_amout = 10 ** weth.decimals() - user.transfer(weth, weth_amout) - yield weth_amout - - -@pytest.fixture -def live_ssb_weth(Strategy): - strat = "0xb8A245f9a066AD49fEAF15443E7704b83e2A9bF0" - yield Strategy.at(strat) +def beets_whale(accounts): + yield accounts.at("0xa2503804ec837D1E4699932D58a3bdB767DeA505", force=True) @pytest.fixture -def live_dai_vault(): - vault = "0x1F8ad2cec4a2595Ff3cdA9e8a39C0b1BE1A02014" - yield Contract(vault) +def wftm_amount(user, wftm, accounts): + wftm_amount = 10 ** wftm.decimals() + whale = accounts.at("0x20dd72Ed959b6147912C2e529F0a0C651c33c9ce", force=True) + wftm.transfer(user, wftm_amount, {'from': whale}) + yield wftm_amount @pytest.fixture @@ -152,79 +109,60 @@ def vault(pm, gov, rewards, guardian, management, token): yield vault -@pytest.fixture -def vault2(pm, gov, rewards, guardian, management, token2): - Vault = pm(config["dependencies"][0]).Vault - vault = guardian.deploy(Vault) - vault.initialize(token2, gov, rewards, "", "", guardian, management, {"from": gov}) - vault.setDepositLimit(2 ** 256 - 1, {"from": gov}) - vault.setManagement(management, {"from": gov}) - yield vault - - @pytest.fixture def balancer_vault(): - yield Contract("0xBA12222222228d8Ba445958a75a0704d566BF2C8") + yield Contract("0x20dd72Ed959b6147912C2e529F0a0C651c33c9ce") @pytest.fixture def pool(): - # 0x06Df3b2bbB68adc8B0e302443692037ED9f91b42 stable pool - # 0x32296969Ef14EB0c6d29669C550D4a0449130230 metastable eth pool - address = "0x06Df3b2bbB68adc8B0e302443692037ED9f91b42" + # 0xD163415BD34EF06f57C58D2AEd5A5478AfB464cC MIM-USDC-USDT Stable Pool + address = "0xD163415BD34EF06f57C58D2AEd5A5478AfB464cC" yield Contract(address) @pytest.fixture -def balWethPoolId(): - yield 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 +def masterChef(): + address = "0x8166994d9ebBe5829EC86Bd81258149B87faCfd3" + yield Contract(address) @pytest.fixture -def wethTokenPoolId(): - id = 0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a # weth-dai - yield id +def beetsUsdcPool(): + address = "0x03c6B3f09D2504606936b1A4DeCeFaD204687890" + yield Contract(address) -@pytest.fixture -def wethToken2PoolId(): - id = 0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019 # weth-usdc - yield id @pytest.fixture -def ldoWethPoolId(): - id = 0xbf96189eee9357a95c7719f4f5047f76bde804e5000200000000000000000087 # weth-dai - yield id +def beetsUsdcPoolId(): + yield 0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015 @pytest.fixture -def swapStepsBal(balWethPoolId, wethTokenPoolId, bal, weth, token): - yield ([balWethPoolId, wethTokenPoolId], [bal, weth, token]) - -@pytest.fixture -def swapStepsLdo(ldoWethPoolId, wethTokenPoolId, ldo, weth, token): - yield ([ldoWethPoolId, wethTokenPoolId], [ldo, weth, token]) +def usdcTokenPoolId(): + id = 0xd163415bd34ef06f57c58d2aed5a5478afb464cc00000000000000000000000e # usdc-mim + yield id -@pytest.fixture -def swapStepsBal2(balWethPoolId, wethToken2PoolId, bal, weth, token2): - yield ([balWethPoolId, wethToken2PoolId], [bal, weth, token2]) @pytest.fixture -def swapStepsLdo2(ldoWethPoolId, wethToken2PoolId, ldo, weth, token2): - yield ([ldoWethPoolId, wethToken2PoolId], [ldo, weth, token2]) +def swapStepsBeets(beetsUsdcPoolId, usdcTokenPoolId, beets, usdc, token): + yield ([beetsUsdcPoolId, usdcTokenPoolId], [beets, usdc, token]) @pytest.fixture -def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, bal, ldo, management, swapStepsBal, - swapStepsLdo): - strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, 5, 5, 1_000_000, 2 * 60 * 60) +def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, management, + masterChef, + swapStepsBeets): + strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 50, 50, 100_000, 2 * 60 * 60, 10) strategy.setKeeper(keeper) - strategy.whitelistRewards(bal, swapStepsBal, {'from': gov}) - strategy.whitelistRewards(ldo, swapStepsLdo, {'from': gov}) + strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) + strategy.setStakeParams(8000, [usdc, beets], beetsUsdcPool, 1, 0, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) yield strategy @pytest.fixture(scope="session") def RELATIVE_APPROX(): - # making this more lenient bc of single sided deposits incurring slippage + # making this more lenient bc of single + # sided deposits incurring slippage yield 1e-3 diff --git a/tests/test_clone.py b/tests/test_clone.py deleted file mode 100644 index 445ca48..0000000 --- a/tests/test_clone.py +++ /dev/null @@ -1,31 +0,0 @@ -import util -import pytest -import brownie -import test_operation - - -def test_clone(accounts, Strategy, strategy, strategist, rewards, keeper, token2, user, vault, vault2, amount2, - balancer_vault, - pool, chain, gov, - RELATIVE_APPROX, - bal, bal_whale, ldo, weth, weth_amout, - ldo_whale, swapStepsBal2, swapStepsLdo2, management): - with brownie.reverts("Strategy already initialized"): - strategy.initialize(vault, strategist, rewards, keeper, balancer_vault, pool, 10, 10, 100_000, 2 * 60 * 60) - - transaction = strategy.clone(vault2, strategist, rewards, keeper, balancer_vault, pool, 10, 10, 100_000, - 2 * 60 * 60) - cloned_strategy = Strategy.at(transaction.return_value) - - with brownie.reverts("Strategy already initialized"): - cloned_strategy.initialize(vault, strategist, rewards, keeper, balancer_vault, pool, 10, 10, 100_000, - 2 * 60 * 60, {'from': gov}) - cloned_strategy.setKeeper(keeper, {'from': gov}) - cloned_strategy.whitelistRewards(bal, swapStepsBal2, {'from': management}) - cloned_strategy.whitelistRewards(ldo, swapStepsLdo2, {'from': management}) - vault2.addStrategy(cloned_strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) - - # test operations with clone strategy - test_operation.test_profitable_harvest( - chain, token2, vault2, cloned_strategy, user, strategist, amount2, RELATIVE_APPROX, bal, bal_whale, ldo, - ldo_whale, management) diff --git a/tests/test_migration.py b/tests/test_migration.py index c5406b6..8f2ae09 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -13,7 +13,8 @@ def test_migration( user, RELATIVE_APPROX, balancer_vault, - pool + pool, + masterChef ): # Deposit to the vault and harvest token.approve(vault.address, amount, {"from": user}) @@ -23,6 +24,7 @@ def test_migration( assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount # migrate to a new strategy - new_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, 5, 5, 100_000, 2 * 60 * 60) + new_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 10, 10, 100_000, 2 * 60 * 60, + 10) vault.migrateStrategy(strategy, new_strategy, {"from": gov}) assert (pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount) diff --git a/tests/test_operation.py b/tests/test_operation.py index 4a1044d..ba85e20 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -44,8 +44,7 @@ def test_emergency_exit( def test_profitable_harvest( - chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, bal, bal_whale, ldo, - ldo_whale, management + chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, management ): # Deposit to the vault token.approve(vault.address, amount, {"from": user}) @@ -58,22 +57,27 @@ def test_profitable_harvest( assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount before_pps = vault.pricePerShare() - util.airdrop_rewards(strategy, bal, bal_whale, ldo, ldo_whale) # Harvest 2: Realize profit - chain.sleep(1) + + util.airdrop_rewards(strategy, beets, beets_whale) + strategy.harvest({"from": strategist}) chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock chain.mine(1) - profit = token.balanceOf(vault.address) # Profits go to vault + # do a second harvest so the first harvest's stake is sold + strategy.harvest({"from": strategist}) + chain.sleep(3600 * 6) + chain.mine(1) + + profit = token.balanceOf(vault.address) assert strategy.estimatedTotalAssets() + profit > amount assert vault.pricePerShare() > before_pps -def test_deposit_all(chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, bal, bal_whale, - ldo, gov, - ldo_whale): +def test_deposit_all(chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, + gov): # Deposit to the vault token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) @@ -94,14 +98,21 @@ def test_deposit_all(chain, token, vault, strategy, user, strategist, amount, RE chain.mine(1) before_pps = vault.pricePerShare() - util.airdrop_rewards(strategy, bal, bal_whale, ldo, ldo_whale) + util.airdrop_rewards(strategy, beets, beets_whale) + util.stateOfStrat("after airdrop", strategy, beets) # Harvest 2: Realize profit chain.sleep(1) strategy.harvest({"from": strategist}) chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock chain.mine(1) + util.stateOfStrat("first harvest", strategy, beets) + # do a second harvest so the first harvest's stake is sold + strategy.harvest({"from": strategist}) + chain.sleep(3600 * 6) + chain.mine(1) profit = token.balanceOf(vault.address) # Profits go to vault + util.stateOfStrat("second harvest", strategy, beets) slippageIn = amount * strategy.maxSlippageIn() / 10000 assert strategy.estimatedTotalAssets() + profit > (amount - slippageIn) @@ -114,11 +125,11 @@ def test_deposit_all(chain, token, vault, strategy, user, strategist, amount, RE half = int(amount / 2) # profits - assert strategy.estimatedTotalAssets() >= half - slippageIn/2 + assert strategy.estimatedTotalAssets() >= half - slippageIn / 2 def test_change_debt( - chain, gov, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, bal, bal_whale, ldo, ldo_whale + chain, gov, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale ): # Deposit to the vault and harvest token.approve(vault.address, amount, {"from": user}) @@ -136,16 +147,22 @@ def test_change_debt( assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount util.stateOfStrat("before airdrop", strategy, token) - util.airdrop_rewards(strategy, bal, bal_whale, ldo, ldo_whale) + util.airdrop_rewards(strategy, beets, beets_whale) util.stateOfStrat("after airdrop", strategy, token) vault.updateStrategyDebtRatio(strategy.address, 5_000, {"from": gov}) - chain.sleep(1) strategy.harvest({"from": strategist}) + chain.sleep(3600 * 6) + chain.mine(1) + # do a second harvest so the first harvest's stake is sold + strategy.harvest({"from": strategist}) + chain.sleep(3600 * 6) + chain.mine(1) util.stateOfStrat("after harvest 5000", strategy, token) # compounded slippage - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == half + assert pytest.approx(strategy.estimatedTotalAssets(), + rel=RELATIVE_APPROX) == half or strategy.estimatedTotalAssets() >= half vault.updateStrategyDebtRatio(strategy.address, 0, {"from": gov}) chain.sleep(1) @@ -156,7 +173,7 @@ def test_change_debt( rel=RELATIVE_APPROX) >= amount -def test_sweep(gov, vault, strategy, token, user, amount, weth, weth_amout): +def test_sweep(gov, vault, strategy, token, user, amount, wftm, wftm_amount): # Strategy want token doesn't work token.transfer(strategy, amount, {"from": user}) assert token.address == strategy.want() @@ -168,21 +185,16 @@ def test_sweep(gov, vault, strategy, token, user, amount, weth, weth_amout): with brownie.reverts("!shares"): strategy.sweep(vault.address, {"from": gov}) - # # Protected token doesn't work - # for i in range(strategy.numRewards()): - # with brownie.reverts("!protected"): - # strategy.sweep(strategy.rewardTokens(i), {"from": gov}) - - before_balance = weth.balanceOf(gov) - weth.transfer(strategy, weth_amout, {"from": user}) - assert weth.address != strategy.want() - assert weth.balanceOf(user) == 0 - strategy.sweep(weth, {"from": gov}) - assert weth.balanceOf(gov) == weth_amout + before_balance + before_balance = wftm.balanceOf(gov) + wftm.transfer(strategy, wftm_amount, {"from": user}) + assert wftm.address != strategy.want() + assert wftm.balanceOf(user) == 0 + strategy.sweep(wftm, {"from": gov}) + assert wftm.balanceOf(gov) == wftm_amount + before_balance def test_triggers( - chain, gov, vault, strategy, token, amount, user, weth, weth_amout, strategist, bal, bal_whale, ldo, ldo_whale, + chain, gov, vault, strategy, token, amount, user, wftm, wftm_amount, strategist, beets, beets_whale, token_whale ): # Deposit to the vault and harvest @@ -192,20 +204,6 @@ def test_triggers( chain.sleep(1) strategy.harvest({"from": strategist}) - assert strategy.harvestTrigger(0) == False - util.airdrop_rewards(strategy, bal, bal_whale, ldo, ldo_whale) - assert strategy.harvestTrigger(0) == True + strategy.harvestTrigger(0) + strategy.tendTrigger(0) - assert strategy.tendTrigger(0) == False - chain.sleep(strategy.minDepositPeriod() + 1) - chain.mine(1) - assert strategy.tendTrigger(0) == True - - -def test_rewards( - strategy, strategist, gov -): - # added in setup - assert strategy.numRewards() == 2 - strategy.delistAllRewards({'from': gov}) - assert strategy.numRewards() == 0 diff --git a/tests/util.py b/tests/util.py index c4e2fd8..45f253c 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,9 +1,9 @@ from brownie import Contract -def airdrop_rewards(strategy, bal, bal_whale, ldo, ldo_whale): - bal.approve(strategy, 2 ** 256 - 1, {'from': bal_whale}) - bal.transfer(strategy, 100 * 1e18, {'from': bal_whale}) - ldo.approve(strategy, 2 ** 256 - 1, {'from': ldo_whale}) - ldo.transfer(strategy, 100 * 1e18, {'from': ldo_whale}) + + +def airdrop_rewards(strategy, beets, beets_whale): + beets.approve(strategy, 2 ** 256 - 1, {'from': beets_whale}) + beets.transfer(strategy, 10_000 * 1e18, {'from': beets_whale}) def stateOfStrat(msg, strategy, token): @@ -11,6 +11,8 @@ def stateOfStrat(msg, strategy, token): wantDec = 10 ** token.decimals() print(f'Balance of {token.symbol()}: {strategy.balanceOfWant() / wantDec}') print(f'Balance of Bpt: {strategy.balanceOfBpt() / wantDec}') - for i in range(strategy.numRewards()): - print(f'Balance of {Contract(strategy.rewardTokens(i)).symbol()}: {Contract(strategy.rewardTokens(i)).balanceOf(strategy.address)}') + print(f'balanceOfBptInMasterChef: {strategy.balanceOfBptInMasterChef() / wantDec}') + print(f'balanceOfStakeBptInMasterChef : {strategy.balanceOfStakeBptInMasterChef() / wantDec}') + print(f'Balance of BEETS: {strategy.balanceOfReward()/ wantDec}') + print(f'Balance of pending BEETS: {strategy.balanceOfPendingReward()/ wantDec}') print(f'Estimated Total Assets: {strategy.estimatedTotalAssets() / wantDec}') diff --git a/yearn-strategy/tests/conftest.py b/yearn-strategy/tests/conftest.py index 64a065d..9da770b 100644 --- a/yearn-strategy/tests/conftest.py +++ b/yearn-strategy/tests/conftest.py @@ -61,9 +61,9 @@ def weth(): @pytest.fixture -def weth_amout(user, weth): - weth_amout = 10 ** weth.decimals() - user.transfer(weth, weth_amout) +def weth_amout(user, wftm): + weth_amout = 10 ** wftm.decimals() + user.transfer(wftm, weth_amout) yield weth_amout diff --git a/yearn-strategy/tests/test_operation.py b/yearn-strategy/tests/test_operation.py index 136e714..6f49cc8 100644 --- a/yearn-strategy/tests/test_operation.py +++ b/yearn-strategy/tests/test_operation.py @@ -96,7 +96,7 @@ def test_change_debt( # assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == half -def test_sweep(gov, vault, strategy, token, user, amount, weth, weth_amout): +def test_sweep(gov, vault, strategy, token, user, amount, wftm, wftm_amount): # Strategy want token doesn't work token.transfer(strategy, amount, {"from": user}) assert token.address == strategy.want() @@ -113,16 +113,16 @@ def test_sweep(gov, vault, strategy, token, user, amount, weth, weth_amout): # with brownie.reverts("!protected"): # strategy.sweep(strategy.protectedToken(), {"from": gov}) - before_balance = weth.balanceOf(gov) - weth.transfer(strategy, weth_amout, {"from": user}) - assert weth.address != strategy.want() - assert weth.balanceOf(user) == 0 - strategy.sweep(weth, {"from": gov}) - assert weth.balanceOf(gov) == weth_amout + before_balance + before_balance = wftm.balanceOf(gov) + wftm.transfer(strategy, wftm_amount, {"from": user}) + assert wftm.address != strategy.want() + assert wftm.balanceOf(user) == 0 + strategy.sweep(wftm, {"from": gov}) + assert wftm.balanceOf(gov) == wftm_amount + before_balance def test_triggers( - chain, gov, vault, strategy, token, amount, user, weth, weth_amout, strategist + chain, gov, vault, strategy, token, amount, user, wftm, wftm_amount, strategist ): # Deposit to the vault and harvest token.approve(vault.address, amount, {"from": user}) From 58d663a5868fade92df7fe6abe00ae93392b7feb Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Thu, 21 Oct 2021 18:44:11 -0700 Subject: [PATCH 02/54] access check --- scripts/authorizor_check.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 scripts/authorizor_check.py diff --git a/scripts/authorizor_check.py b/scripts/authorizor_check.py new file mode 100644 index 0000000..21312d8 --- /dev/null +++ b/scripts/authorizor_check.py @@ -0,0 +1,29 @@ +from brownie import Contract + + +def main(): + vault = [Contract("0x20dd72Ed959b6147912C2e529F0a0C651c33c9ce"), "vault"] + stablePool = [Contract("0xD163415BD34EF06f57C58D2AEd5A5478AfB464cC"), "stablepool"] + lbp = [Contract("0x03c6b3f09d2504606936b1a4decefad204687890"), "lbp"] + protocolFeesCollector = [Contract("0xC6920d3a369E7c8BD1A22DbE385e11d1F7aF948F"), "fees collector"] + authorizor = Contract("0x974D3FF709D84Ba44cde3257C0B5B0b14C081Ce9") + timelock = Contract("0xB5CaEe3CD5d86c138f879B3abC5B1bebB63c6471") + masterchef = Contract("0x8166994d9ebBe5829EC86Bd81258149B87faCfd3") + contracts = [vault, stablePool, protocolFeesCollector, lbp] + grantedRoles = {} + + for contract in contracts: + selectors = contract[0].selectors + for key in selectors: + role = contract[0].getActionId(key) + count = authorizor.getRoleMemberCount(role) + + for i in range(0, count): + member = authorizor.getRoleMember(role, i) + s = f'{contract[1]} {selectors[key]}:' + grantedRoles[s] = member + grantedRoles[f'{"timelock admin": }'] = timelock.admin() + grantedRoles[f'{"masterchef owner": }'] = masterchef.owner() + + for key in grantedRoles: + print(f'{key} {grantedRoles[key]}') From 6f48e387c467a8bb4ce60a4952f1a74acc108d26 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Thu, 21 Oct 2021 20:32:37 -0700 Subject: [PATCH 03/54] approve stake --- contracts/Strategy.sol | 2 ++ scripts/deploy.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index b61e793..ead8e35 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -384,8 +384,10 @@ contract Strategy is BaseStrategy { function setMasterChef(address _masterChef) public onlyVaultManagers { bpt.approve(address(masterChef), 0); + stakeBpt.approve(address(masterChef), 0); masterChef = IBeethovenxMasterChef(_masterChef); bpt.approve(address(masterChef), max); + stakeBpt.approve(address(masterChef), max); } // stake all beets diff --git a/scripts/deploy.py b/scripts/deploy.py index 61ded47..b44a516 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -55,4 +55,7 @@ def main(): if input("Deploy Strategy? y/[N]: ").lower() != "y": return - strategy = Strategy.deploy(vault, {"from": dev}, publish_source=publish_source) + balancer_vault = "0x20dd72Ed959b6147912C2e529F0a0C651c33c9ce" + pool = "0xD163415BD34EF06f57C58D2AEd5A5478AfB464cC" + masterchef = "0x8166994d9ebBe5829EC86Bd81258149B87faCfd3" + strategy = Strategy.deploy(vault, balancer_vault, pool, masterchef, 50, 50, 100_000, 2 * 60 * 60, 10, {"from": dev}, publish_source=publish_source) From 2aee2062a468467a7d6d109290cb39a2b32daaf9 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Thu, 21 Oct 2021 23:55:22 -0700 Subject: [PATCH 04/54] enforceslippage to view --- contracts/Strategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index ead8e35..c944384 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -370,7 +370,7 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; } - function _enforceSlippageOut(uint _intended, uint _actual) internal { + function _enforceSlippageOut(uint _intended, uint _actual) internal view { // enforce that amount exited didn't slip beyond our tolerance // just in case there's positive slippage uint256 exitSlipped = _intended > _actual ? _intended.sub(_actual) : 0; From 7ff260ff76bca9d2bc70510aeffb1c9b9d9502c3 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Wed, 27 Oct 2021 21:50:24 -0700 Subject: [PATCH 05/54] scripts --- scripts/flatten.py | 6 ++++++ scripts/mim_setup.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 scripts/flatten.py create mode 100644 scripts/mim_setup.py diff --git a/scripts/flatten.py b/scripts/flatten.py new file mode 100644 index 0000000..3dfddc2 --- /dev/null +++ b/scripts/flatten.py @@ -0,0 +1,6 @@ +from brownie import Strategy, accounts, config, network, project, web3 + + +def main(): + with open('./build/contracts/StrategyFlat.sol', 'w') as f: + f.write(Strategy.get_verification_info()['flattened_source']) \ No newline at end of file diff --git a/scripts/mim_setup.py b/scripts/mim_setup.py new file mode 100644 index 0000000..441fecd --- /dev/null +++ b/scripts/mim_setup.py @@ -0,0 +1,20 @@ +from brownie import Strategy, Contract, accounts, config, network, project, web3 +from eth_utils import is_checksum_address +import click + + +def main(): + dev = accounts.load(click.prompt("Account", type=click.Choice(accounts.load()))) + + ssb_mim = Contract("0x04A67AdFF3D9E4b3F40915ea06aC797d490874E6") + + beets = "0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e" + usdc = "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75" + mim = "0x82f0B8B456c1A451378467398982d4834b6829c1" + beetsUsdcPoolId = 0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015 + usdcTokenPoolId = 0xd163415bd34ef06f57c58d2aed5a5478afb464cc00000000000000000000000e + swapStepsBeets = ([beetsUsdcPoolId, usdcTokenPoolId], [beets, usdc, mim]) + beetsUsdcPool = 0x03c6B3f09D2504606936b1A4DeCeFaD204687890 + + ssb_mim.whitelistReward(beets, swapStepsBeets, {'from': dev}) + ssb_mim.setStakeParams(8000, [usdc, beets], beetsUsdcPool, 1, 0, {'from': dev}) From 3b1137e9002c76f3b2b0ce33227cd9484c517bec Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Wed, 27 Oct 2021 21:52:07 -0700 Subject: [PATCH 06/54] remove debug --- contracts/Strategy.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index c944384..9b9b6a7 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -142,8 +142,6 @@ contract Strategy is BaseStrategy { } } - event Debug(string msg, uint val); - function adjustPosition(uint256 _debtOutstanding) internal override { if (now - lastDepositTime < minDepositPeriod) { return; @@ -152,13 +150,11 @@ contract Strategy is BaseStrategy { // put want into lp then put want-lp into masterchef uint256 pooledBefore = balanceOfPooled(); uint256 amountIn = Math.min(maxSingleDeposit, balanceOfWant()); - emit Debug("amountIn", amountIn); if (joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { // put all want-lp into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); uint256 pooledDelta = balanceOfPooled().sub(pooledBefore); - emit Debug("pooledDelta", pooledDelta); uint256 joinSlipped = amountIn > pooledDelta ? amountIn.sub(pooledDelta) : 0; uint256 maxLoss = amountIn.mul(maxSlippageIn).div(basisOne); require(joinSlipped <= maxLoss, "Exceeded maxSlippageIn!"); From 63419cf8b428a62e56cac82f32bba799d48585f9 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Mon, 1 Nov 2021 17:28:42 -0700 Subject: [PATCH 07/54] add unstake percentage --- contracts/Strategy.sol | 59 +++++++++++++++++++++++++----------------- tests/conftest.py | 2 +- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 9b9b6a7..151c841 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -35,6 +35,7 @@ contract Strategy is BaseStrategy { IBalancerPool internal stakeBpt; uint internal stakeTokenIndex; uint internal stakePercentage; + uint internal unstakePercentage; struct SwapSteps { bytes32[] poolIds; @@ -120,13 +121,8 @@ contract Strategy is BaseStrategy { collectTradingFees(); // claim beets claimRewards(); - - // calc amount beets to stake - uint256 toStake = balanceOfReward().mul(stakePercentage).div(basisOne); - // unstake all staked beets - unstake(); - // stake pre-calc amount of beets for higher apy - stake(toStake); + // consolidate % to stake and unstake + consolidate(); // sell the % not staking sellRewards(); @@ -199,13 +195,7 @@ contract Strategy is BaseStrategy { // withdraw all bpt out of masterchef masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); // sell all bpt - uint256 bpts = balanceOfBpt(); - if (bpts > 0) { - // exit entire position for single token. Could revert due to single exit limit enforced by balancer - bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, bpts, tokenIndex); - IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(assets, minAmountsOut, userData, false); - balancerVault.exitPool(balancerPoolId, address(this), address(this), request); - } + exitPoolExactBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, minAmountsOut); liquidated = balanceOfWant(); _enforceSlippageOut(eta, liquidated); @@ -329,6 +319,17 @@ contract Strategy is BaseStrategy { ); } + // exit a pool given exact bpt amount + function exitPoolExactBpt(uint256 _bpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256[] memory _minAmountsOut) internal { + if (_bpts > 0) { + // exit entire position for single token. Could revert due to single exit limit enforced by balancer + bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, _bpts, _tokenIndex); + IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(_assets, _minAmountsOut, userData, false); + balancerVault.exitPool(_balancerPoolId, address(this), address(this), request); + } + } + + // exit a pool given exact token amount function exitPoolExactToken(uint256 _amountTokenOut) internal { uint256[] memory amountsOut = new uint256[](numTokens); amountsOut[tokenIndex] = _amountTokenOut; @@ -337,6 +338,7 @@ contract Strategy is BaseStrategy { balancerVault.exitPool(balancerPoolId, address(this), address(this), request); } + // join pool given exact token in function joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ uint256[] memory maxAmountsIn = new uint256[](_numTokens); maxAmountsIn[_tokenIndex] = _amountIn; @@ -366,6 +368,7 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; } + // revert if slippage out exceeds our requirement function _enforceSlippageOut(uint _intended, uint _actual) internal view { // enforce that amount exited didn't slip beyond our tolerance // just in case there's positive slippage @@ -374,10 +377,12 @@ contract Strategy is BaseStrategy { require(exitSlipped <= maxLoss, "Exceeded maxSlippageOut!"); } + // swap step contains information on multihop sells function getSwapSteps() public view returns (SwapSteps memory){ return swapSteps; } + // masterchef contract in case of masterchef migration function setMasterChef(address _masterChef) public onlyVaultManagers { bpt.approve(address(masterChef), 0); stakeBpt.approve(address(masterChef), 0); @@ -386,6 +391,16 @@ contract Strategy is BaseStrategy { stakeBpt.approve(address(masterChef), max); } + // calculate how much beets to unstake and stake + function consolidate() internal { + // pre-calc amount beets to stake + uint256 toStake = balanceOfReward().mul(stakePercentage).div(basisOne); + // unstake a % of staked beets + unstake(); + // stake pre-calc amount of beets for higher apy + stake(toStake); + } + // stake all beets function stakeAllRewards() internal { stake(balanceOfReward()); @@ -398,28 +413,24 @@ contract Strategy is BaseStrategy { } } - // unstake all beets-lp from masterchef, single sided withdraw all beets from beets-lp + // unstake a % beets-lp from masterchef, single sided withdraw beets from beets-lp function unstake() internal { - uint256 bpts = balanceOfStakeBptInMasterChef(); + uint256 bpts = balanceOfStakeBptInMasterChef().mul(unstakePercentage).div(basisOne); masterChef.withdrawAndHarvest(masterChefStakePoolId, bpts, address(this)); - - uint256[] memory minAmtsOut = new uint256[](stakeAssets.length); - if (bpts > 0) { - // exit all beets from beets pool. Don't care about slippage since it's rewards - bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, bpts, stakeTokenIndex); - IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(stakeAssets, minAmtsOut, userData, false); - balancerVault.exitPool(stakeBpt.getPoolId(), address(this), address(this), request); - } + exitPoolExactBpt(bpts, stakeAssets, stakeTokenIndex, stakeBpt.getPoolId(), new uint256[](stakeAssets.length)); } + // set params of where to stake the beets. Managers can change this to follow optimal yield function setStakeParams( uint256 _stakePercentageBips, + uint256 _unstakePercentageBips, IAsset[] memory _stakeAssets, address _stakePool, uint256 _stakeTokenIndex, uint256 _masterChefStakePoolId ) public onlyVaultManagers { stakePercentage = _stakePercentageBips; + unstakePercentage = _unstakePercentageBips; stakeAssets = _stakeAssets; masterChefStakePoolId = _masterChefStakePoolId; stakeBpt = IBalancerPool(_stakePool); diff --git a/tests/conftest.py b/tests/conftest.py index 8ca00bc..62d1907 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -156,7 +156,7 @@ def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, bee strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 50, 50, 100_000, 2 * 60 * 60, 10) strategy.setKeeper(keeper) strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) - strategy.setStakeParams(8000, [usdc, beets], beetsUsdcPool, 1, 0, {'from': gov}) + strategy.setStakeParams(8000, 7000, [usdc, beets], beetsUsdcPool, 1, 0, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) yield strategy From 0b5e6faf0025449535fef32cd3c103aa2232bafd Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Sat, 6 Nov 2021 23:21:54 -0700 Subject: [PATCH 08/54] @monoloco's comment fixes --- contracts/Strategy.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 151c841..5bccfb4 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -71,7 +71,7 @@ contract Strategy is BaseStrategy { uint256 _minDepositPeriod, uint256 _masterChefPoolId) public BaseStrategy(_vault){ - // healthCheck = address(0xDDCea799fF1699e98EDF118e0629A974Df7DF012); + healthCheck = address(0xf13Cd6887C62B5beC145e30c38c4938c5E627fe0); bpt = IBalancerPool(_balancerPool); balancerPoolId = bpt.getPoolId(); balancerVault = IBalancerVault(_balancerVault); @@ -203,6 +203,7 @@ contract Strategy is BaseStrategy { return liquidated; } + // note that this withdraws into newStrategy. function prepareMigration(address _newStrategy) internal override { masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(_newStrategy)); masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef(), address(_newStrategy)); @@ -259,8 +260,12 @@ contract Strategy is BaseStrategy { uint256 total = estimatedTotalAssets(); uint256 debt = vault.strategies(address(this)).totalDebt; if (total > debt) { + // withdraw all bpt out of masterchef + masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); uint256 profit = total.sub(debt); exitPoolExactToken(profit); + // put remaining bpt back into masterchef + masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); } } @@ -384,6 +389,8 @@ contract Strategy is BaseStrategy { // masterchef contract in case of masterchef migration function setMasterChef(address _masterChef) public onlyVaultManagers { + masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); + masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef(), address(this)); bpt.approve(address(masterChef), 0); stakeBpt.approve(address(masterChef), 0); masterChef = IBeethovenxMasterChef(_masterChef); From 393298e931408f37558a91725b9f0f3456e80391 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Sun, 7 Nov 2021 01:00:42 -0800 Subject: [PATCH 09/54] tests --- tests/test_migration.py | 30 ++++++++++++++++++++++++++++++ tests/test_operation.py | 7 +++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/test_migration.py b/tests/test_migration.py index 8f2ae09..bee6a45 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -28,3 +28,33 @@ def test_migration( 10) vault.migrateStrategy(strategy, new_strategy, {"from": gov}) assert (pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount) + + +def test_masterchef( + chain, + token, + vault, + strategy, + amount, + Strategy, + strategist, + gov, + user, + RELATIVE_APPROX, + balancer_vault, + pool, + masterChef +): + # Deposit to the vault and harvest + token.approve(vault.address, amount, {"from": user}) + vault.deposit(amount, {"from": user}) + chain.sleep(1) + strategy.harvest() + assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount + + chain.mine(3) + strategy.tend({"from": gov}) + + strategy.setMasterChef(masterChef, {"from": gov}) + assert strategy.balanceOfBptInMasterChef() == 0 + assert strategy.balanceOfStakeBptInMasterChef() == 0 diff --git a/tests/test_operation.py b/tests/test_operation.py index ba85e20..517b8b0 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -27,18 +27,22 @@ def test_operation( def test_emergency_exit( - chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX + chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, gov ): # Deposit to the vault token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) + chain.sleep(1) strategy.harvest({"from": strategist}) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount # set emergency and exit strategy.setEmergencyExit() + chain.mine(5) chain.sleep(1) + # some slippage won't pass healthcheck + strategy.setDoHealthCheck(False, {'from': gov}) strategy.harvest({"from": strategist}) assert strategy.estimatedTotalAssets() < amount @@ -206,4 +210,3 @@ def test_triggers( strategy.harvestTrigger(0) strategy.tendTrigger(0) - From 7fa563a0e81125b032370ac873d52bb54476ba4b Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Wed, 17 Nov 2021 22:44:25 -0800 Subject: [PATCH 10/54] add manual withdraw lp method --- contracts/Strategy.sol | 56 +++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 5bccfb4..328e28a 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -25,9 +25,10 @@ contract Strategy is BaseStrategy { IAsset[] internal assets; SwapSteps internal swapSteps; uint256[] internal minAmountsOut; - bytes32 public balancerPoolId; - uint8 public numTokens; - uint8 public tokenIndex; + bytes32 internal balancerPoolId; + uint8 internal numTokens; + uint8 internal tokenIndex; + bool internal abandonRewards; // masterchef IBeethovenxMasterChef internal masterChef; @@ -85,7 +86,7 @@ contract Strategy is BaseStrategy { } assets[i] = IAsset(address(tokens[i])); } - require(tokenIndex != type(uint8).max, "token not supported in pool!"); + require(tokenIndex != type(uint8).max, "token not in pool!"); maxSlippageIn = _maxSlippageIn; maxSlippageOut = _maxSlippageOut; @@ -94,7 +95,7 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; masterChefPoolId = _masterChefPoolId; masterChef = IBeethovenxMasterChef(_masterChef); - require(masterChef.lpTokens(masterChefPoolId) == address(bpt), "Wrong MasterChef Pool!"); + require(masterChef.lpTokens(masterChefPoolId) == address(bpt)); want.safeApprove(address(balancerVault), max); bpt.approve(address(masterChef), max); @@ -153,11 +154,10 @@ contract Strategy is BaseStrategy { uint256 pooledDelta = balanceOfPooled().sub(pooledBefore); uint256 joinSlipped = amountIn > pooledDelta ? amountIn.sub(pooledDelta) : 0; uint256 maxLoss = amountIn.mul(maxSlippageIn).div(basisOne); - require(joinSlipped <= maxLoss, "Exceeded maxSlippageIn!"); + require(joinSlipped <= maxLoss, "Slipped in!"); lastDepositTime = now; } - // claim all beets claimRewards(); // and stake all @@ -205,8 +205,7 @@ contract Strategy is BaseStrategy { // note that this withdraws into newStrategy. function prepareMigration(address _newStrategy) internal override { - masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(_newStrategy)); - masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef(), address(_newStrategy)); + _withdrawFromMasterChef(_newStrategy); uint256 rewards = balanceOfReward(); if (rewards > 0) { rewardToken.transfer(_newStrategy, rewards); @@ -223,6 +222,28 @@ contract Strategy is BaseStrategy { // HELPERS // + // Manually returns lps in masterchef to the strategy. Used in emergencies. + function emergencyWithdrawFromMasterChef() external onlyVaultManagers { + _withdrawFromMasterChef(address(this)); + } + + // AbandonRewards withdraws lp without rewards. Specify where to withdraw to + function _withdrawFromMasterChef(address _to) internal { + uint balanceOfBptInMasterChef = balanceOfBptInMasterChef(); + if (balanceOfBptInMasterChef > 0) { + abandonRewards + ? masterChef.emergencyWithdraw(masterChefPoolId, address(_to)) + : masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef, address(_to)); + } + + uint balanceOfStakeBptInMasterChef = balanceOfStakeBptInMasterChef(); + if (balanceOfStakeBptInMasterChef > 0) { + abandonRewards + ? masterChef.emergencyWithdraw(masterChefStakePoolId, address(_to)) + : masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef, address(_to)); + } + } + // claim all beets rewards from masterchef function claimRewards() internal { masterChef.harvest(masterChefPoolId, address(this)); @@ -363,10 +384,10 @@ contract Strategy is BaseStrategy { } function setParams(uint256 _maxSlippageIn, uint256 _maxSlippageOut, uint256 _maxSingleDeposit, uint256 _minDepositPeriod) public onlyVaultManagers { - require(_maxSlippageIn <= basisOne, "maxSlippageIn too high"); + require(_maxSlippageIn <= basisOne); maxSlippageIn = _maxSlippageIn; - require(_maxSlippageOut <= basisOne, "maxSlippageOut too high"); + require(_maxSlippageOut <= basisOne); maxSlippageOut = _maxSlippageOut; maxSingleDeposit = _maxSingleDeposit; @@ -379,7 +400,7 @@ contract Strategy is BaseStrategy { // just in case there's positive slippage uint256 exitSlipped = _intended > _actual ? _intended.sub(_actual) : 0; uint256 maxLoss = _intended.mul(maxSlippageOut).div(basisOne); - require(exitSlipped <= maxLoss, "Exceeded maxSlippageOut!"); + require(exitSlipped <= maxLoss, "Slipped Out!"); } // swap step contains information on multihop sells @@ -388,9 +409,9 @@ contract Strategy is BaseStrategy { } // masterchef contract in case of masterchef migration - function setMasterChef(address _masterChef) public onlyVaultManagers { - masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); - masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef(), address(this)); + function setMasterChef(address _masterChef) public onlyGovernance { + _withdrawFromMasterChef(address(this)); + bpt.approve(address(masterChef), 0); stakeBpt.approve(address(masterChef), 0); masterChef = IBeethovenxMasterChef(_masterChef); @@ -445,6 +466,11 @@ contract Strategy is BaseStrategy { stakeTokenIndex = _stakeTokenIndex; } + // toggle for whether to abandon rewards or not on emergency withdraws from masterchef + function setAbandonRewards(bool abandon) external onlyVaultManagers { + abandonRewards = abandon; + } + receive() external payable {} } From a584ecc70ff78635bf6fae70f0cfb290b6290f0e Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Wed, 17 Nov 2021 23:10:35 -0800 Subject: [PATCH 11/54] remove unused method --- contracts/Strategy.sol | 6 +----- tests/test_operation.py | 28 ++++++++++++++++++++++++++++ tests/test_shutdown.py | 3 ++- tests/util.py | 1 - 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 328e28a..d125852 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -33,7 +33,7 @@ contract Strategy is BaseStrategy { // masterchef IBeethovenxMasterChef internal masterChef; IAsset[] internal stakeAssets; - IBalancerPool internal stakeBpt; + IBalancerPool public stakeBpt; uint internal stakeTokenIndex; uint internal stakePercentage; uint internal unstakePercentage; @@ -310,10 +310,6 @@ contract Strategy is BaseStrategy { return rewardToken.balanceOf(address(this)); } - function balanceOfPendingReward() public view returns (uint256 _amount){ - return masterChef.pendingBeets(masterChefStakePoolId, address(this)).add(masterChef.pendingBeets(masterChefPoolId, address(this))); - } - function balanceOfPooled() public view returns (uint256 _amount){ uint256 totalWantPooled; (IERC20[] memory tokens, uint256[] memory totalBalances, uint256 lastChangeBlock) = balancerVault.getPoolTokens(balancerPoolId); diff --git a/tests/test_operation.py b/tests/test_operation.py index 517b8b0..930cd5f 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -47,6 +47,34 @@ def test_emergency_exit( assert strategy.estimatedTotalAssets() < amount +def test_manual_exit( + chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, gov +): + # Deposit to the vault + token.approve(vault.address, amount, {"from": user}) + vault.deposit(amount, {"from": user}) + + chain.sleep(1) + strategy.harvest({"from": strategist}) + assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount + + chain.mine(5) + chain.sleep(1) + # some slippage won't pass healthcheck + strategy.harvest({"from": strategist}) + + stakeBpt = Contract(strategy.stakeBpt()) + assert strategy.balanceOfBpt() == 0 + assert strategy.balanceOfBptInMasterChef() > 0 + assert stakeBpt.balanceOf(strategy) == 0 + assert strategy.balanceOfStakeBptInMasterChef() > 0 + strategy.emergencyWithdrawFromMasterChef({"from": gov}) + assert strategy.balanceOfBpt() > 0 + assert strategy.balanceOfBptInMasterChef() == 0 + assert stakeBpt.balanceOf(strategy) > 0 + assert strategy.balanceOfStakeBptInMasterChef() == 0 + + def test_profitable_harvest( chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, management ): diff --git a/tests/test_shutdown.py b/tests/test_shutdown.py index e7c9f21..fe89d44 100644 --- a/tests/test_shutdown.py +++ b/tests/test_shutdown.py @@ -29,7 +29,7 @@ def test_vault_shutdown_can_withdraw( def test_basic_shutdown( - chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX + chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, gov ): # Deposit to the vault token.approve(vault.address, amount, {"from": user}) @@ -53,6 +53,7 @@ def test_basic_shutdown( ## Set emergency strategy.setEmergencyExit({"from": strategist}) + strategy.setDoHealthCheck(False, {'from': gov}) strategy.harvest() ## Remove funds from strategy assert token.balanceOf(strategy) == 0 diff --git a/tests/util.py b/tests/util.py index 45f253c..7e93bf5 100644 --- a/tests/util.py +++ b/tests/util.py @@ -14,5 +14,4 @@ def stateOfStrat(msg, strategy, token): print(f'balanceOfBptInMasterChef: {strategy.balanceOfBptInMasterChef() / wantDec}') print(f'balanceOfStakeBptInMasterChef : {strategy.balanceOfStakeBptInMasterChef() / wantDec}') print(f'Balance of BEETS: {strategy.balanceOfReward()/ wantDec}') - print(f'Balance of pending BEETS: {strategy.balanceOfPendingReward()/ wantDec}') print(f'Estimated Total Assets: {strategy.estimatedTotalAssets() / wantDec}') From 698000215035fef686b45431a5704f995010c328 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Thu, 18 Nov 2021 18:45:21 -0800 Subject: [PATCH 12/54] split param setters --- contracts/Strategy.sol | 12 ++++++++---- tests/conftest.py | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index d125852..c9f8fa0 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -444,17 +444,21 @@ contract Strategy is BaseStrategy { exitPoolExactBpt(bpts, stakeAssets, stakeTokenIndex, stakeBpt.getPoolId(), new uint256[](stakeAssets.length)); } - // set params of where to stake the beets. Managers can change this to follow optimal yield + // set params for staking % function setStakeParams( uint256 _stakePercentageBips, - uint256 _unstakePercentageBips, + uint256 _unstakePercentageBips) public onlyVaultManagers { + stakePercentage = _stakePercentageBips; + unstakePercentage = _unstakePercentageBips; + } + + // set info of where to stake the beets. Managers can change this to follow optimal yield + function setStakeInfo( IAsset[] memory _stakeAssets, address _stakePool, uint256 _stakeTokenIndex, uint256 _masterChefStakePoolId ) public onlyVaultManagers { - stakePercentage = _stakePercentageBips; - unstakePercentage = _unstakePercentageBips; stakeAssets = _stakeAssets; masterChefStakePoolId = _masterChefStakePoolId; stakeBpt = IBalancerPool(_stakePool); diff --git a/tests/conftest.py b/tests/conftest.py index 62d1907..53ffd66 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -156,7 +156,8 @@ def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, bee strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 50, 50, 100_000, 2 * 60 * 60, 10) strategy.setKeeper(keeper) strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) - strategy.setStakeParams(8000, 7000, [usdc, beets], beetsUsdcPool, 1, 0, {'from': gov}) + strategy.setStakeInfo(8000, 7000, {'from': gov}) + strategy.setStakeInfo([usdc, beets], beetsUsdcPool, 1, 0, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) yield strategy From 2a2fc97c3a841b2bdcaf9977da75b1b89aaaa8b9 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Thu, 25 Nov 2021 23:45:42 -0800 Subject: [PATCH 13/54] name change --- contracts/Strategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index c9f8fa0..5f4ac3f 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -105,7 +105,7 @@ contract Strategy is BaseStrategy { function name() external view override returns (string memory) { // Add your own name here, suggestion e.g. "StrategyCreamYFI" - return string(abi.encodePacked("SingleSidedBalancer ", bpt.symbol(), "Pool ", ERC20(address(want)).symbol())); + return string(abi.encodePacked("SingleSidedBeethoven ", bpt.symbol(), "Pool ", ERC20(address(want)).symbol())); } function estimatedTotalAssets() public view override returns (uint256) { From a31b85f85e45b186f5281134048094ad318229ca Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Mon, 27 Dec 2021 00:12:37 -0800 Subject: [PATCH 14/54] update pid script --- scripts/pid.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/pid.py b/scripts/pid.py index 49e0849..119a3d1 100644 --- a/scripts/pid.py +++ b/scripts/pid.py @@ -1,6 +1,7 @@ from brownie import Contract import time + # 0 Symphony Nr 10. Opus 138. # 1 The Grand Orchestra # 2 Steady Beets @@ -13,10 +14,30 @@ # 9 The Fidelio Duetto # 10 MIM-USDC-USDT Stable Pool # 11 Guqin qi +# 12 The Magic Touch by Daniele +# 13 The Sound of Moosic +# 14 0x7cA132d9E8c420b84578a6618F10b23545513058 +# 15 Fantom Conservatory of Music +# 16 Guqin qi +# 17 A Late Quartet +# 18 A Song of Ice and Fire +# 19 Tubular Bells: Curved and Linked +# 20 When Two Became One (Hundred) +# 21 Baron von Binance +# 22 FreshBeets +# 23 0x63386eF152E1Ddef96c065636D6cC0165Ff33291 +# 24 0x41870439b607A29293D48f7c9da10e6714217624 +# 25 Daniele and Do's Double Dollar Fugue +# 26 Beethoven's Battle of the Bands +# 27 Variations on a theme by USD Circle +# 28 BeethovenxOhmEmissionToken def main(): masterChef = Contract("0x8166994d9ebBe5829EC86Bd81258149B87faCfd3") for i in range(masterChef.poolLength()): - pool = Contract(masterChef.lpTokens(i)) - print(f'{i} {pool.name()}') - time.sleep(3) \ No newline at end of file + try: + pool = Contract(masterChef.lpTokens(i)) + print(f'{i} {pool.name()}') + except ValueError: + print(f'{i} {masterChef.lpTokens(i)}') + From ef62f4668b679601b81ab6352d1ec926e74f61e1 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Tue, 18 Jan 2022 12:15:13 -0800 Subject: [PATCH 15/54] update pid --- scripts/pid.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/pid.py b/scripts/pid.py index 119a3d1..9332dde 100644 --- a/scripts/pid.py +++ b/scripts/pid.py @@ -1,7 +1,6 @@ from brownie import Contract import time - # 0 Symphony Nr 10. Opus 138. # 1 The Grand Orchestra # 2 Steady Beets @@ -31,6 +30,11 @@ # 26 Beethoven's Battle of the Bands # 27 Variations on a theme by USD Circle # 28 BeethovenxOhmEmissionToken +# 29 WAGMI Index +# 30 0xC7f084bCB91F779617B41602f85102849098D6a2 +# 31 Ode to Joy - Symphony Nr 9 +# 32 Dante Symphony +# 33 Steady Beets, Act II def main(): masterChef = Contract("0x8166994d9ebBe5829EC86Bd81258149B87faCfd3") From 1b63dfe9c9c9183eb235b5fc115fc37263db8391 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Tue, 18 Jan 2022 16:02:52 -0800 Subject: [PATCH 16/54] profit check script --- scripts/profit_check.py | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 scripts/profit_check.py diff --git a/scripts/profit_check.py b/scripts/profit_check.py new file mode 100644 index 0000000..8c7034b --- /dev/null +++ b/scripts/profit_check.py @@ -0,0 +1,43 @@ +from brownie import Contract + + +def main(): + # DAI + ssbeet_staBeetPool_dai = Contract("0xB905eabA7A23424265638bdACFFE55564c7B299B") + ssbeet_guqinQiPool_dai = Contract("0x85c307D24da7086c41537b994de9bFc4C21BAEB5") + + # USDC + ssbeet_staBeetPool_usdc = Contract("0x56aF79e182a7f98ff6d0bF99d589ac2CabA24e2d") + ssbeet_guqinQiPool_usdc = Contract("0xBd3791F3Dcf9DD5633cd30662381C80a2Cd945bd") + ssbeet_beetXlpMimUsdcUsdtPool_usdc = Contract("0x4003eE222d44953B0C3eB61318dD211a4A6f109f") + ssbeet_mimUsdcUstPool_usdc = Contract("0x1c13C43f8F2fa0CdDEE6DFF6F785757650B8c2BF") + ssbeet_asUsdcPool_usdc = Contract("0x8Bb79E595E1a21d160Ba3f7f6C94efF1484FB4c9") + + # MIM + ssbeet_beetXlpMimUsdcUsdtPool_mim = Contract("0xbBdc83357287a29Aae30cCa520D4ed6C750a2a11") + ssbeet_mimUsdcUstPool_mim = Contract("0xfD7E0cCc4dE0E3022F47834d7f0122274c37a0d1") + + # USDT + ssbeet_beetXlpMimUsdcUsdtPool_usdt = Contract("0x36E74086C388305CEcdeff83d6cf31a2762A3c91") + + ssbs = [ + ssbeet_staBeetPool_dai, + ssbeet_guqinQiPool_dai, + ssbeet_staBeetPool_usdc, + ssbeet_guqinQiPool_usdc, + ssbeet_beetXlpMimUsdcUsdtPool_usdc, + ssbeet_mimUsdcUstPool_usdc, + ssbeet_asUsdcPool_usdc, + ssbeet_mimUsdcUstPool_mim, + ssbeet_beetXlpMimUsdcUsdtPool_mim, + ssbeet_beetXlpMimUsdcUsdtPool_usdt + ] + + profits = 0; + for ssb in ssbs: + yv = Contract(ssb.vault()) + profit = yv.balanceOf(ssb) / (10 ** yv.decimals()) + print(f'{ssb.name()} has {profit}') + profits += profit + + print(f'estimated total: {profits}') From f6f209bf3be6b58be337e7ae4bf6f74de02fb7ab Mon Sep 17 00:00:00 2001 From: Tonkers Date: Fri, 28 Jan 2022 00:56:40 -0800 Subject: [PATCH 17/54] default network --- brownie-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brownie-config.yml b/brownie-config.yml index 376f6b9..221c378 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -1,7 +1,7 @@ # use Ganache's forked mainnet mode as the default network # NOTE: You don't *have* to do this, but it is often helpful for testing networks: - default: ftm-mainnet-fork + default: ftm-archive-fork # automatically fetch contract sources from Etherscan autofetch_sources: True From b1108706e993de741c1af77db17eb5616b6b1151 Mon Sep 17 00:00:00 2001 From: Tonkers Kuma Date: Sat, 29 Jan 2022 11:50:34 -0800 Subject: [PATCH 18/54] optimize debt script (wip) --- brownie-config.yml | 2 +- scripts/optimize_debt.py | 112 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 scripts/optimize_debt.py diff --git a/brownie-config.yml b/brownie-config.yml index 376f6b9..221c378 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -1,7 +1,7 @@ # use Ganache's forked mainnet mode as the default network # NOTE: You don't *have* to do this, but it is often helpful for testing networks: - default: ftm-mainnet-fork + default: ftm-archive-fork # automatically fetch contract sources from Etherscan autofetch_sources: True diff --git a/scripts/optimize_debt.py b/scripts/optimize_debt.py new file mode 100644 index 0000000..abd92f7 --- /dev/null +++ b/scripts/optimize_debt.py @@ -0,0 +1,112 @@ +import sympy +import numpy +from brownie import Contract +import requests + + +def main(): + # Steady Beets 2 + ssbeet_staBeetPool_dai = Contract("0xB905eabA7A23424265638bdACFFE55564c7B299B") + ssbeet_staBeetPool_usdc = Contract("0x56aF79e182a7f98ff6d0bF99d589ac2CabA24e2d") + + # Guqin Qi V2 + ssbeet_guqinQiPool_dai = Contract("0x85c307D24da7086c41537b994de9bFc4C21BAEB5") + ssbeet_guqinQiPool_usdc = Contract("0xBd3791F3Dcf9DD5633cd30662381C80a2Cd945bd") + + # Ziggy Stardust & Magic Internet Money + ssbeet_beetXlpMimUsdcUsdtPool_mim = Contract("0xbBdc83357287a29Aae30cCa520D4ed6C750a2a11") + ssbeet_beetXlpMimUsdcUsdtPool_usdc = Contract("0x4003eE222d44953B0C3eB61318dD211a4A6f109f") + ssbeet_beetXlpMimUsdcUsdtPool_usdt = Contract("0x36E74086C388305CEcdeff83d6cf31a2762A3c91") + + # Daniele and Do's Double Dollar Fugue + ssbeet_mimUsdcUstPool_usdc = Contract("0x1c13C43f8F2fa0CdDEE6DFF6F785757650B8c2BF") + ssbeet_mimUsdcUstPool_mim = Contract("0xfD7E0cCc4dE0E3022F47834d7f0122274c37a0d1") + + # Variations on a theme by USD Circle + ssbeet_asUsdcPool_usdc = Contract("0x8Bb79E595E1a21d160Ba3f7f6C94efF1484FB4c9") + + query = """query { + pools { + id + name + apr { + total + } + } + }""" + + # Call the public hosted TheGraph endpoint + url = 'https://backend.beets-ftm-node.com/graphql' + pools = requests.post(url, json={'query': query}).json()["data"]["pools"] + aprs = {} + for pool in pools: + aprs[pool["id"]] = float(pool["apr"]["total"]) + + print(f'== aprs ==') + print(aprs) + + strategies = [ + # ssbeet_staBeetPool_dai, + # ssbeet_guqinQiPool_dai, + ssbeet_staBeetPool_usdc, + ssbeet_guqinQiPool_usdc, + ssbeet_beetXlpMimUsdcUsdtPool_usdc, + ssbeet_mimUsdcUstPool_usdc, + ssbeet_asUsdcPool_usdc, + ] + print(f'== strategies ==') + print(strategies) + + tvls = [] + + for strategy in strategies: + if strategy.balanceOfBptInMasterChef() == 0: + continue + yv = Contract(strategy.vault()) + pool = Contract(strategy.bpt()) + tvl = yv.strategies(strategy)["totalDebt"] / strategy.balanceOfBptInMasterChef() * pool.totalSupply() + tvls.append(tvl) + print(f'== tvls ==') + print(tvls) + + length = len(tvls) + A = numpy.zeros((length, length)) + b = numpy.zeros((length, 1)) + + x = 0 + for i in range(0, length): + A[x, i] = 1 + + coeffs = [] + for i in range(1, len(tvls)): + tvl1 = tvls[i - 1] + strat1 = strategies[i - 1] + tvl2 = tvls[i] + strat2 = strategies[i] + print(Contract(strat1.bpt()).getPoolId()) + coeffs.append(coefficients(tvl1, aprs[str(Contract(strat1.bpt()).getPoolId())], tvl2, aprs[str(Contract(strat2.bpt()).getPoolId())])) + + x += 1 + for coeff in coeffs: + A[x, x - 1] = coeff[1] + A[x, x] = coeff[2] + b[x, 0] = coeff[0] + x += 1 + + print(f'== A ==') + print(A) + print(f'== b ==') + print(b) + x = numpy.linalg.lstsq(A, b) + print(f'== x ==') + print(x) + + +def coefficients(tvl1, apy1, tvl2, apy2): + a, b = sympy.symbols('a,b') + l = (tvl1 * apy1) * (tvl2 + b) + r = (tvl2 * apy2) * (tvl1 + a) + eq = sympy.simplify(l - r) + (constant, variables) = eq.as_coeff_Add() + + return (constant * -1, variables.coeff(a, 1), variables.coeff(b, 1)) From 96da6f465ecb1024de063975bfaa88a5b4114129 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 10:05:03 -0800 Subject: [PATCH 19/54] bpt fix and removing staking --- contracts/Strategy.sol | 197 ++++++++++++-------------------------- interfaces/BalancerV2.sol | 3 + tests/conftest.py | 20 ++-- tests/test_migration.py | 3 +- tests/test_operation.py | 25 +---- tests/util.py | 1 - 6 files changed, 80 insertions(+), 169 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 5f4ac3f..6c1ea35 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -25,18 +25,13 @@ contract Strategy is BaseStrategy { IAsset[] internal assets; SwapSteps internal swapSteps; uint256[] internal minAmountsOut; - bytes32 internal balancerPoolId; - uint8 internal numTokens; - uint8 internal tokenIndex; + bytes32 public balancerPoolId; + uint8 public numTokens; + uint8 public tokenIndex; bool internal abandonRewards; // masterchef - IBeethovenxMasterChef internal masterChef; - IAsset[] internal stakeAssets; - IBalancerPool public stakeBpt; - uint internal stakeTokenIndex; - uint internal stakePercentage; - uint internal unstakePercentage; + IBeethovenxMasterChef public masterChef; struct SwapSteps { bytes32[] poolIds; @@ -57,8 +52,7 @@ contract Strategy is BaseStrategy { uint256 public maxSingleDeposit; uint256 public minDepositPeriod; // seconds uint256 public lastDepositTime; - uint256 internal masterChefPoolId; - uint256 internal masterChefStakePoolId; + uint256 public masterChefPoolId; uint256 internal constant basisOne = 10000; constructor( @@ -105,7 +99,7 @@ contract Strategy is BaseStrategy { function name() external view override returns (string memory) { // Add your own name here, suggestion e.g. "StrategyCreamYFI" - return string(abi.encodePacked("SingleSidedBeethoven ", bpt.symbol(), "Pool ", ERC20(address(want)).symbol())); + return string(abi.encodePacked("ssbeet ", bpt.symbol(), "Pool ", ERC20(address(want)).symbol())); } function estimatedTotalAssets() public view override returns (uint256) { @@ -119,24 +113,26 @@ contract Strategy is BaseStrategy { uint256 beforeWant = balanceOfWant(); - collectTradingFees(); + _collectTradingFees(); // claim beets - claimRewards(); - // consolidate % to stake and unstake - consolidate(); + _claimRewards(); // sell the % not staking - sellRewards(); + _sellRewards(); uint256 afterWant = balanceOfWant(); - _profit = afterWant.sub(beforeWant); if (_profit > _loss) { _profit = _profit.sub(_loss); + _debtPayment += _loss; _loss = 0; } else { _loss = _loss.sub(_profit); + _debtPayment += _profit; _profit = 0; } + + // final check to make sure accounting is correct + require(_debtOutstanding == _debtPayment.add(_loss)); } function adjustPosition(uint256 _debtOutstanding) internal override { @@ -158,10 +154,6 @@ contract Strategy is BaseStrategy { lastDepositTime = now; } - // claim all beets - claimRewards(); - // and stake all - stakeAllRewards(); } function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss){ @@ -177,7 +169,7 @@ contract Strategy is BaseStrategy { // withdraw all bpt out of masterchef masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); // sell some bpt - exitPoolExactToken(toExitAmount); + _sellBpt(tokensToBpts(toExitAmount), assets, tokenIndex, balancerPoolId, minAmountsOut, balanceOfBpt()); // put remaining bpt back into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); @@ -191,21 +183,21 @@ contract Strategy is BaseStrategy { } function liquidateAllPositions() internal override returns (uint256 liquidated) { - uint eta = estimatedTotalAssets(); - // withdraw all bpt out of masterchef + uint totalDebt = vault.strategies(address(this)).totalDebt; + masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); // sell all bpt - exitPoolExactBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, minAmountsOut); + _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, minAmountsOut, balanceOfBpt()); liquidated = balanceOfWant(); - _enforceSlippageOut(eta, liquidated); + _enforceSlippageOut(totalDebt, liquidated); return liquidated; } // note that this withdraws into newStrategy. function prepareMigration(address _newStrategy) internal override { - _withdrawFromMasterChef(_newStrategy); + _withdrawFromMasterChef(_newStrategy, balanceOfBptInMasterChef(), masterChefPoolId); uint256 rewards = balanceOfReward(); if (rewards > 0) { rewardToken.transfer(_newStrategy, rewards); @@ -222,35 +214,25 @@ contract Strategy is BaseStrategy { // HELPERS // - // Manually returns lps in masterchef to the strategy. Used in emergencies. - function emergencyWithdrawFromMasterChef() external onlyVaultManagers { - _withdrawFromMasterChef(address(this)); + function withdrawFromMasterChef(uint256 _amount, uint256 _masterChefPooId) external onlyVaultManagers { + _withdrawFromMasterChef(address(this), _amount, _masterChefPooId); } // AbandonRewards withdraws lp without rewards. Specify where to withdraw to - function _withdrawFromMasterChef(address _to) internal { - uint balanceOfBptInMasterChef = balanceOfBptInMasterChef(); - if (balanceOfBptInMasterChef > 0) { - abandonRewards - ? masterChef.emergencyWithdraw(masterChefPoolId, address(_to)) - : masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef, address(_to)); - } - - uint balanceOfStakeBptInMasterChef = balanceOfStakeBptInMasterChef(); - if (balanceOfStakeBptInMasterChef > 0) { + function _withdrawFromMasterChef(address _to, uint256 _amount, uint256 _masterChefPoolId) internal { + if (_amount > 0) { abandonRewards - ? masterChef.emergencyWithdraw(masterChefStakePoolId, address(_to)) - : masterChef.withdrawAndHarvest(masterChefStakePoolId, balanceOfStakeBptInMasterChef, address(_to)); + ? masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)) + : masterChef.withdrawAndHarvest(_masterChefPoolId, _amount, address(_to)); } } // claim all beets rewards from masterchef - function claimRewards() internal { + function _claimRewards() internal { masterChef.harvest(masterChefPoolId, address(this)); - masterChef.harvest(masterChefStakePoolId, address(this)); } - function sellRewards() internal { + function _sellRewards() internal { uint256 amount = balanceOfReward(); uint decReward = ERC20(address(rewardToken)).decimals(); uint decWant = ERC20(address(want)).decimals(); @@ -277,14 +259,14 @@ contract Strategy is BaseStrategy { } } - function collectTradingFees() internal { + function _collectTradingFees() internal { uint256 total = estimatedTotalAssets(); uint256 debt = vault.strategies(address(this)).totalDebt; if (total > debt) { // withdraw all bpt out of masterchef masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); uint256 profit = total.sub(debt); - exitPoolExactToken(profit); + _sellBpt(tokensToBpts(profit), assets, tokenIndex, balancerPoolId, minAmountsOut, balanceOfBpt()); // put remaining bpt back into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); } @@ -302,30 +284,33 @@ contract Strategy is BaseStrategy { (_amount,) = masterChef.userInfo(masterChefPoolId, address(this)); } - function balanceOfStakeBptInMasterChef() public view returns (uint256 _amount){ - (_amount,) = masterChef.userInfo(masterChefStakePoolId, address(this)); - } - function balanceOfReward() public view returns (uint256 _amount){ return rewardToken.balanceOf(address(this)); } function balanceOfPooled() public view returns (uint256 _amount){ - uint256 totalWantPooled; - (IERC20[] memory tokens, uint256[] memory totalBalances, uint256 lastChangeBlock) = balancerVault.getPoolTokens(balancerPoolId); - for (uint8 i = 0; i < numTokens; i++) { - uint256 tokenPooled = totalBalances[i].mul(balanceOfBpt().add(balanceOfBptInMasterChef())).div(bpt.totalSupply()); - if (tokenPooled > 0) { - IERC20 token = tokens[i]; - if (token != want) { - IBalancerPool.SwapRequest memory request = _getSwapRequest(token, tokenPooled, lastChangeBlock); - // now denomated in want - tokenPooled = bpt.onSwap(request, totalBalances, i, tokenIndex); - } - totalWantPooled += tokenPooled; - } - } - return totalWantPooled; + return bptsToTokens(balanceOfBpt().add(balanceOfBptInMasterChef())); + } + + function pendingRewards() public view returns (uint256 _amount){ + return masterChef.pendingBeets(masterChefPoolId, address(this)); + } + + /// use bpt rate to estimate equivalent amount of want. + function bptsToTokens(uint _amountBpt) public view returns (uint _amount){ + uint unscaled = _amountBpt * bpt.getRate() / 1e18; + return _scaleDecimals(unscaled, ERC20(address(bpt)), ERC20(address(want))); + } + + function tokensToBpts(uint _amountTokens) public view returns (uint _amount){ + uint unscaled = _amountTokens * 1e18 / bpt.getRate(); + return _scaleDecimals(unscaled, ERC20(address(want)), ERC20(address(bpt))); + } + + function _scaleDecimals(uint _amount, ERC20 _fromToken, ERC20 _toToken) internal view returns (uint _scaled){ + uint decFrom = _fromToken.decimals(); + uint decTo = _toToken.decimals(); + return decTo > decFrom ? _amount * 10 ** (decTo.sub(decFrom)) : _amount / 10 ** (decFrom.sub(decTo)); } function _getSwapRequest(IERC20 token, uint256 amount, uint256 lastChangeBlock) internal view returns (IBalancerPool.SwapRequest memory request){ @@ -341,25 +326,20 @@ contract Strategy is BaseStrategy { ); } - // exit a pool given exact bpt amount - function exitPoolExactBpt(uint256 _bpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256[] memory _minAmountsOut) internal { - if (_bpts > 0) { - // exit entire position for single token. Could revert due to single exit limit enforced by balancer - bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, _bpts, _tokenIndex); + // this allows us to also sell stakedBpts externally + function sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256[] memory _minAmountsOut, uint256 _maxBpts) external onlyVaultManagers { + _sellBpt(_amountBpts, _assets, _tokenIndex, _balancerPoolId, _minAmountsOut, _maxBpts); + } + + // sell bpt for want at current bpt rate + function _sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256[] memory _minAmountsOut, uint256 _maxBpts) internal { + if (_amountBpts > 0) { + bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, Math.min(_amountBpts, _maxBpts), _tokenIndex); IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(_assets, _minAmountsOut, userData, false); balancerVault.exitPool(_balancerPoolId, address(this), address(this), request); } } - // exit a pool given exact token amount - function exitPoolExactToken(uint256 _amountTokenOut) internal { - uint256[] memory amountsOut = new uint256[](numTokens); - amountsOut[tokenIndex] = _amountTokenOut; - bytes memory userData = abi.encode(IBalancerVault.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, balanceOfBpt()); - IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(assets, minAmountsOut, userData, false); - balancerVault.exitPool(balancerPoolId, address(this), address(this), request); - } - // join pool given exact token in function joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ uint256[] memory maxAmountsIn = new uint256[](_numTokens); @@ -406,64 +386,11 @@ contract Strategy is BaseStrategy { // masterchef contract in case of masterchef migration function setMasterChef(address _masterChef) public onlyGovernance { - _withdrawFromMasterChef(address(this)); + _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); bpt.approve(address(masterChef), 0); - stakeBpt.approve(address(masterChef), 0); masterChef = IBeethovenxMasterChef(_masterChef); bpt.approve(address(masterChef), max); - stakeBpt.approve(address(masterChef), max); - } - - // calculate how much beets to unstake and stake - function consolidate() internal { - // pre-calc amount beets to stake - uint256 toStake = balanceOfReward().mul(stakePercentage).div(basisOne); - // unstake a % of staked beets - unstake(); - // stake pre-calc amount of beets for higher apy - stake(toStake); - } - - // stake all beets - function stakeAllRewards() internal { - stake(balanceOfReward()); - } - - // stake beets into beets-lp, then beets-lp into masterchef - function stake(uint256 _amount) internal { - if (joinPool(_amount, stakeAssets, stakeAssets.length, stakeTokenIndex, stakeBpt.getPoolId())) { - masterChef.deposit(masterChefStakePoolId, stakeBpt.balanceOf(address(this)), address(this)); - } - } - - // unstake a % beets-lp from masterchef, single sided withdraw beets from beets-lp - function unstake() internal { - uint256 bpts = balanceOfStakeBptInMasterChef().mul(unstakePercentage).div(basisOne); - masterChef.withdrawAndHarvest(masterChefStakePoolId, bpts, address(this)); - exitPoolExactBpt(bpts, stakeAssets, stakeTokenIndex, stakeBpt.getPoolId(), new uint256[](stakeAssets.length)); - } - - // set params for staking % - function setStakeParams( - uint256 _stakePercentageBips, - uint256 _unstakePercentageBips) public onlyVaultManagers { - stakePercentage = _stakePercentageBips; - unstakePercentage = _unstakePercentageBips; - } - - // set info of where to stake the beets. Managers can change this to follow optimal yield - function setStakeInfo( - IAsset[] memory _stakeAssets, - address _stakePool, - uint256 _stakeTokenIndex, - uint256 _masterChefStakePoolId - ) public onlyVaultManagers { - stakeAssets = _stakeAssets; - masterChefStakePoolId = _masterChefStakePoolId; - stakeBpt = IBalancerPool(_stakePool); - stakeBpt.approve(address(masterChef), max); - stakeTokenIndex = _stakeTokenIndex; } // toggle for whether to abandon rewards or not on emergency withdraws from masterchef diff --git a/interfaces/BalancerV2.sol b/interfaces/BalancerV2.sol index 768f104..8de99e0 100644 --- a/interfaces/BalancerV2.sol +++ b/interfaces/BalancerV2.sol @@ -20,6 +20,9 @@ interface IBalancerPool is IERC20 { bytes userData; } + // virtual price of bpt + function getRate() external view returns (uint); + function getPoolId() external view returns (bytes32 poolId); function symbol() external view returns (string memory s); diff --git a/tests/conftest.py b/tests/conftest.py index 53ffd66..1c4c6d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import pytest from brownie import config -from brownie import Contract +from brownie import Contract, chain @pytest.fixture(autouse=True) @@ -48,7 +48,8 @@ def token(): # 0x6B175474E89094C44Da98b954EedeAC495271d0F DAI # 0x049d68029688eAbF473097a2fC38ef61633A3C7A fUSDT # 0x04068DA6C83AFCFA0e13ba15A6696662335D5B75 USDC - token_address = "0x82f0B8B456c1A451378467398982d4834b6829c1" + # 0x82f0B8B456c1A451378467398982d4834b6829c1 MIM + token_address = "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75" yield Contract(token_address) @@ -117,7 +118,8 @@ def balancer_vault(): @pytest.fixture def pool(): # 0xD163415BD34EF06f57C58D2AEd5A5478AfB464cC MIM-USDC-USDT Stable Pool - address = "0xD163415BD34EF06f57C58D2AEd5A5478AfB464cC" + # 0xeCAa1cBd28459d34B766F9195413Cb20122Fb942 dai-usdc stable pool + address = "0xeCAa1cBd28459d34B766F9195413Cb20122Fb942" yield Contract(address) @@ -140,25 +142,25 @@ def beetsUsdcPoolId(): @pytest.fixture def usdcTokenPoolId(): - id = 0xd163415bd34ef06f57c58d2aed5a5478afb464cc00000000000000000000000e # usdc-mim + id = 0xecaa1cbd28459d34b766f9195413cb20122fb942000200000000000000000120 # usdc-mim yield id @pytest.fixture -def swapStepsBeets(beetsUsdcPoolId, usdcTokenPoolId, beets, usdc, token): - yield ([beetsUsdcPoolId, usdcTokenPoolId], [beets, usdc, token]) +def swapStepsBeets(beetsUsdcPoolId, beets, token): + yield ([beetsUsdcPoolId], [beets, token]) @pytest.fixture def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, management, masterChef, swapStepsBeets): - strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 50, 50, 100_000, 2 * 60 * 60, 10) + strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 50, 50, 100_000, 2 * 60 * 60, 33) strategy.setKeeper(keeper) strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) - strategy.setStakeInfo(8000, 7000, {'from': gov}) - strategy.setStakeInfo([usdc, beets], beetsUsdcPool, 1, 0, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) + vault.setManagementFee(0, {"from": gov}) + chain.sleep(1) yield strategy diff --git a/tests/test_migration.py b/tests/test_migration.py index bee6a45..1322373 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -25,7 +25,7 @@ def test_migration( # migrate to a new strategy new_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 10, 10, 100_000, 2 * 60 * 60, - 10) + 33) vault.migrateStrategy(strategy, new_strategy, {"from": gov}) assert (pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount) @@ -57,4 +57,3 @@ def test_masterchef( strategy.setMasterChef(masterChef, {"from": gov}) assert strategy.balanceOfBptInMasterChef() == 0 - assert strategy.balanceOfStakeBptInMasterChef() == 0 diff --git a/tests/test_operation.py b/tests/test_operation.py index 930cd5f..6cf66fb 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -63,17 +63,9 @@ def test_manual_exit( # some slippage won't pass healthcheck strategy.harvest({"from": strategist}) - stakeBpt = Contract(strategy.stakeBpt()) - assert strategy.balanceOfBpt() == 0 - assert strategy.balanceOfBptInMasterChef() > 0 - assert stakeBpt.balanceOf(strategy) == 0 - assert strategy.balanceOfStakeBptInMasterChef() > 0 - strategy.emergencyWithdrawFromMasterChef({"from": gov}) + strategy.withdrawFromMasterChef(strategy.balanceOfBptInMasterChef(), strategy.masterChefPoolId(), {"from": gov}) assert strategy.balanceOfBpt() > 0 assert strategy.balanceOfBptInMasterChef() == 0 - assert stakeBpt.balanceOf(strategy) > 0 - assert strategy.balanceOfStakeBptInMasterChef() == 0 - def test_profitable_harvest( chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, management @@ -97,10 +89,6 @@ def test_profitable_harvest( strategy.harvest({"from": strategist}) chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock chain.mine(1) - # do a second harvest so the first harvest's stake is sold - strategy.harvest({"from": strategist}) - chain.sleep(3600 * 6) - chain.mine(1) profit = token.balanceOf(vault.address) @@ -139,12 +127,8 @@ def test_deposit_all(chain, token, vault, strategy, user, strategist, amount, RE chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock chain.mine(1) util.stateOfStrat("first harvest", strategy, beets) - # do a second harvest so the first harvest's stake is sold - strategy.harvest({"from": strategist}) - chain.sleep(3600 * 6) - chain.mine(1) + profit = token.balanceOf(vault.address) # Profits go to vault - util.stateOfStrat("second harvest", strategy, beets) slippageIn = amount * strategy.maxSlippageIn() / 10000 assert strategy.estimatedTotalAssets() + profit > (amount - slippageIn) @@ -186,10 +170,7 @@ def test_change_debt( strategy.harvest({"from": strategist}) chain.sleep(3600 * 6) chain.mine(1) - # do a second harvest so the first harvest's stake is sold - strategy.harvest({"from": strategist}) - chain.sleep(3600 * 6) - chain.mine(1) + util.stateOfStrat("after harvest 5000", strategy, token) # compounded slippage diff --git a/tests/util.py b/tests/util.py index 7e93bf5..3c1a331 100644 --- a/tests/util.py +++ b/tests/util.py @@ -12,6 +12,5 @@ def stateOfStrat(msg, strategy, token): print(f'Balance of {token.symbol()}: {strategy.balanceOfWant() / wantDec}') print(f'Balance of Bpt: {strategy.balanceOfBpt() / wantDec}') print(f'balanceOfBptInMasterChef: {strategy.balanceOfBptInMasterChef() / wantDec}') - print(f'balanceOfStakeBptInMasterChef : {strategy.balanceOfStakeBptInMasterChef() / wantDec}') print(f'Balance of BEETS: {strategy.balanceOfReward()/ wantDec}') print(f'Estimated Total Assets: {strategy.estimatedTotalAssets() / wantDec}') From d200373014965b1321534494771dcfef7341c7d5 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 11:53:08 -0800 Subject: [PATCH 20/54] add tests for migration and unbalanced pool --- tests/conftest.py | 26 +++++++++++++++- tests/test_migration.py | 47 ++++++++++++++++++++++++++-- tests/test_operation.py | 68 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1c4c6d2..298923a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,14 +52,31 @@ def token(): token_address = "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75" yield Contract(token_address) +@pytest.fixture +def token2(): + # 0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E DAI + # 0x049d68029688eAbF473097a2fC38ef61633A3C7A fUSDT + # 0x04068DA6C83AFCFA0e13ba15A6696662335D5B75 USDC + # 0x82f0B8B456c1A451378467398982d4834b6829c1 MIM + token_address = "0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E" + yield Contract(token_address) @pytest.fixture def token_whale(accounts): # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 fUSDT # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 USDC + # 0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75 DAI return accounts.at("0x2dd7C9371965472E5A5fD28fbE165007c61439E1", force=True) +@pytest.fixture +def token2_whale(accounts): + # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM + # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 fUSDT + # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 USDC + # 0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75 DAI + return accounts.at("0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75", force=True) + @pytest.fixture def amount(accounts, token, user, token_whale): @@ -68,6 +85,13 @@ def amount(accounts, token, user, token_whale): token.transfer(user, amount, {"from": token_whale}) yield amount +@pytest.fixture +def amount2(accounts, token2, user, token2_whale): + amount = 1_000_000 * 10 ** token.decimals() + # In order to get some funds for the token you are about to use, + token.transfer(user, amount, {"from": token_whale}) + yield amount + @pytest.fixture def wftm(): @@ -155,7 +179,7 @@ def swapStepsBeets(beetsUsdcPoolId, beets, token): def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, management, masterChef, swapStepsBeets): - strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 50, 50, 100_000, 2 * 60 * 60, 33) + strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 5, 5, 100_000, 2 * 60 * 60, 33) strategy.setKeeper(keeper) strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) diff --git a/tests/test_migration.py b/tests/test_migration.py index 1322373..23b5716 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -1,5 +1,6 @@ import pytest - +from brownie import accounts, Contract +import util def test_migration( chain, @@ -24,7 +25,7 @@ def test_migration( assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount # migrate to a new strategy - new_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 10, 10, 100_000, 2 * 60 * 60, + new_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 5, 5, 100_000, 2 * 60 * 60, 33) vault.migrateStrategy(strategy, new_strategy, {"from": gov}) assert (pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount) @@ -57,3 +58,45 @@ def test_masterchef( strategy.setMasterChef(masterChef, {"from": gov}) assert strategy.balanceOfBptInMasterChef() == 0 + + +def test_real_migration( + chain, + token, + vault, + strategy, + amount, + Strategy, + strategist, + gov, + user, + masterChef, + RELATIVE_APPROX, + balancer_vault, pool, management, swapStepsBeets, beets +): + old = Contract("0x56aF79e182a7f98ff6d0bF99d589ac2CabA24e2d") + vault = Contract(old.vault()) + gov = accounts.at(vault.governance(), force=True) + fromGov = {'from': gov} + + util.stateOfStrat("old strategy before migration", strategy, token) + + fixed_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef,5, 5, 100_000, 2 * 60 * 60, 33) + fixed_strategy.whitelistReward(beets, swapStepsBeets, fromGov) + + vault.migrateStrategy(old, fixed_strategy, fromGov) + util.stateOfStrat("old strategy after migration", strategy, token) + util.stateOfStrat("new strategy after migration", fixed_strategy, token) + + total_debt = vault.strategies(fixed_strategy)["totalDebt"] + assert fixed_strategy.estimatedTotalAssets() >= total_debt + + # exit everything out and see how much we get + fixed_strategy.setEmergencyExit(fromGov) + fixed_strategy.harvest(fromGov) + + util.stateOfStrat("debt ratio 0", fixed_strategy, token) + + # hopefully the gains from trading fees cancels out slippage + print(f'net loss from exit: {vault.strategies(fixed_strategy)["totalLoss"]}') + assert fixed_strategy.estimatedTotalAssets() == 0 diff --git a/tests/test_operation.py b/tests/test_operation.py index 6cf66fb..5dca066 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -67,6 +67,7 @@ def test_manual_exit( assert strategy.balanceOfBpt() > 0 assert strategy.balanceOfBptInMasterChef() == 0 + def test_profitable_harvest( chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, management ): @@ -219,3 +220,70 @@ def test_triggers( strategy.harvestTrigger(0) strategy.tendTrigger(0) + + +def test_unbalanced_pool_withdraw(chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, + token2_whale, token2, + gov, pool, balancer_vault): + # Deposit to the vault + token.approve(vault.address, amount, {"from": user}) + vault.deposit(amount, {"from": user}) + assert token.balanceOf(vault.address) == amount + + # Harvest 1: Send funds through the strategy + chain.sleep(1) + print(f'pool rate: {pool.getRate()}') + + strategy.harvest({"from": strategist}) + assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount + + chain.sleep(strategy.minDepositPeriod() + 1) + chain.mine(1) + + # iterate to get all the funds in + while strategy.tendTrigger(0) == True: + strategy.tend({'from': gov}) + util.stateOfStrat("tend", strategy, token) + assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount + chain.sleep(strategy.minDepositPeriod() + 1) + chain.mine(1) + + print(f'pool rate: {pool.getRate()}') + tokens = balancer_vault.getPoolTokens(pool.getPoolId())[0] + token2Index = 0 + if (tokens[0] == token2): + token2Index = 0 + elif tokens[1] == token2: + token2Index = 1 + + util.stateOfStrat("after deposit all ", strategy, token) + pooled = balancer_vault.getPoolTokens(pool.getPoolId())[1][strategy.tokenIndex()] + print(balancer_vault.getPoolTokens(pool.getPoolId())) + print(f'pooled: {pooled}') + token2.approve(balancer_vault, 2 ** 256 - 1, {'from': token2_whale}) + + # simulate bad pool state by whale to swap out 98% of one side of the pool so pool only has 2% of the original want + singleSwap = (pool.getPoolId(), 1, token2, token, pooled * 0.98, b'0x0') + balancer_vault.swap(singleSwap, (token2_whale, False, token2_whale, False), token2.balanceOf(token2_whale), + chain.time(), {'from': token2_whale}) + print(balancer_vault.getPoolTokens(pool.getPoolId())) + print(f'pool rate: {pool.getRate()}') + + # now pool is in a bad state low-want + print(f'pool state: {balancer_vault.getPoolTokens(pool.getPoolId())}') + + # withdraw half to see how much we get back, it should be lossy. Assert that our slippage check prevents this + with brownie.reverts(): + vault.withdraw(vault.balanceOf(user) / 2, user, 10000, {"from": user}) + old_slippage = strategy.maxSlippageOut() + + # loosen the slippage check to let the lossy withdraw go through + strategy.setParams(10000, 10000, strategy.maxSingleDeposit(), strategy.minDepositPeriod(), {'from': gov}) + vault.withdraw(vault.balanceOf(user) / 2, user, 10000, {"from": user}) + print(f'pool state: {balancer_vault.getPoolTokens(pool.getPoolId())}') + print(f'user balance: {token.balanceOf(user)}') + print(f'user lost: {amount / 2 - token.balanceOf(user)}') + util.stateOfStrat("after lossy withdraw", strategy, token) + + # make sure principal is still as expected, aka loss wasn't socialized + assert strategy.estimatedTotalAssets() >= amount / 2 * (10000 - old_slippage) / 10000 From 7008c67b29608aaca6c96eb383a5a56b61080435 Mon Sep 17 00:00:00 2001 From: wavey Date: Wed, 2 Feb 2022 14:42:38 -0500 Subject: [PATCH 21/54] feat: simplify join pool logic and add sweepETH function --- contracts/Strategy.sol | 21 ++++++++++----------- tests/test_operation.py | 9 +++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 6c1ea35..bc524e0 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -98,8 +98,7 @@ contract Strategy is BaseStrategy { // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ function name() external view override returns (string memory) { - // Add your own name here, suggestion e.g. "StrategyCreamYFI" - return string(abi.encodePacked("ssbeet ", bpt.symbol(), "Pool ", ERC20(address(want)).symbol())); + return string(abi.encodePacked("SSBEETv2 ", ERC20(address(want)).symbol(), " ", bpt.symbol())); } function estimatedTotalAssets() public view override returns (uint256) { @@ -146,14 +145,6 @@ contract Strategy is BaseStrategy { if (joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { // put all want-lp into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); - - uint256 pooledDelta = balanceOfPooled().sub(pooledBefore); - uint256 joinSlipped = amountIn > pooledDelta ? amountIn.sub(pooledDelta) : 0; - uint256 maxLoss = amountIn.mul(maxSlippageIn).div(basisOne); - require(joinSlipped <= maxLoss, "Slipped in!"); - lastDepositTime = now; - } - } function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss){ @@ -288,6 +279,7 @@ contract Strategy is BaseStrategy { return rewardToken.balanceOf(address(this)); } + // returns an estimate of want tokens based on bpt balance function balanceOfPooled() public view returns (uint256 _amount){ return bptsToTokens(balanceOfBpt().add(balanceOfBptInMasterChef())); } @@ -342,12 +334,14 @@ contract Strategy is BaseStrategy { // join pool given exact token in function joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ + uint256 expectedBptOut = tokensToBpts(amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); uint256[] memory maxAmountsIn = new uint256[](_numTokens); maxAmountsIn[_tokenIndex] = _amountIn; if (_amountIn > 0) { - bytes memory userData = abi.encode(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, 0); + bytes memory userData = abi.encode(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, expectedBptOut); IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest(_assets, maxAmountsIn, userData, false); balancerVault.joinPool(_poolId, address(this), address(this), request); + lastDepositTime = now; return true; } return false; @@ -396,6 +390,11 @@ contract Strategy is BaseStrategy { // toggle for whether to abandon rewards or not on emergency withdraws from masterchef function setAbandonRewards(bool abandon) external onlyVaultManagers { abandonRewards = abandon; + + // Balancer requires this contract to be payable, so we add ability to sweep stuck ETH + function sweepETH() public onlyGovernance { + (bool success, ) = governance().call{value: address(this).balance}(""); + require(success,"!FailedETHSweep"); } receive() external payable {} diff --git a/tests/test_operation.py b/tests/test_operation.py index 5dca066..3edea13 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -8,6 +8,7 @@ def test_operation( chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX ): # Deposit to the vault + print("Strategy Name:", strategy.name()) user_balance_before = token.balanceOf(user) token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) @@ -206,6 +207,14 @@ def test_sweep(gov, vault, strategy, token, user, amount, wftm, wftm_amount): strategy.sweep(wftm, {"from": gov}) assert wftm.balanceOf(gov) == wftm_amount + before_balance +def test_eth_sweep(chain, token, vault, strategy, user, strategist, gov): + strategist.transfer(strategy,1e18) + with brownie.reverts(): + strategy.sweepETH({"from": strategist}) + + eth_balance = gov.balance() + strategy.sweepETH({"from": gov}) + assert gov.balance() > eth_balance def test_triggers( chain, gov, vault, strategy, token, amount, user, wftm, wftm_amount, strategist, beets, beets_whale, From be45194e188178f2ee4d823f326231c88ffb7627 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 13:18:44 -0800 Subject: [PATCH 22/54] move enforceSlippageOut to native --- contracts/Strategy.sol | 48 +++++++++++++++-------------------------- tests/test_operation.py | 3 +-- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index bc524e0..44637a2 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -24,7 +24,6 @@ contract Strategy is BaseStrategy { IERC20 public rewardToken; IAsset[] internal assets; SwapSteps internal swapSteps; - uint256[] internal minAmountsOut; bytes32 public balancerPoolId; uint8 public numTokens; uint8 public tokenIndex; @@ -85,7 +84,6 @@ contract Strategy is BaseStrategy { maxSlippageIn = _maxSlippageIn; maxSlippageOut = _maxSlippageOut; maxSingleDeposit = _maxSingleDeposit.mul(10 ** uint256(ERC20(address(want)).decimals())); - minAmountsOut = new uint256[](numTokens); minDepositPeriod = _minDepositPeriod; masterChefPoolId = _masterChefPoolId; masterChef = IBeethovenxMasterChef(_masterChef); @@ -145,14 +143,10 @@ contract Strategy is BaseStrategy { if (joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { // put all want-lp into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); + } } function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss){ - if (estimatedTotalAssets() < _amountNeeded) { - _liquidatedAmount = liquidateAllPositions(); - return (_liquidatedAmount, _amountNeeded.sub(_liquidatedAmount)); - } - uint256 looseAmount = balanceOfWant(); if (_amountNeeded > looseAmount) { uint256 toExitAmount = _amountNeeded.sub(looseAmount); @@ -160,14 +154,12 @@ contract Strategy is BaseStrategy { // withdraw all bpt out of masterchef masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); // sell some bpt - _sellBpt(tokensToBpts(toExitAmount), assets, tokenIndex, balancerPoolId, minAmountsOut, balanceOfBpt()); + _sellBpt(tokensToBpts(toExitAmount), assets, tokenIndex, balancerPoolId, balanceOfBpt()); // put remaining bpt back into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); _liquidatedAmount = Math.min(balanceOfWant(), _amountNeeded); _loss = _amountNeeded.sub(_liquidatedAmount); - - _enforceSlippageOut(toExitAmount, _liquidatedAmount.sub(looseAmount)); } else { _liquidatedAmount = _amountNeeded; } @@ -178,11 +170,9 @@ contract Strategy is BaseStrategy { masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); // sell all bpt - _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, minAmountsOut, balanceOfBpt()); + _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, balanceOfBpt()); liquidated = balanceOfWant(); - _enforceSlippageOut(totalDebt, liquidated); - return liquidated; } @@ -257,7 +247,7 @@ contract Strategy is BaseStrategy { // withdraw all bpt out of masterchef masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); uint256 profit = total.sub(debt); - _sellBpt(tokensToBpts(profit), assets, tokenIndex, balancerPoolId, minAmountsOut, balanceOfBpt()); + _sellBpt(tokensToBpts(profit), assets, tokenIndex, balancerPoolId, balanceOfBpt()); // put remaining bpt back into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); } @@ -319,22 +309,25 @@ contract Strategy is BaseStrategy { } // this allows us to also sell stakedBpts externally - function sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256[] memory _minAmountsOut, uint256 _maxBpts) external onlyVaultManagers { - _sellBpt(_amountBpts, _assets, _tokenIndex, _balancerPoolId, _minAmountsOut, _maxBpts); + function sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256 _maxBpts) external onlyVaultManagers { + _sellBpt(_amountBpts, _assets, _tokenIndex, _balancerPoolId, _maxBpts); } // sell bpt for want at current bpt rate - function _sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256[] memory _minAmountsOut, uint256 _maxBpts) internal { + function _sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256 _maxBpts) internal { + _amountBpts = Math.min(_amountBpts, balanceOfBpt()); if (_amountBpts > 0) { - bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, Math.min(_amountBpts, _maxBpts), _tokenIndex); - IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(_assets, _minAmountsOut, userData, false); + uint256[] memory minAmountsOut = new uint256[](numTokens); + minAmountsOut[tokenIndex] = bptsToTokens(_amountBpts).mul(basisOne.sub(maxSlippageOut)).div(basisOne); + bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, _amountBpts, _tokenIndex); + IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(_assets, minAmountsOut, userData, false); balancerVault.exitPool(_balancerPoolId, address(this), address(this), request); } } // join pool given exact token in function joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ - uint256 expectedBptOut = tokensToBpts(amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); + uint256 expectedBptOut = tokensToBpts(_amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); uint256[] memory maxAmountsIn = new uint256[](_numTokens); maxAmountsIn[_tokenIndex] = _amountIn; if (_amountIn > 0) { @@ -364,15 +357,6 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; } - // revert if slippage out exceeds our requirement - function _enforceSlippageOut(uint _intended, uint _actual) internal view { - // enforce that amount exited didn't slip beyond our tolerance - // just in case there's positive slippage - uint256 exitSlipped = _intended > _actual ? _intended.sub(_actual) : 0; - uint256 maxLoss = _intended.mul(maxSlippageOut).div(basisOne); - require(exitSlipped <= maxLoss, "Slipped Out!"); - } - // swap step contains information on multihop sells function getSwapSteps() public view returns (SwapSteps memory){ return swapSteps; @@ -390,13 +374,15 @@ contract Strategy is BaseStrategy { // toggle for whether to abandon rewards or not on emergency withdraws from masterchef function setAbandonRewards(bool abandon) external onlyVaultManagers { abandonRewards = abandon; + } // Balancer requires this contract to be payable, so we add ability to sweep stuck ETH function sweepETH() public onlyGovernance { - (bool success, ) = governance().call{value: address(this).balance}(""); - require(success,"!FailedETHSweep"); + (bool success,) = governance().call{value : address(this).balance}(""); + require(success, "!FailedETHSweep"); } receive() external payable {} } + diff --git a/tests/test_operation.py b/tests/test_operation.py index 3edea13..c7f8103 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -163,6 +163,7 @@ def test_change_debt( chain.sleep(1) strategy.harvest({"from": strategist}) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount + chain.sleep(1) util.stateOfStrat("before airdrop", strategy, token) util.airdrop_rewards(strategy, beets, beets_whale) @@ -172,9 +173,7 @@ def test_change_debt( strategy.harvest({"from": strategist}) chain.sleep(3600 * 6) chain.mine(1) - util.stateOfStrat("after harvest 5000", strategy, token) - # compounded slippage assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == half or strategy.estimatedTotalAssets() >= half From 1d06d359352a3d041b8a07deb8be3d27d3f51cbd Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 14:26:47 -0800 Subject: [PATCH 23/54] keepBeets --- contracts/Strategy.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 44637a2..b6e0da1 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -28,6 +28,8 @@ contract Strategy is BaseStrategy { uint8 public numTokens; uint8 public tokenIndex; bool internal abandonRewards; + address public keep; + uint256 public keepBips; // masterchef IBeethovenxMasterChef public masterChef; @@ -91,6 +93,10 @@ contract Strategy is BaseStrategy { want.safeApprove(address(balancerVault), max); bpt.approve(address(masterChef), max); + + // 10%t to chad by default + keep = governance(); + keepBips = 1000; } // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ @@ -210,7 +216,12 @@ contract Strategy is BaseStrategy { // claim all beets rewards from masterchef function _claimRewards() internal { + uint256 rewardBal = balanceOfReward(); masterChef.harvest(masterChefPoolId, address(this)); + uint256 keepBal = balanceOfReward().sub(rewardBal).mul(keepBips).div(basisOne); + if (keepBal > 0) { + rewardToken.transfer(keep, keepBal); + } } function _sellRewards() internal { @@ -376,6 +387,12 @@ contract Strategy is BaseStrategy { abandonRewards = abandon; } + function setKeepParams(address _keep, uint _keepBips) external onlyGovernance { + require(keepBips < basisOne); + keep = _keep; + keepBips = _keepBips; + } + // Balancer requires this contract to be payable, so we add ability to sweep stuck ETH function sweepETH() public onlyGovernance { (bool success,) = governance().call{value : address(this).balance}(""); From 50a1941555c64f730bd7051a6993563111d50781 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 16:10:21 -0800 Subject: [PATCH 24/54] add toggles --- contracts/Strategy.sol | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index b6e0da1..89b1413 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -27,7 +27,7 @@ contract Strategy is BaseStrategy { bytes32 public balancerPoolId; uint8 public numTokens; uint8 public tokenIndex; - bool internal abandonRewards; + Toggles public toggles; address public keep; uint256 public keepBips; @@ -39,6 +39,12 @@ contract Strategy is BaseStrategy { IAsset[] assets; } + struct Toggles { + bool checkDebtOutstanding; + bool doSellRewards; + bool abandonRewards; + } + uint256 internal constant max = type(uint256).max; //1 0.01% @@ -97,6 +103,8 @@ contract Strategy is BaseStrategy { // 10%t to chad by default keep = governance(); keepBips = 1000; + + toggles = Toggles({checkDebtOutstanding : true, doSellRewards : true, abandonRewards: false}); } // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ @@ -119,8 +127,10 @@ contract Strategy is BaseStrategy { _collectTradingFees(); // claim beets _claimRewards(); - // sell the % not staking - _sellRewards(); + // this would allow finer control over harvesting to get credits in without selling + if (toggles.doSellRewards) { + _sellRewards(); + } uint256 afterWant = balanceOfWant(); _profit = afterWant.sub(beforeWant); @@ -134,8 +144,10 @@ contract Strategy is BaseStrategy { _profit = 0; } - // final check to make sure accounting is correct - require(_debtOutstanding == _debtPayment.add(_loss)); + if (toggles.checkDebtOutstanding) { + // final check to make sure accounting is correct + require(_debtOutstanding == _debtPayment.add(_loss)); + } } function adjustPosition(uint256 _debtOutstanding) internal override { @@ -146,7 +158,7 @@ contract Strategy is BaseStrategy { // put want into lp then put want-lp into masterchef uint256 pooledBefore = balanceOfPooled(); uint256 amountIn = Math.min(maxSingleDeposit, balanceOfWant()); - if (joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { + if (_joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { // put all want-lp into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); } @@ -208,7 +220,7 @@ contract Strategy is BaseStrategy { // AbandonRewards withdraws lp without rewards. Specify where to withdraw to function _withdrawFromMasterChef(address _to, uint256 _amount, uint256 _masterChefPoolId) internal { if (_amount > 0) { - abandonRewards + toggles.abandonRewards ? masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)) : masterChef.withdrawAndHarvest(_masterChefPoolId, _amount, address(_to)); } @@ -337,7 +349,7 @@ contract Strategy is BaseStrategy { } // join pool given exact token in - function joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ + function _joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ uint256 expectedBptOut = tokensToBpts(_amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); uint256[] memory maxAmountsIn = new uint256[](_numTokens); maxAmountsIn[_tokenIndex] = _amountIn; @@ -368,6 +380,12 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; } + function setToggles(bool _checkDebtOustanding, bool _doSellRewards, bool _abandon) external onlyVaultManagers { + toggles.checkDebtOutstanding = _checkDebtOustanding; + toggles.doSellRewards = _doSellRewards; + toggles.abandonRewards = _abandon; + } + // swap step contains information on multihop sells function getSwapSteps() public view returns (SwapSteps memory){ return swapSteps; @@ -382,11 +400,6 @@ contract Strategy is BaseStrategy { bpt.approve(address(masterChef), max); } - // toggle for whether to abandon rewards or not on emergency withdraws from masterchef - function setAbandonRewards(bool abandon) external onlyVaultManagers { - abandonRewards = abandon; - } - function setKeepParams(address _keep, uint _keepBips) external onlyGovernance { require(keepBips < basisOne); keep = _keep; From 6de311a8fd0e2e5f2cd86e88c1eb0b465eb57ed7 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 18:59:02 -0800 Subject: [PATCH 25/54] factory --- contracts/Strategy.sol | 78 +++++++++++++++++++++++------ contracts/StrategyFactory.sol | 93 +++++++++++++++++++++++++++++++++++ tests/conftest.py | 21 ++++++-- 3 files changed, 172 insertions(+), 20 deletions(-) create mode 100644 contracts/StrategyFactory.sol diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 89b1413..635be2b 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -15,7 +15,6 @@ import "../interfaces/MasterChef.sol"; contract Strategy is BaseStrategy { using SafeERC20 for IERC20; - using Address for address; using SafeMath for uint256; IERC20 internal constant weth = IERC20(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); @@ -73,6 +72,57 @@ contract Strategy is BaseStrategy { uint256 _minDepositPeriod, uint256 _masterChefPoolId) public BaseStrategy(_vault){ + _initializeStrat( + _vault, + _balancerVault, + _balancerPool, + _masterChef, + _maxSlippageIn, + _maxSlippageOut, + _maxSingleDeposit, + _minDepositPeriod, + _masterChefPoolId); + } + + function initialize( + address _vault, + address _strategist, + address _rewards, + address _keeper, + address _balancerVault, + address _balancerPool, + address _masterChef, + uint256 _maxSlippageIn, + uint256 _maxSlippageOut, + uint256 _maxSingleDeposit, + uint256 _minDepositPeriod, + uint256 _masterChefPoolId + ) external { + _initialize(_vault, _strategist, _rewards, _keeper); + _initializeStrat( + _vault, + _balancerVault, + _balancerPool, + _masterChef, + _maxSlippageIn, + _maxSlippageOut, + _maxSingleDeposit, + _minDepositPeriod, + _masterChefPoolId + ); + } + + function _initializeStrat( + address _vault, + address _balancerVault, + address _balancerPool, + address _masterChef, + uint256 _maxSlippageIn, + uint256 _maxSlippageOut, + uint256 _maxSingleDeposit, + uint256 _minDepositPeriod, + uint256 _masterChefPoolId + ) internal { healthCheck = address(0xf13Cd6887C62B5beC145e30c38c4938c5E627fe0); bpt = IBalancerPool(_balancerPool); balancerPoolId = bpt.getPoolId(); @@ -104,13 +154,13 @@ contract Strategy is BaseStrategy { keep = governance(); keepBips = 1000; - toggles = Toggles({checkDebtOutstanding : true, doSellRewards : true, abandonRewards: false}); + toggles = Toggles({checkDebtOutstanding : true, doSellRewards : true, abandonRewards : false}); } // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ function name() external view override returns (string memory) { - return string(abi.encodePacked("SSBEETv2 ", ERC20(address(want)).symbol(), " ", bpt.symbol())); + return string(abi.encodePacked("ssbeetV2 ", ERC20(address(want)).symbol(), " ", bpt.symbol())); } function estimatedTotalAssets() public view override returns (uint256) { @@ -157,8 +207,7 @@ contract Strategy is BaseStrategy { // put want into lp then put want-lp into masterchef uint256 pooledBefore = balanceOfPooled(); - uint256 amountIn = Math.min(maxSingleDeposit, balanceOfWant()); - if (_joinPool(amountIn, assets, numTokens, tokenIndex, balancerPoolId)) { + if (_joinPool()) { // put all want-lp into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); } @@ -297,10 +346,6 @@ contract Strategy is BaseStrategy { return bptsToTokens(balanceOfBpt().add(balanceOfBptInMasterChef())); } - function pendingRewards() public view returns (uint256 _amount){ - return masterChef.pendingBeets(masterChefPoolId, address(this)); - } - /// use bpt rate to estimate equivalent amount of want. function bptsToTokens(uint _amountBpt) public view returns (uint _amount){ uint unscaled = _amountBpt * bpt.getRate() / 1e18; @@ -349,14 +394,15 @@ contract Strategy is BaseStrategy { } // join pool given exact token in - function _joinPool(uint256 _amountIn, IAsset[] memory _assets, uint256 _numTokens, uint256 _tokenIndex, bytes32 _poolId) internal returns (bool _joined){ - uint256 expectedBptOut = tokensToBpts(_amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); - uint256[] memory maxAmountsIn = new uint256[](_numTokens); - maxAmountsIn[_tokenIndex] = _amountIn; - if (_amountIn > 0) { + function _joinPool() internal returns (bool _joined){ + uint256 amountIn = Math.min(maxSingleDeposit, balanceOfWant()); + uint256 expectedBptOut = tokensToBpts(amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); + uint256[] memory maxAmountsIn = new uint256[](numTokens); + maxAmountsIn[tokenIndex] = amountIn; + if (amountIn > 0) { bytes memory userData = abi.encode(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, expectedBptOut); - IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest(_assets, maxAmountsIn, userData, false); - balancerVault.joinPool(_poolId, address(this), address(this), request); + IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest(assets, maxAmountsIn, userData, false); + balancerVault.joinPool(balancerPoolId, address(this), address(this), request); lastDepositTime = now; return true; } diff --git a/contracts/StrategyFactory.sol b/contracts/StrategyFactory.sol new file mode 100644 index 0000000..610ca31 --- /dev/null +++ b/contracts/StrategyFactory.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "./Strategy.sol"; + +contract StrategyFactory { + address public immutable original; + + event Cloned(address indexed clone); + event Deployed(address indexed original); + + constructor( + address _vault, + address _balancerVault, + address _balancerPool, + address _masterChef, + uint256 _maxSlippageIn, + uint256 _maxSlippageOut, + uint256 _maxSingleDeposit, + uint256 _minDepositPeriod, + uint256 _masterChefPoolId + ) public { + Strategy _original = new Strategy(_vault, + _balancerVault, + _balancerPool, + _masterChef, + _maxSlippageIn, + _maxSlippageOut, + _maxSingleDeposit, + _minDepositPeriod, + _masterChefPoolId); + emit Deployed(address(_original)); + + original = address(_original); + _original.setRewards(msg.sender); + _original.setKeeper(msg.sender); + _original.setStrategist(msg.sender); + } + + function name() external view returns (string memory) { + return + string( + abi.encodePacked( + "Factory", + Strategy(payable(original)).name(), + "@", + Strategy(payable(original)).apiVersion() + ) + ); + } + + function clone( + address _vault, + address _strategist, + address _rewards, + address _keeper, + address _balancerVault, + address _balancerPool, + address _masterChef, + uint256 _maxSlippageIn, + uint256 _maxSlippageOut, + uint256 _maxSingleDeposit, + uint256 _minDepositPeriod, + uint256 _masterChefPoolId + ) external returns (address payable newStrategy) { + // Copied from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol + bytes20 addressBytes = bytes20(original); + assembly { + // EIP-1167 bytecode + let clone_code := mload(0x40) + mstore(clone_code, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(clone_code, 0x14), addressBytes) + mstore(add(clone_code, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + newStrategy := create(0, clone_code, 0x37) + } + + Strategy(newStrategy).initialize(_vault, + _strategist, + _rewards, + _keeper, + _balancerVault, + _balancerPool, + _masterChef, + _maxSlippageIn, + _maxSlippageOut, + _maxSingleDeposit, + _minDepositPeriod, + _masterChefPoolId + ); + emit Cloned(newStrategy); + } +} diff --git a/tests/conftest.py b/tests/conftest.py index 298923a..827a08d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,6 +52,7 @@ def token(): token_address = "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75" yield Contract(token_address) + @pytest.fixture def token2(): # 0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E DAI @@ -61,6 +62,7 @@ def token2(): token_address = "0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E" yield Contract(token_address) + @pytest.fixture def token_whale(accounts): # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM @@ -69,6 +71,7 @@ def token_whale(accounts): # 0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75 DAI return accounts.at("0x2dd7C9371965472E5A5fD28fbE165007c61439E1", force=True) + @pytest.fixture def token2_whale(accounts): # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM @@ -85,6 +88,7 @@ def amount(accounts, token, user, token_whale): token.transfer(user, amount, {"from": token_whale}) yield amount + @pytest.fixture def amount2(accounts, token2, user, token2_whale): amount = 1_000_000 * 10 ** token.decimals() @@ -176,10 +180,19 @@ def swapStepsBeets(beetsUsdcPoolId, beets, token): @pytest.fixture -def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, management, - masterChef, - swapStepsBeets): - strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 5, 5, 100_000, 2 * 60 * 60, 33) +def strategyFactory(sstrategist, keeper, vault, StrategyFactory, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, + management, + masterChef, + swapStepsBeets): + factory = strategist.deploy(StrategyFactory, vault, balancer_vault, pool, masterChef, 5, 5, 100_000, 2 * 60 * 60, + 33) + yield factory + + +@pytest.fixture +def strategy(strategist, keeper, vault, strategyFactory, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, + management, masterChef, swapStepsBeets): + strategy = Strategy.at(strategyFactory.original()) strategy.setKeeper(keeper) strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) From 02061df3a54e347fc8ac01da086b4a55cf6b074c Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 2 Feb 2022 19:00:58 -0800 Subject: [PATCH 26/54] small things --- contracts/Strategy.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 635be2b..9a7fcaa 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -447,7 +447,7 @@ contract Strategy is BaseStrategy { } function setKeepParams(address _keep, uint _keepBips) external onlyGovernance { - require(keepBips < basisOne); + require(keepBips <= basisOne); keep = _keep; keepBips = _keepBips; } @@ -455,7 +455,7 @@ contract Strategy is BaseStrategy { // Balancer requires this contract to be payable, so we add ability to sweep stuck ETH function sweepETH() public onlyGovernance { (bool success,) = governance().call{value : address(this).balance}(""); - require(success, "!FailedETHSweep"); + require(success, "FailedETHSweep"); } receive() external payable {} From 6e154130b83fe95e2f6f351ab5e2cdcc4e4010bf Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 3 Feb 2022 21:25:41 -0700 Subject: [PATCH 27/54] deposit test --- tests/conftest.py | 12 ++++++------ tests/test_operation.py | 43 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 827a08d..e37de3c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -67,16 +67,16 @@ def token2(): def token_whale(accounts): # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 fUSDT - # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 USDC + # 0x93C08a3168fC469F3fC165cd3A471D19a37ca19e USDC # 0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75 DAI - return accounts.at("0x2dd7C9371965472E5A5fD28fbE165007c61439E1", force=True) + return accounts.at("0x93C08a3168fC469F3fC165cd3A471D19a37ca19e", force=True) @pytest.fixture def token2_whale(accounts): # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 MIM # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 fUSDT - # 0x2dd7C9371965472E5A5fD28fbE165007c61439E1 USDC + # 0x93C08a3168fC469F3fC165cd3A471D19a37ca19e USDC # 0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75 DAI return accounts.at("0x8D9AED9882b4953a0c9fa920168fa1FDfA0eBE75", force=True) @@ -180,7 +180,7 @@ def swapStepsBeets(beetsUsdcPoolId, beets, token): @pytest.fixture -def strategyFactory(sstrategist, keeper, vault, StrategyFactory, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, +def strategyFactory(strategist, keeper, vault, StrategyFactory, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, management, masterChef, swapStepsBeets): @@ -191,9 +191,9 @@ def strategyFactory(sstrategist, keeper, vault, StrategyFactory, gov, balancer_v @pytest.fixture def strategy(strategist, keeper, vault, strategyFactory, gov, balancer_vault, pool, beets, usdc, beetsUsdcPool, - management, masterChef, swapStepsBeets): + management, masterChef, swapStepsBeets, Strategy): strategy = Strategy.at(strategyFactory.original()) - strategy.setKeeper(keeper) + strategy.setKeeper(keeper,{'from': gov}) strategy.whitelistReward(beets, swapStepsBeets, {'from': gov}) vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov}) vault.setManagementFee(0, {"from": gov}) diff --git a/tests/test_operation.py b/tests/test_operation.py index c7f8103..b43fa99 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -206,8 +206,9 @@ def test_sweep(gov, vault, strategy, token, user, amount, wftm, wftm_amount): strategy.sweep(wftm, {"from": gov}) assert wftm.balanceOf(gov) == wftm_amount + before_balance + def test_eth_sweep(chain, token, vault, strategy, user, strategist, gov): - strategist.transfer(strategy,1e18) + strategist.transfer(strategy, 1e18) with brownie.reverts(): strategy.sweepETH({"from": strategist}) @@ -215,6 +216,7 @@ def test_eth_sweep(chain, token, vault, strategy, user, strategist, gov): strategy.sweepETH({"from": gov}) assert gov.balance() > eth_balance + def test_triggers( chain, gov, vault, strategy, token, amount, user, wftm, wftm_amount, strategist, beets, beets_whale, token_whale @@ -229,6 +231,45 @@ def test_triggers( strategy.harvestTrigger(0) strategy.tendTrigger(0) +# simulate a bad deposit, aka pool has too much of the want you're trying to deposit already +def test_unbalance_deposit(chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, token2_whale, + token2, token_whale, gov, pool, balancer_vault): + token.approve(vault.address, 2 ** 256 - 1, {"from": user}) + vault.deposit(amount, {"from": user}) + assert token.balanceOf(vault.address) == amount + + print(f'pool rate before whale swap: {pool.getRate()}') + pooled = balancer_vault.getPoolTokens(pool.getPoolId())[1][strategy.tokenIndex()] + token.approve(balancer_vault, 2 ** 256 - 1, {'from': token_whale}) + chain.snapshot() + + print(f'pool rate: {pool.getRate()}') + tokens = balancer_vault.getPoolTokens(pool.getPoolId())[0] + token2Index = 0 + if (tokens[0] == token2): + token2Index = 0 + elif tokens[1] == token2: + token2Index = 1 + + pooled2 = balancer_vault.getPoolTokens(pool.getPoolId())[1][token2Index] + print(balancer_vault.getPoolTokens(pool.getPoolId())) + print(f'pooled: {pooled2}') + token.approve(balancer_vault, 2 ** 256 - 1, {'from': token_whale}) + + # simulate bad pool state by whale to swap out 98% of one side of the pool so pool only has excess want + singleSwap = (pool.getPoolId(), 1, token, token2, pooled2 * 0.98, b'0x0') + balancer_vault.swap(singleSwap, (token_whale, False, token_whale, False), token.balanceOf(token_whale), + chain.time(), {'from': token_whale}) + print(balancer_vault.getPoolTokens(pool.getPoolId())) + print(f'pool rate: {pool.getRate()}') + + + print(f'pool rate after whale swap: {pool.getRate()}') + + with brownie.reverts("BAL#208"): + tx = strategy.harvest({ + "from": strategist}) # Error Code BAL#208 BPT_OUT_MIN_AMOUNT - Slippage/front-running protection check failed on a pool join + def test_unbalanced_pool_withdraw(chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, token2_whale, token2, From 2227d8e63a3aa7b2eb7459e8704b3bc4f5f499cf Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 3 Feb 2022 22:01:24 -0700 Subject: [PATCH 28/54] tests --- tests/test_migration.py | 4 ++-- tests/test_operation.py | 4 ++-- tests/test_revoke.py | 10 +++++----- tests/test_shutdown.py | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_migration.py b/tests/test_migration.py index 23b5716..05fc31d 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -21,7 +21,7 @@ def test_migration( token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) chain.sleep(1) - strategy.harvest() + strategy.harvest({"from": gov}) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount # migrate to a new strategy @@ -50,7 +50,7 @@ def test_masterchef( token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) chain.sleep(1) - strategy.harvest() + strategy.harvest({"from": gov}) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount chain.mine(3) diff --git a/tests/test_operation.py b/tests/test_operation.py index b43fa99..daf17eb 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -39,7 +39,7 @@ def test_emergency_exit( assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount # set emergency and exit - strategy.setEmergencyExit() + strategy.setEmergencyExit({"from": gov}) chain.mine(5) chain.sleep(1) # some slippage won't pass healthcheck @@ -263,8 +263,8 @@ def test_unbalance_deposit(chain, token, vault, strategy, user, strategist, amou print(balancer_vault.getPoolTokens(pool.getPoolId())) print(f'pool rate: {pool.getRate()}') - print(f'pool rate after whale swap: {pool.getRate()}') + print(f'pool state after whale swap: {balancer_vault.getPoolTokens(pool.getPoolId())}') with brownie.reverts("BAL#208"): tx = strategy.harvest({ diff --git a/tests/test_revoke.py b/tests/test_revoke.py index 9fcf146..bed34ff 100644 --- a/tests/test_revoke.py +++ b/tests/test_revoke.py @@ -8,13 +8,13 @@ def test_revoke_strategy_from_vault( token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) chain.sleep(1) - strategy.harvest() + strategy.harvest({"from": gov}) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount # In order to pass this tests, you will need to implement prepareReturn. vault.revokeStrategy(strategy.address, {"from": gov}) chain.sleep(1) - strategy.harvest() + strategy.harvest({"from": gov}) assert pytest.approx(token.balanceOf(vault.address), rel=RELATIVE_APPROX) == amount @@ -25,10 +25,10 @@ def test_revoke_strategy_from_strategy( token.approve(vault.address, amount, {"from": user}) vault.deposit(amount, {"from": user}) chain.sleep(1) - strategy.harvest() + strategy.harvest({"from": gov}) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - strategy.setEmergencyExit() + strategy.setEmergencyExit({"from": gov}) chain.sleep(1) - strategy.harvest() + strategy.harvest({"from": gov}) assert pytest.approx(token.balanceOf(vault.address), rel=RELATIVE_APPROX) == amount diff --git a/tests/test_shutdown.py b/tests/test_shutdown.py index fe89d44..34d12ac 100644 --- a/tests/test_shutdown.py +++ b/tests/test_shutdown.py @@ -3,7 +3,7 @@ def test_vault_shutdown_can_withdraw( - chain, token, vault, strategy, user, amount, RELATIVE_APPROX + chain, token, vault, strategy, user, amount, RELATIVE_APPROX, gov ): ## Deposit in Vault token.approve(vault.address, amount, {"from": user}) @@ -14,7 +14,7 @@ def test_vault_shutdown_can_withdraw( token.transfer(ZERO_ADDRESS, token.balanceOf(user), {"from": user}) # Harvest 1: Send funds through the strategy - strategy.harvest() + strategy.harvest({"from": gov}) chain.sleep(3600 * 7) chain.mine(1) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount @@ -37,7 +37,7 @@ def test_basic_shutdown( assert token.balanceOf(vault.address) == amount # Harvest 1: Send funds through the strategy - strategy.harvest() + strategy.harvest({"from": gov}) chain.mine(100) assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount @@ -46,7 +46,7 @@ def test_basic_shutdown( chain.mine(1) # Harvest 2: Realize profit - strategy.harvest() + strategy.harvest({"from": gov}) chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock chain.mine(1) @@ -54,7 +54,7 @@ def test_basic_shutdown( strategy.setEmergencyExit({"from": strategist}) strategy.setDoHealthCheck(False, {'from': gov}) - strategy.harvest() ## Remove funds from strategy + strategy.harvest({"from": gov}) ## Remove funds from strategy assert token.balanceOf(strategy) == 0 assert pytest.approx(token.balanceOf(vault), rel=RELATIVE_APPROX) == amount From a3c17ff7c5fcb290468288a3425a28fcb8af4ad8 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Fri, 4 Feb 2022 20:11:38 -0700 Subject: [PATCH 29/54] keepbeets comment --- contracts/Strategy.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 9a7fcaa..6f36062 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -27,6 +27,9 @@ contract Strategy is BaseStrategy { uint8 public numTokens; uint8 public tokenIndex; Toggles public toggles; + + // The keep mechanism is intended for governance voting purposes. Strategy will route a percentage (default 10%) + // to the governance or eventually a voter proxy in order to slowly amass voting power for Beethoven pool gauges address public keep; uint256 public keepBips; From 8e094f4dc32269af0624748cb3a5f8a261bc7c65 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Fri, 4 Feb 2022 21:32:28 -0700 Subject: [PATCH 30/54] wavey's comments --- contracts/Strategy.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 6f36062..e4593dc 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -17,7 +17,6 @@ contract Strategy is BaseStrategy { using SafeERC20 for IERC20; using SafeMath for uint256; - IERC20 internal constant weth = IERC20(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); IBalancerVault public balancerVault; IBalancerPool public bpt; IERC20 public rewardToken; @@ -209,7 +208,6 @@ contract Strategy is BaseStrategy { } // put want into lp then put want-lp into masterchef - uint256 pooledBefore = balanceOfPooled(); if (_joinPool()) { // put all want-lp into masterchef masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); @@ -222,7 +220,7 @@ contract Strategy is BaseStrategy { uint256 toExitAmount = _amountNeeded.sub(looseAmount); // withdraw all bpt out of masterchef - masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); + _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); // sell some bpt _sellBpt(tokensToBpts(toExitAmount), assets, tokenIndex, balancerPoolId, balanceOfBpt()); // put remaining bpt back into masterchef @@ -238,7 +236,7 @@ contract Strategy is BaseStrategy { function liquidateAllPositions() internal override returns (uint256 liquidated) { uint totalDebt = vault.strategies(address(this)).totalDebt; - masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); + _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); // sell all bpt _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, balanceOfBpt()); @@ -320,7 +318,7 @@ contract Strategy is BaseStrategy { uint256 debt = vault.strategies(address(this)).totalDebt; if (total > debt) { // withdraw all bpt out of masterchef - masterChef.withdrawAndHarvest(masterChefPoolId, balanceOfBptInMasterChef(), address(this)); + _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); uint256 profit = total.sub(debt); _sellBpt(tokensToBpts(profit), assets, tokenIndex, balancerPoolId, balanceOfBpt()); // put remaining bpt back into masterchef @@ -414,6 +412,7 @@ contract Strategy is BaseStrategy { function whitelistReward(address _rewardToken, SwapSteps memory _steps) public onlyVaultManagers { rewardToken = IERC20(_rewardToken); + rewardToken.approve(address(balancerVault), 0); rewardToken.approve(address(balancerVault), max); swapSteps = _steps; } From af998345057daa77449530f06ee0aa75fa8d6ef5 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Sat, 5 Feb 2022 10:35:27 -0500 Subject: [PATCH 31/54] feat: function to withdraw and sell also, add bpt migration to prepareMigrate and a deposit to masterchef after changing masterchef contract --- contracts/Strategy.sol | 44 ++++++++++++++++++++++++++--------------- tests/test_migration.py | 8 +++++++- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index e4593dc..403b5b8 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -219,12 +219,11 @@ contract Strategy is BaseStrategy { if (_amountNeeded > looseAmount) { uint256 toExitAmount = _amountNeeded.sub(looseAmount); - // withdraw all bpt out of masterchef - _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); - // sell some bpt - _sellBpt(tokensToBpts(toExitAmount), assets, tokenIndex, balancerPoolId, balanceOfBpt()); - // put remaining bpt back into masterchef - masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); + //need to know how much we need to withdraw in bpt + toExitAmount = tokensToBpts(toExitAmount); + + // withdraw needed bpt out of masterchef and sell it for want + _withdrawFromMasterChefAndSellBpt(toExitAmount); _liquidatedAmount = Math.min(balanceOfWant(), _amountNeeded); _loss = _amountNeeded.sub(_liquidatedAmount); @@ -234,12 +233,9 @@ contract Strategy is BaseStrategy { } function liquidateAllPositions() internal override returns (uint256 liquidated) { - uint totalDebt = vault.strategies(address(this)).totalDebt; - _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); // sell all bpt _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, balanceOfBpt()); - liquidated = balanceOfWant(); return liquidated; } @@ -247,6 +243,10 @@ contract Strategy is BaseStrategy { // note that this withdraws into newStrategy. function prepareMigration(address _newStrategy) internal override { _withdrawFromMasterChef(_newStrategy, balanceOfBptInMasterChef(), masterChefPoolId); + uint256 _balanceOfBpt = balanceOfBpt(); + if (_balanceOfBpt > 0) { + bpt.transfer(_newStrategy, _balanceOfBpt); + } uint256 rewards = balanceOfReward(); if (rewards > 0) { rewardToken.transfer(_newStrategy, rewards); @@ -276,6 +276,19 @@ contract Strategy is BaseStrategy { } } + // Withdraw the desired amount in bpt from masterchef and sell it for want + function _withdrawFromMasterChefAndSellBpt(uint256 amount) internal { + // don't try to withdraw more than what we have in masterchef + amount = Math.min(amount, balanceOfBptInMasterChef()); + + // withdraw the desired amount out of masterchef + _withdrawFromMasterChef(address(this), amount, masterChefPoolId); + + // sell the desired amount for want + _sellBpt(amount, assets, tokenIndex, balancerPoolId, balanceOfBpt()); + } + + // claim all beets rewards from masterchef function _claimRewards() internal { uint256 rewardBal = balanceOfReward(); @@ -316,13 +329,10 @@ contract Strategy is BaseStrategy { function _collectTradingFees() internal { uint256 total = estimatedTotalAssets(); uint256 debt = vault.strategies(address(this)).totalDebt; + // if there is a profit from trading fees, we sell it for want if (total > debt) { - // withdraw all bpt out of masterchef - _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); uint256 profit = total.sub(debt); - _sellBpt(tokensToBpts(profit), assets, tokenIndex, balancerPoolId, balanceOfBpt()); - // put remaining bpt back into masterchef - masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); + _withdrawFromMasterChefAndSellBpt(tokensToBpts(profit)); } } @@ -441,11 +451,15 @@ contract Strategy is BaseStrategy { // masterchef contract in case of masterchef migration function setMasterChef(address _masterChef) public onlyGovernance { + // withdraw all bpt from masterchef before migration _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); bpt.approve(address(masterChef), 0); masterChef = IBeethovenxMasterChef(_masterChef); bpt.approve(address(masterChef), max); + + // deposit bpt in new masterchef + masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); } function setKeepParams(address _keep, uint _keepBips) external onlyGovernance { @@ -462,5 +476,3 @@ contract Strategy is BaseStrategy { receive() external payable {} } - - diff --git a/tests/test_migration.py b/tests/test_migration.py index 05fc31d..b8eb0ac 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -56,8 +56,14 @@ def test_masterchef( chain.mine(3) strategy.tend({"from": gov}) + oldMasterChef = Contract(strategy.masterChef()) + mcpID = strategy.masterChefPoolId() + + prevBptBalanceInMasterChef = strategy.balanceOfBptInMasterChef() strategy.setMasterChef(masterChef, {"from": gov}) - assert strategy.balanceOfBptInMasterChef() == 0 + + #new masterchef should have all the bpt deposited + assert strategy.balanceOfBptInMasterChef() == prevBptBalanceInMasterChef def test_real_migration( From 18cc5a325ac7808c92c35862c501d4f66c5a5404 Mon Sep 17 00:00:00 2001 From: wavey Date: Mon, 7 Feb 2022 16:03:21 -0500 Subject: [PATCH 32/54] feat: simplify bpt to exit calc --- contracts/Strategy.sol | 12 +++++------- tests/test_operation.py | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 403b5b8..f647c57 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -217,10 +217,8 @@ contract Strategy is BaseStrategy { function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss){ uint256 looseAmount = balanceOfWant(); if (_amountNeeded > looseAmount) { - uint256 toExitAmount = _amountNeeded.sub(looseAmount); - - //need to know how much we need to withdraw in bpt - toExitAmount = tokensToBpts(toExitAmount); + // convert amount still needed to bpt + uint256 toExitAmount = tokensToBpts(_amountNeeded.sub(looseAmount)); // withdraw needed bpt out of masterchef and sell it for want _withdrawFromMasterChefAndSellBpt(toExitAmount); @@ -407,10 +405,10 @@ contract Strategy is BaseStrategy { // join pool given exact token in function _joinPool() internal returns (bool _joined){ uint256 amountIn = Math.min(maxSingleDeposit, balanceOfWant()); - uint256 expectedBptOut = tokensToBpts(amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); - uint256[] memory maxAmountsIn = new uint256[](numTokens); - maxAmountsIn[tokenIndex] = amountIn; if (amountIn > 0) { + uint256 expectedBptOut = tokensToBpts(amountIn).mul(basisOne.sub(maxSlippageIn)).div(basisOne); + uint256[] memory maxAmountsIn = new uint256[](numTokens); + maxAmountsIn[tokenIndex] = amountIn; bytes memory userData = abi.encode(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, expectedBptOut); IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest(assets, maxAmountsIn, userData, false); balancerVault.joinPool(balancerPoolId, address(this), address(this), request); diff --git a/tests/test_operation.py b/tests/test_operation.py index daf17eb..bb8680d 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -70,7 +70,7 @@ def test_manual_exit( def test_profitable_harvest( - chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, management + chain, token, vault, gov, strategy, user, strategist, amount, RELATIVE_APPROX, beets, beets_whale, management ): # Deposit to the vault token.approve(vault.address, amount, {"from": user}) @@ -85,10 +85,10 @@ def test_profitable_harvest( before_pps = vault.pricePerShare() # Harvest 2: Realize profit - util.airdrop_rewards(strategy, beets, beets_whale) - strategy.harvest({"from": strategist}) + tx = strategy.harvest({"from": strategist}) + print(tx.events["StrategyReported"]) chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock chain.mine(1) From b51c620729f2a326f3a0ff900bb42aa6eb84c1c9 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Tue, 8 Feb 2022 09:53:51 -0500 Subject: [PATCH 33/54] fix: added same changes from the balancer strat --- contracts/Strategy.sol | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index f647c57..993c700 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -41,7 +41,6 @@ contract Strategy is BaseStrategy { } struct Toggles { - bool checkDebtOutstanding; bool doSellRewards; bool abandonRewards; } @@ -130,6 +129,7 @@ contract Strategy is BaseStrategy { balancerPoolId = bpt.getPoolId(); balancerVault = IBalancerVault(_balancerVault); (IERC20[] memory tokens,,) = balancerVault.getPoolTokens(balancerPoolId); + require(tokens.length > 0, "Empty Pool"); numTokens = uint8(tokens.length); assets = new IAsset[](numTokens); tokenIndex = type(uint8).max; @@ -156,7 +156,7 @@ contract Strategy is BaseStrategy { keep = governance(); keepBips = 1000; - toggles = Toggles({checkDebtOutstanding : true, doSellRewards : true, abandonRewards : false}); + toggles = Toggles({doSellRewards : true, abandonRewards : false}); } // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ @@ -195,11 +195,6 @@ contract Strategy is BaseStrategy { _debtPayment += _profit; _profit = 0; } - - if (toggles.checkDebtOutstanding) { - // final check to make sure accounting is correct - require(_debtOutstanding == _debtPayment.add(_loss)); - } } function adjustPosition(uint256 _debtOutstanding) internal override { @@ -228,6 +223,7 @@ contract Strategy is BaseStrategy { } else { _liquidatedAmount = _amountNeeded; } + require(_amountNeeded == _liquidatedAmount.add(_loss), "!sanitycheck"); } function liquidateAllPositions() internal override returns (uint256 liquidated) { @@ -436,8 +432,7 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; } - function setToggles(bool _checkDebtOustanding, bool _doSellRewards, bool _abandon) external onlyVaultManagers { - toggles.checkDebtOutstanding = _checkDebtOustanding; + function setToggles(bool _doSellRewards, bool _abandon) external onlyVaultManagers { toggles.doSellRewards = _doSellRewards; toggles.abandonRewards = _abandon; } From ea5354bbe1ec9535aa5b15a930be243e3fc13751 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Tue, 8 Feb 2022 12:03:15 -0500 Subject: [PATCH 34/54] fix: added tonkers' suggestion --- contracts/Strategy.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 993c700..e9386b6 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -257,29 +257,29 @@ contract Strategy is BaseStrategy { // HELPERS // - function withdrawFromMasterChef(uint256 _amount, uint256 _masterChefPooId) external onlyVaultManagers { - _withdrawFromMasterChef(address(this), _amount, _masterChefPooId); + function withdrawFromMasterChef(uint256 _amountBpt, uint256 _masterChefPooId) external onlyVaultManagers { + _withdrawFromMasterChef(address(this), _amountBpt, _masterChefPooId); } // AbandonRewards withdraws lp without rewards. Specify where to withdraw to - function _withdrawFromMasterChef(address _to, uint256 _amount, uint256 _masterChefPoolId) internal { - if (_amount > 0) { + function _withdrawFromMasterChef(address _to, uint256 _amountBpt, uint256 _masterChefPoolId) internal { + if (_amountBpt > 0) { toggles.abandonRewards ? masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)) - : masterChef.withdrawAndHarvest(_masterChefPoolId, _amount, address(_to)); + : masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); } } // Withdraw the desired amount in bpt from masterchef and sell it for want - function _withdrawFromMasterChefAndSellBpt(uint256 amount) internal { + function _withdrawFromMasterChefAndSellBpt(uint256 _amountBpt) internal { // don't try to withdraw more than what we have in masterchef - amount = Math.min(amount, balanceOfBptInMasterChef()); + _amountBpt = Math.min(_amountBpt, balanceOfBptInMasterChef()); // withdraw the desired amount out of masterchef - _withdrawFromMasterChef(address(this), amount, masterChefPoolId); + _withdrawFromMasterChef(address(this), _amountBpt, masterChefPoolId); // sell the desired amount for want - _sellBpt(amount, assets, tokenIndex, balancerPoolId, balanceOfBpt()); + _sellBpt(_amountBpt, assets, tokenIndex, balancerPoolId, balanceOfBpt()); } From 42a3d830709eacf3d68679544b60338525ef1267 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Tue, 8 Feb 2022 14:05:27 -0500 Subject: [PATCH 35/54] fix: weasal's comments on ssb --- contracts/Strategy.sol | 12 ++++++------ tests/util.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index e9386b6..b33c770 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -188,17 +188,17 @@ contract Strategy is BaseStrategy { _profit = afterWant.sub(beforeWant); if (_profit > _loss) { _profit = _profit.sub(_loss); - _debtPayment += _loss; + _debtPayment = _debtPayment.add(_loss); _loss = 0; } else { _loss = _loss.sub(_profit); - _debtPayment += _profit; + _debtPayment = _debtPayment.add(_profit); _profit = 0; } } function adjustPosition(uint256 _debtOutstanding) internal override { - if (now - lastDepositTime < minDepositPeriod) { + if (now.sub(lastDepositTime) < minDepositPeriod) { return; } @@ -353,19 +353,19 @@ contract Strategy is BaseStrategy { /// use bpt rate to estimate equivalent amount of want. function bptsToTokens(uint _amountBpt) public view returns (uint _amount){ - uint unscaled = _amountBpt * bpt.getRate() / 1e18; + uint unscaled = _amountBpt.mul(bpt.getRate()).div(1e18); return _scaleDecimals(unscaled, ERC20(address(bpt)), ERC20(address(want))); } function tokensToBpts(uint _amountTokens) public view returns (uint _amount){ - uint unscaled = _amountTokens * 1e18 / bpt.getRate(); + uint unscaled = _amountTokens.mul(1e18).div(bpt.getRate()); return _scaleDecimals(unscaled, ERC20(address(want)), ERC20(address(bpt))); } function _scaleDecimals(uint _amount, ERC20 _fromToken, ERC20 _toToken) internal view returns (uint _scaled){ uint decFrom = _fromToken.decimals(); uint decTo = _toToken.decimals(); - return decTo > decFrom ? _amount * 10 ** (decTo.sub(decFrom)) : _amount / 10 ** (decFrom.sub(decTo)); + return decTo > decFrom ? _amount.mul(10 ** (decTo.sub(decFrom))) : _amount.div(10 ** (decFrom.sub(decTo))); } function _getSwapRequest(IERC20 token, uint256 amount, uint256 lastChangeBlock) internal view returns (IBalancerPool.SwapRequest memory request){ diff --git a/tests/util.py b/tests/util.py index 3c1a331..5838bac 100644 --- a/tests/util.py +++ b/tests/util.py @@ -3,7 +3,7 @@ def airdrop_rewards(strategy, beets, beets_whale): beets.approve(strategy, 2 ** 256 - 1, {'from': beets_whale}) - beets.transfer(strategy, 10_000 * 1e18, {'from': beets_whale}) + beets.transfer(strategy, 8_000 * 1e18, {'from': beets_whale}) def stateOfStrat(msg, strategy, token): From 363ebde597f67d9639b795b29ed4d92b9e5a8867 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 9 Feb 2022 19:12:17 -0800 Subject: [PATCH 36/54] weasel's comments --- contracts/Strategy.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index b33c770..3b5b1cb 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -7,15 +7,13 @@ pragma experimental ABIEncoderV2; // These are the core Yearn libraries import {BaseStrategy, StrategyParams} from "@yearnvaults/contracts/BaseStrategy.sol"; -import {SafeERC20, SafeMath, IERC20, Address} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import {Address} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Math} from "@openzeppelin/contracts/math/Math.sol"; import "../interfaces/BalancerV2.sol"; import "../interfaces/MasterChef.sol"; contract Strategy is BaseStrategy { - using SafeERC20 for IERC20; - using SafeMath for uint256; IBalancerVault public balancerVault; IBalancerPool public bpt; @@ -239,11 +237,11 @@ contract Strategy is BaseStrategy { _withdrawFromMasterChef(_newStrategy, balanceOfBptInMasterChef(), masterChefPoolId); uint256 _balanceOfBpt = balanceOfBpt(); if (_balanceOfBpt > 0) { - bpt.transfer(_newStrategy, _balanceOfBpt); + bpt.safeTransfer(_newStrategy, _balanceOfBpt); } uint256 rewards = balanceOfReward(); if (rewards > 0) { - rewardToken.transfer(_newStrategy, rewards); + rewardToken.safeTransfer(_newStrategy, rewards); } } @@ -289,7 +287,7 @@ contract Strategy is BaseStrategy { masterChef.harvest(masterChefPoolId, address(this)); uint256 keepBal = balanceOfReward().sub(rewardBal).mul(keepBips).div(basisOne); if (keepBal > 0) { - rewardToken.transfer(keep, keepBal); + rewardToken.safeTransfer(keep, keepBal); } } From 185c8e4a2c8da957b1335d82872aca8adc06986f Mon Sep 17 00:00:00 2001 From: Tonkers Date: Wed, 9 Feb 2022 19:14:51 -0800 Subject: [PATCH 37/54] regular transfer for bpt --- contracts/Strategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 3b5b1cb..73af4a8 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -237,7 +237,7 @@ contract Strategy is BaseStrategy { _withdrawFromMasterChef(_newStrategy, balanceOfBptInMasterChef(), masterChefPoolId); uint256 _balanceOfBpt = balanceOfBpt(); if (_balanceOfBpt > 0) { - bpt.safeTransfer(_newStrategy, _balanceOfBpt); + bpt.transfer(_newStrategy, _balanceOfBpt); } uint256 rewards = balanceOfReward(); if (rewards > 0) { From da20700d80de894fb276d7581793cf8c66a821fe Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Thu, 10 Feb 2022 13:01:21 -0500 Subject: [PATCH 38/54] fix: remove setMasterChef --- contracts/Strategy.sol | 13 ------------- tests/test_migration.py | 36 ------------------------------------ 2 files changed, 49 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 73af4a8..5637423 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -440,19 +440,6 @@ contract Strategy is BaseStrategy { return swapSteps; } - // masterchef contract in case of masterchef migration - function setMasterChef(address _masterChef) public onlyGovernance { - // withdraw all bpt from masterchef before migration - _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); - - bpt.approve(address(masterChef), 0); - masterChef = IBeethovenxMasterChef(_masterChef); - bpt.approve(address(masterChef), max); - - // deposit bpt in new masterchef - masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); - } - function setKeepParams(address _keep, uint _keepBips) external onlyGovernance { require(keepBips <= basisOne); keep = _keep; diff --git a/tests/test_migration.py b/tests/test_migration.py index b8eb0ac..9162ed5 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -30,42 +30,6 @@ def test_migration( vault.migrateStrategy(strategy, new_strategy, {"from": gov}) assert (pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount) - -def test_masterchef( - chain, - token, - vault, - strategy, - amount, - Strategy, - strategist, - gov, - user, - RELATIVE_APPROX, - balancer_vault, - pool, - masterChef -): - # Deposit to the vault and harvest - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - chain.sleep(1) - strategy.harvest({"from": gov}) - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - chain.mine(3) - strategy.tend({"from": gov}) - - oldMasterChef = Contract(strategy.masterChef()) - mcpID = strategy.masterChefPoolId() - - prevBptBalanceInMasterChef = strategy.balanceOfBptInMasterChef() - strategy.setMasterChef(masterChef, {"from": gov}) - - #new masterchef should have all the bpt deposited - assert strategy.balanceOfBptInMasterChef() == prevBptBalanceInMasterChef - - def test_real_migration( chain, token, From dd737b8a25c325c1547c7b187dc618b2f5f38008 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 10 Feb 2022 10:31:01 -0800 Subject: [PATCH 39/54] assert pending --- tests/test_migration.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_migration.py b/tests/test_migration.py index 9162ed5..a5c87e1 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -34,7 +34,6 @@ def test_real_migration( chain, token, vault, - strategy, amount, Strategy, strategist, @@ -49,18 +48,25 @@ def test_real_migration( gov = accounts.at(vault.governance(), force=True) fromGov = {'from': gov} - util.stateOfStrat("old strategy before migration", strategy, token) + util.stateOfStrat("old strategy before migration", old, token) fixed_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef,5, 5, 100_000, 2 * 60 * 60, 33) fixed_strategy.whitelistReward(beets, swapStepsBeets, fromGov) + # steady beets 2 pool = #33 + old_pending = masterChef.pendingBeets(33, old) + print(f'pending beets from old strategy: {old_pending}') + vault.migrateStrategy(old, fixed_strategy, fromGov) - util.stateOfStrat("old strategy after migration", strategy, token) + util.stateOfStrat("old strategy after migration", old, token) util.stateOfStrat("new strategy after migration", fixed_strategy, token) total_debt = vault.strategies(fixed_strategy)["totalDebt"] assert fixed_strategy.estimatedTotalAssets() >= total_debt + # this lets gov sweep and take x% cut + assert beets.balanceOf(fixed_strategy) >= old_pending + # exit everything out and see how much we get fixed_strategy.setEmergencyExit(fromGov) fixed_strategy.harvest(fromGov) From 91bfec1f7af007f9ae0f143a1ab4a3e9ab633a8a Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 10 Feb 2022 14:39:03 -0800 Subject: [PATCH 40/54] minor fixes --- contracts/Strategy.sol | 12 +++++++++--- tests/test_migration.py | 15 +++++++++++++-- tests/util.py | 4 ++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 5637423..cbf79b9 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -167,6 +167,7 @@ contract Strategy is BaseStrategy { return balanceOfWant().add(balanceOfPooled()); } + function prepareReturn(uint256 _debtOutstanding) internal override returns (uint256 _profit, uint256 _loss, uint256 _debtPayment){ if (_debtOutstanding > 0) { (_debtPayment, _loss) = liquidatePosition(_debtOutstanding); @@ -201,9 +202,11 @@ contract Strategy is BaseStrategy { } // put want into lp then put want-lp into masterchef - if (_joinPool()) { + _joinPool(); + uint bpts = balanceOfBpt(); + if (bpts > 0) { // put all want-lp into masterchef - masterChef.deposit(masterChefPoolId, balanceOfBpt(), address(this)); + masterChef.deposit(masterChefPoolId, bpts, address(this)); } } @@ -212,7 +215,6 @@ contract Strategy is BaseStrategy { if (_amountNeeded > looseAmount) { // convert amount still needed to bpt uint256 toExitAmount = tokensToBpts(_amountNeeded.sub(looseAmount)); - // withdraw needed bpt out of masterchef and sell it for want _withdrawFromMasterChefAndSellBpt(toExitAmount); @@ -291,6 +293,10 @@ contract Strategy is BaseStrategy { } } + function sellRewards() external onlyVaultManagers { + _sellRewards(); + } + function _sellRewards() internal { uint256 amount = balanceOfReward(); uint decReward = ERC20(address(rewardToken)).decimals(); diff --git a/tests/test_migration.py b/tests/test_migration.py index a5c87e1..3b7f418 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -32,7 +32,6 @@ def test_migration( def test_real_migration( chain, - token, vault, amount, Strategy, @@ -44,6 +43,7 @@ def test_real_migration( balancer_vault, pool, management, swapStepsBeets, beets ): old = Contract("0x56aF79e182a7f98ff6d0bF99d589ac2CabA24e2d") + token = Contract(old.want()) vault = Contract(old.vault()) gov = accounts.at(vault.governance(), force=True) fromGov = {'from': gov} @@ -66,11 +66,22 @@ def test_real_migration( # this lets gov sweep and take x% cut assert beets.balanceOf(fixed_strategy) >= old_pending + print(f'vault state: {vault.strategies(fixed_strategy)}') + + fixed_strategy.tend(fromGov) + print(f'vault state: {vault.strategies(fixed_strategy)}') - # exit everything out and see how much we get + util.stateOfStrat("old strategy after harvest", old, token) + util.stateOfStrat("new strategy after harvest", fixed_strategy, token) + + # sell everything fixed_strategy.setEmergencyExit(fromGov) + # rewards sold separately + fixed_strategy.sellRewards(fromGov) fixed_strategy.harvest(fromGov) + print(f'vault state: {vault.strategies(fixed_strategy)}') + util.stateOfStrat("debt ratio 0", fixed_strategy, token) # hopefully the gains from trading fees cancels out slippage diff --git a/tests/util.py b/tests/util.py index 5838bac..b1d8d9b 100644 --- a/tests/util.py +++ b/tests/util.py @@ -10,7 +10,7 @@ def stateOfStrat(msg, strategy, token): print(f'\n===={msg}====') wantDec = 10 ** token.decimals() print(f'Balance of {token.symbol()}: {strategy.balanceOfWant() / wantDec}') - print(f'Balance of Bpt: {strategy.balanceOfBpt() / wantDec}') - print(f'balanceOfBptInMasterChef: {strategy.balanceOfBptInMasterChef() / wantDec}') + print(f'Balance of Bpt: {strategy.balanceOfBpt() / 1e18}') + print(f'balanceOfBptInMasterChef: {strategy.balanceOfBptInMasterChef() / 1e18}') print(f'Balance of BEETS: {strategy.balanceOfReward()/ wantDec}') print(f'Estimated Total Assets: {strategy.estimatedTotalAssets() / wantDec}') From ad2f84c9f299bccdb23488d0d5da1677c18f7804 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 10 Feb 2022 15:10:22 -0800 Subject: [PATCH 41/54] external depositToMC --- contracts/Strategy.sol | 14 ++++++++++---- scripts/flatten.py | 6 ++++-- tests/test_migration.py | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index cbf79b9..3fb0b25 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -202,14 +202,20 @@ contract Strategy is BaseStrategy { } // put want into lp then put want-lp into masterchef - _joinPool(); - uint bpts = balanceOfBpt(); - if (bpts > 0) { + if (_joinPool()) { // put all want-lp into masterchef - masterChef.deposit(masterChefPoolId, bpts, address(this)); + _depositIntoMasterChef(balanceOfBpt()); } } + function depositIntoMasterChef(uint _bpts) external onlyVaultManagers { + _depositIntoMasterChef(_bpts); + } + + function _depositIntoMasterChef(uint _bpts) internal { + masterChef.deposit(masterChefPoolId, _bpts, address(this)); + } + function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss){ uint256 looseAmount = balanceOfWant(); if (_amountNeeded > looseAmount) { diff --git a/scripts/flatten.py b/scripts/flatten.py index 3dfddc2..ec4ee03 100644 --- a/scripts/flatten.py +++ b/scripts/flatten.py @@ -1,6 +1,8 @@ -from brownie import Strategy, accounts, config, network, project, web3 +from brownie import Strategy, StrategyFactory, accounts, config, network, project, web3 def main(): with open('./build/contracts/StrategyFlat.sol', 'w') as f: - f.write(Strategy.get_verification_info()['flattened_source']) \ No newline at end of file + f.write(Strategy.get_verification_info()['flattened_source']) + with open('./build/contracts/StrategyFactoryFlat.sol', 'w') as f: + f.write(StrategyFactory.get_verification_info()['flattened_source']) \ No newline at end of file diff --git a/tests/test_migration.py b/tests/test_migration.py index 3b7f418..4dcba9e 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -2,6 +2,7 @@ from brownie import accounts, Contract import util + def test_migration( chain, token, @@ -30,6 +31,7 @@ def test_migration( vault.migrateStrategy(strategy, new_strategy, {"from": gov}) assert (pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount) + def test_real_migration( chain, vault, @@ -50,7 +52,8 @@ def test_real_migration( util.stateOfStrat("old strategy before migration", old, token) - fixed_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef,5, 5, 100_000, 2 * 60 * 60, 33) + fixed_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 5, 5, 100_000, 2 * 60 * 60, + 33) fixed_strategy.whitelistReward(beets, swapStepsBeets, fromGov) # steady beets 2 pool = #33 @@ -68,7 +71,7 @@ def test_real_migration( assert beets.balanceOf(fixed_strategy) >= old_pending print(f'vault state: {vault.strategies(fixed_strategy)}') - fixed_strategy.tend(fromGov) + fixed_strategy.depositIntoMasterChef(fixed_strategy.balanceOfBpt(), fromGov) print(f'vault state: {vault.strategies(fixed_strategy)}') util.stateOfStrat("old strategy after harvest", old, token) From de7f4e4048bb27eeb957446eb63d74bfd542112d Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 10 Feb 2022 16:20:37 -0800 Subject: [PATCH 42/54] flatten script and remove unused --- contracts/Strategy.sol | 6 +++--- scripts/flatten.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 3fb0b25..122c13d 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -392,12 +392,12 @@ contract Strategy is BaseStrategy { } // this allows us to also sell stakedBpts externally - function sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256 _maxBpts) external onlyVaultManagers { - _sellBpt(_amountBpts, _assets, _tokenIndex, _balancerPoolId, _maxBpts); + function sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId) external onlyVaultManagers { + _sellBpt(_amountBpts, _assets, _tokenIndex, _balancerPoolId); } // sell bpt for want at current bpt rate - function _sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId, uint256 _maxBpts) internal { + function _sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId) internal { _amountBpts = Math.min(_amountBpts, balanceOfBpt()); if (_amountBpts > 0) { uint256[] memory minAmountsOut = new uint256[](numTokens); diff --git a/scripts/flatten.py b/scripts/flatten.py index ec4ee03..a87ee12 100644 --- a/scripts/flatten.py +++ b/scripts/flatten.py @@ -3,6 +3,8 @@ def main(): with open('./build/contracts/StrategyFlat.sol', 'w') as f: - f.write(Strategy.get_verification_info()['flattened_source']) + Strategy.get_verification_info() + f.write(Strategy._flattener.flattened_source) with open('./build/contracts/StrategyFactoryFlat.sol', 'w') as f: - f.write(StrategyFactory.get_verification_info()['flattened_source']) \ No newline at end of file + StrategyFactory.get_verification_info() + f.write(StrategyFactory._flattener.flattened_source) From c4d9d9c1c51e4fef94cd71d704a285cac054f600 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Thu, 10 Feb 2022 16:48:32 -0800 Subject: [PATCH 43/54] factory name + sellbpt param --- contracts/Strategy.sol | 4 ++-- contracts/StrategyFactory.sol | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 122c13d..1fb60ef 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -235,7 +235,7 @@ contract Strategy is BaseStrategy { function liquidateAllPositions() internal override returns (uint256 liquidated) { _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); // sell all bpt - _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId, balanceOfBpt()); + _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId); liquidated = balanceOfWant(); return liquidated; } @@ -285,7 +285,7 @@ contract Strategy is BaseStrategy { _withdrawFromMasterChef(address(this), _amountBpt, masterChefPoolId); // sell the desired amount for want - _sellBpt(_amountBpt, assets, tokenIndex, balancerPoolId, balanceOfBpt()); + _sellBpt(_amountBpt, assets, tokenIndex, balancerPoolId); } diff --git a/contracts/StrategyFactory.sol b/contracts/StrategyFactory.sol index 610ca31..aeed03e 100644 --- a/contracts/StrategyFactory.sol +++ b/contracts/StrategyFactory.sol @@ -42,8 +42,7 @@ contract StrategyFactory { return string( abi.encodePacked( - "Factory", - Strategy(payable(original)).name(), + "Factory", "ssbV2", "@", Strategy(payable(original)).apiVersion() ) From 52be3e2bde0b992dd6663dd5c403e13cbf6d19a5 Mon Sep 17 00:00:00 2001 From: wavey0x Date: Sun, 13 Feb 2022 13:33:23 -0500 Subject: [PATCH 44/54] feat: masterchef revert fix --- contracts/Strategy.sol | 27 ++++++++++----- tests/conftest.py | 7 ++-- tests/test_fix.py | 77 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 tests/test_fix.py diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 1fb60ef..11b772a 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -269,10 +269,15 @@ contract Strategy is BaseStrategy { // AbandonRewards withdraws lp without rewards. Specify where to withdraw to function _withdrawFromMasterChef(address _to, uint256 _amountBpt, uint256 _masterChefPoolId) internal { + _amountBpt = Math.min(balanceOfBptInMasterChef(), _amountBpt); if (_amountBpt > 0) { - toggles.abandonRewards - ? masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)) - : masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); + if(toggles.abandonRewards){ + masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)); + } + else{ + masterChef.withdrawAndHarvest(_masterChefPoolId, balanceOfBptInMasterChef(), address(_to)); + _depositIntoMasterChef(balanceOfBpt() - _amountBpt); + } } } @@ -291,11 +296,13 @@ contract Strategy is BaseStrategy { // claim all beets rewards from masterchef function _claimRewards() internal { - uint256 rewardBal = balanceOfReward(); - masterChef.harvest(masterChefPoolId, address(this)); - uint256 keepBal = balanceOfReward().sub(rewardBal).mul(keepBips).div(basisOne); - if (keepBal > 0) { - rewardToken.safeTransfer(keep, keepBal); + if(getPendingBeets() > 0){ + uint256 rewardBal = balanceOfReward(); + masterChef.harvest(masterChefPoolId, address(this)); + uint256 keepBal = balanceOfReward().sub(rewardBal).mul(keepBips).div(basisOne); + if (keepBal > 0) { + rewardToken.safeTransfer(keep, keepBal); + } } } @@ -352,6 +359,10 @@ contract Strategy is BaseStrategy { (_amount,) = masterChef.userInfo(masterChefPoolId, address(this)); } + function getPendingBeets() public view returns (uint256){ + return masterChef.pendingBeets(masterChefPoolId, address(this)); + } + function balanceOfReward() public view returns (uint256 _amount){ return rewardToken.balanceOf(address(this)); } diff --git a/tests/conftest.py b/tests/conftest.py index e37de3c..0a94926 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,10 @@ def isolation(fn_isolation): @pytest.fixture def gov(accounts): - yield accounts.at("0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52", force=True) + if chain.id == 1: + yield accounts.at("0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52", force=True) + if chain.id == 250: + yield accounts.at("0xC0E2830724C946a6748dDFE09753613cd38f6767", force=True) @pytest.fixture @@ -205,4 +208,4 @@ def strategy(strategist, keeper, vault, strategyFactory, gov, balancer_vault, po def RELATIVE_APPROX(): # making this more lenient bc of single # sided deposits incurring slippage - yield 1e-3 + yield 1e-3 \ No newline at end of file diff --git a/tests/test_fix.py b/tests/test_fix.py new file mode 100644 index 0000000..20b0a45 --- /dev/null +++ b/tests/test_fix.py @@ -0,0 +1,77 @@ +import pytest +from brownie import accounts, Contract +import util + +def test_real_migration_and_multiple_harvest( + chain, + vault, + amount, + Strategy, + strategist, + gov, + user, + masterChef, + RELATIVE_APPROX, + balancer_vault, pool, management, swapStepsBeets, beets +): + old = Contract("0xF864f92e88054AA05639324090b411C1D55B4a5B") + token = Contract(old.want()) + vault = Contract(old.vault()) + gov = accounts.at(vault.governance(), force=True) + fromGov = {'from': gov} + + util.stateOfStrat("old strategy before migration", old, token) + + fixed_strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, masterChef, 5, 5, 100_000, 2 * 60 * 60, + 33) + fixed_strategy.whitelistReward(beets, swapStepsBeets, fromGov) + + + + # steady beets 2 pool = #33 + old_pending = masterChef.pendingBeets(33, old) + print(f'pending beets from old strategy: {old_pending}') + + vault.migrateStrategy(old, fixed_strategy, fromGov) + util.stateOfStrat("old strategy after migration", old, token) + util.stateOfStrat("new strategy after migration", fixed_strategy, token) + + total_debt = vault.strategies(fixed_strategy)["totalDebt"] + assert fixed_strategy.estimatedTotalAssets() >= total_debt + + # this lets gov sweep and take x% cut + assert beets.balanceOf(fixed_strategy) >= old_pending + print(f'vault state: {vault.strategies(fixed_strategy)}') + + fixed_strategy.depositIntoMasterChef(fixed_strategy.balanceOfBpt(), fromGov) + print(f'vault state: {vault.strategies(fixed_strategy)}') + + util.stateOfStrat("old strategy after harvest", old, token) + util.stateOfStrat("new strategy after harvest", fixed_strategy, token) + + tx = fixed_strategy.harvest(fromGov) + tx = fixed_strategy.harvest(fromGov) + + # sell everything + # fixed_strategy.setEmergencyExit(fromGov) + fixed_strategy.updateStrategyDebtRatio(0,fromGov) + # rewards sold separately + fixed_strategy.sellRewards(fromGov) + + fixed_strategy.setParams( + 5_000, + 5_000, + fixed_strategy.maxSingleDeposit(), + fixed_strategy.minDepositPeriod(), + fromGov + ) + fixed_strategy.setDoHealthCheck(False, fromGov) + fixed_strategy.harvest(fromGov) + + print(f'vault state: {vault.strategies(fixed_strategy)}') + + util.stateOfStrat("debt ratio 0", fixed_strategy, token) + + # hopefully the gains from trading fees cancels out slippage + print(f'net loss from exit: {vault.strategies(fixed_strategy)["totalLoss"]}') + assert fixed_strategy.estimatedTotalAssets() == 0 From a7e7a04a18b37155b11e054c673eb6ead8deb83e Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Sun, 13 Feb 2022 17:25:28 -0500 Subject: [PATCH 45/54] fix: bug fix proposal --- contracts/Strategy.sol | 48 +++++++++++++++++++++++++----------------- tests/test_fix.py | 14 ++++++------ 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 11b772a..3c32a10 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -169,30 +169,40 @@ contract Strategy is BaseStrategy { function prepareReturn(uint256 _debtOutstanding) internal override returns (uint256 _profit, uint256 _loss, uint256 _debtPayment){ - if (_debtOutstanding > 0) { - (_debtPayment, _loss) = liquidatePosition(_debtOutstanding); - } - - uint256 beforeWant = balanceOfWant(); - - _collectTradingFees(); - // claim beets _claimRewards(); - // this would allow finer control over harvesting to get credits in without selling if (toggles.doSellRewards) { _sellRewards(); } - uint256 afterWant = balanceOfWant(); - _profit = afterWant.sub(beforeWant); - if (_profit > _loss) { - _profit = _profit.sub(_loss); - _debtPayment = _debtPayment.add(_loss); - _loss = 0; - } else { + uint256 totalDebt = vault.strategies(address(this)).totalDebt; + uint256 totalAssetsAfterProfit = estimatedTotalAssets(); + + _profit = totalAssetsAfterProfit > totalDebt + ? totalAssetsAfterProfit.sub(totalDebt) + : 0; + + uint256 _amountFreed; + uint256 _toLiquidate = _debtOutstanding.add(_profit); + if (_toLiquidate > 0) { + (_amountFreed, _loss) = liquidatePosition(_toLiquidate); + } + + _debtPayment = Math.min(_debtOutstanding, _amountFreed); + + if (_loss > _profit) { + // Example: + // debtOutstanding 100, profit 50, _amountFreed 100, _loss 50 + // loss should be 0, (50-50) + // profit should endup in 0 _loss = _loss.sub(_profit); - _debtPayment = _debtPayment.add(_profit); _profit = 0; + } else { + // Example: + // debtOutstanding 100, profit 50, _amountFreed 140, _loss 10 + // _profit should be 40, (50 profit - 10 loss) + // loss should end up in 0 + _profit = _profit.sub(_loss); + _loss = 0; } } @@ -275,8 +285,8 @@ contract Strategy is BaseStrategy { masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)); } else{ - masterChef.withdrawAndHarvest(_masterChefPoolId, balanceOfBptInMasterChef(), address(_to)); - _depositIntoMasterChef(balanceOfBpt() - _amountBpt); + masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); + /* _depositIntoMasterChef(balanceOfBpt() - _amountBpt); */ } } } diff --git a/tests/test_fix.py b/tests/test_fix.py index 20b0a45..0c43123 100644 --- a/tests/test_fix.py +++ b/tests/test_fix.py @@ -26,7 +26,7 @@ def test_real_migration_and_multiple_harvest( 33) fixed_strategy.whitelistReward(beets, swapStepsBeets, fromGov) - + # steady beets 2 pool = #33 old_pending = masterChef.pendingBeets(33, old) @@ -51,16 +51,16 @@ def test_real_migration_and_multiple_harvest( tx = fixed_strategy.harvest(fromGov) tx = fixed_strategy.harvest(fromGov) - + # sell everything - # fixed_strategy.setEmergencyExit(fromGov) - fixed_strategy.updateStrategyDebtRatio(0,fromGov) + fixed_strategy.setEmergencyExit(fromGov) + # fixed_strategy.updateStrategyDebtRatio(0,fromGov) # rewards sold separately - fixed_strategy.sellRewards(fromGov) + # fixed_strategy.sellRewards(fromGov) fixed_strategy.setParams( - 5_000, - 5_000, + 2_000, + 2_000, fixed_strategy.maxSingleDeposit(), fixed_strategy.minDepositPeriod(), fromGov From 6d6170134a15df3b07687c7dc7f4fbf13bffa587 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Sun, 13 Feb 2022 16:20:33 -0800 Subject: [PATCH 46/54] add toggle + remove min decimal sell --- contracts/Strategy.sol | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 3c32a10..d7ea0cb 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -21,8 +21,8 @@ contract Strategy is BaseStrategy { IAsset[] internal assets; SwapSteps internal swapSteps; bytes32 public balancerPoolId; - uint8 public numTokens; - uint8 public tokenIndex; + uint8 internal numTokens; + uint8 internal tokenIndex; Toggles public toggles; // The keep mechanism is intended for governance voting purposes. Strategy will route a percentage (default 10%) @@ -39,6 +39,7 @@ contract Strategy is BaseStrategy { } struct Toggles { + bool claimRewards; bool doSellRewards; bool abandonRewards; } @@ -127,7 +128,7 @@ contract Strategy is BaseStrategy { balancerPoolId = bpt.getPoolId(); balancerVault = IBalancerVault(_balancerVault); (IERC20[] memory tokens,,) = balancerVault.getPoolTokens(balancerPoolId); - require(tokens.length > 0, "Empty Pool"); + require(tokens.length > 0, "empty"); numTokens = uint8(tokens.length); assets = new IAsset[](numTokens); tokenIndex = type(uint8).max; @@ -137,7 +138,7 @@ contract Strategy is BaseStrategy { } assets[i] = IAsset(address(tokens[i])); } - require(tokenIndex != type(uint8).max, "token not in pool!"); + require(tokenIndex != type(uint8).max, "!inPool"); maxSlippageIn = _maxSlippageIn; maxSlippageOut = _maxSlippageOut; @@ -154,7 +155,7 @@ contract Strategy is BaseStrategy { keep = governance(); keepBips = 1000; - toggles = Toggles({doSellRewards : true, abandonRewards : false}); + toggles = Toggles({claimRewards : true, doSellRewards : true, abandonRewards : false}); } // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ @@ -167,9 +168,10 @@ contract Strategy is BaseStrategy { return balanceOfWant().add(balanceOfPooled()); } - function prepareReturn(uint256 _debtOutstanding) internal override returns (uint256 _profit, uint256 _loss, uint256 _debtPayment){ - _claimRewards(); + if(toggles.claimRewards){ + _claimRewards(); + } if (toggles.doSellRewards) { _sellRewards(); } @@ -178,8 +180,8 @@ contract Strategy is BaseStrategy { uint256 totalAssetsAfterProfit = estimatedTotalAssets(); _profit = totalAssetsAfterProfit > totalDebt - ? totalAssetsAfterProfit.sub(totalDebt) - : 0; + ? totalAssetsAfterProfit.sub(totalDebt) + : 0; uint256 _amountFreed; uint256 _toLiquidate = _debtOutstanding.add(_profit); @@ -239,7 +241,7 @@ contract Strategy is BaseStrategy { } else { _liquidatedAmount = _amountNeeded; } - require(_amountNeeded == _liquidatedAmount.add(_loss), "!sanitycheck"); + require(_amountNeeded == _liquidatedAmount.add(_loss), "!check"); } function liquidateAllPositions() internal override returns (uint256 liquidated) { @@ -281,10 +283,10 @@ contract Strategy is BaseStrategy { function _withdrawFromMasterChef(address _to, uint256 _amountBpt, uint256 _masterChefPoolId) internal { _amountBpt = Math.min(balanceOfBptInMasterChef(), _amountBpt); if (_amountBpt > 0) { - if(toggles.abandonRewards){ + if (toggles.abandonRewards) { masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)); } - else{ + else { masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); /* _depositIntoMasterChef(balanceOfBpt() - _amountBpt); */ } @@ -303,10 +305,13 @@ contract Strategy is BaseStrategy { _sellBpt(_amountBpt, assets, tokenIndex, balancerPoolId); } + function claimRewards() external onlyVaultManagers { + _claimRewards(); + } // claim all beets rewards from masterchef function _claimRewards() internal { - if(getPendingBeets() > 0){ + if (getPendingBeets() > 0) { uint256 rewardBal = balanceOfReward(); masterChef.harvest(masterChefPoolId, address(this)); uint256 keepBal = balanceOfReward().sub(rewardBal).mul(keepBips).div(basisOne); @@ -322,10 +327,7 @@ contract Strategy is BaseStrategy { function _sellRewards() internal { uint256 amount = balanceOfReward(); - uint decReward = ERC20(address(rewardToken)).decimals(); - uint decWant = ERC20(address(want)).decimals(); - - if (amount > 10 ** (decReward > decWant ? decReward.sub(decWant) : 0)) { + if (amount > 0) { uint length = swapSteps.poolIds.length; IBalancerVault.BatchSwapStep[] memory steps = new IBalancerVault.BatchSwapStep[](length); int[] memory limits = new int[](length + 1); @@ -463,7 +465,8 @@ contract Strategy is BaseStrategy { minDepositPeriod = _minDepositPeriod; } - function setToggles(bool _doSellRewards, bool _abandon) external onlyVaultManagers { + function setToggles(bool _claimRewards, bool _doSellRewards, bool _abandon) external onlyVaultManagers { + toggles.claimRewards = _claimRewards; toggles.doSellRewards = _doSellRewards; toggles.abandonRewards = _abandon; } @@ -482,7 +485,7 @@ contract Strategy is BaseStrategy { // Balancer requires this contract to be payable, so we add ability to sweep stuck ETH function sweepETH() public onlyGovernance { (bool success,) = governance().call{value : address(this).balance}(""); - require(success, "FailedETHSweep"); + require(success, "eth"); } receive() external payable {} From ff5ff5069ce28634237edf20afbb23755f50cf04 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Sun, 13 Feb 2022 17:34:31 -0800 Subject: [PATCH 47/54] fix tests --- tests/test_fix.py | 1 + tests/test_migration.py | 10 ++++++++-- tests/test_operation.py | 9 +++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/test_fix.py b/tests/test_fix.py index 0c43123..a440a35 100644 --- a/tests/test_fix.py +++ b/tests/test_fix.py @@ -66,6 +66,7 @@ def test_real_migration_and_multiple_harvest( fromGov ) fixed_strategy.setDoHealthCheck(False, fromGov) + chain.sleep(1) fixed_strategy.harvest(fromGov) print(f'vault state: {vault.strategies(fixed_strategy)}') diff --git a/tests/test_migration.py b/tests/test_migration.py index 4dcba9e..749cc40 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -1,6 +1,7 @@ import pytest from brownie import accounts, Contract import util +import brownie def test_migration( @@ -44,7 +45,7 @@ def test_real_migration( RELATIVE_APPROX, balancer_vault, pool, management, swapStepsBeets, beets ): - old = Contract("0x56aF79e182a7f98ff6d0bF99d589ac2CabA24e2d") + old = Contract("0xF864f92e88054AA05639324090b411C1D55B4a5B") token = Contract(old.want()) vault = Contract(old.vault()) gov = accounts.at(vault.governance(), force=True) @@ -61,9 +62,9 @@ def test_real_migration( print(f'pending beets from old strategy: {old_pending}') vault.migrateStrategy(old, fixed_strategy, fromGov) + fixed_strategy.depositIntoMasterChef(fixed_strategy.balanceOfBptInMasterChef(), fromGov) util.stateOfStrat("old strategy after migration", old, token) util.stateOfStrat("new strategy after migration", fixed_strategy, token) - total_debt = vault.strategies(fixed_strategy)["totalDebt"] assert fixed_strategy.estimatedTotalAssets() >= total_debt @@ -81,6 +82,11 @@ def test_real_migration( fixed_strategy.setEmergencyExit(fromGov) # rewards sold separately fixed_strategy.sellRewards(fromGov) + with brownie.reverts(): + fixed_strategy.harvest(fromGov) + + fixed_strategy.setParams(fixed_strategy.maxSlippageIn(), 10000, fixed_strategy.maxSingleDeposit(), fixed_strategy.minDepositPeriod(), fromGov) + chain.sleep(1) fixed_strategy.harvest(fromGov) print(f'vault state: {vault.strategies(fixed_strategy)}') diff --git a/tests/test_operation.py b/tests/test_operation.py index bb8680d..d2213e2 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -231,6 +231,7 @@ def test_triggers( strategy.harvestTrigger(0) strategy.tendTrigger(0) + # simulate a bad deposit, aka pool has too much of the want you're trying to deposit already def test_unbalance_deposit(chain, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX, token2_whale, token2, token_whale, gov, pool, balancer_vault): @@ -239,7 +240,7 @@ def test_unbalance_deposit(chain, token, vault, strategy, user, strategist, amou assert token.balanceOf(vault.address) == amount print(f'pool rate before whale swap: {pool.getRate()}') - pooled = balancer_vault.getPoolTokens(pool.getPoolId())[1][strategy.tokenIndex()] + pooled = balancer_vault.getPoolTokens(pool.getPoolId())[1][0] token.approve(balancer_vault, 2 ** 256 - 1, {'from': token_whale}) chain.snapshot() @@ -259,7 +260,7 @@ def test_unbalance_deposit(chain, token, vault, strategy, user, strategist, amou # simulate bad pool state by whale to swap out 98% of one side of the pool so pool only has excess want singleSwap = (pool.getPoolId(), 1, token, token2, pooled2 * 0.98, b'0x0') balancer_vault.swap(singleSwap, (token_whale, False, token_whale, False), token.balanceOf(token_whale), - chain.time(), {'from': token_whale}) + 2 ** 256 - 1, {'from': token_whale}) print(balancer_vault.getPoolTokens(pool.getPoolId())) print(f'pool rate: {pool.getRate()}') @@ -306,7 +307,7 @@ def test_unbalanced_pool_withdraw(chain, token, vault, strategy, user, strategis token2Index = 1 util.stateOfStrat("after deposit all ", strategy, token) - pooled = balancer_vault.getPoolTokens(pool.getPoolId())[1][strategy.tokenIndex()] + pooled = balancer_vault.getPoolTokens(pool.getPoolId())[1][0] print(balancer_vault.getPoolTokens(pool.getPoolId())) print(f'pooled: {pooled}') token2.approve(balancer_vault, 2 ** 256 - 1, {'from': token2_whale}) @@ -314,7 +315,7 @@ def test_unbalanced_pool_withdraw(chain, token, vault, strategy, user, strategis # simulate bad pool state by whale to swap out 98% of one side of the pool so pool only has 2% of the original want singleSwap = (pool.getPoolId(), 1, token2, token, pooled * 0.98, b'0x0') balancer_vault.swap(singleSwap, (token2_whale, False, token2_whale, False), token2.balanceOf(token2_whale), - chain.time(), {'from': token2_whale}) + 2 ** 256 - 1, {'from': token2_whale}) print(balancer_vault.getPoolTokens(pool.getPoolId())) print(f'pool rate: {pool.getRate()}') From a1af4dd5b7a489d87144d0bff3e139538e763198 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Mon, 14 Feb 2022 07:15:03 -0500 Subject: [PATCH 48/54] fix: remove unused lines --- contracts/Strategy.sol | 11 ----------- tests/util.py | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index d7ea0cb..bc146af 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -288,7 +288,6 @@ contract Strategy is BaseStrategy { } else { masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); - /* _depositIntoMasterChef(balanceOfBpt() - _amountBpt); */ } } } @@ -349,16 +348,6 @@ contract Strategy is BaseStrategy { } } - function _collectTradingFees() internal { - uint256 total = estimatedTotalAssets(); - uint256 debt = vault.strategies(address(this)).totalDebt; - // if there is a profit from trading fees, we sell it for want - if (total > debt) { - uint256 profit = total.sub(debt); - _withdrawFromMasterChefAndSellBpt(tokensToBpts(profit)); - } - } - function balanceOfWant() public view returns (uint256 _amount){ return want.balanceOf(address(this)); } diff --git a/tests/util.py b/tests/util.py index b1d8d9b..983c481 100644 --- a/tests/util.py +++ b/tests/util.py @@ -3,7 +3,7 @@ def airdrop_rewards(strategy, beets, beets_whale): beets.approve(strategy, 2 ** 256 - 1, {'from': beets_whale}) - beets.transfer(strategy, 8_000 * 1e18, {'from': beets_whale}) + beets.transfer(strategy, 3_000 * 1e18, {'from': beets_whale}) def stateOfStrat(msg, strategy, token): From 66a0ff519ef294f873593e20f6d7d2df72480089 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Mon, 14 Feb 2022 09:08:59 -0500 Subject: [PATCH 49/54] fix: minor change --- contracts/Strategy.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index bc146af..7880d7b 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -180,8 +180,8 @@ contract Strategy is BaseStrategy { uint256 totalAssetsAfterProfit = estimatedTotalAssets(); _profit = totalAssetsAfterProfit > totalDebt - ? totalAssetsAfterProfit.sub(totalDebt) - : 0; + ? totalAssetsAfterProfit.sub(totalDebt) + : 0; uint256 _amountFreed; uint256 _toLiquidate = _debtOutstanding.add(_profit); From 873b0dab9d17ce28aff8e3380f9379143e640721 Mon Sep 17 00:00:00 2001 From: wavey <82397455+wavey0x@users.noreply.github.com> Date: Mon, 14 Feb 2022 10:50:51 -0500 Subject: [PATCH 50/54] fix: remove unneeded return --- contracts/Strategy.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 7880d7b..5cc6200 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -249,7 +249,6 @@ contract Strategy is BaseStrategy { // sell all bpt _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId); liquidated = balanceOfWant(); - return liquidated; } // note that this withdraws into newStrategy. From f4d77fc7fa69e91d832de1b7890a173d58b02fb4 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Mon, 14 Feb 2022 12:49:36 -0500 Subject: [PATCH 51/54] fix: simplify code --- contracts/Strategy.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 5cc6200..3554438 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -282,12 +282,9 @@ contract Strategy is BaseStrategy { function _withdrawFromMasterChef(address _to, uint256 _amountBpt, uint256 _masterChefPoolId) internal { _amountBpt = Math.min(balanceOfBptInMasterChef(), _amountBpt); if (_amountBpt > 0) { - if (toggles.abandonRewards) { - masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)); - } - else { - masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); - } + toggles.abandonRewards + ? masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)) + : masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); } } From 4cc8d229b76206e9e653e1e8c6866ef6be4c01de Mon Sep 17 00:00:00 2001 From: Tonkers Date: Tue, 15 Feb 2022 15:33:03 -0800 Subject: [PATCH 52/54] disallow sell/withdraw of other bpts --- contracts/Strategy.sol | 32 ++++++++++++++++---------------- tests/test_operation.py | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 3554438..4b5b25d 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -245,15 +245,15 @@ contract Strategy is BaseStrategy { } function liquidateAllPositions() internal override returns (uint256 liquidated) { - _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef(), masterChefPoolId); + _withdrawFromMasterChef(address(this), balanceOfBptInMasterChef()); // sell all bpt - _sellBpt(balanceOfBpt(), assets, tokenIndex, balancerPoolId); + _sellBpt(balanceOfBpt()); liquidated = balanceOfWant(); } // note that this withdraws into newStrategy. function prepareMigration(address _newStrategy) internal override { - _withdrawFromMasterChef(_newStrategy, balanceOfBptInMasterChef(), masterChefPoolId); + _withdrawFromMasterChef(_newStrategy, balanceOfBptInMasterChef()); uint256 _balanceOfBpt = balanceOfBpt(); if (_balanceOfBpt > 0) { bpt.transfer(_newStrategy, _balanceOfBpt); @@ -274,17 +274,17 @@ contract Strategy is BaseStrategy { // HELPERS // - function withdrawFromMasterChef(uint256 _amountBpt, uint256 _masterChefPooId) external onlyVaultManagers { - _withdrawFromMasterChef(address(this), _amountBpt, _masterChefPooId); + function withdrawFromMasterChef(uint256 _amountBpt) external onlyVaultManagers { + _withdrawFromMasterChef(address(this), _amountBpt); } // AbandonRewards withdraws lp without rewards. Specify where to withdraw to - function _withdrawFromMasterChef(address _to, uint256 _amountBpt, uint256 _masterChefPoolId) internal { + function _withdrawFromMasterChef(address _to, uint256 _amountBpt) internal { _amountBpt = Math.min(balanceOfBptInMasterChef(), _amountBpt); if (_amountBpt > 0) { toggles.abandonRewards - ? masterChef.emergencyWithdraw(_masterChefPoolId, address(_to)) - : masterChef.withdrawAndHarvest(_masterChefPoolId, _amountBpt, address(_to)); + ? masterChef.emergencyWithdraw(masterChefPoolId, address(_to)) + : masterChef.withdrawAndHarvest(masterChefPoolId, _amountBpt, address(_to)); } } @@ -294,10 +294,10 @@ contract Strategy is BaseStrategy { _amountBpt = Math.min(_amountBpt, balanceOfBptInMasterChef()); // withdraw the desired amount out of masterchef - _withdrawFromMasterChef(address(this), _amountBpt, masterChefPoolId); + _withdrawFromMasterChef(address(this), _amountBpt); // sell the desired amount for want - _sellBpt(_amountBpt, assets, tokenIndex, balancerPoolId); + _sellBpt(_amountBpt); } function claimRewards() external onlyVaultManagers { @@ -400,19 +400,19 @@ contract Strategy is BaseStrategy { } // this allows us to also sell stakedBpts externally - function sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId) external onlyVaultManagers { - _sellBpt(_amountBpts, _assets, _tokenIndex, _balancerPoolId); + function sellBpt(uint256 _amountBpts) external onlyVaultManagers { + _sellBpt(_amountBpts); } // sell bpt for want at current bpt rate - function _sellBpt(uint256 _amountBpts, IAsset[] memory _assets, uint256 _tokenIndex, bytes32 _balancerPoolId) internal { + function _sellBpt(uint256 _amountBpts) internal { _amountBpts = Math.min(_amountBpts, balanceOfBpt()); if (_amountBpts > 0) { uint256[] memory minAmountsOut = new uint256[](numTokens); minAmountsOut[tokenIndex] = bptsToTokens(_amountBpts).mul(basisOne.sub(maxSlippageOut)).div(basisOne); - bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, _amountBpts, _tokenIndex); - IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(_assets, minAmountsOut, userData, false); - balancerVault.exitPool(_balancerPoolId, address(this), address(this), request); + bytes memory userData = abi.encode(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, _amountBpts, tokenIndex); + IBalancerVault.ExitPoolRequest memory request = IBalancerVault.ExitPoolRequest(assets, minAmountsOut, userData, false); + balancerVault.exitPool(balancerPoolId, address(this), address(this), request); } } diff --git a/tests/test_operation.py b/tests/test_operation.py index d2213e2..22b2f34 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -64,7 +64,7 @@ def test_manual_exit( # some slippage won't pass healthcheck strategy.harvest({"from": strategist}) - strategy.withdrawFromMasterChef(strategy.balanceOfBptInMasterChef(), strategy.masterChefPoolId(), {"from": gov}) + strategy.withdrawFromMasterChef(strategy.balanceOfBptInMasterChef(), {"from": gov}) assert strategy.balanceOfBpt() > 0 assert strategy.balanceOfBptInMasterChef() == 0 From 3194beec9d24c5a67f0b98bc7eaab931beb0fbdd Mon Sep 17 00:00:00 2001 From: Tonkers Date: Tue, 15 Feb 2022 15:38:05 -0800 Subject: [PATCH 53/54] fix comments --- contracts/Strategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 4b5b25d..4a2cfbf 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -399,7 +399,7 @@ contract Strategy is BaseStrategy { ); } - // this allows us to also sell stakedBpts externally + // this allows us to also sell bpt externally function sellBpt(uint256 _amountBpts) external onlyVaultManagers { _sellBpt(_amountBpts); } From 1134be8c4b214fa335e12498bae5bf5febc02146 Mon Sep 17 00:00:00 2001 From: Tonkers Date: Fri, 4 Mar 2022 15:35:11 -0700 Subject: [PATCH 54/54] add stablepool sourcecode --- sourcecodes/stablepool.json | 129 ++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 sourcecodes/stablepool.json diff --git a/sourcecodes/stablepool.json b/sourcecodes/stablepool.json new file mode 100644 index 0000000..e3c9736 --- /dev/null +++ b/sourcecodes/stablepool.json @@ -0,0 +1,129 @@ +{ + "language": "Solidity", + "sources": { + "contracts/StablePool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/WordCodec.sol\";\n\nimport \"@balancer-labs/v2-pool-utils/contracts/BaseGeneralPool.sol\";\nimport \"@balancer-labs/v2-pool-utils/contracts/BaseMinimalSwapInfoPool.sol\";\n\nimport \"./StableMath.sol\";\nimport \"./StablePoolUserDataHelpers.sol\";\n\ncontract StablePool is BaseGeneralPool, BaseMinimalSwapInfoPool, StableMath {\n using FixedPoint for uint256;\n using StablePoolUserDataHelpers for bytes;\n using WordCodec for bytes32;\n\n // This contract uses timestamps to slowly update its Amplification parameter over time. These changes must occur\n // over a minimum time period much larger than the blocktime, making timestamp manipulation a non-issue.\n // solhint-disable not-rely-on-time\n\n // Amplification factor changes must happen over a minimum period of one day, and can at most divide or multiple the\n // current value by 2 every day.\n // WARNING: this only limits *a single* amplification change to have a maximum rate of change of twice the original\n // value daily. It is possible to perform multiple amplification changes in sequence to increase this value more\n // rapidly: for example, by doubling the value every day it can increase by a factor of 8 over three days (2^3).\n uint256 private constant _MIN_UPDATE_TIME = 1 days;\n uint256 private constant _MAX_AMP_UPDATE_DAILY_RATE = 2;\n\n bytes32 private _packedAmplificationData;\n\n event AmpUpdateStarted(uint256 startValue, uint256 endValue, uint256 startTime, uint256 endTime);\n event AmpUpdateStopped(uint256 currentValue);\n\n // To track how many tokens are owed to the Vault as protocol fees, we measure and store the value of the invariant\n // after every join and exit. All invariant growth that happens between join and exit events is due to swap fees.\n uint256 private _lastInvariant;\n // Because the invariant depends on the amplification parameter, and this value may change over time, we should only\n // compare invariants that were computed using the same value. We therefore store it whenever we store\n // _lastInvariant.\n uint256 private _lastInvariantAmp;\n\n enum JoinKind { INIT, EXACT_TOKENS_IN_FOR_BPT_OUT, TOKEN_IN_FOR_EXACT_BPT_OUT }\n enum ExitKind { EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, EXACT_BPT_IN_FOR_TOKENS_OUT, BPT_IN_FOR_EXACT_TOKENS_OUT }\n\n constructor(\n IVault vault,\n string memory name,\n string memory symbol,\n IERC20[] memory tokens,\n uint256 amplificationParameter,\n uint256 swapFeePercentage,\n uint256 pauseWindowDuration,\n uint256 bufferPeriodDuration,\n address owner\n )\n BasePool(\n vault,\n // Because we're inheriting from both BaseGeneralPool and BaseMinimalSwapInfoPool we can choose any\n // specialization setting. Since this Pool never registers or deregisters any tokens after construction,\n // picking Two Token when the Pool only has two tokens is free gas savings.\n tokens.length == 2 ? IVault.PoolSpecialization.TWO_TOKEN : IVault.PoolSpecialization.GENERAL,\n name,\n symbol,\n tokens,\n new address[](tokens.length),\n swapFeePercentage,\n pauseWindowDuration,\n bufferPeriodDuration,\n owner\n )\n {\n _require(tokens.length <= _MAX_STABLE_TOKENS, Errors.MAX_STABLE_TOKENS);\n\n _require(amplificationParameter >= _MIN_AMP, Errors.MIN_AMP);\n _require(amplificationParameter <= _MAX_AMP, Errors.MAX_AMP);\n\n uint256 initialAmp = Math.mul(amplificationParameter, _AMP_PRECISION);\n _setAmplificationData(initialAmp);\n }\n\n // Base Pool handlers\n\n // Swap - General Pool specialization (from BaseGeneralPool)\n\n function _onSwapGivenIn(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n ) internal view virtual override whenNotPaused returns (uint256) {\n (uint256 currentAmp, ) = _getAmplificationParameter();\n uint256 amountOut = StableMath._calcOutGivenIn(currentAmp, balances, indexIn, indexOut, swapRequest.amount);\n return amountOut;\n }\n\n function _onSwapGivenOut(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n ) internal view virtual override whenNotPaused returns (uint256) {\n (uint256 currentAmp, ) = _getAmplificationParameter();\n uint256 amountIn = StableMath._calcInGivenOut(currentAmp, balances, indexIn, indexOut, swapRequest.amount);\n return amountIn;\n }\n\n // Swap - Two Token Pool specialization (from BaseMinimalSwapInfoPool)\n\n function _onSwapGivenIn(\n SwapRequest memory swapRequest,\n uint256 balanceTokenIn,\n uint256 balanceTokenOut\n ) internal view virtual override returns (uint256) {\n _require(_getTotalTokens() == 2, Errors.NOT_TWO_TOKENS);\n\n (uint256[] memory balances, uint256 indexIn, uint256 indexOut) = _getSwapBalanceArrays(\n swapRequest,\n balanceTokenIn,\n balanceTokenOut\n );\n\n return _onSwapGivenIn(swapRequest, balances, indexIn, indexOut);\n }\n\n function _onSwapGivenOut(\n SwapRequest memory swapRequest,\n uint256 balanceTokenIn,\n uint256 balanceTokenOut\n ) internal view virtual override returns (uint256) {\n _require(_getTotalTokens() == 2, Errors.NOT_TWO_TOKENS);\n\n (uint256[] memory balances, uint256 indexIn, uint256 indexOut) = _getSwapBalanceArrays(\n swapRequest,\n balanceTokenIn,\n balanceTokenOut\n );\n return _onSwapGivenOut(swapRequest, balances, indexIn, indexOut);\n }\n\n function _getSwapBalanceArrays(\n SwapRequest memory swapRequest,\n uint256 balanceTokenIn,\n uint256 balanceTokenOut\n )\n private\n view\n returns (\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n )\n {\n balances = new uint256[](2);\n\n if (_token0 == swapRequest.tokenIn) {\n indexIn = 0;\n indexOut = 1;\n\n balances[0] = balanceTokenIn;\n balances[1] = balanceTokenOut;\n } else {\n // _token0 == swapRequest.tokenOut\n indexOut = 0;\n indexIn = 1;\n\n balances[0] = balanceTokenOut;\n balances[1] = balanceTokenIn;\n }\n }\n\n // Initialize\n\n function _onInitializePool(\n bytes32,\n address,\n address,\n uint256[] memory scalingFactors,\n bytes memory userData\n ) internal virtual override whenNotPaused returns (uint256, uint256[] memory) {\n // It would be strange for the Pool to be paused before it is initialized, but for consistency we prevent\n // initialization in this case.\n\n StablePool.JoinKind kind = userData.joinKind();\n _require(kind == StablePool.JoinKind.INIT, Errors.UNINITIALIZED);\n\n uint256[] memory amountsIn = userData.initialAmountsIn();\n InputHelpers.ensureInputLengthMatch(amountsIn.length, _getTotalTokens());\n _upscaleArray(amountsIn, scalingFactors);\n\n (uint256 currentAmp, ) = _getAmplificationParameter();\n uint256 invariantAfterJoin = StableMath._calculateInvariant(currentAmp, amountsIn, true);\n\n // Set the initial BPT to the value of the invariant.\n uint256 bptAmountOut = invariantAfterJoin;\n\n _updateLastInvariant(invariantAfterJoin, currentAmp);\n\n return (bptAmountOut, amountsIn);\n }\n\n // Join\n\n function _onJoinPool(\n bytes32,\n address,\n address,\n uint256[] memory balances,\n uint256,\n uint256 protocolSwapFeePercentage,\n uint256[] memory scalingFactors,\n bytes memory userData\n )\n internal\n virtual\n override\n whenNotPaused\n returns (\n uint256,\n uint256[] memory,\n uint256[] memory\n )\n {\n // Due protocol swap fee amounts are computed by measuring the growth of the invariant between the previous join\n // or exit event and now - the invariant's growth is due exclusively to swap fees. This avoids spending gas to\n // calculate the fee amounts during each individual swap.\n uint256[] memory dueProtocolFeeAmounts = _getDueProtocolFeeAmounts(balances, protocolSwapFeePercentage);\n\n // Update current balances by subtracting the protocol fee amounts\n _mutateAmounts(balances, dueProtocolFeeAmounts, FixedPoint.sub);\n (uint256 bptAmountOut, uint256[] memory amountsIn) = _doJoin(balances, scalingFactors, userData);\n\n // Update the invariant with the balances the Pool will have after the join, in order to compute the\n // protocol swap fee amounts due in future joins and exits.\n _updateInvariantAfterJoin(balances, amountsIn);\n\n return (bptAmountOut, amountsIn, dueProtocolFeeAmounts);\n }\n\n function _doJoin(\n uint256[] memory balances,\n uint256[] memory scalingFactors,\n bytes memory userData\n ) private view returns (uint256, uint256[] memory) {\n JoinKind kind = userData.joinKind();\n\n if (kind == JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT) {\n return _joinExactTokensInForBPTOut(balances, scalingFactors, userData);\n } else if (kind == JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT) {\n return _joinTokenInForExactBPTOut(balances, userData);\n } else {\n _revert(Errors.UNHANDLED_JOIN_KIND);\n }\n }\n\n function _joinExactTokensInForBPTOut(\n uint256[] memory balances,\n uint256[] memory scalingFactors,\n bytes memory userData\n ) private view returns (uint256, uint256[] memory) {\n (uint256[] memory amountsIn, uint256 minBPTAmountOut) = userData.exactTokensInForBptOut();\n InputHelpers.ensureInputLengthMatch(_getTotalTokens(), amountsIn.length);\n\n _upscaleArray(amountsIn, scalingFactors);\n\n (uint256 currentAmp, ) = _getAmplificationParameter();\n uint256 bptAmountOut = StableMath._calcBptOutGivenExactTokensIn(\n currentAmp,\n balances,\n amountsIn,\n totalSupply(),\n _swapFeePercentage\n );\n\n _require(bptAmountOut >= minBPTAmountOut, Errors.BPT_OUT_MIN_AMOUNT);\n\n return (bptAmountOut, amountsIn);\n }\n\n function _joinTokenInForExactBPTOut(uint256[] memory balances, bytes memory userData)\n private\n view\n returns (uint256, uint256[] memory)\n {\n (uint256 bptAmountOut, uint256 tokenIndex) = userData.tokenInForExactBptOut();\n // Note that there is no maximum amountIn parameter: this is handled by `IVault.joinPool`.\n\n _require(tokenIndex < _getTotalTokens(), Errors.OUT_OF_BOUNDS);\n\n uint256[] memory amountsIn = new uint256[](_getTotalTokens());\n (uint256 currentAmp, ) = _getAmplificationParameter();\n amountsIn[tokenIndex] = StableMath._calcTokenInGivenExactBptOut(\n currentAmp,\n balances,\n tokenIndex,\n bptAmountOut,\n totalSupply(),\n _swapFeePercentage\n );\n\n return (bptAmountOut, amountsIn);\n }\n\n // Exit\n\n function _onExitPool(\n bytes32,\n address,\n address,\n uint256[] memory balances,\n uint256,\n uint256 protocolSwapFeePercentage,\n uint256[] memory scalingFactors,\n bytes memory userData\n )\n internal\n virtual\n override\n returns (\n uint256 bptAmountIn,\n uint256[] memory amountsOut,\n uint256[] memory dueProtocolFeeAmounts\n )\n {\n // Exits are not completely disabled while the contract is paused: proportional exits (exact BPT in for tokens\n // out) remain functional.\n\n if (_isNotPaused()) {\n // Due protocol swap fee amounts are computed by measuring the growth of the invariant between the previous\n // join or exit event and now - the invariant's growth is due exclusively to swap fees. This avoids\n // spending gas calculating fee amounts during each individual swap\n dueProtocolFeeAmounts = _getDueProtocolFeeAmounts(balances, protocolSwapFeePercentage);\n\n // Update current balances by subtracting the protocol fee amounts\n _mutateAmounts(balances, dueProtocolFeeAmounts, FixedPoint.sub);\n } else {\n // If the contract is paused, swap protocol fee amounts are not charged to avoid extra calculations and\n // reduce the potential for errors.\n dueProtocolFeeAmounts = new uint256[](_getTotalTokens());\n }\n\n (bptAmountIn, amountsOut) = _doExit(balances, scalingFactors, userData);\n\n // Update the invariant with the balances the Pool will have after the exit, in order to compute the\n // protocol swap fee amounts due in future joins and exits.\n _updateInvariantAfterExit(balances, amountsOut);\n\n return (bptAmountIn, amountsOut, dueProtocolFeeAmounts);\n }\n\n function _doExit(\n uint256[] memory balances,\n uint256[] memory scalingFactors,\n bytes memory userData\n ) private view returns (uint256, uint256[] memory) {\n ExitKind kind = userData.exitKind();\n\n if (kind == ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT) {\n return _exitExactBPTInForTokenOut(balances, userData);\n } else if (kind == ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT) {\n return _exitExactBPTInForTokensOut(balances, userData);\n } else {\n // ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT\n return _exitBPTInForExactTokensOut(balances, scalingFactors, userData);\n }\n }\n\n function _exitExactBPTInForTokenOut(uint256[] memory balances, bytes memory userData)\n private\n view\n whenNotPaused\n returns (uint256, uint256[] memory)\n {\n // This exit function is disabled if the contract is paused.\n\n (uint256 bptAmountIn, uint256 tokenIndex) = userData.exactBptInForTokenOut();\n // Note that there is no minimum amountOut parameter: this is handled by `IVault.exitPool`.\n\n _require(tokenIndex < _getTotalTokens(), Errors.OUT_OF_BOUNDS);\n\n // We exit in a single token, so initialize amountsOut with zeros\n uint256[] memory amountsOut = new uint256[](_getTotalTokens());\n\n // And then assign the result to the selected token\n (uint256 currentAmp, ) = _getAmplificationParameter();\n amountsOut[tokenIndex] = StableMath._calcTokenOutGivenExactBptIn(\n currentAmp,\n balances,\n tokenIndex,\n bptAmountIn,\n totalSupply(),\n _swapFeePercentage\n );\n\n return (bptAmountIn, amountsOut);\n }\n\n function _exitExactBPTInForTokensOut(uint256[] memory balances, bytes memory userData)\n private\n view\n returns (uint256, uint256[] memory)\n {\n // This exit function is the only one that is not disabled if the contract is paused: it remains unrestricted\n // in an attempt to provide users with a mechanism to retrieve their tokens in case of an emergency.\n // This particular exit function is the only one that remains available because it is the simplest one, and\n // therefore the one with the lowest likelihood of errors.\n\n uint256 bptAmountIn = userData.exactBptInForTokensOut();\n // Note that there is no minimum amountOut parameter: this is handled by `IVault.exitPool`.\n\n uint256[] memory amountsOut = StableMath._calcTokensOutGivenExactBptIn(balances, bptAmountIn, totalSupply());\n return (bptAmountIn, amountsOut);\n }\n\n function _exitBPTInForExactTokensOut(\n uint256[] memory balances,\n uint256[] memory scalingFactors,\n bytes memory userData\n ) private view whenNotPaused returns (uint256, uint256[] memory) {\n // This exit function is disabled if the contract is paused.\n\n (uint256[] memory amountsOut, uint256 maxBPTAmountIn) = userData.bptInForExactTokensOut();\n InputHelpers.ensureInputLengthMatch(amountsOut.length, _getTotalTokens());\n _upscaleArray(amountsOut, scalingFactors);\n\n (uint256 currentAmp, ) = _getAmplificationParameter();\n uint256 bptAmountIn = StableMath._calcBptInGivenExactTokensOut(\n currentAmp,\n balances,\n amountsOut,\n totalSupply(),\n _swapFeePercentage\n );\n _require(bptAmountIn <= maxBPTAmountIn, Errors.BPT_IN_MAX_AMOUNT);\n\n return (bptAmountIn, amountsOut);\n }\n\n // Helpers\n\n /**\n * @dev Stores the last measured invariant, and the amplification parameter used to compute it.\n */\n function _updateLastInvariant(uint256 invariant, uint256 amplificationParameter) private {\n _lastInvariant = invariant;\n _lastInvariantAmp = amplificationParameter;\n }\n\n /**\n * @dev Returns the amount of protocol fees to pay, given the value of the last stored invariant and the current\n * balances.\n */\n function _getDueProtocolFeeAmounts(uint256[] memory balances, uint256 protocolSwapFeePercentage)\n private\n view\n returns (uint256[] memory)\n {\n // Initialize with zeros\n uint256[] memory dueProtocolFeeAmounts = new uint256[](_getTotalTokens());\n\n // Early return if the protocol swap fee percentage is zero, saving gas.\n if (protocolSwapFeePercentage == 0) {\n return dueProtocolFeeAmounts;\n }\n\n // Instead of paying the protocol swap fee in all tokens proportionally, we will pay it in a single one. This\n // will reduce gas costs for single asset joins and exits, as at most only two Pool balances will change (the\n // token joined/exited, and the token in which fees will be paid).\n\n // The protocol fee is charged using the token with the highest balance in the pool.\n uint256 chosenTokenIndex = 0;\n uint256 maxBalance = balances[0];\n for (uint256 i = 1; i < _getTotalTokens(); ++i) {\n uint256 currentBalance = balances[i];\n if (currentBalance > maxBalance) {\n chosenTokenIndex = i;\n maxBalance = currentBalance;\n }\n }\n\n // Set the fee amount to pay in the selected token\n dueProtocolFeeAmounts[chosenTokenIndex] = StableMath._calcDueTokenProtocolSwapFeeAmount(\n _lastInvariantAmp,\n balances,\n _lastInvariant,\n chosenTokenIndex,\n protocolSwapFeePercentage\n );\n\n return dueProtocolFeeAmounts;\n }\n\n /**\n * @dev Computes and stores the value of the invariant after a join, which is required to compute due protocol fees\n * in the future.\n */\n function _updateInvariantAfterJoin(uint256[] memory balances, uint256[] memory amountsIn) private {\n _mutateAmounts(balances, amountsIn, FixedPoint.add);\n\n (uint256 currentAmp, ) = _getAmplificationParameter();\n // This invariant is used only to compute the final balance when calculating the protocol fees. These are\n // rounded down, so we round the invariant up.\n _updateLastInvariant(StableMath._calculateInvariant(currentAmp, balances, true), currentAmp);\n }\n\n /**\n * @dev Computes and stores the value of the invariant after an exit, which is required to compute due protocol fees\n * in the future.\n */\n function _updateInvariantAfterExit(uint256[] memory balances, uint256[] memory amountsOut) private {\n _mutateAmounts(balances, amountsOut, FixedPoint.sub);\n\n (uint256 currentAmp, ) = _getAmplificationParameter();\n // This invariant is used only to compute the final balance when calculating the protocol fees. These are\n // rounded down, so we round the invariant up.\n _updateLastInvariant(StableMath._calculateInvariant(currentAmp, balances, true), currentAmp);\n }\n\n /**\n * @dev Mutates `amounts` by applying `mutation` with each entry in `arguments`.\n *\n * Equivalent to `amounts = amounts.map(mutation)`.\n */\n function _mutateAmounts(\n uint256[] memory toMutate,\n uint256[] memory arguments,\n function(uint256, uint256) pure returns (uint256) mutation\n ) private view {\n for (uint256 i = 0; i < _getTotalTokens(); ++i) {\n toMutate[i] = mutation(toMutate[i], arguments[i]);\n }\n }\n\n /**\n * @dev This function returns the appreciation of one BPT relative to the\n * underlying tokens. This starts at 1 when the pool is created and grows over time\n */\n function getRate() public view returns (uint256) {\n (, uint256[] memory balances, ) = getVault().getPoolTokens(getPoolId());\n\n // When calculating the current BPT rate, we may not have paid the protocol fees, therefore\n // the invariant should be smaller than its current value. Then, we round down overall.\n (uint256 currentAmp, ) = _getAmplificationParameter();\n\n _upscaleArray(balances, _scalingFactors());\n\n uint256 invariant = StableMath._calculateInvariant(currentAmp, balances, false);\n return invariant.divDown(totalSupply());\n }\n\n // Amplification\n\n /**\n * @dev Begins changing the amplification parameter to `rawEndValue` over time. The value will change linearly until\n * `endTime` is reached, when it will be `rawEndValue`.\n *\n * NOTE: Internally, the amplification parameter is represented using higher precision. The values returned by\n * `getAmplificationParameter` have to be corrected to account for this when comparing to `rawEndValue`.\n */\n function startAmplificationParameterUpdate(uint256 rawEndValue, uint256 endTime) external authenticate {\n _require(rawEndValue >= _MIN_AMP, Errors.MIN_AMP);\n _require(rawEndValue <= _MAX_AMP, Errors.MAX_AMP);\n\n uint256 duration = Math.sub(endTime, block.timestamp);\n _require(duration >= _MIN_UPDATE_TIME, Errors.AMP_END_TIME_TOO_CLOSE);\n\n (uint256 currentValue, bool isUpdating) = _getAmplificationParameter();\n _require(!isUpdating, Errors.AMP_ONGOING_UPDATE);\n\n uint256 endValue = Math.mul(rawEndValue, _AMP_PRECISION);\n\n // daily rate = (endValue / currentValue) / duration * 1 day\n // We perform all multiplications first to not reduce precision, and round the division up as we want to avoid\n // large rates. Note that these are regular integer multiplications and divisions, not fixed point.\n uint256 dailyRate = endValue > currentValue\n ? Math.divUp(Math.mul(1 days, endValue), Math.mul(currentValue, duration))\n : Math.divUp(Math.mul(1 days, currentValue), Math.mul(endValue, duration));\n _require(dailyRate <= _MAX_AMP_UPDATE_DAILY_RATE, Errors.AMP_RATE_TOO_HIGH);\n\n _setAmplificationData(currentValue, endValue, block.timestamp, endTime);\n }\n\n /**\n * @dev Stops the amplification parameter change process, keeping the current value.\n */\n function stopAmplificationParameterUpdate() external authenticate {\n (uint256 currentValue, bool isUpdating) = _getAmplificationParameter();\n _require(isUpdating, Errors.AMP_NO_ONGOING_UPDATE);\n\n _setAmplificationData(currentValue);\n }\n\n function _isOwnerOnlyAction(bytes32 actionId) internal view virtual override returns (bool) {\n return\n (actionId == getActionId(StablePool.startAmplificationParameterUpdate.selector)) ||\n (actionId == getActionId(StablePool.stopAmplificationParameterUpdate.selector)) ||\n super._isOwnerOnlyAction(actionId);\n }\n\n function getAmplificationParameter()\n external\n view\n returns (\n uint256 value,\n bool isUpdating,\n uint256 precision\n )\n {\n (value, isUpdating) = _getAmplificationParameter();\n precision = _AMP_PRECISION;\n }\n\n function _getAmplificationParameter() internal view returns (uint256 value, bool isUpdating) {\n (uint256 startValue, uint256 endValue, uint256 startTime, uint256 endTime) = _getAmplificationData();\n\n // Note that block.timestamp >= startTime, since startTime is set to the current time when an update starts\n\n if (block.timestamp < endTime) {\n isUpdating = true;\n\n // We can skip checked arithmetic as:\n // - block.timestamp is always larger or equal to startTime\n // - endTime is alawys larger than startTime\n // - the value delta is bounded by the largest amplification paramater, which never causes the\n // multiplication to overflow.\n // This also means that the following computation will never revert nor yield invalid results.\n if (endValue > startValue) {\n value = startValue + ((endValue - startValue) * (block.timestamp - startTime)) / (endTime - startTime);\n } else {\n value = startValue - ((startValue - endValue) * (block.timestamp - startTime)) / (endTime - startTime);\n }\n } else {\n isUpdating = false;\n value = endValue;\n }\n }\n\n function _setAmplificationData(uint256 value) private {\n _setAmplificationData(value, value, block.timestamp, block.timestamp);\n\n emit AmpUpdateStopped(value);\n }\n\n function _setAmplificationData(\n uint256 startValue,\n uint256 endValue,\n uint256 startTime,\n uint256 endTime\n ) private {\n _packedAmplificationData =\n WordCodec.encodeUint(uint64(startValue), 0) |\n WordCodec.encodeUint(uint64(endValue), 64) |\n WordCodec.encodeUint(uint64(startTime), 64 * 2) |\n WordCodec.encodeUint(uint64(endTime), 64 * 3);\n\n emit AmpUpdateStarted(startValue, endValue, startTime, endTime);\n }\n\n function _getAmplificationData()\n private\n view\n returns (\n uint256 startValue,\n uint256 endValue,\n uint256 startTime,\n uint256 endTime\n )\n {\n startValue = _packedAmplificationData.decodeUint64(0);\n endValue = _packedAmplificationData.decodeUint64(64);\n startTime = _packedAmplificationData.decodeUint64(64 * 2);\n endTime = _packedAmplificationData.decodeUint64(64 * 3);\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"./LogExpMath.sol\";\nimport \"../helpers/BalancerErrors.sol\";\n\n/* solhint-disable private-vars-leading-underscore */\n\nlibrary FixedPoint {\n uint256 internal constant ONE = 1e18; // 18 decimal places\n uint256 internal constant MAX_POW_RELATIVE_ERROR = 10000; // 10^(-14)\n\n // Minimum base for the power function when the exponent is 'free' (larger than ONE).\n uint256 internal constant MIN_POW_BASE_FREE_EXPONENT = 0.7e18;\n\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n // Fixed Point addition is the same as regular checked addition\n\n uint256 c = a + b;\n _require(c >= a, Errors.ADD_OVERFLOW);\n return c;\n }\n\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n // Fixed Point addition is the same as regular checked addition\n\n _require(b <= a, Errors.SUB_OVERFLOW);\n uint256 c = a - b;\n return c;\n }\n\n function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 product = a * b;\n _require(a == 0 || product / a == b, Errors.MUL_OVERFLOW);\n\n return product / ONE;\n }\n\n function mulUp(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 product = a * b;\n _require(a == 0 || product / a == b, Errors.MUL_OVERFLOW);\n\n if (product == 0) {\n return 0;\n } else {\n // The traditional divUp formula is:\n // divUp(x, y) := (x + y - 1) / y\n // To avoid intermediate overflow in the addition, we distribute the division and get:\n // divUp(x, y) := (x - 1) / y + 1\n // Note that this requires x != 0, which we already tested for.\n\n return ((product - 1) / ONE) + 1;\n }\n }\n\n function divDown(uint256 a, uint256 b) internal pure returns (uint256) {\n _require(b != 0, Errors.ZERO_DIVISION);\n\n if (a == 0) {\n return 0;\n } else {\n uint256 aInflated = a * ONE;\n _require(aInflated / a == ONE, Errors.DIV_INTERNAL); // mul overflow\n\n return aInflated / b;\n }\n }\n\n function divUp(uint256 a, uint256 b) internal pure returns (uint256) {\n _require(b != 0, Errors.ZERO_DIVISION);\n\n if (a == 0) {\n return 0;\n } else {\n uint256 aInflated = a * ONE;\n _require(aInflated / a == ONE, Errors.DIV_INTERNAL); // mul overflow\n\n // The traditional divUp formula is:\n // divUp(x, y) := (x + y - 1) / y\n // To avoid intermediate overflow in the addition, we distribute the division and get:\n // divUp(x, y) := (x - 1) / y + 1\n // Note that this requires x != 0, which we already tested for.\n\n return ((aInflated - 1) / b) + 1;\n }\n }\n\n /**\n * @dev Returns x^y, assuming both are fixed point numbers, rounding down. The result is guaranteed to not be above\n * the true value (that is, the error function expected - actual is always positive).\n */\n function powDown(uint256 x, uint256 y) internal pure returns (uint256) {\n uint256 raw = LogExpMath.pow(x, y);\n uint256 maxError = add(mulUp(raw, MAX_POW_RELATIVE_ERROR), 1);\n\n if (raw < maxError) {\n return 0;\n } else {\n return sub(raw, maxError);\n }\n }\n\n /**\n * @dev Returns x^y, assuming both are fixed point numbers, rounding up. The result is guaranteed to not be below\n * the true value (that is, the error function expected - actual is always negative).\n */\n function powUp(uint256 x, uint256 y) internal pure returns (uint256) {\n uint256 raw = LogExpMath.pow(x, y);\n uint256 maxError = add(mulUp(raw, MAX_POW_RELATIVE_ERROR), 1);\n\n return add(raw, maxError);\n }\n\n /**\n * @dev Returns the complement of a value (1 - x), capped to 0 if x is larger than 1.\n *\n * Useful when computing the complement for values with some level of relative error, as it strips this error and\n * prevents intermediate negative values.\n */\n function complement(uint256 x) internal pure returns (uint256) {\n return (x < ONE) ? (ONE - x) : 0;\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"../openzeppelin/IERC20.sol\";\n\nimport \"./BalancerErrors.sol\";\n\nlibrary InputHelpers {\n function ensureInputLengthMatch(uint256 a, uint256 b) internal pure {\n _require(a == b, Errors.INPUT_LENGTH_MISMATCH);\n }\n\n function ensureInputLengthMatch(\n uint256 a,\n uint256 b,\n uint256 c\n ) internal pure {\n _require(a == b && b == c, Errors.INPUT_LENGTH_MISMATCH);\n }\n\n function ensureArrayIsSorted(IERC20[] memory array) internal pure {\n address[] memory addressArray;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n addressArray := array\n }\n ensureArrayIsSorted(addressArray);\n }\n\n function ensureArrayIsSorted(address[] memory array) internal pure {\n if (array.length < 2) {\n return;\n }\n\n address previous = array[0];\n for (uint256 i = 1; i < array.length; ++i) {\n address current = array[i];\n _require(previous < current, Errors.UNSORTED_ARRAY);\n previous = current;\n }\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/WordCodec.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Library for encoding and decoding values stored inside a 256 bit word. Typically used to pack multiple values in\n * a single storage slot, saving gas by performing less storage accesses.\n *\n * Each value is defined by its size and the least significant bit in the word, also known as offset. For example, two\n * 128 bit values may be encoded in a word by assigning one an offset of 0, and the other an offset of 128.\n */\nlibrary WordCodec {\n // Masks are values with the least significant N bits set. They can be used to extract an encoded value from a word,\n // or to insert a new one replacing the old.\n uint256 private constant _MASK_1 = 2**(1) - 1;\n uint256 private constant _MASK_10 = 2**(10) - 1;\n uint256 private constant _MASK_22 = 2**(22) - 1;\n uint256 private constant _MASK_31 = 2**(31) - 1;\n uint256 private constant _MASK_53 = 2**(53) - 1;\n uint256 private constant _MASK_64 = 2**(64) - 1;\n\n // Largest positive values that can be represented as N bits signed integers.\n int256 private constant _MAX_INT_22 = 2**(21) - 1;\n int256 private constant _MAX_INT_53 = 2**(52) - 1;\n\n // In-place insertion\n\n /**\n * @dev Inserts a boolean value shifted by an offset into a 256 bit word, replacing the old value. Returns the new\n * word.\n */\n function insertBoolean(\n bytes32 word,\n bool value,\n uint256 offset\n ) internal pure returns (bytes32) {\n bytes32 clearedWord = bytes32(uint256(word) & ~(_MASK_1 << offset));\n return clearedWord | bytes32(uint256(value ? 1 : 0) << offset);\n }\n\n // Unsigned\n\n /**\n * @dev Inserts a 10 bit unsigned integer shifted by an offset into a 256 bit word, replacing the old value. Returns\n * the new word.\n *\n * Assumes `value` can be represented using 10 bits.\n */\n function insertUint10(\n bytes32 word,\n uint256 value,\n uint256 offset\n ) internal pure returns (bytes32) {\n bytes32 clearedWord = bytes32(uint256(word) & ~(_MASK_10 << offset));\n return clearedWord | bytes32(value << offset);\n }\n\n /**\n * @dev Inserts a 31 bit unsigned integer shifted by an offset into a 256 bit word, replacing the old value. Returns\n * the new word.\n *\n * Assumes `value` can be represented using 31 bits.\n */\n function insertUint31(\n bytes32 word,\n uint256 value,\n uint256 offset\n ) internal pure returns (bytes32) {\n bytes32 clearedWord = bytes32(uint256(word) & ~(_MASK_31 << offset));\n return clearedWord | bytes32(value << offset);\n }\n\n /**\n * @dev Inserts a 64 bit unsigned integer shifted by an offset into a 256 bit word, replacing the old value. Returns\n * the new word.\n *\n * Assumes `value` can be represented using 64 bits.\n */\n function insertUint64(\n bytes32 word,\n uint256 value,\n uint256 offset\n ) internal pure returns (bytes32) {\n bytes32 clearedWord = bytes32(uint256(word) & ~(_MASK_64 << offset));\n return clearedWord | bytes32(value << offset);\n }\n\n // Signed\n\n /**\n * @dev Inserts a 22 bits signed integer shifted by an offset into a 256 bit word, replacing the old value. Returns\n * the new word.\n *\n * Assumes `value` can be represented using 22 bits.\n */\n function insertInt22(\n bytes32 word,\n int256 value,\n uint256 offset\n ) internal pure returns (bytes32) {\n bytes32 clearedWord = bytes32(uint256(word) & ~(_MASK_22 << offset));\n // Integer values need masking to remove the upper bits of negative values.\n return clearedWord | bytes32((uint256(value) & _MASK_22) << offset);\n }\n\n // Encoding\n\n // Unsigned\n\n /**\n * @dev Encodes an unsigned integer shifted by an offset. This performs no size checks: it is up to the caller to\n * ensure that the values are bounded.\n *\n * The return value can be logically ORed with other encoded values to form a 256 bit word.\n */\n function encodeUint(uint256 value, uint256 offset) internal pure returns (bytes32) {\n return bytes32(value << offset);\n }\n\n // Signed\n\n /**\n * @dev Encodes a 22 bits signed integer shifted by an offset.\n *\n * The return value can be logically ORed with other encoded values to form a 256 bit word.\n */\n function encodeInt22(int256 value, uint256 offset) internal pure returns (bytes32) {\n // Integer values need masking to remove the upper bits of negative values.\n return bytes32((uint256(value) & _MASK_22) << offset);\n }\n\n /**\n * @dev Encodes a 53 bits signed integer shifted by an offset.\n *\n * The return value can be logically ORed with other encoded values to form a 256 bit word.\n */\n function encodeInt53(int256 value, uint256 offset) internal pure returns (bytes32) {\n // Integer values need masking to remove the upper bits of negative values.\n return bytes32((uint256(value) & _MASK_53) << offset);\n }\n\n // Decoding\n\n /**\n * @dev Decodes and returns a boolean shifted by an offset from a 256 bit word.\n */\n function decodeBool(bytes32 word, uint256 offset) internal pure returns (bool) {\n return (uint256(word >> offset) & _MASK_1) == 1;\n }\n\n // Unsigned\n\n /**\n * @dev Decodes and returns a 10 bit unsigned integer shifted by an offset from a 256 bit word.\n */\n function decodeUint10(bytes32 word, uint256 offset) internal pure returns (uint256) {\n return uint256(word >> offset) & _MASK_10;\n }\n\n /**\n * @dev Decodes and returns a 31 bit unsigned integer shifted by an offset from a 256 bit word.\n */\n function decodeUint31(bytes32 word, uint256 offset) internal pure returns (uint256) {\n return uint256(word >> offset) & _MASK_31;\n }\n\n /**\n * @dev Decodes and returns a 64 bit unsigned integer shifted by an offset from a 256 bit word.\n */\n function decodeUint64(bytes32 word, uint256 offset) internal pure returns (uint256) {\n return uint256(word >> offset) & _MASK_64;\n }\n\n // Signed\n\n /**\n * @dev Decodes and returns a 22 bits signed integer shifted by an offset from a 256 bit word.\n */\n function decodeInt22(bytes32 word, uint256 offset) internal pure returns (int256) {\n int256 value = int256(uint256(word >> offset) & _MASK_22);\n // In case the decoded value is greater than the max positive integer that can be represented with 22 bits,\n // we know it was originally a negative integer. Therefore, we mask it to restore the sign in the 256 bit\n // representation.\n return value > _MAX_INT_22 ? (value | int256(~_MASK_22)) : value;\n }\n\n /**\n * @dev Decodes and returns a 53 bits signed integer shifted by an offset from a 256 bit word.\n */\n function decodeInt53(bytes32 word, uint256 offset) internal pure returns (int256) {\n int256 value = int256(uint256(word >> offset) & _MASK_53);\n // In case the decoded value is greater than the max positive integer that can be represented with 53 bits,\n // we know it was originally a negative integer. Therefore, we mask it to restore the sign in the 256 bit\n // representation.\n\n return value > _MAX_INT_53 ? (value | int256(~_MASK_53)) : value;\n }\n}\n" + }, + "@balancer-labs/v2-pool-utils/contracts/BaseGeneralPool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"./BasePool.sol\";\nimport \"@balancer-labs/v2-vault/contracts/interfaces/IGeneralPool.sol\";\n\n/**\n * @dev Extension of `BasePool`, adding a handler for `IGeneralPool.onSwap`.\n *\n * Derived contracts must call `BasePool`'s constructor, and implement `_onSwapGivenIn` and `_onSwapGivenOut` along with\n * `BasePool`'s virtual functions. Inheriting from this contract lets derived contracts choose the General\n * specialization setting.\n */\nabstract contract BaseGeneralPool is IGeneralPool, BasePool {\n // Swap Hooks\n\n function onSwap(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n ) external view virtual override returns (uint256) {\n _validateIndexes(indexIn, indexOut, _getTotalTokens());\n uint256[] memory scalingFactors = _scalingFactors();\n\n return\n swapRequest.kind == IVault.SwapKind.GIVEN_IN\n ? _swapGivenIn(swapRequest, balances, indexIn, indexOut, scalingFactors)\n : _swapGivenOut(swapRequest, balances, indexIn, indexOut, scalingFactors);\n }\n\n function _swapGivenIn(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut,\n uint256[] memory scalingFactors\n ) internal view returns (uint256) {\n // Fees are subtracted before scaling, to reduce the complexity of the rounding direction analysis.\n swapRequest.amount = _subtractSwapFeeAmount(swapRequest.amount);\n\n _upscaleArray(balances, scalingFactors);\n swapRequest.amount = _upscale(swapRequest.amount, scalingFactors[indexIn]);\n\n uint256 amountOut = _onSwapGivenIn(swapRequest, balances, indexIn, indexOut);\n\n // amountOut tokens are exiting the Pool, so we round down.\n return _downscaleDown(amountOut, scalingFactors[indexOut]);\n }\n\n function _swapGivenOut(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut,\n uint256[] memory scalingFactors\n ) internal view returns (uint256) {\n _upscaleArray(balances, scalingFactors);\n swapRequest.amount = _upscale(swapRequest.amount, scalingFactors[indexOut]);\n\n uint256 amountIn = _onSwapGivenOut(swapRequest, balances, indexIn, indexOut);\n\n // amountIn tokens are entering the Pool, so we round up.\n amountIn = _downscaleUp(amountIn, scalingFactors[indexIn]);\n\n // Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis.\n return _addSwapFeeAmount(amountIn);\n }\n\n /*\n * @dev Called when a swap with the Pool occurs, where the amount of tokens entering the Pool is known.\n *\n * Returns the amount of tokens that will be taken from the Pool in return.\n *\n * All amounts inside `swapRequest` and `balances` are upscaled. The swap fee has already been deducted from\n * `swapRequest.amount`.\n *\n * The return value is also considered upscaled, and will be downscaled (rounding down) before returning it to the\n * Vault.\n */\n function _onSwapGivenIn(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n ) internal view virtual returns (uint256);\n\n /*\n * @dev Called when a swap with the Pool occurs, where the amount of tokens exiting the Pool is known.\n *\n * Returns the amount of tokens that will be granted to the Pool in return.\n *\n * All amounts inside `swapRequest` and `balances` are upscaled.\n *\n * The return value is also considered upscaled, and will be downscaled (rounding up) before applying the swap fee\n * and returning it to the Vault.\n */\n function _onSwapGivenOut(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n ) internal view virtual returns (uint256);\n\n function _validateIndexes(\n uint256 indexIn,\n uint256 indexOut,\n uint256 limit\n ) private pure {\n _require(indexIn < limit && indexOut < limit, Errors.OUT_OF_BOUNDS);\n }\n}\n" + }, + "@balancer-labs/v2-pool-utils/contracts/BaseMinimalSwapInfoPool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"./BasePool.sol\";\nimport \"@balancer-labs/v2-vault/contracts/interfaces/IMinimalSwapInfoPool.sol\";\n\n/**\n * @dev Extension of `BasePool`, adding a handler for `IMinimalSwapInfoPool.onSwap`.\n *\n * Derived contracts must call `BasePool`'s constructor, and implement `_onSwapGivenIn` and `_onSwapGivenOut` along with\n * `BasePool`'s virtual functions. Inheriting from this contract lets derived contracts choose the Two Token or Minimal\n * Swap Info specialization settings.\n */\nabstract contract BaseMinimalSwapInfoPool is IMinimalSwapInfoPool, BasePool {\n // Swap Hooks\n\n function onSwap(\n SwapRequest memory request,\n uint256 balanceTokenIn,\n uint256 balanceTokenOut\n ) external view virtual override returns (uint256) {\n uint256 scalingFactorTokenIn = _scalingFactor(request.tokenIn);\n uint256 scalingFactorTokenOut = _scalingFactor(request.tokenOut);\n\n if (request.kind == IVault.SwapKind.GIVEN_IN) {\n // Fees are subtracted before scaling, to reduce the complexity of the rounding direction analysis.\n request.amount = _subtractSwapFeeAmount(request.amount);\n\n // All token amounts are upscaled.\n balanceTokenIn = _upscale(balanceTokenIn, scalingFactorTokenIn);\n balanceTokenOut = _upscale(balanceTokenOut, scalingFactorTokenOut);\n request.amount = _upscale(request.amount, scalingFactorTokenIn);\n\n uint256 amountOut = _onSwapGivenIn(request, balanceTokenIn, balanceTokenOut);\n\n // amountOut tokens are exiting the Pool, so we round down.\n return _downscaleDown(amountOut, scalingFactorTokenOut);\n } else {\n // All token amounts are upscaled.\n balanceTokenIn = _upscale(balanceTokenIn, scalingFactorTokenIn);\n balanceTokenOut = _upscale(balanceTokenOut, scalingFactorTokenOut);\n request.amount = _upscale(request.amount, scalingFactorTokenOut);\n\n uint256 amountIn = _onSwapGivenOut(request, balanceTokenIn, balanceTokenOut);\n\n // amountIn tokens are entering the Pool, so we round up.\n amountIn = _downscaleUp(amountIn, scalingFactorTokenIn);\n\n // Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis.\n return _addSwapFeeAmount(amountIn);\n }\n }\n\n /*\n * @dev Called when a swap with the Pool occurs, where the amount of tokens entering the Pool is known.\n *\n * Returns the amount of tokens that will be taken from the Pool in return.\n *\n * All amounts inside `swapRequest`, `balanceTokenIn` and `balanceTokenOut` are upscaled. The swap fee has already\n * been deducted from `swapRequest.amount`.\n *\n * The return value is also considered upscaled, and will be downscaled (rounding down) before returning it to the\n * Vault.\n */\n function _onSwapGivenIn(\n SwapRequest memory swapRequest,\n uint256 balanceTokenIn,\n uint256 balanceTokenOut\n ) internal view virtual returns (uint256);\n\n /*\n * @dev Called when a swap with the Pool occurs, where the amount of tokens exiting the Pool is known.\n *\n * Returns the amount of tokens that will be granted to the Pool in return.\n *\n * All amounts inside `swapRequest`, `balanceTokenIn` and `balanceTokenOut` are upscaled.\n *\n * The return value is also considered upscaled, and will be downscaled (rounding up) before applying the swap fee\n * and returning it to the Vault.\n */\n function _onSwapGivenOut(\n SwapRequest memory swapRequest,\n uint256 balanceTokenIn,\n uint256 balanceTokenOut\n ) internal view virtual returns (uint256);\n}\n" + }, + "contracts/StableMath.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/math/Math.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol\";\n\n// This is a contract to emulate file-level functions. Convert to a library\n// after the migration to solc v0.7.1.\n\n// solhint-disable private-vars-leading-underscore\n// solhint-disable var-name-mixedcase\n\ncontract StableMath {\n using FixedPoint for uint256;\n\n uint256 internal constant _MIN_AMP = 1;\n uint256 internal constant _MAX_AMP = 5000;\n uint256 internal constant _AMP_PRECISION = 1e3;\n\n uint256 internal constant _MAX_STABLE_TOKENS = 5;\n\n // Note on unchecked arithmetic:\n // This contract performs a large number of additions, subtractions, multiplications and divisions, often inside\n // loops. Since many of these operations are gas-sensitive (as they happen e.g. during a swap), it is important to\n // not make any unnecessary checks. We rely on a set of invariants to avoid having to use checked arithmetic (the\n // Math library), including:\n // - the number of tokens is bounded by _MAX_STABLE_TOKENS\n // - the amplification parameter is bounded by _MAX_AMP * _AMP_PRECISION, which fits in 23 bits\n // - the token balances are bounded by 2^112 (guaranteed by the Vault) times 1e18 (the maximum scaling factor),\n // which fits in 172 bits\n //\n // This means e.g. we can safely multiply a balance by the amplification parameter without worrying about overflow.\n\n // Computes the invariant given the current balances, using the Newton-Raphson approximation.\n // The amplification parameter equals: A n^(n-1)\n function _calculateInvariant(\n uint256 amplificationParameter,\n uint256[] memory balances,\n bool roundUp\n ) internal pure returns (uint256) {\n /**********************************************************************************************\n // invariant //\n // D = invariant D^(n+1) //\n // A = amplification coefficient A n^n S + D = A D n^n + ----------- //\n // S = sum of balances n^n P //\n // P = product of balances //\n // n = number of tokens //\n *********x************************************************************************************/\n\n // We support rounding up or down.\n\n uint256 sum = 0;\n uint256 numTokens = balances.length;\n for (uint256 i = 0; i < numTokens; i++) {\n sum = sum.add(balances[i]);\n }\n if (sum == 0) {\n return 0;\n }\n\n uint256 prevInvariant = 0;\n uint256 invariant = sum;\n uint256 ampTimesTotal = amplificationParameter * numTokens;\n\n for (uint256 i = 0; i < 255; i++) {\n uint256 P_D = balances[0] * numTokens;\n for (uint256 j = 1; j < numTokens; j++) {\n P_D = Math.div(Math.mul(Math.mul(P_D, balances[j]), numTokens), invariant, roundUp);\n }\n prevInvariant = invariant;\n invariant = Math.div(\n Math.mul(Math.mul(numTokens, invariant), invariant).add(\n Math.div(Math.mul(Math.mul(ampTimesTotal, sum), P_D), _AMP_PRECISION, roundUp)\n ),\n Math.mul(numTokens + 1, invariant).add(\n // No need to use checked arithmetic for the amp precision, the amp is guaranteed to be at least 1\n Math.div(Math.mul(ampTimesTotal - _AMP_PRECISION, P_D), _AMP_PRECISION, !roundUp)\n ),\n roundUp\n );\n\n if (invariant > prevInvariant) {\n if (invariant - prevInvariant <= 1) {\n return invariant;\n }\n } else if (prevInvariant - invariant <= 1) {\n return invariant;\n }\n }\n\n _revert(Errors.STABLE_GET_BALANCE_DIDNT_CONVERGE);\n }\n\n // Computes how many tokens can be taken out of a pool if `tokenAmountIn` are sent, given the current balances.\n // The amplification parameter equals: A n^(n-1)\n function _calcOutGivenIn(\n uint256 amplificationParameter,\n uint256[] memory balances,\n uint256 tokenIndexIn,\n uint256 tokenIndexOut,\n uint256 tokenAmountIn\n ) internal pure returns (uint256) {\n /**************************************************************************************************************\n // outGivenIn token x for y - polynomial equation to solve //\n // ay = amount out to calculate //\n // by = balance token out //\n // y = by - ay (finalBalanceOut) //\n // D = invariant D D^(n+1) //\n // A = amplification coefficient y^2 + ( S - ---------- - D) * y - ------------- = 0 //\n // n = number of tokens (A * n^n) A * n^2n * P //\n // S = sum of final balances but y //\n // P = product of final balances but y //\n **************************************************************************************************************/\n\n // Amount out, so we round down overall.\n\n // Given that we need to have a greater final balance out, the invariant needs to be rounded up\n uint256 invariant = _calculateInvariant(amplificationParameter, balances, true);\n\n balances[tokenIndexIn] = balances[tokenIndexIn].add(tokenAmountIn);\n\n uint256 finalBalanceOut = _getTokenBalanceGivenInvariantAndAllOtherBalances(\n amplificationParameter,\n balances,\n invariant,\n tokenIndexOut\n );\n\n // No need to use checked arithmetic since `tokenAmountIn` was actually added to the same balance right before\n // calling `_getTokenBalanceGivenInvariantAndAllOtherBalances` which doesn't alter the balances array.\n balances[tokenIndexIn] = balances[tokenIndexIn] - tokenAmountIn;\n\n return balances[tokenIndexOut].sub(finalBalanceOut).sub(1);\n }\n\n // Computes how many tokens must be sent to a pool if `tokenAmountOut` are sent given the\n // current balances, using the Newton-Raphson approximation.\n // The amplification parameter equals: A n^(n-1)\n function _calcInGivenOut(\n uint256 amplificationParameter,\n uint256[] memory balances,\n uint256 tokenIndexIn,\n uint256 tokenIndexOut,\n uint256 tokenAmountOut\n ) internal pure returns (uint256) {\n /**************************************************************************************************************\n // inGivenOut token x for y - polynomial equation to solve //\n // ax = amount in to calculate //\n // bx = balance token in //\n // x = bx + ax (finalBalanceIn) //\n // D = invariant D D^(n+1) //\n // A = amplification coefficient x^2 + ( S - ---------- - D) * x - ------------- = 0 //\n // n = number of tokens (A * n^n) A * n^2n * P //\n // S = sum of final balances but x //\n // P = product of final balances but x //\n **************************************************************************************************************/\n\n // Amount in, so we round up overall.\n\n // Given that we need to have a greater final balance in, the invariant needs to be rounded up\n uint256 invariant = _calculateInvariant(amplificationParameter, balances, true);\n\n balances[tokenIndexOut] = balances[tokenIndexOut].sub(tokenAmountOut);\n\n uint256 finalBalanceIn = _getTokenBalanceGivenInvariantAndAllOtherBalances(\n amplificationParameter,\n balances,\n invariant,\n tokenIndexIn\n );\n\n // No need to use checked arithmetic since `tokenAmountOut` was actually subtracted from the same balance right\n // before calling `_getTokenBalanceGivenInvariantAndAllOtherBalances` which doesn't alter the balances array.\n balances[tokenIndexOut] = balances[tokenIndexOut] + tokenAmountOut;\n\n return finalBalanceIn.sub(balances[tokenIndexIn]).add(1);\n }\n\n function _calcBptOutGivenExactTokensIn(\n uint256 amp,\n uint256[] memory balances,\n uint256[] memory amountsIn,\n uint256 bptTotalSupply,\n uint256 swapFeePercentage\n ) internal pure returns (uint256) {\n // BPT out, so we round down overall.\n\n // First loop calculates the sum of all token balances, which will be used to calculate\n // the current weights of each token, relative to this sum\n uint256 sumBalances = 0;\n for (uint256 i = 0; i < balances.length; i++) {\n sumBalances = sumBalances.add(balances[i]);\n }\n\n // Calculate the weighted balance ratio without considering fees\n uint256[] memory balanceRatiosWithFee = new uint256[](amountsIn.length);\n // The weighted sum of token balance ratios without fee\n uint256 invariantRatioWithFees = 0;\n for (uint256 i = 0; i < balances.length; i++) {\n uint256 currentWeight = balances[i].divDown(sumBalances);\n balanceRatiosWithFee[i] = balances[i].add(amountsIn[i]).divDown(balances[i]);\n invariantRatioWithFees = invariantRatioWithFees.add(balanceRatiosWithFee[i].mulDown(currentWeight));\n }\n\n // Second loop calculates new amounts in, taking into account the fee on the percentage excess\n uint256[] memory newBalances = new uint256[](balances.length);\n for (uint256 i = 0; i < balances.length; i++) {\n uint256 amountInWithoutFee;\n\n // Check if the balance ratio is greater than the ideal ratio to charge fees or not\n if (balanceRatiosWithFee[i] > invariantRatioWithFees) {\n uint256 nonTaxableAmount = balances[i].mulDown(invariantRatioWithFees.sub(FixedPoint.ONE));\n uint256 taxableAmount = amountsIn[i].sub(nonTaxableAmount);\n // No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%\n amountInWithoutFee = nonTaxableAmount.add(taxableAmount.mulDown(FixedPoint.ONE - swapFeePercentage));\n } else {\n amountInWithoutFee = amountsIn[i];\n }\n\n newBalances[i] = balances[i].add(amountInWithoutFee);\n }\n\n // Get current and new invariants, taking swap fees into account\n uint256 currentInvariant = _calculateInvariant(amp, balances, true);\n uint256 newInvariant = _calculateInvariant(amp, newBalances, false);\n uint256 invariantRatio = newInvariant.divDown(currentInvariant);\n\n // If the invariant didn't increase for any reason, we simply don't mint BPT\n if (invariantRatio > FixedPoint.ONE) {\n return bptTotalSupply.mulDown(invariantRatio - FixedPoint.ONE);\n } else {\n return 0;\n }\n }\n\n function _calcTokenInGivenExactBptOut(\n uint256 amp,\n uint256[] memory balances,\n uint256 tokenIndex,\n uint256 bptAmountOut,\n uint256 bptTotalSupply,\n uint256 swapFeePercentage\n ) internal pure returns (uint256) {\n // Token in, so we round up overall.\n\n // Get the current invariant\n uint256 currentInvariant = _calculateInvariant(amp, balances, true);\n\n // Calculate new invariant\n uint256 newInvariant = bptTotalSupply.add(bptAmountOut).divUp(bptTotalSupply).mulUp(currentInvariant);\n\n // Calculate amount in without fee.\n uint256 newBalanceTokenIndex = _getTokenBalanceGivenInvariantAndAllOtherBalances(\n amp,\n balances,\n newInvariant,\n tokenIndex\n );\n uint256 amountInWithoutFee = newBalanceTokenIndex.sub(balances[tokenIndex]);\n\n // First calculate the sum of all token balances, which will be used to calculate\n // the current weight of each token\n uint256 sumBalances = 0;\n for (uint256 i = 0; i < balances.length; i++) {\n sumBalances = sumBalances.add(balances[i]);\n }\n\n // We can now compute how much extra balance is being deposited and used in virtual swaps, and charge swap fees\n // accordingly.\n uint256 currentWeight = balances[tokenIndex].divDown(sumBalances);\n uint256 taxablePercentage = currentWeight.complement();\n uint256 taxableAmount = amountInWithoutFee.mulUp(taxablePercentage);\n uint256 nonTaxableAmount = amountInWithoutFee.sub(taxableAmount);\n\n // No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%\n return nonTaxableAmount.add(taxableAmount.divUp(FixedPoint.ONE - swapFeePercentage));\n }\n\n /*\n Flow of calculations:\n amountsTokenOut -> amountsOutProportional ->\n amountOutPercentageExcess -> amountOutBeforeFee -> newInvariant -> amountBPTIn\n */\n function _calcBptInGivenExactTokensOut(\n uint256 amp,\n uint256[] memory balances,\n uint256[] memory amountsOut,\n uint256 bptTotalSupply,\n uint256 swapFeePercentage\n ) internal pure returns (uint256) {\n // BPT in, so we round up overall.\n\n // First loop calculates the sum of all token balances, which will be used to calculate\n // the current weights of each token relative to this sum\n uint256 sumBalances = 0;\n for (uint256 i = 0; i < balances.length; i++) {\n sumBalances = sumBalances.add(balances[i]);\n }\n\n // Calculate the weighted balance ratio without considering fees\n uint256[] memory balanceRatiosWithoutFee = new uint256[](amountsOut.length);\n uint256 invariantRatioWithoutFees = 0;\n for (uint256 i = 0; i < balances.length; i++) {\n uint256 currentWeight = balances[i].divUp(sumBalances);\n balanceRatiosWithoutFee[i] = balances[i].sub(amountsOut[i]).divUp(balances[i]);\n invariantRatioWithoutFees = invariantRatioWithoutFees.add(balanceRatiosWithoutFee[i].mulUp(currentWeight));\n }\n\n // Second loop calculates new amounts in, taking into account the fee on the percentage excess\n uint256[] memory newBalances = new uint256[](balances.length);\n for (uint256 i = 0; i < balances.length; i++) {\n // Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it to\n // 'token out'. This results in slightly larger price impact.\n\n uint256 amountOutWithFee;\n if (invariantRatioWithoutFees > balanceRatiosWithoutFee[i]) {\n uint256 nonTaxableAmount = balances[i].mulDown(invariantRatioWithoutFees.complement());\n uint256 taxableAmount = amountsOut[i].sub(nonTaxableAmount);\n // No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%\n amountOutWithFee = nonTaxableAmount.add(taxableAmount.divUp(FixedPoint.ONE - swapFeePercentage));\n } else {\n amountOutWithFee = amountsOut[i];\n }\n\n newBalances[i] = balances[i].sub(amountOutWithFee);\n }\n\n // Get current and new invariants, taking into account swap fees\n uint256 currentInvariant = _calculateInvariant(amp, balances, true);\n uint256 newInvariant = _calculateInvariant(amp, newBalances, false);\n uint256 invariantRatio = newInvariant.divDown(currentInvariant);\n\n // return amountBPTIn\n return bptTotalSupply.mulUp(invariantRatio.complement());\n }\n\n function _calcTokenOutGivenExactBptIn(\n uint256 amp,\n uint256[] memory balances,\n uint256 tokenIndex,\n uint256 bptAmountIn,\n uint256 bptTotalSupply,\n uint256 swapFeePercentage\n ) internal pure returns (uint256) {\n // Token out, so we round down overall.\n\n // Get the current and new invariants. Since we need a bigger new invariant, we round the current one up.\n uint256 currentInvariant = _calculateInvariant(amp, balances, true);\n uint256 newInvariant = bptTotalSupply.sub(bptAmountIn).divUp(bptTotalSupply).mulUp(currentInvariant);\n\n // Calculate amount out without fee\n uint256 newBalanceTokenIndex = _getTokenBalanceGivenInvariantAndAllOtherBalances(\n amp,\n balances,\n newInvariant,\n tokenIndex\n );\n uint256 amountOutWithoutFee = balances[tokenIndex].sub(newBalanceTokenIndex);\n\n // First calculate the sum of all token balances, which will be used to calculate\n // the current weight of each token\n uint256 sumBalances = 0;\n for (uint256 i = 0; i < balances.length; i++) {\n sumBalances = sumBalances.add(balances[i]);\n }\n\n // We can now compute how much excess balance is being withdrawn as a result of the virtual swaps, which result\n // in swap fees.\n uint256 currentWeight = balances[tokenIndex].divDown(sumBalances);\n uint256 taxablePercentage = currentWeight.complement();\n\n // Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it\n // to 'token out'. This results in slightly larger price impact. Fees are rounded up.\n uint256 taxableAmount = amountOutWithoutFee.mulUp(taxablePercentage);\n uint256 nonTaxableAmount = amountOutWithoutFee.sub(taxableAmount);\n\n // No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%\n return nonTaxableAmount.add(taxableAmount.mulDown(FixedPoint.ONE - swapFeePercentage));\n }\n\n function _calcTokensOutGivenExactBptIn(\n uint256[] memory balances,\n uint256 bptAmountIn,\n uint256 bptTotalSupply\n ) internal pure returns (uint256[] memory) {\n /**********************************************************************************************\n // exactBPTInForTokensOut //\n // (per token) //\n // aO = tokenAmountOut / bptIn \\ //\n // b = tokenBalance a0 = b * | --------------------- | //\n // bptIn = bptAmountIn \\ bptTotalSupply / //\n // bpt = bptTotalSupply //\n **********************************************************************************************/\n\n // Since we're computing an amount out, we round down overall. This means rounding down on both the\n // multiplication and division.\n\n uint256 bptRatio = bptAmountIn.divDown(bptTotalSupply);\n\n uint256[] memory amountsOut = new uint256[](balances.length);\n for (uint256 i = 0; i < balances.length; i++) {\n amountsOut[i] = balances[i].mulDown(bptRatio);\n }\n\n return amountsOut;\n }\n\n // The amplification parameter equals: A n^(n-1)\n function _calcDueTokenProtocolSwapFeeAmount(\n uint256 amplificationParameter,\n uint256[] memory balances,\n uint256 lastInvariant,\n uint256 tokenIndex,\n uint256 protocolSwapFeePercentage\n ) internal pure returns (uint256) {\n /**************************************************************************************************************\n // oneTokenSwapFee - polynomial equation to solve //\n // af = fee amount to calculate in one token //\n // bf = balance of fee token //\n // f = bf - af (finalBalanceFeeToken) //\n // D = old invariant D D^(n+1) //\n // A = amplification coefficient f^2 + ( S - ---------- - D) * f - ------------- = 0 //\n // n = number of tokens (A * n^n) A * n^2n * P //\n // S = sum of final balances but f //\n // P = product of final balances but f //\n **************************************************************************************************************/\n\n // Protocol swap fee amount, so we round down overall.\n\n uint256 finalBalanceFeeToken = _getTokenBalanceGivenInvariantAndAllOtherBalances(\n amplificationParameter,\n balances,\n lastInvariant,\n tokenIndex\n );\n\n if (balances[tokenIndex] <= finalBalanceFeeToken) {\n // This shouldn't happen outside of rounding errors, but have this safeguard nonetheless to prevent the Pool\n // from entering a locked state in which joins and exits revert while computing accumulated swap fees.\n return 0;\n }\n\n // Result is rounded down\n uint256 accumulatedTokenSwapFees = balances[tokenIndex] - finalBalanceFeeToken;\n return accumulatedTokenSwapFees.mulDown(protocolSwapFeePercentage).divDown(FixedPoint.ONE);\n }\n\n // Private functions\n\n // This function calculates the balance of a given token (tokenIndex)\n // given all the other balances and the invariant\n function _getTokenBalanceGivenInvariantAndAllOtherBalances(\n uint256 amplificationParameter,\n uint256[] memory balances,\n uint256 invariant,\n uint256 tokenIndex\n ) internal pure returns (uint256) {\n // Rounds result up overall\n\n uint256 ampTimesTotal = amplificationParameter * balances.length;\n uint256 sum = balances[0];\n uint256 P_D = balances[0] * balances.length;\n for (uint256 j = 1; j < balances.length; j++) {\n P_D = Math.divDown(Math.mul(Math.mul(P_D, balances[j]), balances.length), invariant);\n sum = sum.add(balances[j]);\n }\n // No need to use safe math, based on the loop above `sum` is greater than or equal to `balances[tokenIndex]`\n sum = sum - balances[tokenIndex];\n\n uint256 inv2 = Math.mul(invariant, invariant);\n // We remove the balance fromm c by multiplying it\n uint256 c = Math.mul(\n Math.mul(Math.divUp(inv2, Math.mul(ampTimesTotal, P_D)), _AMP_PRECISION),\n balances[tokenIndex]\n );\n uint256 b = sum.add(Math.mul(Math.divDown(invariant, ampTimesTotal), _AMP_PRECISION));\n\n // We iterate to find the balance\n uint256 prevTokenBalance = 0;\n // We multiply the first iteration outside the loop with the invariant to set the value of the\n // initial approximation.\n uint256 tokenBalance = Math.divUp(inv2.add(c), invariant.add(b));\n\n for (uint256 i = 0; i < 255; i++) {\n prevTokenBalance = tokenBalance;\n\n tokenBalance = Math.divUp(\n Math.mul(tokenBalance, tokenBalance).add(c),\n Math.mul(tokenBalance, 2).add(b).sub(invariant)\n );\n\n if (tokenBalance > prevTokenBalance) {\n if (tokenBalance - prevTokenBalance <= 1) {\n return tokenBalance;\n }\n } else if (prevTokenBalance - tokenBalance <= 1) {\n return tokenBalance;\n }\n }\n\n _revert(Errors.STABLE_GET_BALANCE_DIDNT_CONVERGE);\n }\n}\n" + }, + "contracts/StablePoolUserDataHelpers.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol\";\n\nimport \"./StablePool.sol\";\n\nlibrary StablePoolUserDataHelpers {\n function joinKind(bytes memory self) internal pure returns (StablePool.JoinKind) {\n return abi.decode(self, (StablePool.JoinKind));\n }\n\n function exitKind(bytes memory self) internal pure returns (StablePool.ExitKind) {\n return abi.decode(self, (StablePool.ExitKind));\n }\n\n // Joins\n\n function initialAmountsIn(bytes memory self) internal pure returns (uint256[] memory amountsIn) {\n (, amountsIn) = abi.decode(self, (StablePool.JoinKind, uint256[]));\n }\n\n function exactTokensInForBptOut(bytes memory self)\n internal\n pure\n returns (uint256[] memory amountsIn, uint256 minBPTAmountOut)\n {\n (, amountsIn, minBPTAmountOut) = abi.decode(self, (StablePool.JoinKind, uint256[], uint256));\n }\n\n function tokenInForExactBptOut(bytes memory self) internal pure returns (uint256 bptAmountOut, uint256 tokenIndex) {\n (, bptAmountOut, tokenIndex) = abi.decode(self, (StablePool.JoinKind, uint256, uint256));\n }\n\n // Exits\n\n function exactBptInForTokenOut(bytes memory self) internal pure returns (uint256 bptAmountIn, uint256 tokenIndex) {\n (, bptAmountIn, tokenIndex) = abi.decode(self, (StablePool.ExitKind, uint256, uint256));\n }\n\n function exactBptInForTokensOut(bytes memory self) internal pure returns (uint256 bptAmountIn) {\n (, bptAmountIn) = abi.decode(self, (StablePool.ExitKind, uint256));\n }\n\n function bptInForExactTokensOut(bytes memory self)\n internal\n pure\n returns (uint256[] memory amountsOut, uint256 maxBPTAmountIn)\n {\n (, amountsOut, maxBPTAmountIn) = abi.decode(self, (StablePool.ExitKind, uint256[], uint256));\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/math/LogExpMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the “Software”), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n\n// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\npragma solidity ^0.7.0;\n\nimport \"../helpers/BalancerErrors.sol\";\n\n/* solhint-disable */\n\n/**\n * @dev Exponentiation and logarithm functions for 18 decimal fixed point numbers (both base and exponent/argument).\n *\n * Exponentiation and logarithm with arbitrary bases (x^y and log_x(y)) are implemented by conversion to natural\n * exponentiation and logarithm (where the base is Euler's number).\n *\n * @author Fernando Martinelli - @fernandomartinelli\n * @author Sergio Yuhjtman - @sergioyuhjtman\n * @author Daniel Fernandez - @dmf7z\n */\nlibrary LogExpMath {\n // All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying\n // two numbers, and multiply by ONE when dividing them.\n\n // All arguments and return values are 18 decimal fixed point numbers.\n int256 constant ONE_18 = 1e18;\n\n // Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the\n // case of ln36, 36 decimals.\n int256 constant ONE_20 = 1e20;\n int256 constant ONE_36 = 1e36;\n\n // The domain of natural exponentiation is bound by the word size and number of decimals used.\n //\n // Because internally the result will be stored using 20 decimals, the largest possible result is\n // (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221.\n // The smallest possible result is 10^(-18), which makes largest negative argument\n // ln(10^(-18)) = -41.446531673892822312.\n // We use 130.0 and -41.0 to have some safety margin.\n int256 constant MAX_NATURAL_EXPONENT = 130e18;\n int256 constant MIN_NATURAL_EXPONENT = -41e18;\n\n // Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point\n // 256 bit integer.\n int256 constant LN_36_LOWER_BOUND = ONE_18 - 1e17;\n int256 constant LN_36_UPPER_BOUND = ONE_18 + 1e17;\n\n uint256 constant MILD_EXPONENT_BOUND = 2**254 / uint256(ONE_20);\n\n // 18 decimal constants\n int256 constant x0 = 128000000000000000000; // 2ˆ7\n int256 constant a0 = 38877084059945950922200000000000000000000000000000000000; // eˆ(x0) (no decimals)\n int256 constant x1 = 64000000000000000000; // 2ˆ6\n int256 constant a1 = 6235149080811616882910000000; // eˆ(x1) (no decimals)\n\n // 20 decimal constants\n int256 constant x2 = 3200000000000000000000; // 2ˆ5\n int256 constant a2 = 7896296018268069516100000000000000; // eˆ(x2)\n int256 constant x3 = 1600000000000000000000; // 2ˆ4\n int256 constant a3 = 888611052050787263676000000; // eˆ(x3)\n int256 constant x4 = 800000000000000000000; // 2ˆ3\n int256 constant a4 = 298095798704172827474000; // eˆ(x4)\n int256 constant x5 = 400000000000000000000; // 2ˆ2\n int256 constant a5 = 5459815003314423907810; // eˆ(x5)\n int256 constant x6 = 200000000000000000000; // 2ˆ1\n int256 constant a6 = 738905609893065022723; // eˆ(x6)\n int256 constant x7 = 100000000000000000000; // 2ˆ0\n int256 constant a7 = 271828182845904523536; // eˆ(x7)\n int256 constant x8 = 50000000000000000000; // 2ˆ-1\n int256 constant a8 = 164872127070012814685; // eˆ(x8)\n int256 constant x9 = 25000000000000000000; // 2ˆ-2\n int256 constant a9 = 128402541668774148407; // eˆ(x9)\n int256 constant x10 = 12500000000000000000; // 2ˆ-3\n int256 constant a10 = 113314845306682631683; // eˆ(x10)\n int256 constant x11 = 6250000000000000000; // 2ˆ-4\n int256 constant a11 = 106449445891785942956; // eˆ(x11)\n\n /**\n * @dev Exponentiation (x^y) with unsigned 18 decimal fixed point base and exponent.\n *\n * Reverts if ln(x) * y is smaller than `MIN_NATURAL_EXPONENT`, or larger than `MAX_NATURAL_EXPONENT`.\n */\n function pow(uint256 x, uint256 y) internal pure returns (uint256) {\n if (y == 0) {\n // We solve the 0^0 indetermination by making it equal one.\n return uint256(ONE_18);\n }\n\n if (x == 0) {\n return 0;\n }\n\n // Instead of computing x^y directly, we instead rely on the properties of logarithms and exponentiation to\n // arrive at that result. In particular, exp(ln(x)) = x, and ln(x^y) = y * ln(x). This means\n // x^y = exp(y * ln(x)).\n\n // The ln function takes a signed value, so we need to make sure x fits in the signed 256 bit range.\n _require(x < 2**255, Errors.X_OUT_OF_BOUNDS);\n int256 x_int256 = int256(x);\n\n // We will compute y * ln(x) in a single step. Depending on the value of x, we can either use ln or ln_36. In\n // both cases, we leave the division by ONE_18 (due to fixed point multiplication) to the end.\n\n // This prevents y * ln(x) from overflowing, and at the same time guarantees y fits in the signed 256 bit range.\n _require(y < MILD_EXPONENT_BOUND, Errors.Y_OUT_OF_BOUNDS);\n int256 y_int256 = int256(y);\n\n int256 logx_times_y;\n if (LN_36_LOWER_BOUND < x_int256 && x_int256 < LN_36_UPPER_BOUND) {\n int256 ln_36_x = _ln_36(x_int256);\n\n // ln_36_x has 36 decimal places, so multiplying by y_int256 isn't as straightforward, since we can't just\n // bring y_int256 to 36 decimal places, as it might overflow. Instead, we perform two 18 decimal\n // multiplications and add the results: one with the first 18 decimals of ln_36_x, and one with the\n // (downscaled) last 18 decimals.\n logx_times_y = ((ln_36_x / ONE_18) * y_int256 + ((ln_36_x % ONE_18) * y_int256) / ONE_18);\n } else {\n logx_times_y = _ln(x_int256) * y_int256;\n }\n logx_times_y /= ONE_18;\n\n // Finally, we compute exp(y * ln(x)) to arrive at x^y\n _require(\n MIN_NATURAL_EXPONENT <= logx_times_y && logx_times_y <= MAX_NATURAL_EXPONENT,\n Errors.PRODUCT_OUT_OF_BOUNDS\n );\n\n return uint256(exp(logx_times_y));\n }\n\n /**\n * @dev Natural exponentiation (e^x) with signed 18 decimal fixed point exponent.\n *\n * Reverts if `x` is smaller than MIN_NATURAL_EXPONENT, or larger than `MAX_NATURAL_EXPONENT`.\n */\n function exp(int256 x) internal pure returns (int256) {\n _require(x >= MIN_NATURAL_EXPONENT && x <= MAX_NATURAL_EXPONENT, Errors.INVALID_EXPONENT);\n\n if (x < 0) {\n // We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it\n // fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT).\n // Fixed point division requires multiplying by ONE_18.\n return ((ONE_18 * ONE_18) / exp(-x));\n }\n\n // First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n,\n // where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7\n // because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the\n // decomposition.\n // At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this\n // decomposition, which will be lower than the smallest x_n.\n // exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1.\n // We mutate x by subtracting x_n, making it the remainder of the decomposition.\n\n // The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause\n // intermediate overflows. Instead we store them as plain integers, with 0 decimals.\n // Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the\n // decomposition.\n\n // For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct\n // it and compute the accumulated product.\n\n int256 firstAN;\n if (x >= x0) {\n x -= x0;\n firstAN = a0;\n } else if (x >= x1) {\n x -= x1;\n firstAN = a1;\n } else {\n firstAN = 1; // One with no decimal places\n }\n\n // We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the\n // smaller terms.\n x *= 100;\n\n // `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point\n // one. Recall that fixed point multiplication requires dividing by ONE_20.\n int256 product = ONE_20;\n\n if (x >= x2) {\n x -= x2;\n product = (product * a2) / ONE_20;\n }\n if (x >= x3) {\n x -= x3;\n product = (product * a3) / ONE_20;\n }\n if (x >= x4) {\n x -= x4;\n product = (product * a4) / ONE_20;\n }\n if (x >= x5) {\n x -= x5;\n product = (product * a5) / ONE_20;\n }\n if (x >= x6) {\n x -= x6;\n product = (product * a6) / ONE_20;\n }\n if (x >= x7) {\n x -= x7;\n product = (product * a7) / ONE_20;\n }\n if (x >= x8) {\n x -= x8;\n product = (product * a8) / ONE_20;\n }\n if (x >= x9) {\n x -= x9;\n product = (product * a9) / ONE_20;\n }\n\n // x10 and x11 are unnecessary here since we have high enough precision already.\n\n // Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series\n // expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!).\n\n int256 seriesSum = ONE_20; // The initial one in the sum, with 20 decimal places.\n int256 term; // Each term in the sum, where the nth term is (x^n / n!).\n\n // The first term is simply x.\n term = x;\n seriesSum += term;\n\n // Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number,\n // multiplying by it requires dividing by ONE_20, but dividing by the non-fixed point n values does not.\n\n term = ((term * x) / ONE_20) / 2;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 3;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 4;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 5;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 6;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 7;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 8;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 9;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 10;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 11;\n seriesSum += term;\n\n term = ((term * x) / ONE_20) / 12;\n seriesSum += term;\n\n // 12 Taylor terms are sufficient for 18 decimal precision.\n\n // We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor\n // approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply\n // all three (one 20 decimal fixed point multiplication, dividing by ONE_20, and one integer multiplication),\n // and then drop two digits to return an 18 decimal value.\n\n return (((product * seriesSum) / ONE_20) * firstAN) / 100;\n }\n\n /**\n * @dev Logarithm (log(arg, base), with signed 18 decimal fixed point base and argument.\n */\n function log(int256 arg, int256 base) internal pure returns (int256) {\n // This performs a simple base change: log(arg, base) = ln(arg) / ln(base).\n\n // Both logBase and logArg are computed as 36 decimal fixed point numbers, either by using ln_36, or by\n // upscaling.\n\n int256 logBase;\n if (LN_36_LOWER_BOUND < base && base < LN_36_UPPER_BOUND) {\n logBase = _ln_36(base);\n } else {\n logBase = _ln(base) * ONE_18;\n }\n\n int256 logArg;\n if (LN_36_LOWER_BOUND < arg && arg < LN_36_UPPER_BOUND) {\n logArg = _ln_36(arg);\n } else {\n logArg = _ln(arg) * ONE_18;\n }\n\n // When dividing, we multiply by ONE_18 to arrive at a result with 18 decimal places\n return (logArg * ONE_18) / logBase;\n }\n\n /**\n * @dev Natural logarithm (ln(a)) with signed 18 decimal fixed point argument.\n */\n function ln(int256 a) internal pure returns (int256) {\n // The real natural logarithm is not defined for negative numbers or zero.\n _require(a > 0, Errors.OUT_OF_BOUNDS);\n if (LN_36_LOWER_BOUND < a && a < LN_36_UPPER_BOUND) {\n return _ln_36(a) / ONE_18;\n } else {\n return _ln(a);\n }\n }\n\n /**\n * @dev Internal natural logarithm (ln(a)) with signed 18 decimal fixed point argument.\n */\n function _ln(int256 a) private pure returns (int256) {\n if (a < ONE_18) {\n // Since ln(a^k) = k * ln(a), we can compute ln(a) as ln(a) = ln((1/a)^(-1)) = - ln((1/a)). If a is less\n // than one, 1/a will be greater than one, and this if statement will not be entered in the recursive call.\n // Fixed point division requires multiplying by ONE_18.\n return (-_ln((ONE_18 * ONE_18) / a));\n }\n\n // First, we use the fact that ln^(a * b) = ln(a) + ln(b) to decompose ln(a) into a sum of powers of two, which\n // we call x_n, where x_n == 2^(7 - n), which are the natural logarithm of precomputed quantities a_n (that is,\n // ln(a_n) = x_n). We choose the first x_n, x0, to equal 2^7 because the exponential of all larger powers cannot\n // be represented as 18 fixed point decimal numbers in 256 bits, and are therefore larger than a.\n // At the end of this process we will have the sum of all x_n = ln(a_n) that apply, and the remainder of this\n // decomposition, which will be lower than the smallest a_n.\n // ln(a) = k_0 * x_0 + k_1 * x_1 + ... + k_n * x_n + ln(remainder), where each k_n equals either 0 or 1.\n // We mutate a by subtracting a_n, making it the remainder of the decomposition.\n\n // For reasons related to how `exp` works, the first two a_n (e^(2^7) and e^(2^6)) are not stored as fixed point\n // numbers with 18 decimals, but instead as plain integers with 0 decimals, so we need to multiply them by\n // ONE_18 to convert them to fixed point.\n // For each a_n, we test if that term is present in the decomposition (if a is larger than it), and if so divide\n // by it and compute the accumulated sum.\n\n int256 sum = 0;\n if (a >= a0 * ONE_18) {\n a /= a0; // Integer, not fixed point division\n sum += x0;\n }\n\n if (a >= a1 * ONE_18) {\n a /= a1; // Integer, not fixed point division\n sum += x1;\n }\n\n // All other a_n and x_n are stored as 20 digit fixed point numbers, so we convert the sum and a to this format.\n sum *= 100;\n a *= 100;\n\n // Because further a_n are 20 digit fixed point numbers, we multiply by ONE_20 when dividing by them.\n\n if (a >= a2) {\n a = (a * ONE_20) / a2;\n sum += x2;\n }\n\n if (a >= a3) {\n a = (a * ONE_20) / a3;\n sum += x3;\n }\n\n if (a >= a4) {\n a = (a * ONE_20) / a4;\n sum += x4;\n }\n\n if (a >= a5) {\n a = (a * ONE_20) / a5;\n sum += x5;\n }\n\n if (a >= a6) {\n a = (a * ONE_20) / a6;\n sum += x6;\n }\n\n if (a >= a7) {\n a = (a * ONE_20) / a7;\n sum += x7;\n }\n\n if (a >= a8) {\n a = (a * ONE_20) / a8;\n sum += x8;\n }\n\n if (a >= a9) {\n a = (a * ONE_20) / a9;\n sum += x9;\n }\n\n if (a >= a10) {\n a = (a * ONE_20) / a10;\n sum += x10;\n }\n\n if (a >= a11) {\n a = (a * ONE_20) / a11;\n sum += x11;\n }\n\n // a is now a small number (smaller than a_11, which roughly equals 1.06). This means we can use a Taylor series\n // that converges rapidly for values of `a` close to one - the same one used in ln_36.\n // Let z = (a - 1) / (a + 1).\n // ln(a) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))\n\n // Recall that 20 digit fixed point division requires multiplying by ONE_20, and multiplication requires\n // division by ONE_20.\n int256 z = ((a - ONE_20) * ONE_20) / (a + ONE_20);\n int256 z_squared = (z * z) / ONE_20;\n\n // num is the numerator of the series: the z^(2 * n + 1) term\n int256 num = z;\n\n // seriesSum holds the accumulated sum of each term in the series, starting with the initial z\n int256 seriesSum = num;\n\n // In each step, the numerator is multiplied by z^2\n num = (num * z_squared) / ONE_20;\n seriesSum += num / 3;\n\n num = (num * z_squared) / ONE_20;\n seriesSum += num / 5;\n\n num = (num * z_squared) / ONE_20;\n seriesSum += num / 7;\n\n num = (num * z_squared) / ONE_20;\n seriesSum += num / 9;\n\n num = (num * z_squared) / ONE_20;\n seriesSum += num / 11;\n\n // 6 Taylor terms are sufficient for 36 decimal precision.\n\n // Finally, we multiply by 2 (non fixed point) to compute ln(remainder)\n seriesSum *= 2;\n\n // We now have the sum of all x_n present, and the Taylor approximation of the logarithm of the remainder (both\n // with 20 decimals). All that remains is to sum these two, and then drop two digits to return a 18 decimal\n // value.\n\n return (sum + seriesSum) / 100;\n }\n\n /**\n * @dev Intrnal high precision (36 decimal places) natural logarithm (ln(x)) with signed 18 decimal fixed point argument,\n * for x close to one.\n *\n * Should only be used if x is between LN_36_LOWER_BOUND and LN_36_UPPER_BOUND.\n */\n function _ln_36(int256 x) private pure returns (int256) {\n // Since ln(1) = 0, a value of x close to one will yield a very small result, which makes using 36 digits\n // worthwhile.\n\n // First, we transform x to a 36 digit fixed point value.\n x *= ONE_18;\n\n // We will use the following Taylor expansion, which converges very rapidly. Let z = (x - 1) / (x + 1).\n // ln(x) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))\n\n // Recall that 36 digit fixed point division requires multiplying by ONE_36, and multiplication requires\n // division by ONE_36.\n int256 z = ((x - ONE_36) * ONE_36) / (x + ONE_36);\n int256 z_squared = (z * z) / ONE_36;\n\n // num is the numerator of the series: the z^(2 * n + 1) term\n int256 num = z;\n\n // seriesSum holds the accumulated sum of each term in the series, starting with the initial z\n int256 seriesSum = num;\n\n // In each step, the numerator is multiplied by z^2\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 3;\n\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 5;\n\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 7;\n\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 9;\n\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 11;\n\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 13;\n\n num = (num * z_squared) / ONE_36;\n seriesSum += num / 15;\n\n // 8 Taylor terms are sufficient for 36 decimal precision.\n\n // All that remains is multiplying by 2 (non fixed point).\n return seriesSum * 2;\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/BalancerErrors.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\n// solhint-disable\n\n/**\n * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are\n * supported.\n */\nfunction _require(bool condition, uint256 errorCode) pure {\n if (!condition) _revert(errorCode);\n}\n\n/**\n * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.\n */\nfunction _revert(uint256 errorCode) pure {\n // We're going to dynamically create a revert string based on the error code, with the following format:\n // 'BAL#{errorCode}'\n // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).\n //\n // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a\n // number (8 to 16 bits) than the individual string characters.\n //\n // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a\n // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a\n // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.\n assembly {\n // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999\n // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for\n // the '0' character.\n\n let units := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let tenths := add(mod(errorCode, 10), 0x30)\n\n errorCode := div(errorCode, 10)\n let hundreds := add(mod(errorCode, 10), 0x30)\n\n // With the individual characters, we can now construct the full string. The \"BAL#\" part is a known constant\n // (0x42414c23): we simply shift this by 24 (to provide space for the 3 bytes of the error code), and add the\n // characters to it, each shifted by a multiple of 8.\n // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits\n // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte\n // array).\n\n let revertReason := shl(200, add(0x42414c23000000, add(add(units, shl(8, tenths)), shl(16, hundreds))))\n\n // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded\n // message will have the following layout:\n // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]\n\n // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We\n // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.\n mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).\n mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)\n // The string length is fixed: 7 characters.\n mstore(0x24, 7)\n // Finally, the string itself is stored.\n mstore(0x44, revertReason)\n\n // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of\n // the encoded message is therefore 4 + 32 + 32 + 32 = 100.\n revert(0, 100)\n }\n}\n\nlibrary Errors {\n // Math\n uint256 internal constant ADD_OVERFLOW = 0;\n uint256 internal constant SUB_OVERFLOW = 1;\n uint256 internal constant SUB_UNDERFLOW = 2;\n uint256 internal constant MUL_OVERFLOW = 3;\n uint256 internal constant ZERO_DIVISION = 4;\n uint256 internal constant DIV_INTERNAL = 5;\n uint256 internal constant X_OUT_OF_BOUNDS = 6;\n uint256 internal constant Y_OUT_OF_BOUNDS = 7;\n uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;\n uint256 internal constant INVALID_EXPONENT = 9;\n\n // Input\n uint256 internal constant OUT_OF_BOUNDS = 100;\n uint256 internal constant UNSORTED_ARRAY = 101;\n uint256 internal constant UNSORTED_TOKENS = 102;\n uint256 internal constant INPUT_LENGTH_MISMATCH = 103;\n uint256 internal constant ZERO_TOKEN = 104;\n\n // Shared pools\n uint256 internal constant MIN_TOKENS = 200;\n uint256 internal constant MAX_TOKENS = 201;\n uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;\n uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;\n uint256 internal constant MINIMUM_BPT = 204;\n uint256 internal constant CALLER_NOT_VAULT = 205;\n uint256 internal constant UNINITIALIZED = 206;\n uint256 internal constant BPT_IN_MAX_AMOUNT = 207;\n uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;\n uint256 internal constant EXPIRED_PERMIT = 209;\n uint256 internal constant NOT_TWO_TOKENS = 210;\n\n // Pools\n uint256 internal constant MIN_AMP = 300;\n uint256 internal constant MAX_AMP = 301;\n uint256 internal constant MIN_WEIGHT = 302;\n uint256 internal constant MAX_STABLE_TOKENS = 303;\n uint256 internal constant MAX_IN_RATIO = 304;\n uint256 internal constant MAX_OUT_RATIO = 305;\n uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;\n uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;\n uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;\n uint256 internal constant INVALID_TOKEN = 309;\n uint256 internal constant UNHANDLED_JOIN_KIND = 310;\n uint256 internal constant ZERO_INVARIANT = 311;\n uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;\n uint256 internal constant ORACLE_NOT_INITIALIZED = 313;\n uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;\n uint256 internal constant ORACLE_INVALID_INDEX = 315;\n uint256 internal constant ORACLE_BAD_SECS = 316;\n uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317;\n uint256 internal constant AMP_ONGOING_UPDATE = 318;\n uint256 internal constant AMP_RATE_TOO_HIGH = 319;\n uint256 internal constant AMP_NO_ONGOING_UPDATE = 320;\n uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;\n uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;\n uint256 internal constant RELAYER_NOT_CONTRACT = 323;\n uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;\n uint256 internal constant REBALANCING_RELAYER_REENTERED = 325;\n\n // Lib\n uint256 internal constant REENTRANCY = 400;\n uint256 internal constant SENDER_NOT_ALLOWED = 401;\n uint256 internal constant PAUSED = 402;\n uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;\n uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;\n uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;\n uint256 internal constant INSUFFICIENT_BALANCE = 406;\n uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;\n uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;\n uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;\n uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;\n uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;\n uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;\n uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;\n uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;\n uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;\n uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;\n uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;\n uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;\n uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;\n uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;\n uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;\n uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;\n uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;\n uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;\n uint256 internal constant CALLER_IS_NOT_OWNER = 426;\n uint256 internal constant NEW_OWNER_IS_ZERO = 427;\n uint256 internal constant CODE_DEPLOYMENT_FAILED = 428;\n\n // Vault\n uint256 internal constant INVALID_POOL_ID = 500;\n uint256 internal constant CALLER_NOT_POOL = 501;\n uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;\n uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;\n uint256 internal constant INVALID_SIGNATURE = 504;\n uint256 internal constant EXIT_BELOW_MIN = 505;\n uint256 internal constant JOIN_ABOVE_MAX = 506;\n uint256 internal constant SWAP_LIMIT = 507;\n uint256 internal constant SWAP_DEADLINE = 508;\n uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;\n uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;\n uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;\n uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;\n uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;\n uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;\n uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;\n uint256 internal constant INSUFFICIENT_ETH = 516;\n uint256 internal constant UNALLOCATED_ETH = 517;\n uint256 internal constant ETH_TRANSFER = 518;\n uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;\n uint256 internal constant TOKENS_MISMATCH = 520;\n uint256 internal constant TOKEN_NOT_REGISTERED = 521;\n uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;\n uint256 internal constant TOKENS_ALREADY_SET = 523;\n uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;\n uint256 internal constant NONZERO_TOKEN_BALANCE = 525;\n uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;\n uint256 internal constant POOL_NO_TOKENS = 527;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;\n\n // Fees\n uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;\n uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;\n uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@balancer-labs/v2-pool-utils/contracts/BasePool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/math/Math.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/TemporarilyPausable.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20.sol\";\n\nimport \"@balancer-labs/v2-vault/contracts/interfaces/IVault.sol\";\nimport \"@balancer-labs/v2-vault/contracts/interfaces/IBasePool.sol\";\n\nimport \"./BalancerPoolToken.sol\";\nimport \"./BasePoolAuthorization.sol\";\n\nimport \"@balancer-labs/v2-asset-manager-utils/contracts/IAssetManager.sol\";\n\n// This contract relies on tons of immutable state variables to perform efficient lookup, without resorting to storage\n// reads. Because immutable arrays are not supported, we instead declare a fixed set of state variables plus a total\n// count, resulting in a large number of state variables.\n\n// solhint-disable max-states-count\n\n/**\n * @dev Reference implementation for the base layer of a Pool contract that manages a single Pool with an immutable set\n * of registered tokens, no Asset Managers, an admin-controlled swap fee percentage, and an emergency pause mechanism.\n *\n * Note that neither swap fees nor the pause mechanism are used by this contract. They are passed through so that\n * derived contracts can use them via the `_addSwapFeeAmount` and `_subtractSwapFeeAmount` functions, and the\n * `whenNotPaused` modifier.\n *\n * No admin permissions are checked here: instead, this contract delegates that to the Vault's own Authorizer.\n *\n * Because this contract doesn't implement the swap hooks, derived contracts should generally inherit from\n * BaseGeneralPool or BaseMinimalSwapInfoPool. Otherwise, subclasses must inherit from the corresponding interfaces\n * and implement the swap callbacks themselves.\n */\nabstract contract BasePool is IBasePool, BasePoolAuthorization, BalancerPoolToken, TemporarilyPausable {\n using FixedPoint for uint256;\n\n uint256 private constant _MIN_TOKENS = 2;\n uint256 private constant _MAX_TOKENS = 8;\n\n // 1e18 corresponds to 1.0, or a 100% fee\n uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 1e12; // 0.0001%\n uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 1e17; // 10%\n\n uint256 private constant _MINIMUM_BPT = 1e6;\n\n uint256 internal _swapFeePercentage;\n\n IVault private immutable _vault;\n bytes32 private immutable _poolId;\n uint256 private immutable _totalTokens;\n\n IERC20 internal immutable _token0;\n IERC20 internal immutable _token1;\n IERC20 internal immutable _token2;\n IERC20 internal immutable _token3;\n IERC20 internal immutable _token4;\n IERC20 internal immutable _token5;\n IERC20 internal immutable _token6;\n IERC20 internal immutable _token7;\n\n // All token balances are normalized to behave as if the token had 18 decimals. We assume a token's decimals will\n // not change throughout its lifetime, and store the corresponding scaling factor for each at construction time.\n // These factors are always greater than or equal to one: tokens with more than 18 decimals are not supported.\n\n uint256 private immutable _scalingFactor0;\n uint256 private immutable _scalingFactor1;\n uint256 private immutable _scalingFactor2;\n uint256 private immutable _scalingFactor3;\n uint256 private immutable _scalingFactor4;\n uint256 private immutable _scalingFactor5;\n uint256 private immutable _scalingFactor6;\n uint256 private immutable _scalingFactor7;\n\n event SwapFeePercentageChanged(uint256 swapFeePercentage);\n\n constructor(\n IVault vault,\n IVault.PoolSpecialization specialization,\n string memory name,\n string memory symbol,\n IERC20[] memory tokens,\n address[] memory assetManagers,\n uint256 swapFeePercentage,\n uint256 pauseWindowDuration,\n uint256 bufferPeriodDuration,\n address owner\n )\n // Base Pools are expected to be deployed using factories. By using the factory address as the action\n // disambiguator, we make all Pools deployed by the same factory share action identifiers. This allows for\n // simpler management of permissions (such as being able to manage granting the 'set fee percentage' action in\n // any Pool created by the same factory), while still making action identifiers unique among different factories\n // if the selectors match, preventing accidental errors.\n Authentication(bytes32(uint256(msg.sender)))\n BalancerPoolToken(name, symbol)\n BasePoolAuthorization(owner)\n TemporarilyPausable(pauseWindowDuration, bufferPeriodDuration)\n {\n _require(tokens.length >= _MIN_TOKENS, Errors.MIN_TOKENS);\n _require(tokens.length <= _MAX_TOKENS, Errors.MAX_TOKENS);\n\n // The Vault only requires the token list to be ordered for the Two Token Pools specialization. However,\n // to make the developer experience consistent, we are requiring this condition for all the native pools.\n // Also, since these Pools will register tokens only once, we can ensure the Pool tokens will follow the same\n // order. We rely on this property to make Pools simpler to write, as it lets us assume that the\n // order of token-specific parameters (such as token weights) will not change.\n InputHelpers.ensureArrayIsSorted(tokens);\n\n _setSwapFeePercentage(swapFeePercentage);\n\n bytes32 poolId = vault.registerPool(specialization);\n\n vault.registerTokens(poolId, tokens, assetManagers);\n\n // Set immutable state variables - these cannot be read from during construction\n uint256 totalTokens = tokens.length;\n _vault = vault;\n _poolId = poolId;\n _totalTokens = totalTokens;\n\n // Immutable variables cannot be initialized inside an if statement, so we must do conditional assignments\n _token0 = totalTokens > 0 ? tokens[0] : IERC20(0);\n _token1 = totalTokens > 1 ? tokens[1] : IERC20(0);\n _token2 = totalTokens > 2 ? tokens[2] : IERC20(0);\n _token3 = totalTokens > 3 ? tokens[3] : IERC20(0);\n _token4 = totalTokens > 4 ? tokens[4] : IERC20(0);\n _token5 = totalTokens > 5 ? tokens[5] : IERC20(0);\n _token6 = totalTokens > 6 ? tokens[6] : IERC20(0);\n _token7 = totalTokens > 7 ? tokens[7] : IERC20(0);\n\n _scalingFactor0 = totalTokens > 0 ? _computeScalingFactor(tokens[0]) : 0;\n _scalingFactor1 = totalTokens > 1 ? _computeScalingFactor(tokens[1]) : 0;\n _scalingFactor2 = totalTokens > 2 ? _computeScalingFactor(tokens[2]) : 0;\n _scalingFactor3 = totalTokens > 3 ? _computeScalingFactor(tokens[3]) : 0;\n _scalingFactor4 = totalTokens > 4 ? _computeScalingFactor(tokens[4]) : 0;\n _scalingFactor5 = totalTokens > 5 ? _computeScalingFactor(tokens[5]) : 0;\n _scalingFactor6 = totalTokens > 6 ? _computeScalingFactor(tokens[6]) : 0;\n _scalingFactor7 = totalTokens > 7 ? _computeScalingFactor(tokens[7]) : 0;\n }\n\n // Getters / Setters\n\n function getVault() public view returns (IVault) {\n return _vault;\n }\n\n function getPoolId() public view override returns (bytes32) {\n return _poolId;\n }\n\n function _getTotalTokens() internal view returns (uint256) {\n return _totalTokens;\n }\n\n function getSwapFeePercentage() external view returns (uint256) {\n return _swapFeePercentage;\n }\n\n function setSwapFeePercentage(uint256 swapFeePercentage) public virtual authenticate whenNotPaused {\n _setSwapFeePercentage(swapFeePercentage);\n }\n\n function _setSwapFeePercentage(uint256 swapFeePercentage) private {\n _require(swapFeePercentage >= _MIN_SWAP_FEE_PERCENTAGE, Errors.MIN_SWAP_FEE_PERCENTAGE);\n _require(swapFeePercentage <= _MAX_SWAP_FEE_PERCENTAGE, Errors.MAX_SWAP_FEE_PERCENTAGE);\n\n _swapFeePercentage = swapFeePercentage;\n emit SwapFeePercentageChanged(swapFeePercentage);\n }\n\n function setAssetManagerPoolConfig(IERC20 token, bytes memory poolConfig)\n public\n virtual\n authenticate\n whenNotPaused\n {\n _setAssetManagerPoolConfig(token, poolConfig);\n }\n\n function _setAssetManagerPoolConfig(IERC20 token, bytes memory poolConfig) private {\n bytes32 poolId = getPoolId();\n (, , , address assetManager) = getVault().getPoolTokenInfo(poolId, token);\n\n IAssetManager(assetManager).setConfig(poolId, poolConfig);\n }\n\n function setPaused(bool paused) external authenticate {\n _setPaused(paused);\n }\n\n function _isOwnerOnlyAction(bytes32 actionId) internal view virtual override returns (bool) {\n return\n (actionId == getActionId(this.setSwapFeePercentage.selector)) ||\n (actionId == getActionId(this.setAssetManagerPoolConfig.selector));\n }\n\n // Join / Exit Hooks\n\n modifier onlyVault(bytes32 poolId) {\n _require(msg.sender == address(getVault()), Errors.CALLER_NOT_VAULT);\n _require(poolId == getPoolId(), Errors.INVALID_POOL_ID);\n _;\n }\n\n function onJoinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData\n ) public virtual override onlyVault(poolId) returns (uint256[] memory, uint256[] memory) {\n uint256[] memory scalingFactors = _scalingFactors();\n\n if (totalSupply() == 0) {\n (uint256 bptAmountOut, uint256[] memory amountsIn) = _onInitializePool(\n poolId,\n sender,\n recipient,\n scalingFactors,\n userData\n );\n\n // On initialization, we lock _MINIMUM_BPT by minting it for the zero address. This BPT acts as a minimum\n // as it will never be burned, which reduces potential issues with rounding, and also prevents the Pool from\n // ever being fully drained.\n _require(bptAmountOut >= _MINIMUM_BPT, Errors.MINIMUM_BPT);\n _mintPoolTokens(address(0), _MINIMUM_BPT);\n _mintPoolTokens(recipient, bptAmountOut - _MINIMUM_BPT);\n\n // amountsIn are amounts entering the Pool, so we round up.\n _downscaleUpArray(amountsIn, scalingFactors);\n\n return (amountsIn, new uint256[](_getTotalTokens()));\n } else {\n _upscaleArray(balances, scalingFactors);\n (uint256 bptAmountOut, uint256[] memory amountsIn, uint256[] memory dueProtocolFeeAmounts) = _onJoinPool(\n poolId,\n sender,\n recipient,\n balances,\n lastChangeBlock,\n protocolSwapFeePercentage,\n scalingFactors,\n userData\n );\n\n // Note we no longer use `balances` after calling `_onJoinPool`, which may mutate it.\n\n _mintPoolTokens(recipient, bptAmountOut);\n\n // amountsIn are amounts entering the Pool, so we round up.\n _downscaleUpArray(amountsIn, scalingFactors);\n // dueProtocolFeeAmounts are amounts exiting the Pool, so we round down.\n _downscaleDownArray(dueProtocolFeeAmounts, scalingFactors);\n\n return (amountsIn, dueProtocolFeeAmounts);\n }\n }\n\n function onExitPool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData\n ) public virtual override onlyVault(poolId) returns (uint256[] memory, uint256[] memory) {\n uint256[] memory scalingFactors = _scalingFactors();\n _upscaleArray(balances, scalingFactors);\n\n (uint256 bptAmountIn, uint256[] memory amountsOut, uint256[] memory dueProtocolFeeAmounts) = _onExitPool(\n poolId,\n sender,\n recipient,\n balances,\n lastChangeBlock,\n protocolSwapFeePercentage,\n scalingFactors,\n userData\n );\n\n // Note we no longer use `balances` after calling `_onExitPool`, which may mutate it.\n\n _burnPoolTokens(sender, bptAmountIn);\n\n // Both amountsOut and dueProtocolFeeAmounts are amounts exiting the Pool, so we round down.\n _downscaleDownArray(amountsOut, scalingFactors);\n _downscaleDownArray(dueProtocolFeeAmounts, scalingFactors);\n\n return (amountsOut, dueProtocolFeeAmounts);\n }\n\n // Query functions\n\n /**\n * @dev Returns the amount of BPT that would be granted to `recipient` if the `onJoinPool` hook were called by the\n * Vault with the same arguments, along with the number of tokens `sender` would have to supply.\n *\n * This function is not meant to be called directly, but rather from a helper contract that fetches current Vault\n * data, such as the protocol swap fee percentage and Pool balances.\n *\n * Like `IVault.queryBatchSwap`, this function is not view due to internal implementation details: the caller must\n * explicitly use eth_call instead of eth_sendTransaction.\n */\n function queryJoin(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData\n ) external returns (uint256 bptOut, uint256[] memory amountsIn) {\n InputHelpers.ensureInputLengthMatch(balances.length, _getTotalTokens());\n\n _queryAction(\n poolId,\n sender,\n recipient,\n balances,\n lastChangeBlock,\n protocolSwapFeePercentage,\n userData,\n _onJoinPool,\n _downscaleUpArray\n );\n\n // The `return` opcode is executed directly inside `_queryAction`, so execution never reaches this statement,\n // and we don't need to return anything here - it just silences compiler warnings.\n return (bptOut, amountsIn);\n }\n\n /**\n * @dev Returns the amount of BPT that would be burned from `sender` if the `onExitPool` hook were called by the\n * Vault with the same arguments, along with the number of tokens `recipient` would receive.\n *\n * This function is not meant to be called directly, but rather from a helper contract that fetches current Vault\n * data, such as the protocol swap fee percentage and Pool balances.\n *\n * Like `IVault.queryBatchSwap`, this function is not view due to internal implementation details: the caller must\n * explicitly use eth_call instead of eth_sendTransaction.\n */\n function queryExit(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData\n ) external returns (uint256 bptIn, uint256[] memory amountsOut) {\n InputHelpers.ensureInputLengthMatch(balances.length, _getTotalTokens());\n\n _queryAction(\n poolId,\n sender,\n recipient,\n balances,\n lastChangeBlock,\n protocolSwapFeePercentage,\n userData,\n _onExitPool,\n _downscaleDownArray\n );\n\n // The `return` opcode is executed directly inside `_queryAction`, so execution never reaches this statement,\n // and we don't need to return anything here - it just silences compiler warnings.\n return (bptIn, amountsOut);\n }\n\n // Internal hooks to be overridden by derived contracts - all token amounts (except BPT) in these interfaces are\n // upscaled.\n\n /**\n * @dev Called when the Pool is joined for the first time; that is, when the BPT total supply is zero.\n *\n * Returns the amount of BPT to mint, and the token amounts the Pool will receive in return.\n *\n * Minted BPT will be sent to `recipient`, except for _MINIMUM_BPT, which will be deducted from this amount and sent\n * to the zero address instead. This will cause that BPT to remain forever locked there, preventing total BTP from\n * ever dropping below that value, and ensuring `_onInitializePool` can only be called once in the entire Pool's\n * lifetime.\n *\n * The tokens granted to the Pool will be transferred from `sender`. These amounts are considered upscaled and will\n * be downscaled (rounding up) before being returned to the Vault.\n */\n function _onInitializePool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory scalingFactors,\n bytes memory userData\n ) internal virtual returns (uint256 bptAmountOut, uint256[] memory amountsIn);\n\n /**\n * @dev Called whenever the Pool is joined after the first initialization join (see `_onInitializePool`).\n *\n * Returns the amount of BPT to mint, the token amounts that the Pool will receive in return, and the number of\n * tokens to pay in protocol swap fees.\n *\n * Implementations of this function might choose to mutate the `balances` array to save gas (e.g. when\n * performing intermediate calculations, such as subtraction of due protocol fees). This can be done safely.\n *\n * Minted BPT will be sent to `recipient`.\n *\n * The tokens granted to the Pool will be transferred from `sender`. These amounts are considered upscaled and will\n * be downscaled (rounding up) before being returned to the Vault.\n *\n * Due protocol swap fees will be taken from the Pool's balance in the Vault (see `IBasePool.onJoinPool`). These\n * amounts are considered upscaled and will be downscaled (rounding down) before being returned to the Vault.\n */\n function _onJoinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n uint256[] memory scalingFactors,\n bytes memory userData\n )\n internal\n virtual\n returns (\n uint256 bptAmountOut,\n uint256[] memory amountsIn,\n uint256[] memory dueProtocolFeeAmounts\n );\n\n /**\n * @dev Called whenever the Pool is exited.\n *\n * Returns the amount of BPT to burn, the token amounts for each Pool token that the Pool will grant in return, and\n * the number of tokens to pay in protocol swap fees.\n *\n * Implementations of this function might choose to mutate the `balances` array to save gas (e.g. when\n * performing intermediate calculations, such as subtraction of due protocol fees). This can be done safely.\n *\n * BPT will be burnt from `sender`.\n *\n * The Pool will grant tokens to `recipient`. These amounts are considered upscaled and will be downscaled\n * (rounding down) before being returned to the Vault.\n *\n * Due protocol swap fees will be taken from the Pool's balance in the Vault (see `IBasePool.onExitPool`). These\n * amounts are considered upscaled and will be downscaled (rounding down) before being returned to the Vault.\n */\n function _onExitPool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n uint256[] memory scalingFactors,\n bytes memory userData\n )\n internal\n virtual\n returns (\n uint256 bptAmountIn,\n uint256[] memory amountsOut,\n uint256[] memory dueProtocolFeeAmounts\n );\n\n // Internal functions\n\n /**\n * @dev Adds swap fee amount to `amount`, returning a higher value.\n */\n function _addSwapFeeAmount(uint256 amount) internal view returns (uint256) {\n // This returns amount + fee amount, so we round up (favoring a higher fee amount).\n return amount.divUp(FixedPoint.ONE.sub(_swapFeePercentage));\n }\n\n /**\n * @dev Subtracts swap fee amount from `amount`, returning a lower value.\n */\n function _subtractSwapFeeAmount(uint256 amount) internal view returns (uint256) {\n // This returns amount - fee amount, so we round up (favoring a higher fee amount).\n uint256 feeAmount = amount.mulUp(_swapFeePercentage);\n return amount.sub(feeAmount);\n }\n\n // Scaling\n\n /**\n * @dev Returns a scaling factor that, when multiplied to a token amount for `token`, normalizes its balance as if\n * it had 18 decimals.\n */\n function _computeScalingFactor(IERC20 token) private view returns (uint256) {\n // Tokens that don't implement the `decimals` method are not supported.\n uint256 tokenDecimals = ERC20(address(token)).decimals();\n\n // Tokens with more than 18 decimals are not supported.\n uint256 decimalsDifference = Math.sub(18, tokenDecimals);\n return FixedPoint.ONE * 10**decimalsDifference;\n }\n\n /**\n * @dev Returns the scaling factor for one of the Pool's tokens. Reverts if `token` is not a token registered by the\n * Pool.\n *\n * All scaling factors are fixed-point values with 18 decimals, to allow for this function to be overridden by\n * derived contracts that need to apply further scaling, making these factors potentially non-integer.\n *\n * The largest 'base' scaling factor (i.e. in tokens with less than 18 decimals) is 10**18, which in fixed-point is\n * 10**36. This value can be multiplied with a 112 bit Vault balance with no overflow by a factor of ~1e7, making\n * even relatively 'large' factors safe to use.\n *\n * The 1e7 figure is the result of 2**256 / (1e18 * 1e18 * 2**112).\n */\n function _scalingFactor(IERC20 token) internal view virtual returns (uint256) {\n // prettier-ignore\n if (token == _token0) { return _scalingFactor0; }\n else if (token == _token1) { return _scalingFactor1; }\n else if (token == _token2) { return _scalingFactor2; }\n else if (token == _token3) { return _scalingFactor3; }\n else if (token == _token4) { return _scalingFactor4; }\n else if (token == _token5) { return _scalingFactor5; }\n else if (token == _token6) { return _scalingFactor6; }\n else if (token == _token7) { return _scalingFactor7; }\n else {\n _revert(Errors.INVALID_TOKEN);\n }\n }\n\n /**\n * @dev Same as `_scalingFactor()`, except for all registered tokens (in the same order as registered). The Vault\n * will always pass balances in this order when calling any of the Pool hooks.\n */\n function _scalingFactors() internal view virtual returns (uint256[] memory) {\n uint256 totalTokens = _getTotalTokens();\n uint256[] memory scalingFactors = new uint256[](totalTokens);\n\n // prettier-ignore\n {\n if (totalTokens > 0) { scalingFactors[0] = _scalingFactor0; } else { return scalingFactors; }\n if (totalTokens > 1) { scalingFactors[1] = _scalingFactor1; } else { return scalingFactors; }\n if (totalTokens > 2) { scalingFactors[2] = _scalingFactor2; } else { return scalingFactors; }\n if (totalTokens > 3) { scalingFactors[3] = _scalingFactor3; } else { return scalingFactors; }\n if (totalTokens > 4) { scalingFactors[4] = _scalingFactor4; } else { return scalingFactors; }\n if (totalTokens > 5) { scalingFactors[5] = _scalingFactor5; } else { return scalingFactors; }\n if (totalTokens > 6) { scalingFactors[6] = _scalingFactor6; } else { return scalingFactors; }\n if (totalTokens > 7) { scalingFactors[7] = _scalingFactor7; } else { return scalingFactors; }\n }\n\n return scalingFactors;\n }\n\n /**\n * @dev Applies `scalingFactor` to `amount`, resulting in a larger or equal value depending on whether it needed\n * scaling or not.\n */\n function _upscale(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) {\n // Upscale rounding wouldn't necessarily always go in the same direction: in a swap for example the balance of\n // token in should be rounded up, and that of token out rounded down. This is the only place where we round in\n // the same direction for all amounts, as the impact of this rounding is expected to be minimal (and there's no\n // rounding error unless `_scalingFactor()` is overriden).\n return FixedPoint.mulDown(amount, scalingFactor);\n }\n\n /**\n * @dev Same as `_upscale`, but for an entire array. This function does not return anything, but instead *mutates*\n * the `amounts` array.\n */\n function _upscaleArray(uint256[] memory amounts, uint256[] memory scalingFactors) internal view {\n for (uint256 i = 0; i < _getTotalTokens(); ++i) {\n amounts[i] = FixedPoint.mulDown(amounts[i], scalingFactors[i]);\n }\n }\n\n /**\n * @dev Reverses the `scalingFactor` applied to `amount`, resulting in a smaller or equal value depending on\n * whether it needed scaling or not. The result is rounded down.\n */\n function _downscaleDown(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) {\n return FixedPoint.divDown(amount, scalingFactor);\n }\n\n /**\n * @dev Same as `_downscaleDown`, but for an entire array. This function does not return anything, but instead\n * *mutates* the `amounts` array.\n */\n function _downscaleDownArray(uint256[] memory amounts, uint256[] memory scalingFactors) internal view {\n for (uint256 i = 0; i < _getTotalTokens(); ++i) {\n amounts[i] = FixedPoint.divDown(amounts[i], scalingFactors[i]);\n }\n }\n\n /**\n * @dev Reverses the `scalingFactor` applied to `amount`, resulting in a smaller or equal value depending on\n * whether it needed scaling or not. The result is rounded up.\n */\n function _downscaleUp(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) {\n return FixedPoint.divUp(amount, scalingFactor);\n }\n\n /**\n * @dev Same as `_downscaleUp`, but for an entire array. This function does not return anything, but instead\n * *mutates* the `amounts` array.\n */\n function _downscaleUpArray(uint256[] memory amounts, uint256[] memory scalingFactors) internal view {\n for (uint256 i = 0; i < _getTotalTokens(); ++i) {\n amounts[i] = FixedPoint.divUp(amounts[i], scalingFactors[i]);\n }\n }\n\n function _getAuthorizer() internal view override returns (IAuthorizer) {\n // Access control management is delegated to the Vault's Authorizer. This lets Balancer Governance manage which\n // accounts can call permissioned functions: for example, to perform emergency pauses.\n // If the owner is delegated, then *all* permissioned functions, including `setSwapFeePercentage`, will be under\n // Governance control.\n return getVault().getAuthorizer();\n }\n\n function _queryAction(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData,\n function(bytes32, address, address, uint256[] memory, uint256, uint256, uint256[] memory, bytes memory)\n internal\n returns (uint256, uint256[] memory, uint256[] memory) _action,\n function(uint256[] memory, uint256[] memory) internal view _downscaleArray\n ) private {\n // This uses the same technique used by the Vault in queryBatchSwap. Refer to that function for a detailed\n // explanation.\n\n if (msg.sender != address(this)) {\n // We perform an external call to ourselves, forwarding the same calldata. In this call, the else clause of\n // the preceding if statement will be executed instead.\n\n // solhint-disable-next-line avoid-low-level-calls\n (bool success, ) = address(this).call(msg.data);\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // This call should always revert to decode the bpt and token amounts from the revert reason\n switch success\n case 0 {\n // Note we are manually writing the memory slot 0. We can safely overwrite whatever is\n // stored there as we take full control of the execution and then immediately return.\n\n // We copy the first 4 bytes to check if it matches with the expected signature, otherwise\n // there was another revert reason and we should forward it.\n returndatacopy(0, 0, 0x04)\n let error := and(mload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)\n\n // If the first 4 bytes don't match with the expected signature, we forward the revert reason.\n if eq(eq(error, 0x43adbafb00000000000000000000000000000000000000000000000000000000), 0) {\n returndatacopy(0, 0, returndatasize())\n revert(0, returndatasize())\n }\n\n // The returndata contains the signature, followed by the raw memory representation of the\n // `bptAmount` and `tokenAmounts` (array: length + data). We need to return an ABI-encoded\n // representation of these.\n // An ABI-encoded response will include one additional field to indicate the starting offset of\n // the `tokenAmounts` array. The `bptAmount` will be laid out in the first word of the\n // returndata.\n //\n // In returndata:\n // [ signature ][ bptAmount ][ tokenAmounts length ][ tokenAmounts values ]\n // [ 4 bytes ][ 32 bytes ][ 32 bytes ][ (32 * length) bytes ]\n //\n // We now need to return (ABI-encoded values):\n // [ bptAmount ][ tokeAmounts offset ][ tokenAmounts length ][ tokenAmounts values ]\n // [ 32 bytes ][ 32 bytes ][ 32 bytes ][ (32 * length) bytes ]\n\n // We copy 32 bytes for the `bptAmount` from returndata into memory.\n // Note that we skip the first 4 bytes for the error signature\n returndatacopy(0, 0x04, 32)\n\n // The offsets are 32-bytes long, so the array of `tokenAmounts` will start after\n // the initial 64 bytes.\n mstore(0x20, 64)\n\n // We now copy the raw memory array for the `tokenAmounts` from returndata into memory.\n // Since bpt amount and offset take up 64 bytes, we start copying at address 0x40. We also\n // skip the first 36 bytes from returndata, which correspond to the signature plus bpt amount.\n returndatacopy(0x40, 0x24, sub(returndatasize(), 36))\n\n // We finally return the ABI-encoded uint256 and the array, which has a total length equal to\n // the size of returndata, plus the 32 bytes of the offset but without the 4 bytes of the\n // error signature.\n return(0, add(returndatasize(), 28))\n }\n default {\n // This call should always revert, but we fail nonetheless if that didn't happen\n invalid()\n }\n }\n } else {\n uint256[] memory scalingFactors = _scalingFactors();\n _upscaleArray(balances, scalingFactors);\n\n (uint256 bptAmount, uint256[] memory tokenAmounts, ) = _action(\n poolId,\n sender,\n recipient,\n balances,\n lastChangeBlock,\n protocolSwapFeePercentage,\n scalingFactors,\n userData\n );\n\n _downscaleArray(tokenAmounts, scalingFactors);\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // We will return a raw representation of `bptAmount` and `tokenAmounts` in memory, which is composed of\n // a 32-byte uint256, followed by a 32-byte for the array length, and finally the 32-byte uint256 values\n // Because revert expects a size in bytes, we multiply the array length (stored at `tokenAmounts`) by 32\n let size := mul(mload(tokenAmounts), 32)\n\n // We store the `bptAmount` in the previous slot to the `tokenAmounts` array. We can make sure there\n // will be at least one available slot due to how the memory scratch space works.\n // We can safely overwrite whatever is stored in this slot as we will revert immediately after that.\n let start := sub(tokenAmounts, 0x20)\n mstore(start, bptAmount)\n\n // We send one extra value for the error signature \"QueryError(uint256,uint256[])\" which is 0x43adbafb\n // We use the previous slot to `bptAmount`.\n mstore(sub(start, 0x20), 0x0000000000000000000000000000000000000000000000000000000043adbafb)\n start := sub(start, 0x04)\n\n // When copying from `tokenAmounts` into returndata, we copy the additional 68 bytes to also return\n // the `bptAmount`, the array 's length, and the error signature.\n revert(start, add(size, 68))\n }\n }\n }\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IGeneralPool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"./IBasePool.sol\";\n\n/**\n * @dev IPools with the General specialization setting should implement this interface.\n *\n * This is called by the Vault when a user calls `IVault.swap` or `IVault.batchSwap` to swap with this Pool.\n * Returns the number of tokens the Pool will grant to the user in a 'given in' swap, or that the user will\n * grant to the pool in a 'given out' swap.\n *\n * This can often be implemented by a `view` function, since many pricing algorithms don't need to track state\n * changes in swaps. However, contracts implementing this in non-view functions should check that the caller is\n * indeed the Vault.\n */\ninterface IGeneralPool is IBasePool {\n function onSwap(\n SwapRequest memory swapRequest,\n uint256[] memory balances,\n uint256 indexIn,\n uint256 indexOut\n ) external returns (uint256 amount);\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\nimport \"../helpers/BalancerErrors.sol\";\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations with added overflow checks.\n * Adapted from OpenZeppelin's SafeMath library\n */\nlibrary Math {\n /**\n * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n _require(c >= a, Errors.ADD_OVERFLOW);\n return c;\n }\n\n /**\n * @dev Returns the addition of two signed integers, reverting on overflow.\n */\n function add(int256 a, int256 b) internal pure returns (int256) {\n int256 c = a + b;\n _require((b >= 0 && c >= a) || (b < 0 && c < a), Errors.ADD_OVERFLOW);\n return c;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n _require(b <= a, Errors.SUB_OVERFLOW);\n uint256 c = a - b;\n return c;\n }\n\n /**\n * @dev Returns the subtraction of two signed integers, reverting on overflow.\n */\n function sub(int256 a, int256 b) internal pure returns (int256) {\n int256 c = a - b;\n _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW);\n return c;\n }\n\n /**\n * @dev Returns the largest of two numbers of 256 bits.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers of 256 bits.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a * b;\n _require(a == 0 || c / a == b, Errors.MUL_OVERFLOW);\n return c;\n }\n\n function div(\n uint256 a,\n uint256 b,\n bool roundUp\n ) internal pure returns (uint256) {\n return roundUp ? divUp(a, b) : divDown(a, b);\n }\n\n function divDown(uint256 a, uint256 b) internal pure returns (uint256) {\n _require(b != 0, Errors.ZERO_DIVISION);\n return a / b;\n }\n\n function divUp(uint256 a, uint256 b) internal pure returns (uint256) {\n _require(b != 0, Errors.ZERO_DIVISION);\n\n if (a == 0) {\n return 0;\n } else {\n return 1 + (a - 1) / b;\n }\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/TemporarilyPausable.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"./BalancerErrors.sol\";\nimport \"./ITemporarilyPausable.sol\";\n\n/**\n * @dev Allows for a contract to be paused during an initial period after deployment, disabling functionality. Can be\n * used as an emergency switch in case a security vulnerability or threat is identified.\n *\n * The contract can only be paused during the Pause Window, a period that starts at deployment. It can also be\n * unpaused and repaused any number of times during this period. This is intended to serve as a safety measure: it lets\n * system managers react quickly to potentially dangerous situations, knowing that this action is reversible if careful\n * analysis later determines there was a false alarm.\n *\n * If the contract is paused when the Pause Window finishes, it will remain in the paused state through an additional\n * Buffer Period, after which it will be automatically unpaused forever. This is to ensure there is always enough time\n * to react to an emergency, even if the threat is discovered shortly before the Pause Window expires.\n *\n * Note that since the contract can only be paused within the Pause Window, unpausing during the Buffer Period is\n * irreversible.\n */\nabstract contract TemporarilyPausable is ITemporarilyPausable {\n // The Pause Window and Buffer Period are timestamp-based: they should not be relied upon for sub-minute accuracy.\n // solhint-disable not-rely-on-time\n\n uint256 private constant _MAX_PAUSE_WINDOW_DURATION = 90 days;\n uint256 private constant _MAX_BUFFER_PERIOD_DURATION = 30 days;\n\n uint256 private immutable _pauseWindowEndTime;\n uint256 private immutable _bufferPeriodEndTime;\n\n bool private _paused;\n\n constructor(uint256 pauseWindowDuration, uint256 bufferPeriodDuration) {\n _require(pauseWindowDuration <= _MAX_PAUSE_WINDOW_DURATION, Errors.MAX_PAUSE_WINDOW_DURATION);\n _require(bufferPeriodDuration <= _MAX_BUFFER_PERIOD_DURATION, Errors.MAX_BUFFER_PERIOD_DURATION);\n\n uint256 pauseWindowEndTime = block.timestamp + pauseWindowDuration;\n\n _pauseWindowEndTime = pauseWindowEndTime;\n _bufferPeriodEndTime = pauseWindowEndTime + bufferPeriodDuration;\n }\n\n /**\n * @dev Reverts if the contract is paused.\n */\n modifier whenNotPaused() {\n _ensureNotPaused();\n _;\n }\n\n /**\n * @dev Returns the current contract pause status, as well as the end times of the Pause Window and Buffer\n * Period.\n */\n function getPausedState()\n external\n view\n override\n returns (\n bool paused,\n uint256 pauseWindowEndTime,\n uint256 bufferPeriodEndTime\n )\n {\n paused = !_isNotPaused();\n pauseWindowEndTime = _getPauseWindowEndTime();\n bufferPeriodEndTime = _getBufferPeriodEndTime();\n }\n\n /**\n * @dev Sets the pause state to `paused`. The contract can only be paused until the end of the Pause Window, and\n * unpaused until the end of the Buffer Period.\n *\n * Once the Buffer Period expires, this function reverts unconditionally.\n */\n function _setPaused(bool paused) internal {\n if (paused) {\n _require(block.timestamp < _getPauseWindowEndTime(), Errors.PAUSE_WINDOW_EXPIRED);\n } else {\n _require(block.timestamp < _getBufferPeriodEndTime(), Errors.BUFFER_PERIOD_EXPIRED);\n }\n\n _paused = paused;\n emit PausedStateChanged(paused);\n }\n\n /**\n * @dev Reverts if the contract is paused.\n */\n function _ensureNotPaused() internal view {\n _require(_isNotPaused(), Errors.PAUSED);\n }\n\n /**\n * @dev Returns true if the contract is unpaused.\n *\n * Once the Buffer Period expires, the gas cost of calling this function is reduced dramatically, as storage is no\n * longer accessed.\n */\n function _isNotPaused() internal view returns (bool) {\n // After the Buffer Period, the (inexpensive) timestamp check short-circuits the storage access.\n return block.timestamp > _getBufferPeriodEndTime() || !_paused;\n }\n\n // These getters lead to reduced bytecode size by inlining the immutable variables in a single place.\n\n function _getPauseWindowEndTime() private view returns (uint256) {\n return _pauseWindowEndTime;\n }\n\n function _getBufferPeriodEndTime() private view returns (uint256) {\n return _bufferPeriodEndTime;\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\nimport \"../helpers/BalancerErrors.sol\";\n\nimport \"./IERC20.sol\";\nimport \"./SafeMath.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin guidelines: functions revert instead\n * of returning `false` on failure. This behavior is nonetheless conventional\n * and does not conflict with the expectations of ERC20 applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is IERC20 {\n using SafeMath for uint256;\n\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n uint8 private _decimals;\n\n /**\n * @dev Sets the values for {name} and {symbol}, initializes {decimals} with\n * a default value of 18.\n *\n * To select a different value for {decimals}, use {_setupDecimals}.\n *\n * All three of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n _decimals = 18;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is\n * called.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view returns (uint8) {\n return _decimals;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(msg.sender, recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n _approve(msg.sender, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * Requirements:\n *\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public virtual override returns (bool) {\n _transfer(sender, recipient, amount);\n _approve(\n sender,\n msg.sender,\n _allowances[sender][msg.sender].sub(amount, Errors.ERC20_TRANSFER_EXCEEDS_ALLOWANCE)\n );\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n _approve(\n msg.sender,\n spender,\n _allowances[msg.sender][spender].sub(subtractedValue, Errors.ERC20_DECREASED_ALLOWANCE_BELOW_ZERO)\n );\n return true;\n }\n\n /**\n * @dev Moves tokens `amount` from `sender` to `recipient`.\n *\n * This is internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(\n address sender,\n address recipient,\n uint256 amount\n ) internal virtual {\n _require(sender != address(0), Errors.ERC20_TRANSFER_FROM_ZERO_ADDRESS);\n _require(recipient != address(0), Errors.ERC20_TRANSFER_TO_ZERO_ADDRESS);\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n _balances[sender] = _balances[sender].sub(amount, Errors.ERC20_TRANSFER_EXCEEDS_BALANCE);\n _balances[recipient] = _balances[recipient].add(amount);\n emit Transfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply = _totalSupply.add(amount);\n _balances[account] = _balances[account].add(amount);\n emit Transfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n _require(account != address(0), Errors.ERC20_BURN_FROM_ZERO_ADDRESS);\n\n _beforeTokenTransfer(account, address(0), amount);\n\n _balances[account] = _balances[account].sub(amount, Errors.ERC20_BURN_EXCEEDS_ALLOWANCE);\n _totalSupply = _totalSupply.sub(amount);\n emit Transfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Sets {decimals} to a value other than the default one of 18.\n *\n * WARNING: This function should only be called from the constructor. Most\n * applications that interact with token contracts will not expect\n * {decimals} to ever change, and may work incorrectly if it does.\n */\n function _setupDecimals(uint8 decimals_) internal {\n _decimals = decimals_;\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be to transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IVault.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma experimental ABIEncoderV2;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/ISignaturesValidator.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/ITemporarilyPausable.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/misc/IWETH.sol\";\n\nimport \"./IAsset.sol\";\nimport \"./IAuthorizer.sol\";\nimport \"./IFlashLoanRecipient.sol\";\nimport \"./IProtocolFeesCollector.sol\";\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Full external interface for the Vault core contract - no external or public methods exist in the contract that\n * don't override one of these declarations.\n */\ninterface IVault is ISignaturesValidator, ITemporarilyPausable {\n // Generalities about the Vault:\n //\n // - Whenever documentation refers to 'tokens', it strictly refers to ERC20-compliant token contracts. Tokens are\n // transferred out of the Vault by calling the `IERC20.transfer` function, and transferred in by calling\n // `IERC20.transferFrom`. In these cases, the sender must have previously allowed the Vault to use their tokens by\n // calling `IERC20.approve`. The only deviation from the ERC20 standard that is supported is functions not returning\n // a boolean value: in these scenarios, a non-reverting call is assumed to be successful.\n //\n // - All non-view functions in the Vault are non-reentrant: calling them while another one is mid-execution (e.g.\n // while execution control is transferred to a token contract during a swap) will result in a revert. View\n // functions can be called in a re-reentrant way, but doing so might cause them to return inconsistent results.\n // Contracts calling view functions in the Vault must make sure the Vault has not already been entered.\n //\n // - View functions revert if referring to either unregistered Pools, or unregistered tokens for registered Pools.\n\n // Authorizer\n //\n // Some system actions are permissioned, like setting and collecting protocol fees. This permissioning system exists\n // outside of the Vault in the Authorizer contract: the Vault simply calls the Authorizer to check if the caller\n // can perform a given action.\n\n /**\n * @dev Returns the Vault's Authorizer.\n */\n function getAuthorizer() external view returns (IAuthorizer);\n\n /**\n * @dev Sets a new Authorizer for the Vault. The caller must be allowed by the current Authorizer to do this.\n *\n * Emits an `AuthorizerChanged` event.\n */\n function setAuthorizer(IAuthorizer newAuthorizer) external;\n\n /**\n * @dev Emitted when a new authorizer is set by `setAuthorizer`.\n */\n event AuthorizerChanged(IAuthorizer indexed newAuthorizer);\n\n // Relayers\n //\n // Additionally, it is possible for an account to perform certain actions on behalf of another one, using their\n // Vault ERC20 allowance and Internal Balance. These accounts are said to be 'relayers' for these Vault functions,\n // and are expected to be smart contracts with sound authentication mechanisms. For an account to be able to wield\n // this power, two things must occur:\n // - The Authorizer must grant the account the permission to be a relayer for the relevant Vault function. This\n // means that Balancer governance must approve each individual contract to act as a relayer for the intended\n // functions.\n // - Each user must approve the relayer to act on their behalf.\n // This double protection means users cannot be tricked into approving malicious relayers (because they will not\n // have been allowed by the Authorizer via governance), nor can malicious relayers approved by a compromised\n // Authorizer or governance drain user funds, since they would also need to be approved by each individual user.\n\n /**\n * @dev Returns true if `user` has approved `relayer` to act as a relayer for them.\n */\n function hasApprovedRelayer(address user, address relayer) external view returns (bool);\n\n /**\n * @dev Allows `relayer` to act as a relayer for `sender` if `approved` is true, and disallows it otherwise.\n *\n * Emits a `RelayerApprovalChanged` event.\n */\n function setRelayerApproval(\n address sender,\n address relayer,\n bool approved\n ) external;\n\n /**\n * @dev Emitted every time a relayer is approved or disapproved by `setRelayerApproval`.\n */\n event RelayerApprovalChanged(address indexed relayer, address indexed sender, bool approved);\n\n // Internal Balance\n //\n // Users can deposit tokens into the Vault, where they are allocated to their Internal Balance, and later\n // transferred or withdrawn. It can also be used as a source of tokens when joining Pools, as a destination\n // when exiting them, and as either when performing swaps. This usage of Internal Balance results in greatly reduced\n // gas costs when compared to relying on plain ERC20 transfers, leading to large savings for frequent users.\n //\n // Internal Balance management features batching, which means a single contract call can be used to perform multiple\n // operations of different kinds, with different senders and recipients, at once.\n\n /**\n * @dev Returns `user`'s Internal Balance for a set of tokens.\n */\n function getInternalBalance(address user, IERC20[] memory tokens) external view returns (uint256[] memory);\n\n /**\n * @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)\n * and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as\n * it lets integrators reuse a user's Vault allowance.\n *\n * For each operation, if the caller is not `sender`, it must be an authorized relayer for them.\n */\n function manageUserBalance(UserBalanceOp[] memory ops) external payable;\n\n /**\n * @dev Data for `manageUserBalance` operations, which include the possibility for ETH to be sent and received\n without manual WETH wrapping or unwrapping.\n */\n struct UserBalanceOp {\n UserBalanceOpKind kind;\n IAsset asset;\n uint256 amount;\n address sender;\n address payable recipient;\n }\n\n // There are four possible operations in `manageUserBalance`:\n //\n // - DEPOSIT_INTERNAL\n // Increases the Internal Balance of the `recipient` account by transferring tokens from the corresponding\n // `sender`. The sender must have allowed the Vault to use their tokens via `IERC20.approve()`.\n //\n // ETH can be used by passing the ETH sentinel value as the asset and forwarding ETH in the call: it will be wrapped\n // and deposited as WETH. Any ETH amount remaining will be sent back to the caller (not the sender, which is\n // relevant for relayers).\n //\n // Emits an `InternalBalanceChanged` event.\n //\n //\n // - WITHDRAW_INTERNAL\n // Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient`.\n //\n // ETH can be used by passing the ETH sentinel value as the asset. This will deduct WETH instead, unwrap it and send\n // it to the recipient as ETH.\n //\n // Emits an `InternalBalanceChanged` event.\n //\n //\n // - TRANSFER_INTERNAL\n // Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient`.\n //\n // Reverts if the ETH sentinel value is passed.\n //\n // Emits an `InternalBalanceChanged` event.\n //\n //\n // - TRANSFER_EXTERNAL\n // Transfers tokens from `sender` to `recipient`, using the Vault's ERC20 allowance. This is typically used by\n // relayers, as it lets them reuse a user's Vault allowance.\n //\n // Reverts if the ETH sentinel value is passed.\n //\n // Emits an `ExternalBalanceTransfer` event.\n\n enum UserBalanceOpKind { DEPOSIT_INTERNAL, WITHDRAW_INTERNAL, TRANSFER_INTERNAL, TRANSFER_EXTERNAL }\n\n /**\n * @dev Emitted when a user's Internal Balance changes, either from calls to `manageUserBalance`, or through\n * interacting with Pools using Internal Balance.\n *\n * Because Internal Balance works exclusively with ERC20 tokens, ETH deposits and withdrawals will use the WETH\n * address.\n */\n event InternalBalanceChanged(address indexed user, IERC20 indexed token, int256 delta);\n\n /**\n * @dev Emitted when a user's Vault ERC20 allowance is used by the Vault to transfer tokens to an external account.\n */\n event ExternalBalanceTransfer(IERC20 indexed token, address indexed sender, address recipient, uint256 amount);\n\n // Pools\n //\n // There are three specialization settings for Pools, which allow for cheaper swaps at the cost of reduced\n // functionality:\n //\n // - General: no specialization, suited for all Pools. IGeneralPool is used for swap request callbacks, passing the\n // balance of all tokens in the Pool. These Pools have the largest swap costs (because of the extra storage reads),\n // which increase with the number of registered tokens.\n //\n // - Minimal Swap Info: IMinimalSwapInfoPool is used instead of IGeneralPool, which saves gas by only passing the\n // balance of the two tokens involved in the swap. This is suitable for some pricing algorithms, like the weighted\n // constant product one popularized by Balancer V1. Swap costs are smaller compared to general Pools, and are\n // independent of the number of registered tokens.\n //\n // - Two Token: only allows two tokens to be registered. This achieves the lowest possible swap gas cost. Like\n // minimal swap info Pools, these are called via IMinimalSwapInfoPool.\n\n enum PoolSpecialization { GENERAL, MINIMAL_SWAP_INFO, TWO_TOKEN }\n\n /**\n * @dev Registers the caller account as a Pool with a given specialization setting. Returns the Pool's ID, which\n * is used in all Pool-related functions. Pools cannot be deregistered, nor can the Pool's specialization be\n * changed.\n *\n * The caller is expected to be a smart contract that implements either `IGeneralPool` or `IMinimalSwapInfoPool`,\n * depending on the chosen specialization setting. This contract is known as the Pool's contract.\n *\n * Note that the same contract may register itself as multiple Pools with unique Pool IDs, or in other words,\n * multiple Pools may share the same contract.\n *\n * Emits a `PoolRegistered` event.\n */\n function registerPool(PoolSpecialization specialization) external returns (bytes32);\n\n /**\n * @dev Emitted when a Pool is registered by calling `registerPool`.\n */\n event PoolRegistered(bytes32 indexed poolId, address indexed poolAddress, PoolSpecialization specialization);\n\n /**\n * @dev Returns a Pool's contract address and specialization setting.\n */\n function getPool(bytes32 poolId) external view returns (address, PoolSpecialization);\n\n /**\n * @dev Registers `tokens` for the `poolId` Pool. Must be called by the Pool's contract.\n *\n * Pools can only interact with tokens they have registered. Users join a Pool by transferring registered tokens,\n * exit by receiving registered tokens, and can only swap registered tokens.\n *\n * Each token can only be registered once. For Pools with the Two Token specialization, `tokens` must have a length\n * of two, that is, both tokens must be registered in the same `registerTokens` call, and they must be sorted in\n * ascending order.\n *\n * The `tokens` and `assetManagers` arrays must have the same length, and each entry in these indicates the Asset\n * Manager for the corresponding token. Asset Managers can manage a Pool's tokens via `managePoolBalance`,\n * depositing and withdrawing them directly, and can even set their balance to arbitrary amounts. They are therefore\n * expected to be highly secured smart contracts with sound design principles, and the decision to register an\n * Asset Manager should not be made lightly.\n *\n * Pools can choose not to assign an Asset Manager to a given token by passing in the zero address. Once an Asset\n * Manager is set, it cannot be changed except by deregistering the associated token and registering again with a\n * different Asset Manager.\n *\n * Emits a `TokensRegistered` event.\n */\n function registerTokens(\n bytes32 poolId,\n IERC20[] memory tokens,\n address[] memory assetManagers\n ) external;\n\n /**\n * @dev Emitted when a Pool registers tokens by calling `registerTokens`.\n */\n event TokensRegistered(bytes32 indexed poolId, IERC20[] tokens, address[] assetManagers);\n\n /**\n * @dev Deregisters `tokens` for the `poolId` Pool. Must be called by the Pool's contract.\n *\n * Only registered tokens (via `registerTokens`) can be deregistered. Additionally, they must have zero total\n * balance. For Pools with the Two Token specialization, `tokens` must have a length of two, that is, both tokens\n * must be deregistered in the same `deregisterTokens` call.\n *\n * A deregistered token can be re-registered later on, possibly with a different Asset Manager.\n *\n * Emits a `TokensDeregistered` event.\n */\n function deregisterTokens(bytes32 poolId, IERC20[] memory tokens) external;\n\n /**\n * @dev Emitted when a Pool deregisters tokens by calling `deregisterTokens`.\n */\n event TokensDeregistered(bytes32 indexed poolId, IERC20[] tokens);\n\n /**\n * @dev Returns detailed information for a Pool's registered token.\n *\n * `cash` is the number of tokens the Vault currently holds for the Pool. `managed` is the number of tokens\n * withdrawn and held outside the Vault by the Pool's token Asset Manager. The Pool's total balance for `token`\n * equals the sum of `cash` and `managed`.\n *\n * Internally, `cash` and `managed` are stored using 112 bits. No action can ever cause a Pool's token `cash`,\n * `managed` or `total` balance to be greater than 2^112 - 1.\n *\n * `lastChangeBlock` is the number of the block in which `token`'s total balance was last modified (via either a\n * join, exit, swap, or Asset Manager update). This value is useful to avoid so-called 'sandwich attacks', for\n * example when developing price oracles. A change of zero (e.g. caused by a swap with amount zero) is considered a\n * change for this purpose, and will update `lastChangeBlock`.\n *\n * `assetManager` is the Pool's token Asset Manager.\n */\n function getPoolTokenInfo(bytes32 poolId, IERC20 token)\n external\n view\n returns (\n uint256 cash,\n uint256 managed,\n uint256 lastChangeBlock,\n address assetManager\n );\n\n /**\n * @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of\n * the tokens' `balances` changed.\n *\n * The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all\n * Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.\n *\n * If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same\n * order as passed to `registerTokens`.\n *\n * Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are\n * the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`\n * instead.\n */\n function getPoolTokens(bytes32 poolId)\n external\n view\n returns (\n IERC20[] memory tokens,\n uint256[] memory balances,\n uint256 lastChangeBlock\n );\n\n /**\n * @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will\n * trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized\n * Pool shares.\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount\n * to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces\n * these maximums.\n *\n * If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable\n * this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the\n * WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent\n * back to the caller (not the sender, which is important for relayers).\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be\n * sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final\n * `assets` array might not be sorted. Pools with no registered tokens cannot be joined.\n *\n * If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only\n * be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be\n * withdrawn from Internal Balance: attempting to do so will trigger a revert.\n *\n * This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed\n * directly to the Pool's contract, as is `recipient`.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function joinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n JoinPoolRequest memory request\n ) external payable;\n\n struct JoinPoolRequest {\n IAsset[] assets;\n uint256[] maxAmountsIn;\n bytes userData;\n bool fromInternalBalance;\n }\n\n /**\n * @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will\n * trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized\n * Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see\n * `getPoolTokenInfo`).\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum\n * token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:\n * it just enforces these minimums.\n *\n * If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To\n * enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead\n * of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.\n *\n * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when\n * interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must\n * be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the\n * final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.\n *\n * If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,\n * an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to\n * do so will trigger a revert.\n *\n * `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the\n * `tokens` array. This array must match the Pool's registered tokens.\n *\n * This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement\n * their own custom logic. This typically requires additional information from the user (such as the expected number\n * of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and\n * passed directly to the Pool's contract.\n *\n * Emits a `PoolBalanceChanged` event.\n */\n function exitPool(\n bytes32 poolId,\n address sender,\n address payable recipient,\n ExitPoolRequest memory request\n ) external;\n\n struct ExitPoolRequest {\n IAsset[] assets;\n uint256[] minAmountsOut;\n bytes userData;\n bool toInternalBalance;\n }\n\n /**\n * @dev Emitted when a user joins or exits a Pool by calling `joinPool` or `exitPool`, respectively.\n */\n event PoolBalanceChanged(\n bytes32 indexed poolId,\n address indexed liquidityProvider,\n IERC20[] tokens,\n int256[] deltas,\n uint256[] protocolFeeAmounts\n );\n\n enum PoolBalanceChangeKind { JOIN, EXIT }\n\n // Swaps\n //\n // Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,\n // they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be\n // aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.\n //\n // The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.\n // In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),\n // and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').\n // More complex swaps, such as one token in to multiple tokens out can be achieved by batching together\n // individual swaps.\n //\n // There are two swap kinds:\n // - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the\n // `onSwap` hook) the amount of tokens out (to send to the recipient).\n // - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines\n // (via the `onSwap` hook) the amount of tokens in (to receive from the sender).\n //\n // Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with\n // the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated\n // tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended\n // swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at\n // the final intended token.\n //\n // In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal\n // Balance) after all individual swaps have been completed, and the net token balance change computed. This makes\n // certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost\n // much less gas than they would otherwise.\n //\n // It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple\n // Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only\n // updating the Pool's internal accounting).\n //\n // To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token\n // involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the\n // minimum amount of tokens to receive (by passing a negative value) is specified.\n //\n // Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after\n // this point in time (e.g. if the transaction failed to be included in a block promptly).\n //\n // If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do\n // the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be\n // passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the\n // same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).\n //\n // Finally, Internal Balance can be used when either sending or receiving tokens.\n\n enum SwapKind { GIVEN_IN, GIVEN_OUT }\n\n /**\n * @dev Performs a swap with a single Pool.\n *\n * If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens\n * taken from the Pool, which must be greater than or equal to `limit`.\n *\n * If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens\n * sent to the Pool, which must be less than or equal to `limit`.\n *\n * Internal Balance usage and the recipient are determined by the `funds` struct.\n *\n * Emits a `Swap` event.\n */\n function swap(\n SingleSwap memory singleSwap,\n FundManagement memory funds,\n uint256 limit,\n uint256 deadline\n ) external payable returns (uint256);\n\n /**\n * @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on\n * the `kind` value.\n *\n * `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).\n * Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.\n *\n * The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be\n * used to extend swap behavior.\n */\n struct SingleSwap {\n bytes32 poolId;\n SwapKind kind;\n IAsset assetIn;\n IAsset assetOut;\n uint256 amount;\n bytes userData;\n }\n\n /**\n * @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either\n * the amount of tokens sent to or received from the Pool, depending on the `kind` value.\n *\n * Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the\n * Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at\n * the same index in the `assets` array.\n *\n * Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a\n * Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or\n * `amountOut` depending on the swap kind.\n *\n * Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out\n * of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal\n * the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.\n *\n * The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,\n * or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and\n * out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to\n * or unwrapped from WETH by the Vault.\n *\n * Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies\n * the minimum or maximum amount of each token the vault is allowed to transfer.\n *\n * `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the\n * equivalent `swap` call.\n *\n * Emits `Swap` events.\n */\n function batchSwap(\n SwapKind kind,\n BatchSwapStep[] memory swaps,\n IAsset[] memory assets,\n FundManagement memory funds,\n int256[] memory limits,\n uint256 deadline\n ) external payable returns (int256[] memory);\n\n /**\n * @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the\n * `assets` array passed to that function, and ETH assets are converted to WETH.\n *\n * If `amount` is zero, the multihop mechanism is used to determine the actual amount based on the amount in/out\n * from the previous swap, depending on the swap kind.\n *\n * The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be\n * used to extend swap behavior.\n */\n struct BatchSwapStep {\n bytes32 poolId;\n uint256 assetInIndex;\n uint256 assetOutIndex;\n uint256 amount;\n bytes userData;\n }\n\n /**\n * @dev Emitted for each individual swap performed by `swap` or `batchSwap`.\n */\n event Swap(\n bytes32 indexed poolId,\n IERC20 indexed tokenIn,\n IERC20 indexed tokenOut,\n uint256 amountIn,\n uint256 amountOut\n );\n\n /**\n * @dev All tokens in a swap are either sent from the `sender` account to the Vault, or from the Vault to the\n * `recipient` account.\n *\n * If the caller is not `sender`, it must be an authorized relayer for them.\n *\n * If `fromInternalBalance` is true, the `sender`'s Internal Balance will be preferred, performing an ERC20\n * transfer for the difference between the requested amount and the User's Internal Balance (if any). The `sender`\n * must have allowed the Vault to use their tokens via `IERC20.approve()`. This matches the behavior of\n * `joinPool`.\n *\n * If `toInternalBalance` is true, tokens will be deposited to `recipient`'s internal balance instead of\n * transferred. This matches the behavior of `exitPool`.\n *\n * Note that ETH cannot be deposited to or withdrawn from Internal Balance: attempting to do so will trigger a\n * revert.\n */\n struct FundManagement {\n address sender;\n bool fromInternalBalance;\n address payable recipient;\n bool toInternalBalance;\n }\n\n /**\n * @dev Simulates a call to `batchSwap`, returning an array of Vault asset deltas. Calls to `swap` cannot be\n * simulated directly, but an equivalent `batchSwap` call can and will yield the exact same result.\n *\n * Each element in the array corresponds to the asset at the same index, and indicates the number of tokens (or ETH)\n * the Vault would take from the sender (if positive) or send to the recipient (if negative). The arguments it\n * receives are the same that an equivalent `batchSwap` call would receive.\n *\n * Unlike `batchSwap`, this function performs no checks on the sender or recipient field in the `funds` struct.\n * This makes it suitable to be called by off-chain applications via eth_call without needing to hold tokens,\n * approve them for the Vault, or even know a user's address.\n *\n * Note that this function is not 'view' (due to implementation details): the client code must explicitly execute\n * eth_call instead of eth_sendTransaction.\n */\n function queryBatchSwap(\n SwapKind kind,\n BatchSwapStep[] memory swaps,\n IAsset[] memory assets,\n FundManagement memory funds\n ) external returns (int256[] memory assetDeltas);\n\n // Flash Loans\n\n /**\n * @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,\n * and then reverting unless the tokens plus a proportional protocol fee have been returned.\n *\n * The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount\n * for each token contract. `tokens` must be sorted in ascending order.\n *\n * The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the\n * `receiveFlashLoan` call.\n *\n * Emits `FlashLoan` events.\n */\n function flashLoan(\n IFlashLoanRecipient recipient,\n IERC20[] memory tokens,\n uint256[] memory amounts,\n bytes memory userData\n ) external;\n\n /**\n * @dev Emitted for each individual flash loan performed by `flashLoan`.\n */\n event FlashLoan(IFlashLoanRecipient indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount);\n\n // Asset Management\n //\n // Each token registered for a Pool can be assigned an Asset Manager, which is able to freely withdraw the Pool's\n // tokens from the Vault, deposit them, or assign arbitrary values to its `managed` balance (see\n // `getPoolTokenInfo`). This makes them extremely powerful and dangerous. Even if an Asset Manager only directly\n // controls one of the tokens in a Pool, a malicious manager could set that token's balance to manipulate the\n // prices of the other tokens, and then drain the Pool with swaps. The risk of using Asset Managers is therefore\n // not constrained to the tokens they are managing, but extends to the entire Pool's holdings.\n //\n // However, a properly designed Asset Manager smart contract can be safely used for the Pool's benefit,\n // for example by lending unused tokens out for interest, or using them to participate in voting protocols.\n //\n // This concept is unrelated to the IAsset interface.\n\n /**\n * @dev Performs a set of Pool balance operations, which may be either withdrawals, deposits or updates.\n *\n * Pool Balance management features batching, which means a single contract call can be used to perform multiple\n * operations of different kinds, with different Pools and tokens, at once.\n *\n * For each operation, the caller must be registered as the Asset Manager for `token` in `poolId`.\n */\n function managePoolBalance(PoolBalanceOp[] memory ops) external;\n\n struct PoolBalanceOp {\n PoolBalanceOpKind kind;\n bytes32 poolId;\n IERC20 token;\n uint256 amount;\n }\n\n /**\n * Withdrawals decrease the Pool's cash, but increase its managed balance, leaving the total balance unchanged.\n *\n * Deposits increase the Pool's cash, but decrease its managed balance, leaving the total balance unchanged.\n *\n * Updates don't affect the Pool's cash balance, but because the managed balance changes, it does alter the total.\n * The external amount can be either increased or decreased by this call (i.e., reporting a gain or a loss).\n */\n enum PoolBalanceOpKind { WITHDRAW, DEPOSIT, UPDATE }\n\n /**\n * @dev Emitted when a Pool's token Asset Manager alters its balance via `managePoolBalance`.\n */\n event PoolBalanceManaged(\n bytes32 indexed poolId,\n address indexed assetManager,\n IERC20 indexed token,\n int256 cashDelta,\n int256 managedDelta\n );\n\n // Protocol Fees\n //\n // Some operations cause the Vault to collect tokens in the form of protocol fees, which can then be withdrawn by\n // permissioned accounts.\n //\n // There are two kinds of protocol fees:\n //\n // - flash loan fees: charged on all flash loans, as a percentage of the amounts lent.\n //\n // - swap fees: a percentage of the fees charged by Pools when performing swaps. For a number of reasons, including\n // swap gas costs and interface simplicity, protocol swap fees are not charged on each individual swap. Rather,\n // Pools are expected to keep track of how much they have charged in swap fees, and pay any outstanding debts to the\n // Vault when they are joined or exited. This prevents users from joining a Pool with unpaid debt, as well as\n // exiting a Pool in debt without first paying their share.\n\n /**\n * @dev Returns the current protocol fee module.\n */\n function getProtocolFeesCollector() external view returns (IProtocolFeesCollector);\n\n /**\n * @dev Safety mechanism to pause most Vault operations in the event of an emergency - typically detection of an\n * error in some part of the system.\n *\n * The Vault can only be paused during an initial time period, after which pausing is forever disabled.\n *\n * While the contract is paused, the following features are disabled:\n * - depositing and transferring internal balance\n * - transferring external balance (using the Vault's allowance)\n * - swaps\n * - joining Pools\n * - Asset Manager interactions\n *\n * Internal Balance can still be withdrawn, and Pools exited.\n */\n function setPaused(bool paused) external;\n\n /**\n * @dev Returns the Vault's WETH instance.\n */\n function WETH() external view returns (IWETH);\n // solhint-disable-previous-line func-name-mixedcase\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IBasePool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"./IVault.sol\";\nimport \"./IPoolSwapStructs.sol\";\n\n/**\n * @dev Interface for adding and removing liquidity that all Pool contracts should implement. Note that this is not\n * the complete Pool contract interface, as it is missing the swap hooks. Pool contracts should also inherit from\n * either IGeneralPool or IMinimalSwapInfoPool\n */\ninterface IBasePool is IPoolSwapStructs {\n /**\n * @dev Called by the Vault when a user calls `IVault.joinPool` to add liquidity to this Pool. Returns how many of\n * each registered token the user should provide, as well as the amount of protocol fees the Pool owes to the Vault.\n * The Vault will then take tokens from `sender` and add them to the Pool's balances, as well as collect\n * the reported amount in protocol fees, which the pool should calculate based on `protocolSwapFeePercentage`.\n *\n * Protocol fees are reported and charged on join events so that the Pool is free of debt whenever new users join.\n *\n * `sender` is the account performing the join (from which tokens will be withdrawn), and `recipient` is the account\n * designated to receive any benefits (typically pool shares). `currentBalances` contains the total balances\n * for each token the Pool registered in the Vault, in the same order that `IVault.getPoolTokens` would return.\n *\n * `lastChangeBlock` is the last block in which *any* of the Pool's registered tokens last changed its total\n * balance.\n *\n * `userData` contains any pool-specific instructions needed to perform the calculations, such as the type of\n * join (e.g., proportional given an amount of pool shares, single-asset, multi-asset, etc.)\n *\n * Contracts implementing this function should check that the caller is indeed the Vault before performing any\n * state-changing operations, such as minting pool shares.\n */\n function onJoinPool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData\n ) external returns (uint256[] memory amountsIn, uint256[] memory dueProtocolFeeAmounts);\n\n /**\n * @dev Called by the Vault when a user calls `IVault.exitPool` to remove liquidity from this Pool. Returns how many\n * tokens the Vault should deduct from the Pool's balances, as well as the amount of protocol fees the Pool owes\n * to the Vault. The Vault will then take tokens from the Pool's balances and send them to `recipient`,\n * as well as collect the reported amount in protocol fees, which the Pool should calculate based on\n * `protocolSwapFeePercentage`.\n *\n * Protocol fees are charged on exit events to guarantee that users exiting the Pool have paid their share.\n *\n * `sender` is the account performing the exit (typically the pool shareholder), and `recipient` is the account\n * to which the Vault will send the proceeds. `currentBalances` contains the total token balances for each token\n * the Pool registered in the Vault, in the same order that `IVault.getPoolTokens` would return.\n *\n * `lastChangeBlock` is the last block in which *any* of the Pool's registered tokens last changed its total\n * balance.\n *\n * `userData` contains any pool-specific instructions needed to perform the calculations, such as the type of\n * exit (e.g., proportional given an amount of pool shares, single-asset, multi-asset, etc.)\n *\n * Contracts implementing this function should check that the caller is indeed the Vault before performing any\n * state-changing operations, such as burning pool shares.\n */\n function onExitPool(\n bytes32 poolId,\n address sender,\n address recipient,\n uint256[] memory balances,\n uint256 lastChangeBlock,\n uint256 protocolSwapFeePercentage,\n bytes memory userData\n ) external returns (uint256[] memory amountsOut, uint256[] memory dueProtocolFeeAmounts);\n\n function getPoolId() external view returns (bytes32);\n}\n" + }, + "@balancer-labs/v2-pool-utils/contracts/BalancerPoolToken.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20.sol\";\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20Permit.sol\";\n\n/**\n * @title Highly opinionated token implementation\n * @author Balancer Labs\n * @dev\n * - Includes functions to increase and decrease allowance as a workaround\n * for the well-known issue with `approve`:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n * - Allows for 'infinite allowance', where an allowance of 0xff..ff is not\n * decreased by calls to transferFrom\n * - Lets a token holder use `transferFrom` to send their own tokens,\n * without first setting allowance\n * - Emits 'Approval' events whenever allowance is changed by `transferFrom`\n */\ncontract BalancerPoolToken is ERC20, ERC20Permit {\n constructor(string memory tokenName, string memory tokenSymbol)\n ERC20(tokenName, tokenSymbol)\n ERC20Permit(tokenName)\n {\n // solhint-disable-previous-line no-empty-blocks\n }\n\n // Overrides\n\n /**\n * @dev Override to allow for 'infinite allowance' and let the token owner use `transferFrom` with no self-allowance\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public override returns (bool) {\n uint256 currentAllowance = allowance(sender, msg.sender);\n _require(msg.sender == sender || currentAllowance >= amount, Errors.ERC20_TRANSFER_EXCEEDS_ALLOWANCE);\n\n _transfer(sender, recipient, amount);\n\n if (msg.sender != sender && currentAllowance != uint256(-1)) {\n // Because of the previous require, we know that if msg.sender != sender then currentAllowance >= amount\n _approve(sender, msg.sender, currentAllowance - amount);\n }\n\n return true;\n }\n\n /**\n * @dev Override to allow decreasing allowance by more than the current amount (setting it to zero)\n */\n function decreaseAllowance(address spender, uint256 amount) public override returns (bool) {\n uint256 currentAllowance = allowance(msg.sender, spender);\n\n if (amount >= currentAllowance) {\n _approve(msg.sender, spender, 0);\n } else {\n // No risk of underflow due to if condition\n _approve(msg.sender, spender, currentAllowance - amount);\n }\n\n return true;\n }\n\n // Internal functions\n\n function _mintPoolTokens(address recipient, uint256 amount) internal {\n _mint(recipient, amount);\n }\n\n function _burnPoolTokens(address sender, uint256 amount) internal {\n _burn(sender, amount);\n }\n}\n" + }, + "@balancer-labs/v2-pool-utils/contracts/BasePoolAuthorization.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/helpers/Authentication.sol\";\nimport \"@balancer-labs/v2-vault/contracts/interfaces/IAuthorizer.sol\";\n\nimport \"./BasePool.sol\";\n\n/**\n * @dev Base authorization layer implementation for Pools.\n *\n * The owner account can call some of the permissioned functions - access control of the rest is delegated to the\n * Authorizer. Note that this owner is immutable: more sophisticated permission schemes, such as multiple ownership,\n * granular roles, etc., could be built on top of this by making the owner a smart contract.\n *\n * Access control of all other permissioned functions is delegated to an Authorizer. It is also possible to delegate\n * control of *all* permissioned functions to the Authorizer by setting the owner address to `_DELEGATE_OWNER`.\n */\nabstract contract BasePoolAuthorization is Authentication {\n address private immutable _owner;\n\n address private constant _DELEGATE_OWNER = 0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B;\n\n constructor(address owner) {\n _owner = owner;\n }\n\n function getOwner() public view returns (address) {\n return _owner;\n }\n\n function getAuthorizer() external view returns (IAuthorizer) {\n return _getAuthorizer();\n }\n\n function _canPerform(bytes32 actionId, address account) internal view override returns (bool) {\n if ((getOwner() != _DELEGATE_OWNER) && _isOwnerOnlyAction(actionId)) {\n // Only the owner can perform \"owner only\" actions, unless the owner is delegated.\n return msg.sender == getOwner();\n } else {\n // Non-owner actions are always processed via the Authorizer, as \"owner only\" ones are when delegated.\n return _getAuthorizer().canPerform(actionId, account, address(this));\n }\n }\n\n function _isOwnerOnlyAction(bytes32 actionId) internal view virtual returns (bool);\n\n function _getAuthorizer() internal view virtual returns (IAuthorizer);\n}\n" + }, + "@balancer-labs/v2-asset-manager-utils/contracts/IAssetManager.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol\";\n\ninterface IAssetManager {\n /**\n * @notice Emitted when asset manager is rebalanced\n */\n event Rebalance(bytes32 poolId);\n\n /**\n * @notice Sets the config\n */\n function setConfig(bytes32 poolId, bytes calldata config) external;\n\n /**\n * @notice Returns the asset manager's token\n */\n function getToken() external view returns (IERC20);\n\n /**\n * @return the current assets under management of this asset manager\n */\n function getAUM(bytes32 poolId) external view returns (uint256);\n\n /**\n * @return poolCash - The up-to-date cash balance of the pool\n * @return poolManaged - The up-to-date managed balance of the pool\n */\n function getPoolBalances(bytes32 poolId) external view returns (uint256 poolCash, uint256 poolManaged);\n\n /**\n * @return The difference in tokens between the target investment\n * and the currently invested amount (i.e. the amount that can be invested)\n */\n function maxInvestableBalance(bytes32 poolId) external view returns (int256);\n\n /**\n * @notice Updates the Vault on the value of the pool's investment returns\n */\n function updateBalanceOfPool(bytes32 poolId) external;\n\n /**\n * @notice Determines whether the pool should rebalance given the provided balances\n */\n function shouldRebalance(uint256 cash, uint256 managed) external view returns (bool);\n\n /**\n * @notice Rebalances funds between the pool and the asset manager to maintain target investment percentage.\n * @param poolId - the poolId of the pool to be rebalanced\n * @param force - a boolean representing whether a rebalance should be forced even when the pool is near balance\n */\n function rebalance(bytes32 poolId, bool force) external;\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/ITemporarilyPausable.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Interface for the TemporarilyPausable helper.\n */\ninterface ITemporarilyPausable {\n /**\n * @dev Emitted every time the pause state changes by `_setPaused`.\n */\n event PausedStateChanged(bool paused);\n\n /**\n * @dev Returns the current paused state.\n */\n function getPausedState()\n external\n view\n returns (\n bool paused,\n uint256 pauseWindowEndTime,\n uint256 bufferPeriodEndTime\n );\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\nimport \"../helpers/BalancerErrors.sol\";\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\n * checks.\n *\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\n * in bugs, because programmers usually assume that an overflow raises an\n * error, which is the standard behavior in high level programming languages.\n * `SafeMath` restores this intuition by reverting the transaction when an\n * operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n _require(c >= a, Errors.ADD_OVERFLOW);\n\n return c;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return sub(a, b, Errors.SUB_OVERFLOW);\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b, uint256 errorCode) internal pure returns (uint256) {\n _require(b <= a, errorCode);\n uint256 c = a - b;\n\n return c;\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/ISignaturesValidator.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Interface for the SignatureValidator helper, used to support meta-transactions.\n */\ninterface ISignaturesValidator {\n /**\n * @dev Returns the EIP712 domain separator.\n */\n function getDomainSeparator() external view returns (bytes32);\n\n /**\n * @dev Returns the next nonce used by an address to sign messages.\n */\n function getNextNonce(address user) external view returns (uint256);\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/misc/IWETH.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"../openzeppelin/IERC20.sol\";\n\n/**\n * @dev Interface for WETH9.\n * See https://github.com/gnosis/canonical-weth/blob/0dd1ea3e295eef916d0c6223ec63141137d22d67/contracts/WETH9.sol\n */\ninterface IWETH is IERC20 {\n function deposit() external payable;\n\n function withdraw(uint256 amount) external;\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IAsset.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\n/**\n * @dev This is an empty interface used to represent either ERC20-conforming token contracts or ETH (using the zero\n * address sentinel value). We're just relying on the fact that `interface` can be used to declare new address-like\n * types.\n *\n * This concept is unrelated to a Pool's Asset Managers.\n */\ninterface IAsset {\n // solhint-disable-previous-line no-empty-blocks\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IAuthorizer.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\ninterface IAuthorizer {\n /**\n * @dev Returns true if `account` can perform the action described by `actionId` in the contract `where`.\n */\n function canPerform(\n bytes32 actionId,\n address account,\n address where\n ) external view returns (bool);\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IFlashLoanRecipient.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\n// Inspired by Aave Protocol's IFlashLoanReceiver.\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol\";\n\ninterface IFlashLoanRecipient {\n /**\n * @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.\n *\n * At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this\n * call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the\n * Vault, or else the entire flash loan will revert.\n *\n * `userData` is the same value passed in the `IVault.flashLoan` call.\n */\n function receiveFlashLoan(\n IERC20[] memory tokens,\n uint256[] memory amounts,\n uint256[] memory feeAmounts,\n bytes memory userData\n ) external;\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IProtocolFeesCollector.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol\";\n\nimport \"./IVault.sol\";\nimport \"./IAuthorizer.sol\";\n\ninterface IProtocolFeesCollector {\n event SwapFeePercentageChanged(uint256 newSwapFeePercentage);\n event FlashLoanFeePercentageChanged(uint256 newFlashLoanFeePercentage);\n\n function withdrawCollectedFees(\n IERC20[] calldata tokens,\n uint256[] calldata amounts,\n address recipient\n ) external;\n\n function setSwapFeePercentage(uint256 newSwapFeePercentage) external;\n\n function setFlashLoanFeePercentage(uint256 newFlashLoanFeePercentage) external;\n\n function getSwapFeePercentage() external view returns (uint256);\n\n function getFlashLoanFeePercentage() external view returns (uint256);\n\n function getCollectedFeeAmounts(IERC20[] memory tokens) external view returns (uint256[] memory feeAmounts);\n\n function getAuthorizer() external view returns (IAuthorizer);\n\n function vault() external view returns (IVault);\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IPoolSwapStructs.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol\";\n\nimport \"./IVault.sol\";\n\ninterface IPoolSwapStructs {\n // This is not really an interface - it just defines common structs used by other interfaces: IGeneralPool and\n // IMinimalSwapInfoPool.\n //\n // This data structure represents a request for a token swap, where `kind` indicates the swap type ('given in' or\n // 'given out') which indicates whether or not the amount sent by the pool is known.\n //\n // The pool receives `tokenIn` and sends `tokenOut`. `amount` is the number of `tokenIn` tokens the pool will take\n // in, or the number of `tokenOut` tokens the Pool will send out, depending on the given swap `kind`.\n //\n // All other fields are not strictly necessary for most swaps, but are provided to support advanced scenarios in\n // some Pools.\n //\n // `poolId` is the ID of the Pool involved in the swap - this is useful for Pool contracts that implement more than\n // one Pool.\n //\n // The meaning of `lastChangeBlock` depends on the Pool specialization:\n // - Two Token or Minimal Swap Info: the last block in which either `tokenIn` or `tokenOut` changed its total\n // balance.\n // - General: the last block in which *any* of the Pool's registered tokens changed its total balance.\n //\n // `from` is the origin address for the funds the Pool receives, and `to` is the destination address\n // where the Pool sends the outgoing tokens.\n //\n // `userData` is extra data provided by the caller - typically a signature from a trusted party.\n struct SwapRequest {\n IVault.SwapKind kind;\n IERC20 tokenIn;\n IERC20 tokenOut;\n uint256 amount;\n // Misc data\n bytes32 poolId;\n uint256 lastChangeBlock;\n address from;\n address to;\n bytes userData;\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20Permit.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\nimport \"./ERC20.sol\";\nimport \"./IERC20Permit.sol\";\nimport \"./EIP712.sol\";\n\n/**\n * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * _Available since v3.4._\n */\nabstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {\n mapping(address => uint256) private _nonces;\n\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private immutable _PERMIT_TYPEHASH =\n keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\n\n /**\n * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `\"1\"`.\n *\n * It's a good idea to use the same `name` that is defined as the ERC20 token name.\n */\n constructor(string memory name) EIP712(name, \"1\") {}\n\n /**\n * @dev See {IERC20Permit-permit}.\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual override {\n // solhint-disable-next-line not-rely-on-time\n _require(block.timestamp <= deadline, Errors.EXPIRED_PERMIT);\n\n uint256 nonce = _nonces[owner];\n bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, nonce, deadline));\n\n bytes32 hash = _hashTypedDataV4(structHash);\n\n address signer = ecrecover(hash, v, r, s);\n _require((signer != address(0)) && (signer == owner), Errors.INVALID_SIGNATURE);\n\n _nonces[owner] = nonce + 1;\n _approve(owner, spender, value);\n }\n\n /**\n * @dev See {IERC20Permit-nonces}.\n */\n function nonces(address owner) public view override returns (uint256) {\n return _nonces[owner];\n }\n\n /**\n * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view override returns (bytes32) {\n return _domainSeparatorV4();\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20Permit.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,\n * given `owner`'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/EIP712.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,\n * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding\n * they need in their contracts using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * _Available since v3.4._\n */\nabstract contract EIP712 {\n /* solhint-disable var-name-mixedcase */\n bytes32 private immutable _HASHED_NAME;\n bytes32 private immutable _HASHED_VERSION;\n bytes32 private immutable _TYPE_HASH;\n\n /* solhint-enable var-name-mixedcase */\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n _HASHED_NAME = keccak256(bytes(name));\n _HASHED_VERSION = keccak256(bytes(version));\n _TYPE_HASH = keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\");\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view virtual returns (bytes32) {\n return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _getChainId(), address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", _domainSeparatorV4(), structHash));\n }\n\n function _getChainId() private view returns (uint256 chainId) {\n // Silence state mutability warning without generating bytecode.\n // See https://github.com/ethereum/solidity/issues/10090#issuecomment-741789128 and\n // https://github.com/ethereum/solidity/issues/2691\n this;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n }\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/Authentication.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\nimport \"./BalancerErrors.sol\";\nimport \"./IAuthentication.sol\";\n\n/**\n * @dev Building block for performing access control on external functions.\n *\n * This contract is used via the `authenticate` modifier (or the `_authenticateCaller` function), which can be applied\n * to external functions to only make them callable by authorized accounts.\n *\n * Derived contracts must implement the `_canPerform` function, which holds the actual access control logic.\n */\nabstract contract Authentication is IAuthentication {\n bytes32 private immutable _actionIdDisambiguator;\n\n /**\n * @dev The main purpose of the `actionIdDisambiguator` is to prevent accidental function selector collisions in\n * multi contract systems.\n *\n * There are two main uses for it:\n * - if the contract is a singleton, any unique identifier can be used to make the associated action identifiers\n * unique. The contract's own address is a good option.\n * - if the contract belongs to a family that shares action identifiers for the same functions, an identifier\n * shared by the entire family (and no other contract) should be used instead.\n */\n constructor(bytes32 actionIdDisambiguator) {\n _actionIdDisambiguator = actionIdDisambiguator;\n }\n\n /**\n * @dev Reverts unless the caller is allowed to call this function. Should only be applied to external functions.\n */\n modifier authenticate() {\n _authenticateCaller();\n _;\n }\n\n /**\n * @dev Reverts unless the caller is allowed to call the entry point function.\n */\n function _authenticateCaller() internal view {\n bytes32 actionId = getActionId(msg.sig);\n _require(_canPerform(actionId, msg.sender), Errors.SENDER_NOT_ALLOWED);\n }\n\n function getActionId(bytes4 selector) public view override returns (bytes32) {\n // Each external function is dynamically assigned an action identifier as the hash of the disambiguator and the\n // function selector. Disambiguation is necessary to avoid potential collisions in the function selectors of\n // multiple contracts.\n return keccak256(abi.encodePacked(_actionIdDisambiguator, selector));\n }\n\n function _canPerform(bytes32 actionId, address user) internal view virtual returns (bool);\n}\n" + }, + "@balancer-labs/v2-solidity-utils/contracts/helpers/IAuthentication.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\n\ninterface IAuthentication {\n /**\n * @dev Returns the action identifier associated with the external function described by `selector`.\n */\n function getActionId(bytes4 selector) external view returns (bytes32);\n}\n" + }, + "@balancer-labs/v2-vault/contracts/interfaces/IMinimalSwapInfoPool.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-or-later\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n\npragma solidity ^0.7.0;\npragma experimental ABIEncoderV2;\n\nimport \"./IBasePool.sol\";\n\n/**\n * @dev Pool contracts with the MinimalSwapInfo or TwoToken specialization settings should implement this interface.\n *\n * This is called by the Vault when a user calls `IVault.swap` or `IVault.batchSwap` to swap with this Pool.\n * Returns the number of tokens the Pool will grant to the user in a 'given in' swap, or that the user will grant\n * to the pool in a 'given out' swap.\n *\n * This can often be implemented by a `view` function, since many pricing algorithms don't need to track state\n * changes in swaps. However, contracts implementing this in non-view functions should check that the caller is\n * indeed the Vault.\n */\ninterface IMinimalSwapInfoPool is IBasePool {\n function onSwap(\n SwapRequest memory swapRequest,\n uint256 currentBalanceTokenIn,\n uint256 currentBalanceTokenOut\n ) external returns (uint256 amount);\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 9999 + }, + "outputSelection": { + "*": { + "*": [ + "evm.bytecode", + "evm.deployedBytecode", + "abi" + ] + } + }, + "libraries": {} + } +} \ No newline at end of file