Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
369 changes: 180 additions & 189 deletions contracts/Strategy.sol

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions contracts/StrategyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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,
uint256 _maxSlippageIn,
uint256 _maxSlippageOut,
uint256 _maxSingleDeposit,
uint256 _minDepositPeriod
) public {
Strategy _original = new Strategy(_vault, _balancerVault, _balancerPool, _maxSlippageIn, _maxSlippageOut, _maxSingleDeposit, _minDepositPeriod);
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,
uint256 _maxSlippageIn,
uint256 _maxSlippageOut,
uint256 _maxSingleDeposit,
uint256 _minDepositPeriod
) 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, _maxSlippageIn, _maxSlippageOut, _maxSingleDeposit, _minDepositPeriod);
emit Cloned(newStrategy);
}
}
73 changes: 68 additions & 5 deletions interfaces/BalancerV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface IBalancerPool is IERC20 {
uint256[] memory balances,
uint256 indexIn,
uint256 indexOut
) external returns (uint256 amount);
) external view returns (uint256 amount);
}

interface IBalancerVault {
Expand Down Expand Up @@ -92,8 +92,8 @@ interface IBalancerVault {
struct SingleSwap {
bytes32 poolId;
SwapKind kind;
IAsset assetIn;
IAsset assetOut;
address assetIn;
address assetOut;
uint256 amount;
bytes userData;
}
Expand Down Expand Up @@ -193,7 +193,7 @@ interface IBalancerVault {
function batchSwap(
SwapKind kind,
BatchSwapStep[] memory swaps,
IAsset[] memory assets,
address[] memory assets,
FundManagement memory funds,
int256[] memory limits,
uint256 deadline
Expand All @@ -203,9 +203,16 @@ interface IBalancerVault {
function queryBatchSwap(
SwapKind kind,
BatchSwapStep[] memory swaps,
IAsset[] memory assets,
address[] memory assets,
FundManagement memory funds
) external returns (int256[] memory);

function flashLoan(
address recipient,
IERC20[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external;
}

interface IAsset {
Expand Down Expand Up @@ -240,4 +247,60 @@ interface IBalancerVaultHelper {
address recipient,
IBalancerVault.ExitPoolRequest memory request
) external view returns (uint256 bptIn, uint256[] memory amountsOut);
}

interface IFlashLoanRecipient {
/**
* @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
*
* At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
* call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
* Vault, or else the entire flash loan will revert.
*
* `userData` is the same value passed in the `IVault.flashLoan` call.
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external;
}

interface ILinearPool is IBalancerPool {
function getVault() external view returns (address);

function getMainToken() external view returns (address);

function getWrappedToken() external view returns (address);

function getBptIndex() external view returns (uint256);

function getMainIndex() external view returns (uint256);

function getWrappedIndex() external view returns (uint256);

// Price rates

/**
* @dev For a Linear Pool, the rate represents the appreciation of BPT with respect to the underlying tokens. This
* rate increases slowly as the wrapped token appreciates in value.
*/
function getRate() external view returns (uint256);

function getWrappedTokenRate() external view returns (uint256);

function getTargets() external view returns (uint256 lowerTarget, uint256 upperTarget);

}

interface IStablePhantomPool is IBalancerPool {

/**
* @dev Returns the number of tokens in circulation.
*
* In other pools, this would be the same as `totalSupply`, but since this pool pre-mints all BPT, `totalSupply`
* remains constant, whereas `virtualSupply` increases as users join the pool and decreases as they exit it.
*/
function getVirtualSupply() external view returns (uint256);
}
84 changes: 37 additions & 47 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from brownie import config
from brownie import config, chain
from brownie import Contract


Expand Down Expand Up @@ -50,7 +50,7 @@ def token():
# 0xdAC17F958D2ee523a2206206994597C13D831ec7 USDT
# 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 wSTETH
# 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 WETH
token_address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
token_address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
yield Contract(token_address)


Expand All @@ -61,7 +61,7 @@ def token2():
# 0xdAC17F958D2ee523a2206206994597C13D831ec7 USDT
# 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 wSTETH
# 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 WETH
token_address = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
token_address = "0x6B175474E89094C44Da98b954EedeAC495271d0F"
yield Contract(token_address)


Expand All @@ -72,27 +72,27 @@ def token_whale(accounts):
# 0xA929022c9107643515F5c777cE9a910F0D1e490C USDT
# 0xba12222222228d8ba445958a75a0704d566bf2c8 wSTETH
# 0x2F0b23f53734252Bda2277357e97e1517d6B042A WETH
return accounts.at("0x2F0b23f53734252Bda2277357e97e1517d6B042A", force=True)
return accounts.at("0x0A59649758aa4d66E25f08Dd01271e891fe52199", force=True)


@pytest.fixture
@pytest.fixture(scope="function")
def amount(accounts, token, user, token_whale):
amount = 300 * 10 ** token.decimals()
amount = 5_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
@pytest.fixture(scope="function")
def amount2(accounts, token2, user):
amount = 300 * 10 ** token2.decimals()
amount = 5_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
# 0xba12222222228d8ba445958a75a0704d566bf2c8 wSTETH
# 0x2F0b23f53734252Bda2277357e97e1517d6B042A WETH
reserve = accounts.at("0xba12222222228d8ba445958a75a0704d566bf2c8", force=True)
reserve = accounts.at("0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643", force=True)
token2.transfer(user, amount, {"from": reserve})
yield amount

Expand Down Expand Up @@ -138,19 +138,7 @@ def weth_amout(user, weth):
yield weth_amout


@pytest.fixture
def live_ssb_weth(Strategy):
strat = "0xb8A245f9a066AD49fEAF15443E7704b83e2A9bF0"
yield Strategy.at(strat)


@pytest.fixture
def live_dai_vault():
vault = "0x1F8ad2cec4a2595Ff3cdA9e8a39C0b1BE1A02014"
yield Contract(vault)


@pytest.fixture
@pytest.fixture(scope="function")
def vault(pm, gov, rewards, guardian, management, token):
Vault = pm(config["dependencies"][0]).Vault
vault = guardian.deploy(Vault)
Expand Down Expand Up @@ -179,62 +167,64 @@ def balancer_vault():
def pool():
# 0x06Df3b2bbB68adc8B0e302443692037ED9f91b42 stable pool
# 0x32296969Ef14EB0c6d29669C550D4a0449130230 metastable eth pool
address = "0x32296969Ef14EB0c6d29669C550D4a0449130230"
# 0x7B50775383d3D6f0215A8F290f2C9e2eEBBEceb2 boosted pool
address = "0x7B50775383d3D6f0215A8F290f2C9e2eEBBEceb2"
yield Contract(address)


@pytest.fixture
def balWethPoolId():
yield 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 # bal-weth


@pytest.fixture
def wethTokenPoolId():
id = 0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a # weth-dai
id = 0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019 # weth-usdc
yield id


@pytest.fixture
def wethToken2PoolId():
# id = 0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019 # weth-usdc
id = 0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080 # weth-wsteth
yield id


@pytest.fixture
def ldoWethPoolId():
id = 0xbf96189eee9357a95c7719f4f5047f76bde804e5000200000000000000000087 # ldo-weth
yield id


@pytest.fixture
def swapStepsBal(balWethPoolId, bal, weth):
yield ([balWethPoolId], [bal, weth])
def wethToken2PoolId():
id = 0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a # weth-dai
yield id

@pytest.fixture
def swapStepsBal(balWethPoolId, wethTokenPoolId, bal, weth, token):
yield ([balWethPoolId, wethTokenPoolId], [bal, weth, token])

@pytest.fixture
def swapStepsLdo(ldoWethPoolId, ldo, weth):
yield ([ldoWethPoolId], [ldo, weth])
def swapStepsLdo(ldoWethPoolId, wethTokenPoolId, ldo, weth, token):
yield ([ldoWethPoolId, wethTokenPoolId], [ldo, weth, token])

@pytest.fixture
def swapStepsBal2(balWethPoolId, wethToken2PoolId, bal, weth, token2):
yield ([balWethPoolId, wethToken2PoolId], [bal, weth, token2])

@pytest.fixture
def swapStepsBal2(balWethPoolId, wethToken2PoolId, bal, weth, wsteth):
yield ([balWethPoolId], [bal, weth, wsteth])
def swapStepsLdo2(ldoWethPoolId, wethToken2PoolId, ldo, weth, token2):
yield ([ldoWethPoolId, wethToken2PoolId], [ldo, weth, token2])


@pytest.fixture
def swapStepsLdo2(ldoWethPoolId, wethToken2PoolId, ldo, weth, wsteth):
yield ([ldoWethPoolId], [ldo, weth, wsteth])
@pytest.fixture(scope="function")
def strategyFactory(strategist, vault, StrategyFactory, balancer_vault, pool):
factory = strategist.deploy(StrategyFactory, vault, balancer_vault, pool, 5, 5, 1_000_000, 2 * 60 * 60)
yield factory


@pytest.fixture
def strategy(strategist, keeper, vault, Strategy, gov, balancer_vault, pool, bal, ldo, management, swapStepsBal,
@pytest.fixture(scope="function")
def strategy(strategist, strategyFactory, keeper, vault, Strategy, gov, balancer_vault, pool, bal, ldo, management, swapStepsBal,
swapStepsLdo):
strategy = strategist.deploy(Strategy, vault, balancer_vault, pool, 5, 5, 30, 2 * 60 * 60)
strategy.setKeeper(keeper)
strategy = Strategy.at(strategyFactory.original())
strategy.setKeeper(keeper, {'from': gov})
strategy.whitelistRewards(bal, swapStepsBal, {'from': gov})
strategy.whitelistRewards(ldo, swapStepsLdo, {'from': gov})
strategy.setDoHealthCheck(True, {'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


Expand Down
2 changes: 1 addition & 1 deletion tests/test_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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.estimateTotalAssets({"from": user}).return_value, rel=RELATIVE_APPROX) == amount

# migrate to a new strategy
Expand Down
Loading