├── packages
├── vaults
│ ├── .husky
│ │ ├── .gitignore
│ │ └── pre-commit
│ ├── tests
│ │ ├── functional
│ │ │ ├── wrappers
│ │ │ │ ├── __init__.py
│ │ │ │ └── conftest.py
│ │ │ ├── vault
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_permit.py
│ │ │ │ └── test_losses.py
│ │ │ ├── strategy
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_withdrawal.py
│ │ │ │ ├── test_clone.py
│ │ │ │ ├── test_migration.py
│ │ │ │ └── test_startup.py
│ │ │ ├── registry
│ │ │ │ ├── test_release.py
│ │ │ │ └── test_config.py
│ │ │ ├── common_health
│ │ │ │ └── test_common_health_check.py
│ │ │ └── conftest.py
│ │ └── integration
│ │ │ ├── __init__.py
│ │ │ ├── test_operation.py
│ │ │ ├── conftest.py
│ │ │ ├── test_wrapper.py
│ │ │ └── test_release.py
│ ├── .prettierignore
│ ├── .npmignore
│ ├── requirements-dev.txt
│ ├── .gitattributes
│ ├── commitlint.config.js
│ ├── .solhint.json
│ ├── ethpm-config.yaml
│ ├── .gitignore
│ ├── index.ts
│ ├── docs
│ │ └── README.md
│ ├── contracts
│ │ ├── interfaces
│ │ │ └── ICustomHealthCheck.sol
│ │ ├── test
│ │ │ ├── TestHealthCheck.sol
│ │ │ ├── Token.sol
│ │ │ └── TestStrategy.sol
│ │ └── CommonHealthCheck.sol
│ ├── .github
│ │ ├── workflows
│ │ │ ├── release-drafter.yml
│ │ │ ├── title.yaml
│ │ │ ├── lint.yaml
│ │ │ └── test.yaml
│ │ └── release-drafter.yml
│ ├── .prettierrc.json
│ ├── brownie-config.yaml
│ ├── SECURITY.md
│ ├── package.json
│ ├── .soliumrc.json
│ ├── scripts
│ │ └── keep.py
│ ├── CONTRIBUTING.md
│ └── package-lock.json
├── core
│ ├── requirements-dev.txt
│ ├── .npmignore
│ ├── .gitattributes
│ ├── commitlint.config.js
│ ├── .gitignore
│ ├── tests
│ │ └── functional
│ │ │ ├── vaults
│ │ │ ├── __init__.py
│ │ │ └── test_config.py
│ │ │ ├── controllers
│ │ │ ├── __init__.py
│ │ │ └── test_config.py
│ │ │ ├── strategies
│ │ │ ├── __init__.py
│ │ │ └── test_config.py
│ │ │ ├── conftest.py
│ │ │ └── utils
│ │ │ └── test_oracles.py
│ ├── interfaces
│ │ ├── aave
│ │ │ ├── AaveToken.sol
│ │ │ ├── Oracle.sol
│ │ │ ├── LendingPoolAddressesProvider.sol
│ │ │ └── Aave.sol
│ │ ├── curve
│ │ │ ├── Mintr.sol
│ │ │ ├── VoteEscrow.sol
│ │ │ ├── FeeDistribution.sol
│ │ │ ├── Gauge.sol
│ │ │ └── Curve.sol
│ │ ├── cream
│ │ │ └── Controller.sol
│ │ ├── yearn
│ │ │ ├── IConverter.sol
│ │ │ ├── IProxy.sol
│ │ │ ├── IToken.sol
│ │ │ ├── IDelegatedVault.sol
│ │ │ ├── IGovernance.sol
│ │ │ ├── IWrappedVault.sol
│ │ │ ├── IController.sol
│ │ │ ├── IVoterProxy.sol
│ │ │ ├── IOneSplitAudit.sol
│ │ │ ├── IStrategy.sol
│ │ │ └── IVault.sol
│ │ ├── uniswap
│ │ │ └── Uni.sol
│ │ ├── weth
│ │ │ └── WETH.sol
│ │ ├── maker
│ │ │ ├── OracleSecurityModule.sol
│ │ │ └── Maker.sol
│ │ ├── dforce
│ │ │ ├── Rewards.sol
│ │ │ └── Token.sol
│ │ └── compound
│ │ │ └── Token.sol
│ ├── package.json
│ ├── index.js
│ ├── contracts
│ │ ├── test
│ │ │ └── Token.sol
│ │ ├── exploits
│ │ │ └── EvilGauge.sol
│ │ ├── strategies
│ │ │ ├── StrategyGUSDRescue.sol
│ │ │ └── CurveYCRVVoter.sol
│ │ ├── utils
│ │ │ ├── ETHOSMedianizer.sol
│ │ │ └── BTCOSMedianizer.sol
│ │ ├── abi
│ │ │ └── interfaces
│ │ │ │ ├── Mintr.json
│ │ │ │ ├── AaveToken.json
│ │ │ │ ├── Creamtroller.json
│ │ │ │ └── IConverter.json
│ │ └── vaults
│ │ │ └── yVault.sol
│ ├── .prettierrc.json
│ ├── brownie-config.yaml
│ ├── SECURITY.md
│ ├── .github
│ │ ├── ISSUE_TEMPLATE
│ │ │ └── bug.md
│ │ └── workflows
│ │ │ ├── test.yaml
│ │ │ └── lint.yaml
│ ├── .soliumrc.json
│ ├── CONTRIBUTING.md
│ └── README.md
├── protocol
│ ├── requirements-dev.txt
│ ├── interfaces
│ │ ├── aave
│ │ │ ├── AaveToken.sol
│ │ │ ├── Oracle.sol
│ │ │ ├── LendingPoolAddressesProvider.sol
│ │ │ └── Aave.sol
│ │ ├── yearn
│ │ │ ├── Mintr.sol
│ │ │ ├── Converter.sol
│ │ │ ├── Vault.sol
│ │ │ ├── Token.sol
│ │ │ ├── IController.sol
│ │ │ ├── Strategy.sol
│ │ │ └── OneSplitAudit.sol
│ │ ├── cream
│ │ │ └── Controller.sol
│ │ ├── uniswap
│ │ │ └── Uni.sol
│ │ ├── curve
│ │ │ ├── Gauge.sol
│ │ │ └── Curve.sol
│ │ ├── weth
│ │ │ └── WETH.sol
│ │ ├── dforce
│ │ │ ├── Rewards.sol
│ │ │ └── Token.sol
│ │ ├── compound
│ │ │ └── Token.sol
│ │ └── maker
│ │ │ └── Maker.sol
│ ├── hardhat.config.js
│ ├── contracts
│ │ ├── test
│ │ │ └── Token.sol
│ │ ├── exploits
│ │ │ └── EvilGauge.sol
│ │ ├── strategies
│ │ │ ├── StrategyGUSDRescue.sol
│ │ │ └── CurveYCRVVoter.sol
│ │ ├── utils
│ │ │ ├── ETHOSMedianizer.sol
│ │ │ └── BTCOSMedianizer.sol
│ │ └── vaults
│ │ │ └── yVault.sol
│ ├── brownie-config.yaml
│ ├── README.md
│ ├── hh.ts
│ ├── truffle-config.js
│ └── package.json
├── .gitattributes
├── setup.sh
├── .gitignore
└── compile.sh
├── lerna.json
├── .gitignore
├── package.json
└── .github
└── workflows
└── nodejs.yml
/packages/vaults/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/wrappers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/vaults/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | reports/
3 |
--------------------------------------------------------------------------------
/packages/vaults/.npmignore:
--------------------------------------------------------------------------------
1 | docs/
2 | tests/
3 | .github/
4 | build
5 | reports
6 |
--------------------------------------------------------------------------------
/packages/core/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black==19.10b0
2 | eth-brownie>=1.11.7,<2.0.0
3 |
--------------------------------------------------------------------------------
/packages/protocol/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black==19.10b0
2 | eth-brownie>=1.11.0,<2.0.0
3 |
--------------------------------------------------------------------------------
/packages/vaults/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black==20.8b1
2 | eth-brownie>=1.14.5,<2.0.0
3 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*"
4 | ],
5 | "version": "0.0.0"
6 | }
7 |
--------------------------------------------------------------------------------
/packages/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/packages/core/.npmignore:
--------------------------------------------------------------------------------
1 | reports/
2 | scripts
3 | tests
4 | .github/
5 | .soliumrc.json
6 |
7 |
--------------------------------------------------------------------------------
/packages/core/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/packages/core/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {extends: ["@commitlint/config-conventional"]};
2 |
--------------------------------------------------------------------------------
/packages/vaults/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/packages/core/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 | node_modules/
--------------------------------------------------------------------------------
/packages/vaults/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {extends: ["@commitlint/config-conventional"]};
2 |
--------------------------------------------------------------------------------
/packages/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | export @openzeppelinV2/=$(pwd)/node_modules/@openzeppelin/ contracts/*.sol
3 |
--------------------------------------------------------------------------------
/packages/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 | node_modules
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/packages/vaults/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn format:fix
5 | yarn hint
6 |
--------------------------------------------------------------------------------
/packages/compile.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | openzeppelin-solidity/=$(pwd)/node_modules/openzeppelin-solidity/ contracts/**/*.sol
3 |
--------------------------------------------------------------------------------
/packages/vaults/tests/integration/__init__.py:
--------------------------------------------------------------------------------
1 | # Just here to disambiguate the test files (can't use same name without a module)
2 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/vaults/__init__.py:
--------------------------------------------------------------------------------
1 | # Just here to disambiguate the test files (can't use same name without a module)
2 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/vault/__init__.py:
--------------------------------------------------------------------------------
1 | # Just here to disambiguate the test files (can't use same name without a module)
2 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/controllers/__init__.py:
--------------------------------------------------------------------------------
1 | # Just here to disambiguate the test files (can't use same name without a module)
2 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/strategies/__init__.py:
--------------------------------------------------------------------------------
1 | # Just here to disambiguate the test files (can't use same name without a module)
2 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/strategy/__init__.py:
--------------------------------------------------------------------------------
1 | # Just here to disambiguate the test files (can't use same name without a module)
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | __pycache__
4 | .history
5 | .hypothesis/
6 | build/
7 | reports/
8 | node_modules
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/packages/vaults/.solhint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solhint:default",
3 | "rules": {
4 | "max-line-length": ["error", 145]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/vaults/ethpm-config.yaml:
--------------------------------------------------------------------------------
1 | package_name: vault
2 | version: 0.4.3
3 | settings:
4 | deployment_networks:
5 | - mainnet
6 | include_dependencies: false
7 |
--------------------------------------------------------------------------------
/packages/vaults/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 | node_modules/
7 | .test_durations
8 | yarn-error.log
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/packages/vaults/index.ts:
--------------------------------------------------------------------------------
1 | import { default as yearnVaults } from "./contracts/abi/vaults";
2 |
3 | export const yearn = {
4 | yearnVaults,
5 | };
6 | /** @exports yearn */
7 |
--------------------------------------------------------------------------------
/packages/core/interfaces/aave/AaveToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface AaveToken {
4 | function underlyingAssetAddress() external view returns (address);
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/interfaces/curve/Mintr.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Mintr {
6 | function mint(address) external;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/aave/AaveToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface AaveToken {
4 | function underlyingAssetAddress() external view returns (address);
5 | }
6 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/Mintr.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Mintr {
6 | function mint(address) external;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/vaults/docs/README.md:
--------------------------------------------------------------------------------
1 | # Docs have been migrated
2 |
3 | This folder has been migrated to [yearn-devdocs](https://github.com/yearn/yearn-devdocs/tree/master/docs/developers/v2)
--------------------------------------------------------------------------------
/packages/core/interfaces/cream/Controller.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Creamtroller {
6 | function claimComp(address holder) external;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/cream/Controller.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Creamtroller {
6 | function claimComp(address holder) external;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IConverter.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IConverter {
6 | function convert(address) external returns (uint256);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/Converter.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.16;
4 |
5 | interface Converter {
6 | function convert(address) external returns (uint);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/aave/Oracle.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface Oracle {
4 | function getAssetPrice(address reserve) external view returns (uint);
5 | function latestAnswer() external view returns (uint);
6 | }
7 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/uniswap/Uni.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Uni {
6 | function swapExactTokensForTokens(uint, uint, address[] calldata, address, uint) external;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/interfaces/aave/Oracle.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface Oracle {
4 | function getAssetPrice(address reserve) external view returns (uint256);
5 |
6 | function latestAnswer() external view returns (uint256);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/protocol/hardhat.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type import('hardhat/config').HardhatUserConfig
3 | */
4 | require("@nomiclabs/hardhat-vyper");
5 | module.exports = {
6 | solidity: "0.5.17",
7 | vyper: {
8 | version: "0.1.0b10",
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/curve/Gauge.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Gauge {
6 | function deposit(uint) external;
7 | function balanceOf(address) external view returns (uint);
8 | function withdraw(uint) external;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/weth/WETH.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface WETH {
4 | function deposit() external payable;
5 | function withdraw(uint wad) external;
6 | event Deposit(address indexed dst, uint wad);
7 | event Withdrawal(address indexed src, uint wad);
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yearn-finance-core",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "directories": {
6 | "contracts": "contracts"
7 | },
8 | "scripts": {
9 | },
10 | "keywords": [],
11 | "author": "Yearn Finance",
12 | "license": "AGPL-v3"
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | module.exports = {
5 | extends: [
6 | require.resolve('./contracts/abi/core'),
7 | require.resolve('./contracts/abi/interfaces'),
8 | require.resolve('./contracts/strategies'),
9 | require.resolve('./contracts/vaults'),
10 | ],
11 | };
12 |
--------------------------------------------------------------------------------
/packages/core/interfaces/curve/VoteEscrow.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface VoteEscrow {
6 | function create_lock(uint256, uint256) external;
7 |
8 | function increase_amount(uint256) external;
9 |
10 | function withdraw() external;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/interfaces/uniswap/Uni.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Uni {
6 | function swapExactTokensForTokens(
7 | uint256,
8 | uint256,
9 | address[] calldata,
10 | address,
11 | uint256
12 | ) external;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/interfaces/weth/WETH.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface WETH {
4 | function deposit() external payable;
5 |
6 | function withdraw(uint256 wad) external;
7 |
8 | event Deposit(address indexed dst, uint256 wad);
9 | event Withdrawal(address indexed src, uint256 wad);
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/interfaces/maker/OracleSecurityModule.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface OracleSecurityModule {
4 | function peek() external view returns (bytes32, bool);
5 |
6 | function peep() external view returns (bytes32, bool);
7 |
8 | function bud(address) external view returns (uint256);
9 | }
10 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/Vault.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface Vault {
4 | function deposit(uint) external;
5 | function depositAll() external;
6 | function withdraw(uint) external;
7 | function withdrawAll() external;
8 | function getPricePerFullShare() external view returns (uint);
9 | }
10 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/aave/LendingPoolAddressesProvider.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface LendingPoolAddressesProvider {
4 | function getLendingPool() external view returns (address);
5 | function getLendingPoolCore() external view returns (address);
6 | function getPriceOracle() external view returns (address);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/interfaces/aave/LendingPoolAddressesProvider.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface LendingPoolAddressesProvider {
4 | function getLendingPool() external view returns (address);
5 |
6 | function getLendingPoolCore() external view returns (address);
7 |
8 | function getPriceOracle() external view returns (address);
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IProxy {
6 | function execute(
7 | address to,
8 | uint256 value,
9 | bytes calldata data
10 | ) external returns (bool, bytes memory);
11 |
12 | function increaseAmount(uint256) external;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | // NOTE: Basically an alias for Vaults
6 | interface yERC20 {
7 | function deposit(uint256 _amount) external;
8 | function withdraw(uint256 _amount) external;
9 | function getPricePerFullShare() external view returns (uint);
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vaults/contracts/interfaces/ICustomHealthCheck.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.6.0 <0.7.0;
2 |
3 | interface ICustomHealthCheck {
4 | function check(
5 | address callerStrategy,
6 | uint256 profit,
7 | uint256 loss,
8 | uint256 debtPayment,
9 | uint256 debtOutstanding
10 | ) external view returns (bool);
11 | }
12 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/dforce/Rewards.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface dRewards {
6 | function withdraw(uint) external;
7 | function getReward() external;
8 | function stake(uint) external;
9 | function balanceOf(address) external view returns (uint);
10 | function exit() external;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/dforce/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface dERC20 {
6 | function mint(address, uint256) external;
7 | function redeem(address, uint) external;
8 | function getTokenBalance(address) external view returns (uint);
9 | function getExchangeRate() external view returns (uint);
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IToken.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | // NOTE: Basically an alias for Vaults
6 | interface yERC20 {
7 | function deposit(uint256 _amount) external;
8 |
9 | function withdraw(uint256 _amount) external;
10 |
11 | function getPricePerFullShare() external view returns (uint256);
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/interfaces/dforce/Rewards.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface dRewards {
6 | function withdraw(uint256) external;
7 |
8 | function getReward() external;
9 |
10 | function stake(uint256) external;
11 |
12 | function balanceOf(address) external view returns (uint256);
13 |
14 | function exit() external;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/interfaces/dforce/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface dERC20 {
6 | function mint(address, uint256) external;
7 |
8 | function redeem(address, uint256) external;
9 |
10 | function getTokenBalance(address) external view returns (uint256);
11 |
12 | function getExchangeRate() external view returns (uint256);
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/interfaces/curve/FeeDistribution.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface FeeDistribution {
4 | function claim_many(address[20] calldata) external returns (bool);
5 |
6 | function last_token_time() external view returns (uint256);
7 |
8 | function time_cursor() external view returns (uint256);
9 |
10 | function time_cursor_of(address) external view returns (uint256);
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/contracts/test/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.5.17;
3 |
4 | import "@openzeppelinV2/contracts/token/ERC20/ERC20.sol";
5 | import "@openzeppelinV2/contracts/token/ERC20/ERC20Detailed.sol";
6 |
7 | contract Token is ERC20, ERC20Detailed {
8 | constructor() public ERC20Detailed("yearn.finance test token", "TEST", 18) {
9 | _mint(msg.sender, 30000 * 10**18);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/test/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.5.17;
3 |
4 | import "@openzeppelinV2/contracts/token/ERC20/ERC20.sol";
5 | import "@openzeppelinV2/contracts/token/ERC20/ERC20Detailed.sol";
6 |
7 | contract Token is ERC20, ERC20Detailed {
8 | constructor() public ERC20Detailed("yearn.finance test token", "TEST", 18) {
9 | _mint(msg.sender, 30000 * 10**18);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/IController.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface IController {
4 | function withdraw(address, uint) external;
5 | function balanceOf(address) external view returns (uint);
6 | function earn(address, uint) external;
7 | function want(address) external view returns (address);
8 | function rewards() external view returns (address);
9 | function vaults(address) external view returns (address);
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "printWidth": 88,
4 | "overrides": [
5 | {
6 | "files": "*.sol",
7 | "options": {
8 | "printWidth": 145,
9 | "tabWidth": 4,
10 | "useTabs": false,
11 | "singleQuote": false,
12 | "bracketSpacing": false,
13 | "explicitTypes": "always"
14 | }
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/contracts/exploits/EvilGauge.sol:
--------------------------------------------------------------------------------
1 | pragma solidity =0.5.17;
2 |
3 | import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";
4 |
5 | contract EvilGauge {
6 | IERC20 token;
7 | address owner;
8 |
9 | constructor(address _token) public {
10 | owner = msg.sender;
11 | token = IERC20(_token);
12 | }
13 |
14 | function deposit(uint256 amount) public {
15 | token.transferFrom(msg.sender, owner, amount);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/exploits/EvilGauge.sol:
--------------------------------------------------------------------------------
1 | pragma solidity =0.5.17;
2 |
3 | import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";
4 |
5 | contract EvilGauge {
6 | IERC20 token;
7 | address owner;
8 |
9 | constructor(address _token) public {
10 | owner = msg.sender;
11 | token = IERC20(_token);
12 | }
13 |
14 | function deposit(uint256 amount) public {
15 | token.transferFrom(msg.sender, owner, amount);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IDelegatedVault.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface IDelegatedVault {
4 | function token() external view returns (address);
5 |
6 | function deposit(uint256) external;
7 |
8 | function depositAll() external;
9 |
10 | function withdraw(uint256) external;
11 |
12 | function withdrawAll() external;
13 |
14 | function getPricePerFullShare() external view returns (uint256);
15 |
16 | function claimInsurance() external;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/interfaces/curve/Gauge.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface Gauge {
6 | function deposit(uint256) external;
7 |
8 | function balanceOf(address) external view returns (uint256);
9 |
10 | function withdraw(uint256) external;
11 |
12 | function claim_rewards(address) external;
13 |
14 | function rewarded_token() external returns (address);
15 |
16 | function reward_tokens(uint256) external returns (address);
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IGovernance.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IGovernance {
6 | function withdraw(uint256) external;
7 |
8 | function getReward() external;
9 |
10 | function stake(uint256) external;
11 |
12 | function balanceOf(address) external view returns (uint256);
13 |
14 | function exit() external;
15 |
16 | function voteFor(uint256) external;
17 |
18 | function voteAgainst(uint256) external;
19 | }
20 |
--------------------------------------------------------------------------------
/packages/vaults/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | # branches to consider in the event; optional, defaults to all
6 | branches:
7 | - main
8 |
9 | jobs:
10 | update_release_draft:
11 | runs-on: ubuntu-latest
12 | steps:
13 | # Drafts your next Release notes as Pull Requests are merged into "main"
14 | - uses: release-drafter/release-drafter@v5
15 | with:
16 | disable-autolabeler: true
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IWrappedVault.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IWrappedVault {
6 | function token() external view returns (address);
7 |
8 | function name() external view returns (string memory);
9 |
10 | function symbol() external view returns (string memory);
11 |
12 | function decimals() external view returns (uint8);
13 |
14 | function governance() external view returns (address);
15 |
16 | function vault() external view returns (address);
17 |
18 | function getPricePerFullShare() external view returns (uint256);
19 | }
20 |
--------------------------------------------------------------------------------
/packages/vaults/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "printWidth": 88,
4 | "overrides": [
5 | {
6 | "files": "*.sol",
7 | "options": {
8 | "printWidth": 145,
9 | "tabWidth": 4,
10 | "useTabs": false,
11 | "singleQuote": false,
12 | "bracketSpacing": false,
13 | "explicitTypes": "always"
14 | }
15 | },
16 | {
17 | "files": "*.md",
18 | "options": {
19 | "tabWidth": 2
20 | }
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/vaults/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | # use Ganache's forked mainnet mode as the default network
2 | networks:
3 | default: development
4 |
5 | autofetch_sources: true
6 |
7 | # require OpenZepplin Contracts
8 | dependencies:
9 | - OpenZeppelin/openzeppelin-contracts@3.1.0
10 |
11 | # path remapping to support OpenZepplin imports with NPM-style path
12 | compiler:
13 | solc:
14 | version: 0.6.12
15 | remappings:
16 | - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@3.1.0"
17 |
18 | reports:
19 | exclude_paths:
20 | - contracts/test/Token.sol
21 | exclude_contracts:
22 | - SafeMath
23 | - SafeERC20
24 | - Address
25 |
--------------------------------------------------------------------------------
/packages/protocol/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | # use Ganache's forked mainnet mode as the default network
2 | networks:
3 | default: mainnet-fork
4 |
5 | # automatically fetch contract sources from Etherscan
6 | autofetch_sources: True
7 |
8 | # require OpenZepplin Contracts
9 | dependencies:
10 | - OpenZeppelin/openzeppelin-contracts@2.5.1
11 | - OpenZeppelin/openzeppelin-contracts@3.1.0
12 |
13 | # path remapping to support OpenZepplin imports with NPM-style path
14 | compiler:
15 | solc:
16 | version: 0.5.17
17 | remappings:
18 | - "@openzeppelinV2=OpenZeppelin/openzeppelin-contracts@2.5.1"
19 | - "@openzeppelinV3=OpenZeppelin/openzeppelin-contracts@3.1.0"
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yearn-protocol-nodejs",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "yearn protocol nodejs version",
6 | "files": ["packages/*"],
7 | "scripts": {
8 | "install": "npx lerna bootstrap"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/sambacha/yearn-protocol-nodejs.git"
13 | },
14 | "author": "Yearn Finance",
15 | "license": "AGPL-3.0",
16 | "bugs": {
17 | "url": "https://github.com/sambacha/yearn-protocol-nodejs/issues"
18 | },
19 | "homepage": "https://github.com/sambacha/yearn-protocol-nodejs#readme",
20 | "devDependencies": {
21 | "lerna": "^4.0.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/compound/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface cToken {
6 | function mint(uint mintAmount) external returns (uint);
7 | function redeem(uint redeemTokens) external returns (uint);
8 | function redeemUnderlying(uint redeemAmount) external returns (uint);
9 | function borrow(uint borrowAmount) external returns (uint);
10 | function repayBorrow(uint repayAmount) external returns (uint);
11 | function exchangeRateStored() external view returns (uint);
12 | function balanceOf(address _owner) external view returns (uint);
13 | function underlying() external view returns (address);
14 | }
15 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IController.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IController {
6 | function withdraw(address, uint256) external;
7 |
8 | function balanceOf(address) external view returns (uint256);
9 |
10 | function earn(address, uint256) external;
11 |
12 | function want(address) external view returns (address);
13 |
14 | function rewards() external view returns (address);
15 |
16 | function vaults(address) external view returns (address);
17 |
18 | function strategies(address) external view returns (address);
19 |
20 | function approvedStrategies(address, address) external view returns (bool);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/vaults/.github/workflows/title.yaml:
--------------------------------------------------------------------------------
1 | name: PR Title
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | check:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - name: Setup Python
18 | uses: actions/setup-python@v2
19 | with:
20 | python-version: 3.8
21 |
22 | - name: Install Dependencies
23 | run: pip install commitizen
24 |
25 | - name: Check PR Title
26 | env:
27 | TITLE: ${{ github.event.pull_request.title }}
28 | run: cz check --message "$TITLE"
29 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IVoterProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IVoterProxy {
6 | function withdraw(
7 | address _gauge,
8 | address _token,
9 | uint256 _amount
10 | ) external returns (uint256);
11 |
12 | function balanceOf(address _gauge) external view returns (uint256);
13 |
14 | function withdrawAll(address _gauge, address _token) external returns (uint256);
15 |
16 | function deposit(address _gauge, address _token) external;
17 |
18 | function harvest(address _gauge) external;
19 |
20 | function lock() external;
21 |
22 | function claimRewards(address _gauge, address _token) external;
23 | }
24 |
--------------------------------------------------------------------------------
/packages/vaults/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'v$RESOLVED_VERSION'
2 | tag-template: 'v$RESOLVED_VERSION'
3 | categories:
4 | - title: 'Features'
5 | labels:
6 | - 'feat'
7 | - title: 'Bug Fixes'
8 | labels:
9 | - 'fix'
10 | - title: 'Refactor'
11 | labels:
12 | - refactor
13 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
14 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
15 | version-resolver:
16 | major:
17 | labels:
18 | - 'major'
19 | minor:
20 | labels:
21 | - 'minor'
22 | patch:
23 | labels:
24 | - 'patch'
25 | default: patch
26 | template: |
27 | ## Changes
28 |
29 | $CHANGES
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IOneSplitAudit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IOneSplitAudit {
6 | function swap(
7 | address fromToken,
8 | address destToken,
9 | uint256 amount,
10 | uint256 minReturn,
11 | uint256[] calldata distribution,
12 | uint256 flags
13 | ) external payable returns (uint256 returnAmount);
14 |
15 | function getExpectedReturn(
16 | address fromToken,
17 | address destToken,
18 | uint256 amount,
19 | uint256 parts,
20 | uint256 flags // See constants in IOneSplit.sol
21 | ) external view returns (uint256 returnAmount, uint256[] memory distribution);
22 | }
23 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/Strategy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.16;
4 |
5 |
6 | interface Strategy {
7 | function want() external view returns (address);
8 |
9 | function deposit() external;
10 |
11 | // NOTE: must exclude any tokens used in the yield
12 | // Controller role - withdraw should return to Controller
13 | function withdraw(address) external;
14 |
15 | // Controller | Vault role - withdraw should always return to Vault
16 | function withdraw(uint) external;
17 |
18 | // Controller | Vault role - withdraw should always return to Vault
19 | function withdrawAll() external returns (uint);
20 |
21 | function balanceOf() external view returns (uint);
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/interfaces/compound/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface cToken {
6 | function mint(uint256 mintAmount) external returns (uint256);
7 |
8 | function redeem(uint256 redeemTokens) external returns (uint256);
9 |
10 | function redeemUnderlying(uint256 redeemAmount) external returns (uint256);
11 |
12 | function borrow(uint256 borrowAmount) external returns (uint256);
13 |
14 | function repayBorrow(uint256 repayAmount) external returns (uint256);
15 |
16 | function exchangeRateStored() external view returns (uint256);
17 |
18 | function balanceOf(address _owner) external view returns (uint256);
19 |
20 | function underlying() external view returns (address);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/vaults/contracts/test/TestHealthCheck.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.6.12;
3 |
4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5 |
6 | import {ICustomHealthCheck} from "../interfaces/ICustomHealthCheck.sol";
7 |
8 | contract TestHealthCheck is ICustomHealthCheck {
9 | bool pass;
10 |
11 | constructor() public {
12 | pass = true;
13 | }
14 |
15 | function togglePass() external {
16 | pass = !pass;
17 | }
18 |
19 | function check(
20 | address callerStrategy,
21 | uint256 profit,
22 | uint256 loss,
23 | uint256 debtPayment,
24 | uint256 debtOutstanding
25 | ) external view override returns (bool) {
26 | return pass;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | # use Ganache's forked mainnet mode as the default network
2 | networks:
3 | default: mainnet-fork
4 |
5 | # automatically fetch contract sources from Etherscan
6 | autofetch_sources: True
7 |
8 | # require OpenZepplin Contracts
9 | dependencies:
10 | - OpenZeppelin/openzeppelin-contracts@2.5.1
11 | - OpenZeppelin/openzeppelin-contracts@3.1.0
12 |
13 | # path remapping to support OpenZepplin imports with NPM-style path
14 | compiler:
15 | solc:
16 | version: 0.5.17
17 | remappings:
18 | - "@openzeppelinV2=OpenZeppelin/openzeppelin-contracts@2.5.1"
19 | - "@openzeppelinV3=OpenZeppelin/openzeppelin-contracts@3.1.0"
20 |
21 | reports:
22 | exclude_paths:
23 | - contracts/test/Token.sol
24 | exclude_contracts:
25 | - SafeMath
26 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture
5 | def rewards(a):
6 | yield a[2]
7 |
8 |
9 | @pytest.fixture
10 | def gov(a):
11 | yield a[3]
12 |
13 |
14 | @pytest.fixture
15 | def token(a, Token):
16 | # Must be ERC20
17 | yield a[0].deploy(Token)
18 |
19 |
20 | @pytest.fixture
21 | def controller(a):
22 | yield a[4]
23 |
24 |
25 | @pytest.fixture
26 | def andre(accounts):
27 | return accounts.at("0x2D407dDb06311396fE14D4b49da5F0471447d45C", force=True)
28 |
29 |
30 | @pytest.fixture
31 | def ychad(accounts):
32 | return accounts.at("0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52", force=True)
33 |
34 |
35 | @pytest.fixture
36 | def binance(accounts):
37 | return accounts.at("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE", force=True)
38 |
--------------------------------------------------------------------------------
/packages/vaults/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security
2 |
3 | For full security process refer to yearn-security [repo](https://github.com/iearn-finance/yearn-security/blob/master/SECURITY.md).
4 |
5 | ## Scope
6 |
7 | The scope of the Bug Bounty program spans smart contracts utilized in the Yearn ecosystem – the Solidity and/or Vyper smart contracts in the `contracts` folder of the `main` branch of the yearn-vaults [repo](https://github.com/iearn-finance/yearn-vaults), including historical deployments that still see active use on Ethereum Mainnet associated with YFI, and excluding any contracts used in a test-only capacity (including test-only deployments).
8 |
9 | Note: Other contracts, outside of the ones mentioned above, might be considered on a case by case basis, please, reach out to the Yearn development team for clarification.
10 |
--------------------------------------------------------------------------------
/packages/core/SECURITY.md:
--------------------------------------------------------------------------------
1 | ## Security
2 |
3 | For full security process refer to yearn-security [repo](https://github.com/iearn-finance/yearn-security/blob/master/SECURITY.md).
4 |
5 | ### Scope
6 |
7 | The scope of the Bug Bounty program spans smart contracts utilized in the Yearn ecosystem – the Solidity and/or Vyper smart contracts in the `contracts` folder of the `master` branch of the yearn-protocol [repo](https://github.com/iearn-finance/yearn-protocol), including historical deployments that still see active use on Ethereum Mainnet associated with YFI, and excluding any contracts used in a test-only capacity (including test-only deployments).
8 |
9 | Note: Other contracts, outside of the ones mentioned above, might be considered on a case by case basis, please, reach out to the Yearn development team for clarification.
10 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/curve/Curve.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface ICurveFi {
6 |
7 | function get_virtual_price() external view returns (uint);
8 | function add_liquidity( // sBTC pool
9 | uint256[3] calldata amounts,
10 | uint256 min_mint_amount
11 | ) external;
12 | function add_liquidity( // bUSD pool
13 | uint256[4] calldata amounts,
14 | uint256 min_mint_amount
15 | ) external;
16 | function remove_liquidity_imbalance(
17 | uint256[4] calldata amounts,
18 | uint256 max_burn_amount
19 | ) external;
20 | function remove_liquidity(
21 | uint256 _amount,
22 | uint256[4] calldata amounts
23 | ) external;
24 | function exchange(
25 | int128 from, int128 to, uint256 _from_amount, uint256 _min_to_amount
26 | ) external;
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Any general feedback or bug reports about the yearn protocol. No new features proposals.
4 | ---
5 |
6 | ### What's your issue about?
7 |
8 | NOTE: For security issue see Security [section](#security) below.
9 |
10 | Please include following information:
11 |
12 | - A clear and concise summary of an issue.
13 | - Steps to reproduce (if applicable).
14 | - What is expected to happen.
15 | - What actually happens.
16 | - Notes.
17 |
18 | ### How can it be fixed?
19 |
20 | Fill this in if you know how to fix it.
21 |
22 | ### Security
23 |
24 | If this is a sensitive issue with potential security implications in the yearn protocol, please, follow the reporting guidelines in the [Security Doc](https://github.com/iearn-finance/yearn-protocol/blob/develop/SECURITY.md).
25 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/yearn/OneSplitAudit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.16;
4 |
5 | interface OneSplitAudit {
6 | function swap(
7 | address fromToken,
8 | address destToken,
9 | uint256 amount,
10 | uint256 minReturn,
11 | uint256[] calldata distribution,
12 | uint256 flags
13 | )
14 | external
15 | payable
16 | returns(uint256 returnAmount);
17 |
18 | function getExpectedReturn(
19 | address fromToken,
20 | address destToken,
21 | uint256 amount,
22 | uint256 parts,
23 | uint256 flags // See constants in IOneSplit.sol
24 | )
25 | external
26 | view
27 | returns(
28 | uint256 returnAmount,
29 | uint256[] memory distribution
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IStrategy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IStrategy {
6 | function want() external view returns (address);
7 |
8 | function deposit() external;
9 |
10 | // NOTE: must exclude any tokens used in the yield
11 | // Controller role - withdraw should return to Controller
12 | function withdraw(address) external;
13 |
14 | // Controller | Vault role - withdraw should always return to Vault
15 | function withdraw(uint256) external;
16 |
17 | function skim() external;
18 |
19 | // Controller | Vault role - withdraw should always return to Vault
20 | function withdrawAll() external returns (uint256);
21 |
22 | function balanceOf() external view returns (uint256);
23 |
24 | function withdrawalFee() external view returns (uint256);
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/interfaces/yearn/IVault.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface IVault {
6 | function token() external view returns (address);
7 |
8 | function underlying() external view returns (address);
9 |
10 | function name() external view returns (string memory);
11 |
12 | function symbol() external view returns (string memory);
13 |
14 | function decimals() external view returns (uint8);
15 |
16 | function controller() external view returns (address);
17 |
18 | function governance() external view returns (address);
19 |
20 | function getPricePerFullShare() external view returns (uint256);
21 |
22 | function deposit(uint256) external;
23 |
24 | function depositAll() external;
25 |
26 | function withdraw(uint256) external;
27 |
28 | function withdrawAll() external;
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # CI for command line NodeJS Applications
2 | name: nodejs
3 | on:
4 | push:
5 | paths:
6 | - "**/**"
7 | - "!**/*.md/**"
8 |
9 | env:
10 | CI: true
11 | FORCE_COLOR: 2
12 |
13 | jobs:
14 | run:
15 | name: Node ${{ matrix.node }} on ${{ matrix.os }}
16 | runs-on: ${{ matrix.os }}
17 |
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | node: ['14']
22 | os: ['ubuntu-latest']
23 |
24 | steps:
25 | - name: Clone repository
26 | uses: actions/checkout@v2
27 |
28 | - name: Set up Node.js
29 | uses: actions/setup-node@v2
30 | with:
31 | node-version: ${{ matrix.node }}
32 |
33 | - name: Install npm dependencies
34 | run: cd packages/ && npm install
35 |
36 | - name: HardHat Compile
37 | run: npx hardhat compile
38 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/strategy/test_withdrawal.py:
--------------------------------------------------------------------------------
1 | import brownie
2 |
3 |
4 | def test_withdraw(chain, gov, token, vault, strategy, rando):
5 | token.approve(vault, token.balanceOf(gov), {"from": gov})
6 | vault.deposit(token.balanceOf(gov) // 2, {"from": gov})
7 | chain.sleep(8640)
8 | strategy.harvest({"from": gov}) # Seed some debt in there
9 | assert strategy.estimatedTotalAssets() > 0
10 |
11 | balance = strategy.estimatedTotalAssets()
12 | strategy.withdraw(balance // 2, {"from": vault.address})
13 | # NOTE: This may be +1 more than just dividing it
14 | assert strategy.estimatedTotalAssets() == balance - balance // 2
15 |
16 | # Not just anyone can call it
17 | with brownie.reverts():
18 | strategy.withdraw(balance // 2, {"from": rando})
19 |
20 | # Anything over what we can liquidate is totally withdrawn
21 | strategy.withdraw(balance, {"from": vault.address})
22 | assert strategy.estimatedTotalAssets() == 0
23 |
--------------------------------------------------------------------------------
/packages/protocol/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Yearn Finance - Yearn Protocol
6 |
7 |
8 |
9 |
10 | [](https://twitter.com/iearnfinance) [](https://discordapp.com/channels/734804446353031319/) [](https://t.me/yearnfinance)
11 |
12 |
13 |
14 |
15 |
16 |
17 | > [github.com/yearn/yearn-protocol](https://github.com/yearn/yearn-protocol)
18 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/wrappers/conftest.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | # 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 on Mainnet
6 | WETH_BYTECODE = (Path(__file__).parent / "weth.bytecode").read_text().strip()
7 |
8 |
9 | @pytest.fixture
10 | def weth(Token, gov):
11 | # WETH9 deployment bytecode
12 | txn = gov.transfer(data=WETH_BYTECODE)
13 | yield Token.at(txn.contract_address)
14 |
15 |
16 | @pytest.fixture
17 | def ytoken(token, gov, registry, yToken):
18 | # Official Yearn Wrapper
19 | yield gov.deploy(yToken, token, registry)
20 |
21 |
22 | @pytest.fixture
23 | def affiliate(management):
24 | yield management # NOTE: Not necessary for these tests
25 |
26 |
27 | @pytest.fixture
28 | def affiliate_token(token, affiliate, registry, AffiliateToken):
29 | # Affliate Wrapper
30 | yield affiliate.deploy(
31 | AffiliateToken,
32 | token,
33 | registry,
34 | f"Affiliate {token.symbol()}",
35 | f"af{token.symbol()}",
36 | )
37 |
38 |
39 | @pytest.fixture
40 | def new_registry(Registry, gov):
41 | yield gov.deploy(Registry)
42 |
--------------------------------------------------------------------------------
/packages/vaults/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yearn-finance-vaults",
3 | "devDependencies": {
4 | "@commitlint/cli": "^12.1.4",
5 | "@commitlint/config-conventional": "^12.1.4",
6 | "husky": "^7.0.0",
7 | "prettier": "^2.3.2",
8 | "prettier-plugin-solidity": "^1.0.0-beta.13",
9 | "pretty-quick": "^3.1.1",
10 | "solhint": "^3.3.6",
11 | "solhint-plugin-prettier": "^0.0.5",
12 | "ts-node": "^10.1.0",
13 | "typescript": "^4.3.5"
14 | },
15 | "scripts": {
16 | "format": "prettier --write 'contracts/**/*.sol' --verbose",
17 | "format:check": "prettier --check '**/*.*(sol|json)'",
18 | "format:fix": "pretty-quick --pattern '**/*.*(sol|json)' --staged --verbose",
19 | "hint": "solhint \"contracts/**/*.sol\""
20 | },
21 | "husky": {
22 | "hooks": {
23 | "pre-commit": "yarn lint:fix",
24 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
25 | }
26 | },
27 | "description": "Please read and be familiar with the [Specification](SPECIFICATION.md).",
28 | "version": "1.0.0",
29 | "main": "index.js",
30 | "directories": {
31 | "abi": "abi/*"
32 | },
33 | "keywords": [],
34 | "author": "Yearn Finance",
35 | "license": "AGPL-3.0"
36 | }
37 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/registry/test_release.py:
--------------------------------------------------------------------------------
1 | import brownie
2 |
3 |
4 | def test_release_management(gov, registry, create_vault, rando):
5 | # No releases yet
6 | with brownie.reverts():
7 | registry.latestRelease()
8 |
9 | # Not just anyone can create a new Release
10 | vault = create_vault()
11 | with brownie.reverts():
12 | registry.newRelease(vault, {"from": rando})
13 |
14 | # Creating the first release makes `latestRelease()` work
15 | v1_vault = create_vault(version="1.0.0")
16 | registry.newRelease(v1_vault, {"from": gov})
17 | assert registry.latestRelease() == v1_vault.apiVersion() == "1.0.0"
18 |
19 | # Can't release same vault twice (cannot have the same api version)
20 | with brownie.reverts():
21 | registry.newRelease(v1_vault, {"from": gov})
22 |
23 | # New release overrides previous release
24 | v2_vault = create_vault(version="2.0.0")
25 | registry.newRelease(v2_vault, {"from": gov})
26 | assert registry.latestRelease() == v2_vault.apiVersion() == "2.0.0"
27 |
28 | # Can only endorse the latest release.
29 | with brownie.reverts():
30 | registry.endorseVault(v1_vault)
31 |
32 | # Check that newRelease works even if vault governance is not gov
33 | bad_vault = create_vault(governance=rando)
34 | registry.newRelease(bad_vault, {"from": gov})
35 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/aave/Aave.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.16;
2 |
3 | interface Aave {
4 | function borrow(address _reserve, uint _amount, uint _interestRateModel, uint16 _referralCode) external;
5 | function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral) external;
6 | function repay(address _reserve, uint _amount, address payable _onBehalfOf) external payable;
7 | function getUserAccountData(address _user)
8 | external
9 | view
10 | returns (
11 | uint totalLiquidityETH,
12 | uint totalCollateralETH,
13 | uint totalBorrowsETH,
14 | uint totalFeesETH,
15 | uint availableBorrowsETH,
16 | uint currentLiquidationThreshold,
17 | uint ltv,
18 | uint healthFactor
19 | );
20 | function getUserReserveData(address _reserve, address _user)
21 | external
22 | view
23 | returns (
24 | uint currentATokenBalance,
25 | uint currentBorrowBalance,
26 | uint principalBorrowBalance,
27 | uint borrowRateMode,
28 | uint borrowRate,
29 | uint liquidityRate,
30 | uint originationFee,
31 | uint variableBorrowIndex,
32 | uint lastUpdateTimestamp,
33 | bool usageAsCollateralEnabled
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/.soliumrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solium:recommended",
3 | "plugins": ["security"],
4 | "rules": {
5 | "imports-on-top": "error",
6 | "variable-declarations": "error",
7 | "array-declarations": "error",
8 | "no-unused-vars": "error",
9 | "quotes": ["error", "double"],
10 | "value-in-payable": "error",
11 | "linebreak-style": ["error", "unix"],
12 | "no-empty-blocks": "warning",
13 | "indentation": ["error", 4],
14 | "whitespace": "warning",
15 | "deprecated-suicide": "warning",
16 | "pragma-on-top": "error",
17 | "function-whitespace": "warning",
18 | "semicolon-whitespace": "warning",
19 | "comma-whitespace": "warning",
20 | "operator-whitespace": "warning",
21 | "emit": "warning",
22 | "no-constant": "warning",
23 | "max-len": "warning",
24 | "error-reason": "warning",
25 | "constructor": "warning",
26 | "visibility-first": "warning",
27 | "lbrace": "warning",
28 | "mixedcase": "off",
29 | "camelcase": "off",
30 | "uppercase": "off",
31 | "blank-lines": "off",
32 | "arg-overflow": "off",
33 | "function-order": "off",
34 | "conditionals-whitespace": "warning",
35 | "no-experimental": "off",
36 | "no-trailing-whitespace": "warning",
37 | "security/no-inline-assembly": "warning"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/vaults/.soliumrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solium:recommended",
3 | "plugins": ["security"],
4 | "rules": {
5 | "imports-on-top": "error",
6 | "variable-declarations": "error",
7 | "array-declarations": "error",
8 | "no-unused-vars": "error",
9 | "quotes": ["error", "double"],
10 | "value-in-payable": "error",
11 | "linebreak-style": ["error", "unix"],
12 | "no-empty-blocks": "warning",
13 | "indentation": ["error", 4],
14 | "whitespace": "warning",
15 | "deprecated-suicide": "warning",
16 | "pragma-on-top": "error",
17 | "function-whitespace": "warning",
18 | "semicolon-whitespace": "warning",
19 | "comma-whitespace": "warning",
20 | "operator-whitespace": "warning",
21 | "emit": "warning",
22 | "no-constant": "warning",
23 | "max-len": "warning",
24 | "error-reason": "warning",
25 | "constructor": "warning",
26 | "visibility-first": "warning",
27 | "lbrace": "warning",
28 | "mixedcase": "off",
29 | "camelcase": "off",
30 | "uppercase": "off",
31 | "blank-lines": "off",
32 | "arg-overflow": "off",
33 | "function-order": "off",
34 | "conditionals-whitespace": "warning",
35 | "no-experimental": "off",
36 | "no-trailing-whitespace": "warning",
37 | "security/no-inline-assembly": "warning"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/controllers/test_config.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 |
4 |
5 | def test_controller_deployment(gov, rewards, Controller):
6 | controller = gov.deploy(Controller, rewards)
7 | # Double check all the deployment variable values
8 | assert controller.governance() == gov
9 | assert controller.rewards() == rewards
10 | assert controller.onesplit() == "0x50FDA034C0Ce7a8f7EFDAebDA7Aa7cA21CC1267e"
11 | assert controller.split() == 500
12 |
13 |
14 | @pytest.mark.parametrize(
15 | "getter,setter,val",
16 | [
17 | ("split", "setSplit", 1000),
18 | ("onesplit", "setOneSplit", None),
19 | ("governance", "setGovernance", None),
20 | ],
21 | )
22 | def test_controller_setParams(accounts, gov, rewards, getter, setter, val, Controller):
23 | if not val:
24 | # Can't access fixtures, so use None to mean an address literal
25 | val = accounts[1]
26 |
27 | controller = gov.deploy(Controller, rewards)
28 |
29 | # Only governance can set this param
30 | with brownie.reverts("!governance"):
31 | getattr(controller, setter)(val, {"from": accounts[1]})
32 | getattr(controller, setter)(val, {"from": gov})
33 | assert getattr(controller, getter)() == val
34 |
35 | # When changing governance contract, make sure previous no longer has access
36 | if getter == "governance":
37 | with brownie.reverts("!governance"):
38 | getattr(controller, setter)(val, {"from": gov})
39 |
--------------------------------------------------------------------------------
/packages/core/interfaces/aave/Aave.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface Aave {
4 | function borrow(
5 | address _reserve,
6 | uint256 _amount,
7 | uint256 _interestRateModel,
8 | uint16 _referralCode
9 | ) external;
10 |
11 | function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral) external;
12 |
13 | function repay(
14 | address _reserve,
15 | uint256 _amount,
16 | address payable _onBehalfOf
17 | ) external payable;
18 |
19 | function getUserAccountData(address _user)
20 | external
21 | view
22 | returns (
23 | uint256 totalLiquidityETH,
24 | uint256 totalCollateralETH,
25 | uint256 totalBorrowsETH,
26 | uint256 totalFeesETH,
27 | uint256 availableBorrowsETH,
28 | uint256 currentLiquidationThreshold,
29 | uint256 ltv,
30 | uint256 healthFactor
31 | );
32 |
33 | function getUserReserveData(address _reserve, address _user)
34 | external
35 | view
36 | returns (
37 | uint256 currentATokenBalance,
38 | uint256 currentBorrowBalance,
39 | uint256 principalBorrowBalance,
40 | uint256 borrowRateMode,
41 | uint256 borrowRate,
42 | uint256 liquidityRate,
43 | uint256 originationFee,
44 | uint256 variableBorrowIndex,
45 | uint256 lastUpdateTimestamp,
46 | bool usageAsCollateralEnabled
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/contracts/strategies/StrategyGUSDRescue.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface IERC20 {
4 | function totalSupply() external view returns (uint256);
5 |
6 | function balanceOf(address account) external view returns (uint256);
7 |
8 | function transfer(address recipient, uint256 amount) external returns (bool);
9 |
10 | function allowance(address owner, address spender) external view returns (uint256);
11 |
12 | function decimals() external view returns (uint256);
13 |
14 | function approve(address spender, uint256 amount) external returns (bool);
15 |
16 | function transferFrom(
17 | address sender,
18 | address recipient,
19 | uint256 amount
20 | ) external returns (bool);
21 |
22 | event Transfer(address indexed from, address indexed to, uint256 value);
23 | event Approval(address indexed owner, address indexed spender, uint256 value);
24 | }
25 |
26 | contract StrategyGUSDRescue {
27 | address public constant want = address(0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd);
28 | address public governance = address(0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52);
29 |
30 | constructor() public {}
31 |
32 | function getName() external pure returns (string memory) {
33 | return "StrategyGUSDRescue";
34 | }
35 |
36 | function deposit() public {
37 | uint256 _want = IERC20(want).balanceOf(address(this));
38 | IERC20(want).transfer(governance, _want);
39 | }
40 |
41 | function withdrawAll() public returns (uint256) {
42 | return 0;
43 | }
44 |
45 | function balanceOf() public pure returns (uint256) {
46 | return 0;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/strategies/StrategyGUSDRescue.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface IERC20 {
4 | function totalSupply() external view returns (uint256);
5 |
6 | function balanceOf(address account) external view returns (uint256);
7 |
8 | function transfer(address recipient, uint256 amount) external returns (bool);
9 |
10 | function allowance(address owner, address spender) external view returns (uint256);
11 |
12 | function decimals() external view returns (uint256);
13 |
14 | function approve(address spender, uint256 amount) external returns (bool);
15 |
16 | function transferFrom(
17 | address sender,
18 | address recipient,
19 | uint256 amount
20 | ) external returns (bool);
21 |
22 | event Transfer(address indexed from, address indexed to, uint256 value);
23 | event Approval(address indexed owner, address indexed spender, uint256 value);
24 | }
25 |
26 | contract StrategyGUSDRescue {
27 | address public constant want = address(0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd);
28 | address public governance = address(0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52);
29 |
30 | constructor() public {}
31 |
32 | function getName() external pure returns (string memory) {
33 | return "StrategyGUSDRescue";
34 | }
35 |
36 | function deposit() public {
37 | uint256 _want = IERC20(want).balanceOf(address(this));
38 | IERC20(want).transfer(governance, _want);
39 | }
40 |
41 | function withdrawAll() public returns (uint256) {
42 | return 0;
43 | }
44 |
45 | function balanceOf() public pure returns (uint256) {
46 | return 0;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/interfaces/curve/Curve.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | interface ICurveFi {
6 | function get_virtual_price() external view returns (uint256);
7 |
8 | function add_liquidity(
9 | // EURs
10 | uint256[2] calldata amounts,
11 | uint256 min_mint_amount
12 | ) external;
13 |
14 | function add_liquidity(
15 | // sBTC pool
16 | uint256[3] calldata amounts,
17 | uint256 min_mint_amount
18 | ) external;
19 |
20 | function add_liquidity(
21 | // bUSD pool
22 | uint256[4] calldata amounts,
23 | uint256 min_mint_amount
24 | ) external;
25 |
26 | function remove_liquidity_imbalance(uint256[4] calldata amounts, uint256 max_burn_amount) external;
27 |
28 | function remove_liquidity(uint256 _amount, uint256[4] calldata amounts) external;
29 |
30 | function remove_liquidity_one_coin(
31 | uint256 _token_amount,
32 | int128 i,
33 | uint256 min_amount
34 | ) external;
35 |
36 | function exchange(
37 | int128 from,
38 | int128 to,
39 | uint256 _from_amount,
40 | uint256 _min_to_amount
41 | ) external;
42 |
43 | function exchange_underlying(
44 | int128 from,
45 | int128 to,
46 | uint256 _from_amount,
47 | uint256 _min_to_amount
48 | ) external;
49 |
50 | function balances(int128) external view returns (uint256);
51 |
52 | function get_dy(
53 | int128 from,
54 | int128 to,
55 | uint256 _from_amount
56 | ) external view returns (uint256);
57 | }
58 |
59 | interface Zap {
60 | function remove_liquidity_one_coin(
61 | uint256,
62 | int128,
63 | uint256
64 | ) external;
65 | }
66 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/vaults/test_config.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 |
4 | from brownie import (
5 | yVault,
6 | yWETH,
7 | yDelegatedVault,
8 | )
9 |
10 | VAULTS = [yVault, yWETH, yDelegatedVault]
11 |
12 |
13 | @pytest.mark.parametrize("Vault", VAULTS)
14 | def test_vault_deployment(gov, token, controller, Vault):
15 | vault = gov.deploy(Vault, token, controller)
16 | # Addresses
17 | assert vault.governance() == gov
18 | assert vault.controller() == controller
19 | assert vault.token() == token
20 | # UI Stuff
21 | assert vault.name() == "yearn " + token.name()
22 | assert vault.symbol() == "y" + token.symbol()
23 | assert vault.decimals() == token.decimals()
24 |
25 |
26 | @pytest.mark.parametrize(
27 | "getter,setter,val",
28 | [
29 | ("min", "setMin", 9000),
30 | ("healthFactor", "setHealthFactor", 100),
31 | ("controller", "setController", None),
32 | ("governance", "setGovernance", None),
33 | ],
34 | )
35 | @pytest.mark.parametrize("Vault", VAULTS)
36 | def test_vault_setParams(accounts, gov, token, controller, getter, setter, val, Vault):
37 | if not val:
38 | # Can't access fixtures, so use None to mean an address literal
39 | val = accounts[1]
40 |
41 | vault = gov.deploy(Vault, token, controller)
42 |
43 | if not hasattr(vault, getter):
44 | return # Some combinations aren't valid
45 |
46 | # Only governance can set this param
47 | with brownie.reverts("!governance"):
48 | getattr(vault, setter)(val, {"from": accounts[1]})
49 | getattr(vault, setter)(val, {"from": gov})
50 | assert getattr(vault, getter)() == val
51 |
52 | # When changing governance contract, make sure previous no longer has access
53 | if getter == "governance":
54 | with brownie.reverts("!governance"):
55 | getattr(vault, setter)(val, {"from": gov})
56 |
--------------------------------------------------------------------------------
/packages/core/contracts/utils/ETHOSMedianizer.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | import "../../interfaces/maker/OracleSecurityModule.sol";
4 |
5 | interface Medianizer {
6 | function read() external view returns (bytes32);
7 | }
8 |
9 | contract ETHOSMedianizer {
10 | mapping(address => bool) public authorized;
11 | address public governance;
12 |
13 | OracleSecurityModule public constant OSM = OracleSecurityModule(0x81FE72B5A8d1A857d176C3E7d5Bd2679A9B85763);
14 | Medianizer public constant MEDIANIZER = Medianizer(0x729D19f657BD0614b4985Cf1D82531c67569197B);
15 |
16 | constructor() public {
17 | governance = msg.sender;
18 | }
19 |
20 | function setGovernance(address _governance) external {
21 | require(msg.sender == governance, "!governance");
22 | governance = _governance;
23 | }
24 |
25 | function setAuthorized(address _authorized) external {
26 | require(msg.sender == governance, "!governance");
27 | authorized[_authorized] = true;
28 | }
29 |
30 | function revokeAuthorized(address _authorized) external {
31 | require(msg.sender == governance, "!governance");
32 | authorized[_authorized] = false;
33 | }
34 |
35 | function read() external view returns (uint256 price, bool osm) {
36 | if (authorized[msg.sender]) {
37 | if (OSM.bud(address(this)) == 1) {
38 | (bytes32 _val, ) = OSM.peek();
39 | return (uint256(_val), true);
40 | }
41 | }
42 | return (uint256(MEDIANIZER.read()), false);
43 | }
44 |
45 | function foresight() external view returns (uint256 price, bool osm) {
46 | if (authorized[msg.sender]) {
47 | if (OSM.bud(address(this)) == 1) {
48 | (bytes32 _val, ) = OSM.peep();
49 | return (uint256(_val), true);
50 | }
51 | }
52 | return (uint256(MEDIANIZER.read()), false);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/utils/ETHOSMedianizer.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | import "../../interfaces/maker/OracleSecurityModule.sol";
4 |
5 | interface Medianizer {
6 | function read() external view returns (bytes32);
7 | }
8 |
9 | contract ETHOSMedianizer {
10 | mapping(address => bool) public authorized;
11 | address public governance;
12 |
13 | OracleSecurityModule public constant OSM = OracleSecurityModule(0x81FE72B5A8d1A857d176C3E7d5Bd2679A9B85763);
14 | Medianizer public constant MEDIANIZER = Medianizer(0x729D19f657BD0614b4985Cf1D82531c67569197B);
15 |
16 | constructor() public {
17 | governance = msg.sender;
18 | }
19 |
20 | function setGovernance(address _governance) external {
21 | require(msg.sender == governance, "!governance");
22 | governance = _governance;
23 | }
24 |
25 | function setAuthorized(address _authorized) external {
26 | require(msg.sender == governance, "!governance");
27 | authorized[_authorized] = true;
28 | }
29 |
30 | function revokeAuthorized(address _authorized) external {
31 | require(msg.sender == governance, "!governance");
32 | authorized[_authorized] = false;
33 | }
34 |
35 | function read() external view returns (uint256 price, bool osm) {
36 | if (authorized[msg.sender]) {
37 | if (OSM.bud(address(this)) == 1) {
38 | (bytes32 _val, ) = OSM.peek();
39 | return (uint256(_val), true);
40 | }
41 | }
42 | return (uint256(MEDIANIZER.read()), false);
43 | }
44 |
45 | function foresight() external view returns (uint256 price, bool osm) {
46 | if (authorized[msg.sender]) {
47 | if (OSM.bud(address(this)) == 1) {
48 | (bytes32 _val, ) = OSM.peep();
49 | return (uint256(_val), true);
50 | }
51 | }
52 | return (uint256(MEDIANIZER.read()), false);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/vaults/tests/integration/test_operation.py:
--------------------------------------------------------------------------------
1 | class NormalOperation:
2 | def __init__(self, web3, token, vault, strategy, user, farm, keeper, chain):
3 | self.web3 = web3
4 | self.token = token
5 | self.vault = vault
6 | self.strategy = strategy
7 | self.farm = farm
8 | self.keeper = keeper
9 | self.user = user
10 | self.chain = chain
11 |
12 | def rule_deposit(self):
13 | print(" Vault.deposit()")
14 |
15 | # Deposit 50% of what they have left
16 | amt = self.token.balanceOf(self.user) // 2
17 | self.vault.deposit(amt, {"from": self.user})
18 |
19 | def rule_withdraw(self):
20 | print(" Vault.withdraw()")
21 |
22 | # Withdraw 50% of what they have in the Vault
23 | amt = self.vault.balanceOf(self.user) // 2
24 | self.vault.withdraw(amt, {"from": self.user})
25 |
26 | def rule_harvest(self):
27 | print(" Strategy.harvest()")
28 |
29 | # Earn 1% yield on deposits in some farming protocol
30 | amt = self.token.balanceOf(self.strategy) // 100
31 | self.token.transfer(self.strategy, amt, {"from": self.farm})
32 |
33 | # Keeper decides to harvest the yield
34 | self.chain.sleep(1)
35 | self.strategy.harvest({"from": self.keeper})
36 |
37 | # TODO: Invariant that user did not get > they should have
38 | # TODO: Invariant that fees/accounting is all perfect
39 | # TODO: Invariant that all economic assumptions are maintained
40 |
41 |
42 | def test_normal_operation(
43 | web3, chain, gov, strategy, vault, token, chad, andre, keeper, state_machine
44 | ):
45 | vault.addStrategy(
46 | strategy,
47 | 10_000, # 100% of Vault AUM
48 | 0,
49 | 2 ** 256 - 1, # no rate limit
50 | 1000, # 10% performance fee for Strategist
51 | {"from": gov},
52 | )
53 | strategy.harvest({"from": keeper})
54 | assert token.balanceOf(vault) == 0
55 | state_machine(
56 | NormalOperation, web3, token, vault, strategy, chad, andre, keeper, chain
57 | )
58 |
--------------------------------------------------------------------------------
/packages/core/contracts/utils/BTCOSMedianizer.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | import "@openzeppelinV2/contracts/math/SafeMath.sol";
4 |
5 | import "../../interfaces/maker/OracleSecurityModule.sol";
6 |
7 | interface UniswapAnchoredView {
8 | function price(string calldata) external view returns (uint256);
9 | }
10 |
11 | contract BTCOSMedianizer {
12 | using SafeMath for uint256;
13 |
14 | mapping(address => bool) public authorized;
15 | address public governance;
16 |
17 | OracleSecurityModule public constant OSM = OracleSecurityModule(0xf185d0682d50819263941e5f4EacC763CC5C6C42);
18 | UniswapAnchoredView public constant MEDIANIZER = UniswapAnchoredView(0x9B8Eb8b3d6e2e0Db36F41455185FEF7049a35CaE);
19 | string public symbol = "BTC";
20 |
21 | constructor() public {
22 | governance = msg.sender;
23 | }
24 |
25 | function setGovernance(address _governance) external {
26 | require(msg.sender == governance, "!governance");
27 | governance = _governance;
28 | }
29 |
30 | function setAuthorized(address _authorized) external {
31 | require(msg.sender == governance, "!governance");
32 | authorized[_authorized] = true;
33 | }
34 |
35 | function revokeAuthorized(address _authorized) external {
36 | require(msg.sender == governance, "!governance");
37 | authorized[_authorized] = false;
38 | }
39 |
40 | function read() external view returns (uint256 price, bool osm) {
41 | if (authorized[msg.sender] && OSM.bud(address(this)) == 1) {
42 | (bytes32 _val, bool _has) = OSM.peek();
43 | if (_has) return (uint256(_val), true);
44 | }
45 | return ((MEDIANIZER.price(symbol)).mul(1e12), false);
46 | }
47 |
48 | function foresight() external view returns (uint256 price, bool osm) {
49 | if (authorized[msg.sender] && OSM.bud(address(this)) == 1) {
50 | (bytes32 _val, bool _has) = OSM.peep();
51 | if (_has) return (uint256(_val), true);
52 | }
53 | return ((MEDIANIZER.price(symbol)).mul(1e12), false);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/utils/BTCOSMedianizer.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | import "@openzeppelinV2/contracts/math/SafeMath.sol";
4 |
5 | import "../../interfaces/maker/OracleSecurityModule.sol";
6 |
7 | interface UniswapAnchoredView {
8 | function price(string calldata) external view returns (uint256);
9 | }
10 |
11 | contract BTCOSMedianizer {
12 | using SafeMath for uint256;
13 |
14 | mapping(address => bool) public authorized;
15 | address public governance;
16 |
17 | OracleSecurityModule public constant OSM = OracleSecurityModule(0xf185d0682d50819263941e5f4EacC763CC5C6C42);
18 | UniswapAnchoredView public constant MEDIANIZER = UniswapAnchoredView(0x9B8Eb8b3d6e2e0Db36F41455185FEF7049a35CaE);
19 | string public symbol = "BTC";
20 |
21 | constructor() public {
22 | governance = msg.sender;
23 | }
24 |
25 | function setGovernance(address _governance) external {
26 | require(msg.sender == governance, "!governance");
27 | governance = _governance;
28 | }
29 |
30 | function setAuthorized(address _authorized) external {
31 | require(msg.sender == governance, "!governance");
32 | authorized[_authorized] = true;
33 | }
34 |
35 | function revokeAuthorized(address _authorized) external {
36 | require(msg.sender == governance, "!governance");
37 | authorized[_authorized] = false;
38 | }
39 |
40 | function read() external view returns (uint256 price, bool osm) {
41 | if (authorized[msg.sender] && OSM.bud(address(this)) == 1) {
42 | (bytes32 _val, bool _has) = OSM.peek();
43 | if (_has) return (uint256(_val), true);
44 | }
45 | return ((MEDIANIZER.price(symbol)).mul(1e12), false);
46 | }
47 |
48 | function foresight() external view returns (uint256 price, bool osm) {
49 | if (authorized[msg.sender] && OSM.bud(address(this)) == 1) {
50 | (bytes32 _val, bool _has) = OSM.peep();
51 | if (_has) return (uint256(_val), true);
52 | }
53 | return ((MEDIANIZER.price(symbol)).mul(1e12), false);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/common_health/test_common_health_check.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 |
4 |
5 | MAX_BPS = 10_000
6 |
7 |
8 | @pytest.fixture
9 | def common_health_check(gov, CommonHealthCheck):
10 | yield gov.deploy(CommonHealthCheck)
11 |
12 |
13 | def test_set_goverance(gov, rando, common_health_check):
14 | with brownie.reverts():
15 | common_health_check.setGovernance(rando, {"from": rando})
16 | common_health_check.setGovernance(rando, {"from": gov})
17 |
18 |
19 | def test_set_management(gov, rando, common_health_check):
20 | with brownie.reverts():
21 | common_health_check.setManagement(rando, {"from": rando})
22 | common_health_check.setManagement(rando, {"from": gov})
23 |
24 |
25 | def test_set_profit_limit_ratio(gov, rando, common_health_check):
26 | with brownie.reverts():
27 | common_health_check.setProfitLimitRatio(10, {"from": rando})
28 |
29 | common_health_check.setProfitLimitRatio(10, {"from": gov})
30 |
31 | with brownie.reverts():
32 | common_health_check.setProfitLimitRatio(MAX_BPS + 1, {"from": gov})
33 |
34 |
35 | def test_set_stop_loss_limit_ratio(gov, rando, common_health_check):
36 | with brownie.reverts():
37 | common_health_check.setlossLimitRatio(10, {"from": rando})
38 |
39 | common_health_check.setlossLimitRatio(10, {"from": gov})
40 |
41 | with brownie.reverts():
42 | common_health_check.setlossLimitRatio(MAX_BPS + 1, {"from": gov})
43 |
44 |
45 | def test_set_stop_loss_limit_ratio(gov, rando, strategy, common_health_check):
46 | with brownie.reverts():
47 | common_health_check.setStrategyLimits(strategy, 10, 10, {"from": rando})
48 |
49 | common_health_check.setStrategyLimits(strategy, 10, 10, {"from": gov})
50 |
51 | with brownie.reverts():
52 | common_health_check.setStrategyLimits(strategy, 10, MAX_BPS + 1, {"from": gov})
53 |
54 |
55 | def test_set_set_check(gov, rando, strategy, common_health_check):
56 | with brownie.reverts():
57 | common_health_check.setCheck(strategy, strategy, {"from": rando})
58 |
59 | common_health_check.setCheck(strategy, strategy, {"from": gov})
60 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/vault/test_permit.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | import pytest
3 |
4 | from eth_account import Account
5 |
6 | AMOUNT = 100
7 |
8 |
9 | @pytest.mark.parametrize("expires", [True, False])
10 | def test_permit(chain, rando, vault, sign_vault_permit, expires):
11 | owner = Account.create()
12 | deadline = chain[-1].timestamp + 3600 if expires else 0
13 | signature = sign_vault_permit(
14 | vault, owner, str(rando), allowance=AMOUNT, deadline=deadline
15 | )
16 | assert vault.allowance(owner.address, rando) == 0
17 | vault.permit(owner.address, rando, AMOUNT, deadline, signature, {"from": rando})
18 | assert vault.allowance(owner.address, rando) == AMOUNT
19 |
20 |
21 | def test_permit_wrong_signature(rando, vault, sign_vault_permit):
22 | owner = Account.create()
23 | # NOTE: Default `allowance` is unlimited, not `AMOUNT`
24 | signature = sign_vault_permit(vault, owner, str(rando))
25 | assert vault.allowance(owner.address, rando) == 0
26 | with brownie.reverts("dev: invalid signature"):
27 | # Fails because wrong `allowance` value provided
28 | vault.permit(owner.address, rando, AMOUNT, 0, signature, {"from": rando})
29 |
30 |
31 | def test_permit_expired(chain, rando, vault, sign_vault_permit):
32 | owner = Account.create()
33 | deadline = chain[-1].timestamp - 600
34 | # NOTE: Default `deadline` is 0, not a timestamp in the past
35 | signature = sign_vault_permit(vault, owner, str(rando), allowance=AMOUNT)
36 | assert vault.allowance(owner.address, rando) == 0
37 | with brownie.reverts("dev: permit expired"):
38 | # Fails because wrong `deadline` timestamp provided (it expired)
39 | vault.permit(owner.address, rando, AMOUNT, deadline, signature, {"from": rando})
40 |
41 |
42 | def test_permit_bad_owner(rando, vault, sign_vault_permit):
43 | owner = Account.create()
44 | signature = sign_vault_permit(vault, owner, str(rando), allowance=AMOUNT)
45 | assert vault.allowance(owner.address, owner.address) == 0
46 | with brownie.reverts("dev: invalid owner"):
47 | # Fails because wrong `owner` provided
48 | vault.permit(brownie.ZERO_ADDRESS, rando, AMOUNT, 0, signature, {"from": rando})
49 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/utils/test_oracles.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from brownie import (
4 | BTCOSMedianizer,
5 | ETHOSMedianizer,
6 | )
7 |
8 | ORACLES = [BTCOSMedianizer, ETHOSMedianizer]
9 |
10 |
11 | @pytest.mark.parametrize(
12 | "Oracle,osm,medianizer",
13 | [
14 | (
15 | BTCOSMedianizer,
16 | "0xf185d0682d50819263941e5f4EacC763CC5C6C42",
17 | "0x9B8Eb8b3d6e2e0Db36F41455185FEF7049a35CaE",
18 | ),
19 | (
20 | ETHOSMedianizer,
21 | "0x81FE72B5A8d1A857d176C3E7d5Bd2679A9B85763",
22 | "0x729D19f657BD0614b4985Cf1D82531c67569197B",
23 | ),
24 | ],
25 | )
26 | def test_hardcoded_config(a, Oracle, osm, medianizer):
27 | oracle = a[0].deploy(Oracle)
28 | assert oracle.OSM() == osm
29 | assert oracle.MEDIANIZER() == medianizer
30 |
31 |
32 | @pytest.mark.parametrize("Oracle", ORACLES)
33 | def test_governance(a, gov, Oracle):
34 | oracle = a[0].deploy(Oracle)
35 | assert oracle.governance() == a[0]
36 | oracle.setGovernance(gov)
37 | assert oracle.governance() == gov
38 |
39 |
40 | @pytest.mark.parametrize("Oracle", ORACLES)
41 | def test_whitelist(a, gov, Oracle):
42 | oracle = a[0].deploy(Oracle)
43 | assert not oracle.authorized(gov)
44 | oracle.setAuthorized(gov)
45 | assert oracle.authorized(gov)
46 | oracle.revokeAuthorized(gov)
47 | assert not oracle.authorized(gov)
48 |
49 |
50 | @pytest.mark.parametrize("Oracle", ORACLES)
51 | @pytest.mark.parametrize("func", ["read", "foresight"])
52 | def test_read(a, Oracle, func):
53 | oracle = a[0].deploy(Oracle)
54 | price, osm = getattr(oracle, func)()
55 | assert price > 0
56 | assert not osm
57 |
58 |
59 | @pytest.mark.xfail
60 | @pytest.mark.parametrize("func", ["read", "foresight"])
61 | def test_read_bud(a, interface, OSMedianizer, func):
62 | oracle = OSMedianizer.at("0x82c93333e4E295AA17a05B15092159597e823e8a")
63 | osm = interface.OracleSecurityModule(oracle.OSM())
64 | assert osm.bud(oracle), "kiss first"
65 | reader = a[0] # TODO: someone authorized
66 | assert oracle.authorized(reader)
67 | price, osm = getattr(oracle, func)({"from": reader})
68 | assert price > 0
69 | assert osm
70 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/registry/test_config.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 |
4 |
5 | def test_registry_deployment(gov, registry):
6 | assert registry.governance() == gov
7 | assert registry.numReleases() == 0
8 |
9 |
10 | def test_registry_setGovernance(gov, registry, rando):
11 | newGov = rando
12 | # No one can set governance but governance
13 | with brownie.reverts():
14 | registry.setGovernance(newGov, {"from": newGov})
15 | # Governance doesn't change until it's accepted
16 | registry.setGovernance(newGov, {"from": gov})
17 | assert registry.pendingGovernance() == newGov
18 |
19 | assert registry.governance() == gov
20 | # Only new governance can accept a change of governance
21 | with brownie.reverts():
22 | registry.acceptGovernance({"from": gov})
23 | # Governance doesn't change until it's accepted
24 | registry.acceptGovernance({"from": newGov})
25 | assert registry.governance() == newGov
26 | # No one can set governance but governance
27 | with brownie.reverts():
28 | registry.setGovernance(newGov, {"from": gov})
29 | # Only new governance can accept a change of governance
30 | with brownie.reverts():
31 | registry.acceptGovernance({"from": gov})
32 |
33 |
34 | def test_banksy(gov, registry, create_vault, rando):
35 | vault = create_vault()
36 | registry.newRelease(vault, {"from": gov})
37 | assert registry.tags(vault) == ""
38 |
39 | # Not just anyone can tag a Vault, only a Banksy can!
40 | with brownie.reverts():
41 | registry.tagVault(vault, "Anything I want!", {"from": rando})
42 |
43 | # Not just anyone can become a banksy either
44 | with brownie.reverts():
45 | registry.setBanksy(rando, {"from": rando})
46 |
47 | assert not registry.banksy(rando)
48 | registry.setBanksy(rando, {"from": gov})
49 | assert registry.banksy(rando)
50 |
51 | registry.tagVault(vault, "Anything I want!", {"from": rando})
52 | assert registry.tags(vault) == "Anything I want!"
53 |
54 | registry.setBanksy(rando, False, {"from": gov})
55 | with brownie.reverts():
56 | registry.tagVault(vault, "", {"from": rando})
57 |
58 | assert not registry.banksy(gov)
59 | registry.tagVault(vault, "", {"from": gov})
60 |
--------------------------------------------------------------------------------
/packages/core/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - develop
8 | pull_request:
9 | branches:
10 | - master
11 | - develop
12 |
13 | env:
14 | ETHERSCAN_TOKEN: 6776WKCX4XHV7C6NQBTIME6XKM2YNBK29V
15 | WEB3_INFURA_PROJECT_ID: 164b1027c8254bf69a309a9ae0b81342
16 |
17 | jobs:
18 |
19 | brownie:
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | type: [functional]
26 |
27 | steps:
28 | - name: Check out github repository
29 | uses: actions/checkout@v2
30 | with:
31 | fetch-depth: 1
32 |
33 | - name: Check ETHERSCAN_TOKEN
34 | run: |
35 | if [ -z "$ETHERSCAN_TOKEN" ] ; then
36 | echo "Missing ENV variable ETHERSCAN_TOKEN"
37 | exit 1
38 | fi
39 |
40 | - name: Check WEB3_INFURA_PROJECT_ID
41 | run: |
42 | if [ -z "$WEB3_INFURA_PROJECT_ID" ] ; then
43 | echo "Missing ENV variable WEB3_INFURA_PROJECT_ID"
44 | exit 1
45 | fi
46 |
47 | - name: Cache compiler installations
48 | uses: actions/cache@v2
49 | with:
50 | path: |
51 | ~/.solcx
52 | ~/.vvm
53 | key: ${{ runner.os }}-compiler-cache
54 |
55 | - name: Setup node.js
56 | uses: actions/setup-node@v1
57 | with:
58 | node-version: '12.x'
59 |
60 | - name: Install ganache
61 | run: npm install -g ganache-cli@6.11
62 |
63 | - name: Set up python 3.8
64 | uses: actions/setup-python@v2
65 | with:
66 | python-version: 3.8
67 |
68 | - name: Set pip cache directory path
69 | id: pip-cache-dir-path
70 | run: |
71 | echo "::set-output name=dir::$(pip cache dir)"
72 |
73 | - name: Restore pip cache
74 | uses: actions/cache@v2
75 | id: pip-cache
76 | with:
77 | path: |
78 | ${{ steps.pip-cache-dir-path.outputs.dir }}
79 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
80 | restore-keys: |
81 | ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
82 | ${{ runner.os }}-pip-
83 |
84 | - name: Install python dependencies
85 | run: pip install -r requirements-dev.txt
86 |
87 | - name: Run tests
88 | run: brownie test tests/${{ matrix.type }}/ -s --coverage
89 |
--------------------------------------------------------------------------------
/packages/protocol/hh.ts:
--------------------------------------------------------------------------------
1 | import '@typechain/hardhat';
2 | import 'dotenv/config';
3 | import 'hardhat-deploy';
4 | import 'hardhat-deploy-ethers';
5 | import 'hardhat-contract-sizer';
6 | import {
7 | utils
8 | } from 'ethers';
9 | import {
10 | HardhatUserConfig
11 | } from 'hardhat/types';
12 |
13 | function node(networkName: string) {
14 | const fallback = 'http://localhost:8545';
15 | const uppercase = networkName.toUpperCase();
16 | const uri = process.env[`ETHEREUM_NODE_${uppercase}`] || process.env.ETHEREUM_NODE || fallback;
17 | return uri.replace('{{NETWORK}}', networkName);
18 | }
19 |
20 | function accounts(networkName: string) {
21 | const uppercase = networkName.toUpperCase();
22 | const accounts =
23 | process.env[`ETHEREUM_ACCOUNTS_${uppercase}`] || process.env.ETHEREUM_ACCOUNTS || '';
24 | return accounts
25 | .split(',')
26 | .map((account) => account.trim())
27 | .filter(Boolean);
28 | }
29 |
30 | const mnemonic = 'test test test test test test test test test test test junk';
31 |
32 | const config: HardhatUserConfig = {
33 | solidity: {
34 | version: '0.5.17',
35 | settings: {
36 | metadata: {
37 | bytecodeHash: "none",
38 | },
39 | optimizer: {
40 | enabled: true,
41 | runs: 200,
42 | details: {
43 | yul: false,
44 | },
45 | },
46 | },
47 | },
48 | networks: {
49 | hardhat: {
50 | accounts: {
51 | mnemonic,
52 | count: 10,
53 | accountsBalance: utils.parseUnits('1', 36).toString(),
54 | },
55 | forking: {
56 | url: node('mainnet'),
57 | blockNumber: 11621050, // Jan 9, 2021
58 | },
59 | },
60 | mainnet: {
61 | url: node('mainnet'),
62 | accounts: accounts('mainnet'),
63 | },
64 | kovan: {
65 | url: node('kovan'),
66 | accounts: accounts('kovan'),
67 | },
68 | },
69 | namedAccounts: {
70 | deployer: 0,
71 | },
72 | contractSizer: {
73 | disambiguatePaths: false,
74 | },
75 | typechain: {
76 | outDir: 'src/types',
77 | target: 'ethers-v5',
78 | },
79 | };
80 |
81 | export default config;
82 |
--------------------------------------------------------------------------------
/packages/core/tests/functional/strategies/test_config.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 |
4 |
5 | from brownie import (
6 | StrategyCreamYFI,
7 | StrategyCurveBTCVoterProxy,
8 | StrategyCurveBUSDVoterProxy,
9 | StrategyCurveEURVoterProxy,
10 | StrategyCurveYVoterProxy,
11 | StrategyCurve3CrvVoterProxy,
12 | StrategyDAICurve,
13 | StrategyDForceUSDC,
14 | StrategyDForceUSDT,
15 | StrategyMKRVaultDAIDelegate,
16 | StrategyTUSDCurve,
17 | StrategyVaultUSDC,
18 | )
19 |
20 | STRATEGIES = [
21 | StrategyCreamYFI,
22 | StrategyCurveBTCVoterProxy,
23 | StrategyCurveBUSDVoterProxy,
24 | StrategyCurveEURVoterProxy,
25 | StrategyCurveYVoterProxy,
26 | StrategyCurve3CrvVoterProxy,
27 | StrategyDAICurve,
28 | StrategyDForceUSDC,
29 | StrategyDForceUSDT,
30 | StrategyMKRVaultDAIDelegate,
31 | StrategyTUSDCurve,
32 | StrategyVaultUSDC,
33 | ]
34 |
35 |
36 | @pytest.mark.parametrize("Strategy", STRATEGIES)
37 | def test_strategy_deployment(gov, controller, Strategy):
38 | strategy = gov.deploy(Strategy, controller)
39 | # Double check all the deployment variable values
40 | assert strategy.governance() == gov
41 | assert strategy.controller() == controller
42 | assert strategy.getName() == Strategy._name
43 |
44 |
45 | @pytest.mark.parametrize(
46 | "getter,setter,val",
47 | [
48 | ("governance", "setGovernance", None),
49 | ("controller", "setController", None),
50 | ("strategist", "setStrategist", None),
51 | ("fee", "setFee", 100),
52 | ("withdrawalFee", "setWithdrawalFee", 100),
53 | ("performanceFee", "setPerformanceFee", 1000),
54 | ],
55 | )
56 | @pytest.mark.parametrize("Strategy", STRATEGIES)
57 | def test_strategy_setParams(accounts, gov, controller, getter, setter, val, Strategy):
58 | if not val:
59 | # Can't access fixtures, so use None to mean an address literal
60 | val = accounts[1]
61 |
62 | strategy = gov.deploy(Strategy, controller)
63 |
64 | if not hasattr(strategy, getter):
65 | return # Some combinations aren't valid
66 |
67 | # Only governance can set this param
68 | with brownie.reverts():
69 | getattr(strategy, setter)(val, {"from": accounts[1]})
70 | getattr(strategy, setter)(val, {"from": gov})
71 | assert getattr(strategy, getter)() == val
72 |
73 | # When changing governance contract, make sure previous no longer has access
74 | if getter == "governance":
75 | with brownie.reverts("!governance"):
76 | getattr(strategy, setter)(val, {"from": gov})
77 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/strategy/test_clone.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 | from brownie import ZERO_ADDRESS
4 |
5 |
6 | @pytest.fixture
7 | def other_token(gov, Token):
8 | yield gov.deploy(Token, 18)
9 |
10 |
11 | @pytest.fixture
12 | def other_vault(gov, Vault, other_token):
13 | vault = gov.deploy(Vault)
14 | vault.initialize(other_token, gov, gov, "", "", gov, gov)
15 | yield vault
16 |
17 |
18 | @pytest.fixture
19 | def strategy(gov, strategist, keeper, rewards, vault, TestStrategy):
20 | strategy = strategist.deploy(TestStrategy, vault)
21 |
22 | strategy.setKeeper(keeper, {"from": strategist})
23 | vault.addStrategy(
24 | strategy,
25 | 4_000, # 40% of Vault
26 | 0, # Minimum debt increase per harvest
27 | 2 ** 256 - 1, # maximum debt increase per harvest
28 | 1000, # 10% performance fee for Strategist
29 | {"from": gov},
30 | )
31 | yield strategy
32 |
33 |
34 | def test_clone(
35 | Token,
36 | token,
37 | other_token,
38 | strategy,
39 | vault,
40 | other_vault,
41 | gov,
42 | strategist,
43 | guardian,
44 | TestStrategy,
45 | rando,
46 | ):
47 |
48 | tx = strategy.clone(other_vault, {"from": rando})
49 | address = tx.events["Cloned"]["clone"]
50 | new_strategy = TestStrategy.at(address)
51 |
52 | assert new_strategy.strategist() == rando
53 | assert new_strategy.rewards() == rando
54 | assert new_strategy.keeper() == rando
55 | assert Token.at(new_strategy.want()).name() == "yearn.finance test token"
56 |
57 | # Test the other clone method with all params
58 | tx = strategy.clone(other_vault, gov, guardian, strategist, {"from": rando})
59 | address = tx.events["Cloned"]["clone"]
60 | new_strategy = TestStrategy.at(address)
61 |
62 | assert new_strategy.isOriginal() == False
63 | with brownie.reverts():
64 | new_strategy.clone(other_vault, {"from": rando})
65 |
66 | assert new_strategy.strategist() == gov
67 | assert new_strategy.rewards() == guardian
68 | assert new_strategy.keeper() == strategist
69 |
70 | # test state variables have been initialized with default (hardcoded) values
71 | assert new_strategy.minReportDelay() == 0
72 | assert new_strategy.maxReportDelay() == 86400
73 | assert new_strategy.profitFactor() == 100
74 | assert new_strategy.debtThreshold() == 0
75 |
76 |
77 | def test_double_initialize(TestStrategy, vault, other_vault, gov):
78 | strategy = gov.deploy(TestStrategy, vault)
79 |
80 | # Sholdn't be able to initialize twice
81 | with brownie.reverts():
82 | strategy.initialize(other_vault, gov, gov, gov)
83 |
--------------------------------------------------------------------------------
/packages/core/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to Yearn
2 |
3 | This is a living guide that defines a development process for yearn.finance protocol.
4 |
5 | We want to make contributing to this project as easy and transparent as possible, whether it's:
6 |
7 | - Bug report.
8 | - Current state of the code discussion.
9 | - Fix submission.
10 | - Feature proposal.
11 | - Maintainer application.
12 |
13 | ### We Develop with Github
14 |
15 | We use Github to host code, to track issues and feature requests, as well as accept pull requests.
16 |
17 | Pull requests are the best way to propose changes to the codebase (we use Github [flow](https://guides.github.com/introduction/flow/index.html)). We welcome your pull requests:
18 |
19 | 1. Fork the repo and create your branch from `master`.
20 | 2. If you've added code that should be tested, add tests or ensure it doesn't break current tests.
21 | 3. If you've changed something impacting current docs, please update the documentation.
22 | 4. Ensure the test suite passes (if applicable).
23 | 5. Ensure your code follows formatting rules.
24 | 6. Issue that pull request!
25 |
26 | ### Release Process
27 |
28 | The `master` branch has the up to date changes of the codebase with working code. Releases should be tracked via git tags to link a specific commit to a deployment for history and documentation purposes.
29 |
30 | ### Github Actions
31 |
32 | Repository uses GH actions to setup CI for [test](https://github.com/iearn-finance/yearn-protocol/blob/master/.github/workflows/test.yaml) harness. Be sure to setup any secret or env variable needed for your GH actions to work.
33 |
34 | ### Bug Reports
35 |
36 | We use GitHub issues to track public bugs. Report a bug by opening a new [issue](https://github.com/iearn-finance/yearn-protocol/issues/new); it's that easy!
37 |
38 | Before adding a new issue, please check that your issue is not already identified or hasn't been handled by searching the active/closed issues.
39 |
40 | **Great Bug Reports** tend to have:
41 |
42 | - A clear and concise summary of an issue.
43 | - Steps to reproduce a bug.
44 | - What is expected to happen.
45 | - What actually happens.
46 | - Notes.
47 |
48 | ### Consistent Coding Style
49 |
50 | - Setup [prettier](https://github.com/prettier/prettier) and [solium](https://github.com/duaraghav8/Ethlint) linters into your local coding environment.
51 | - Check that your changes adhere to the linting rules before pushing by running `yarn lint:check` and `yarn lint:fix`.
52 | - Merging may be blocked if a PR does not follow the coding style guidelines.
53 |
54 | ### License
55 |
56 | By contributing, you agree that your contributions will be licensed under its [APGL License](https://choosealicense.com/licenses/agpl-3.0/).
57 |
58 | ### References
59 |
60 | TBD
61 |
--------------------------------------------------------------------------------
/packages/core/.github/workflows/lint.yaml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - develop
8 | pull_request:
9 | branches:
10 | - master
11 | - develop
12 |
13 | jobs:
14 |
15 | solidity:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - name: Check out github repository
20 | uses: actions/checkout@v2
21 | with:
22 | fetch-depth: 1
23 |
24 | - name: Setup node.js
25 | uses: actions/setup-node@v1
26 | with:
27 | node-version: '12.x'
28 |
29 | - name: Set yarn cache directory path
30 | id: yarn-cache-dir-path
31 | run: echo "::set-output name=dir::$(yarn cache dir)"
32 |
33 | - name: Restore yarn cache
34 | uses: actions/cache@v2
35 | id: yarn-cache
36 | with:
37 | path: |
38 | ${{ steps.yarn-cache-dir-path.outputs.dir }}
39 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
40 | restore-keys: |
41 | ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
42 | ${{ runner.os }}-yarn-
43 |
44 | - name: Install node.js dependencies
45 | run: yarn --frozen-lockfile
46 |
47 | - name: Run linter on *.sol and *.json
48 | run: yarn lint:check
49 |
50 | commits:
51 | runs-on: ubuntu-latest
52 |
53 | steps:
54 | - name: Check out github repository
55 | uses: actions/checkout@v2
56 | with:
57 | fetch-depth: 0
58 |
59 | - name: Run commitlint
60 | uses: wagoid/commitlint-github-action@v2
61 |
62 | brownie:
63 | runs-on: ubuntu-latest
64 |
65 | steps:
66 | - name: Check out github repository
67 | uses: actions/checkout@v2
68 | with:
69 | fetch-depth: 1
70 |
71 | - name: Set up python 3.8
72 | uses: actions/setup-python@v2
73 | with:
74 | python-version: 3.8
75 |
76 | - name: Set pip cache directory path
77 | id: pip-cache-dir-path
78 | run: |
79 | echo "::set-output name=dir::$(pip cache dir)"
80 |
81 | - name: Restore pip cache
82 | uses: actions/cache@v2
83 | id: pip-cache
84 | with:
85 | path: |
86 | ${{ steps.pip-cache-dir-path.outputs.dir }}
87 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
88 | restore-keys: |
89 | ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
90 | ${{ runner.os }}-pip-
91 |
92 | - name: Install python dependencies
93 | run: pip install -r requirements-dev.txt
94 |
95 | - name: Run black
96 | run: black --check --include "(tests|scripts)" .
97 |
98 | # TODO: Add Slither Static Analyzer
99 |
--------------------------------------------------------------------------------
/packages/vaults/.github/workflows/lint.yaml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | pull_request:
9 |
10 | jobs:
11 |
12 | solidity:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Check out github repository
17 | uses: actions/checkout@v2
18 | with:
19 | fetch-depth: 1
20 |
21 | - name: Setup node.js
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: '12.x'
25 |
26 | - name: Set yarn cache directory path
27 | id: yarn-cache-dir-path
28 | run: echo "::set-output name=dir::$(yarn cache dir)"
29 |
30 | - name: Restore yarn cache
31 | uses: actions/cache@v2
32 | id: yarn-cache
33 | with:
34 | path: |
35 | ${{ steps.yarn-cache-dir-path.outputs.dir }}
36 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
37 | restore-keys: |
38 | ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
39 | ${{ runner.os }}-yarn-
40 |
41 | - name: Install node.js dependencies
42 | run: yarn --frozen-lockfile
43 |
44 | - name: Run formater check on *.sol and *.json
45 | run: yarn format:check
46 |
47 | - name: run linter check on *.sol file
48 | run: yarn hint
49 |
50 | commits:
51 | runs-on: ubuntu-latest
52 |
53 | steps:
54 | - name: Check out github repository
55 | uses: actions/checkout@v2
56 | with:
57 | fetch-depth: 0
58 |
59 | - name: Run commitlint
60 | uses: wagoid/commitlint-github-action@v2
61 |
62 | brownie:
63 | runs-on: ubuntu-latest
64 |
65 | steps:
66 | - name: Check out github repository
67 | uses: actions/checkout@v2
68 | with:
69 | fetch-depth: 1
70 |
71 | - name: Set up python 3.8
72 | uses: actions/setup-python@v2
73 | with:
74 | python-version: 3.8
75 |
76 | - name: Set pip cache directory path
77 | id: pip-cache-dir-path
78 | run: |
79 | echo "::set-output name=dir::$(pip cache dir)"
80 |
81 | - name: Restore pip cache
82 | uses: actions/cache@v2
83 | id: pip-cache
84 | with:
85 | path: |
86 | ${{ steps.pip-cache-dir-path.outputs.dir }}
87 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
88 | restore-keys: |
89 | ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
90 | ${{ runner.os }}-pip-
91 |
92 | - name: Install python dependencies
93 | run: pip install -r requirements-dev.txt
94 |
95 | - name: Run black
96 | run: black --check --include "(tests|scripts)" .
97 |
98 | # TODO: Add Slither Static Analyzer
99 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # Yearn Protocol
2 |
3 | [](https://github.com/iearn-finance/yearn-protocol/blob/master/LICENSE)
4 | 
5 | 
6 |
7 | Yearn Protocol is a set of Ethereum Smart Contracts focused on creating a simple way to generate high risk-adjusted returns for depositors of various assets via best-in-class lending protocols, liquidity pools, and community-made yield farming strategies on Ethereum.
8 |
9 | Before getting started with this repo, please read:
10 |
11 | - [Andre's Overview Blog Post](https://medium.com/iearn/yearn-finance-v2-af2c6a6a3613), describing how yearn.finance works.
12 | - The [Delegated Vaults Blog Post](https://medium.com/iearn/delegated-vaults-explained-fa81f1c3fce2), explaining how the delegated vaults work.
13 | - [yETH Vault Explained](https://medium.com/iearn/yeth-vault-explained-c29d6b93a371), describing how the yETH vault works.
14 |
15 | ### Requirements
16 |
17 | To run the project you need:
18 |
19 | - Python 3.8 local development environment and Node.js 10.x development environment for Ganache.
20 | - Brownie local environment setup. See instructions: [ETH Brownie](https://github.com/eth-brownie/brownie).
21 | - Local env variables for [Etherscan API](https://etherscan.io/apis) and [Infura](https://infura.io/) (`ETHERSCAN_TOKEN`, `WEB3_INFURA_PROJECT_ID`).
22 | - Local Ganache environment installed with `npm install -g ganache-cli@6.11`.
23 |
24 | ### Installation
25 |
26 | To run the yearn protocol, pull the repository from GitHub and install its dependencies. You will need [yarn](https://yarnpkg.com/lang/en/docs/install/) installed.
27 |
28 | ```bash
29 | git clone https://github.com/iearn-finance/yearn-protocol
30 | cd yearn-protocol
31 | yarn install --lock-file
32 | ```
33 |
34 | Compile the Smart Contracts:
35 |
36 | ```bash
37 | brownie compile
38 | ```
39 |
40 | ### Tests
41 |
42 | Run tests:
43 |
44 | ```bash
45 | brownie test -s
46 | ```
47 |
48 | Run tests with coverage:
49 |
50 | ```bash
51 | brownie test -s --coverage
52 | ```
53 |
54 | ### Formatting
55 |
56 | Check linter rules for `*.json` and `*.sol` files:
57 |
58 | ```bash
59 | yarn lint:check
60 | ```
61 |
62 | Fix linter errors for `*.json` and `*.sol` files:
63 |
64 | ```bash
65 | yarn lint:fix
66 | ```
67 |
68 | Check linter rules for `*.py` files:
69 |
70 | ```bash
71 | black . --check --config black-config.toml
72 | ```
73 |
74 | Fix linter errors for `*.py` files:
75 |
76 | ```bash
77 | black . --config black-config.toml
78 | ```
79 |
80 | ### Security
81 |
82 | For security concerns, please visit [Bug Bounty](https://github.com/iearn-finance/yearn-protocol/blob/develop/SECURITY.md).
83 |
84 | ### Documentation
85 |
86 | You can read more about yearn finance on our documentation [webpage](https://docs.yearn.finance).
87 |
88 | ### Discussion
89 |
90 | For questions not covered in the docs, please visit [our Discord server](http://discord.yearn.finance).
91 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/strategy/test_migration.py:
--------------------------------------------------------------------------------
1 | import brownie
2 |
3 | from brownie import ZERO_ADDRESS
4 |
5 |
6 | def test_good_migration(
7 | token, strategy, vault, gov, strategist, guardian, TestStrategy, rando, chain
8 | ):
9 | # Call this once to seed the strategy with debt
10 | strategy.harvest({"from": strategist})
11 |
12 | strategy_debt = vault.strategies(strategy).dict()["totalDebt"]
13 | assert strategy_debt == token.balanceOf(strategy)
14 |
15 | new_strategy = strategist.deploy(TestStrategy, vault)
16 | assert vault.strategies(new_strategy).dict()["totalDebt"] == 0
17 | assert token.balanceOf(new_strategy) == 0
18 |
19 | # Only Governance can migrate
20 | with brownie.reverts():
21 | vault.migrateStrategy(strategy, new_strategy, {"from": rando})
22 | with brownie.reverts():
23 | vault.migrateStrategy(strategy, new_strategy, {"from": strategist})
24 | with brownie.reverts():
25 | vault.migrateStrategy(strategy, new_strategy, {"from": guardian})
26 |
27 | vault.migrateStrategy(strategy, new_strategy, {"from": gov})
28 | assert (
29 | vault.strategies(strategy).dict()["totalDebt"] == token.balanceOf(strategy) == 0
30 | )
31 | assert (
32 | vault.strategies(new_strategy).dict()["totalDebt"]
33 | == token.balanceOf(new_strategy)
34 | == strategy_debt
35 | )
36 |
37 | with brownie.reverts():
38 | new_strategy.migrate(strategy, {"from": gov})
39 |
40 |
41 | def test_bad_migration(
42 | token, vault, strategy, gov, strategist, TestStrategy, Vault, rando
43 | ):
44 | different_vault = gov.deploy(Vault)
45 | different_vault.initialize(
46 | token, gov, gov, token.symbol() + " yVault", "yv" + token.symbol(), gov
47 | )
48 | different_vault.setDepositLimit(2 ** 256 - 1, {"from": gov})
49 | new_strategy = strategist.deploy(TestStrategy, different_vault)
50 |
51 | # Can't migrate to a strategy with a different vault
52 | with brownie.reverts():
53 | vault.migrateStrategy(strategy, new_strategy, {"from": gov})
54 |
55 | new_strategy = strategist.deploy(TestStrategy, vault)
56 |
57 | # Can't migrate if you're not the Vault or governance
58 | with brownie.reverts():
59 | strategy.migrate(new_strategy, {"from": rando})
60 |
61 | # Can't migrate if new strategy is 0x0
62 | with brownie.reverts():
63 | vault.migrateStrategy(strategy, ZERO_ADDRESS, {"from": gov})
64 |
65 |
66 | def test_migrated_strategy_can_call_harvest(
67 | token, strategy, vault, gov, TestStrategy, common_health_check
68 | ):
69 |
70 | new_strategy = gov.deploy(TestStrategy, vault)
71 | vault.migrateStrategy(strategy, new_strategy, {"from": gov})
72 |
73 | # send profit to the old strategy
74 | token.transfer(strategy, 10 ** token.decimals(), {"from": gov})
75 |
76 | assert vault.strategies(strategy).dict()["totalGain"] == 0
77 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
78 | strategy.harvest({"from": gov})
79 | assert vault.strategies(strategy).dict()["totalGain"] == 10 ** token.decimals()
80 |
81 | # But after migrated it cannot be added back
82 | vault.updateStrategyDebtRatio(new_strategy, 5_000, {"from": gov})
83 | with brownie.reverts():
84 | vault.addStrategy(strategy, 5_000, 0, 1000, 0, {"from": gov})
85 |
--------------------------------------------------------------------------------
/packages/protocol/interfaces/maker/Maker.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface GemLike {
4 | function approve(address, uint) external;
5 | function transfer(address, uint) external;
6 | function transferFrom(address, address, uint) external;
7 | function deposit() external payable;
8 | function withdraw(uint) external;
9 | }
10 |
11 | interface ManagerLike {
12 | function cdpCan(address, uint, address) external view returns (uint);
13 | function ilks(uint) external view returns (bytes32);
14 | function owns(uint) external view returns (address);
15 | function urns(uint) external view returns (address);
16 | function vat() external view returns (address);
17 | function open(bytes32, address) external returns (uint);
18 | function give(uint, address) external;
19 | function cdpAllow(uint, address, uint) external;
20 | function urnAllow(address, uint) external;
21 | function frob(uint, int, int) external;
22 | function flux(uint, address, uint) external;
23 | function move(uint, address, uint) external;
24 | function exit(address, uint, address, uint) external;
25 | function quit(uint, address) external;
26 | function enter(address, uint) external;
27 | function shift(uint, uint) external;
28 | }
29 |
30 | interface VatLike {
31 | function can(address, address) external view returns (uint);
32 | function ilks(bytes32) external view returns (uint, uint, uint, uint, uint);
33 | function dai(address) external view returns (uint);
34 | function urns(bytes32, address) external view returns (uint, uint);
35 | function frob(bytes32, address, address, address, int, int) external;
36 | function hope(address) external;
37 | function move(address, address, uint) external;
38 | }
39 |
40 | interface GemJoinLike {
41 | function dec() external returns (uint);
42 | function gem() external returns (GemLike);
43 | function join(address, uint) external payable;
44 | function exit(address, uint) external;
45 | }
46 |
47 | interface GNTJoinLike {
48 | function bags(address) external view returns (address);
49 | function make(address) external returns (address);
50 | }
51 |
52 | interface DaiJoinLike {
53 | function vat() external returns (VatLike);
54 | function dai() external returns (GemLike);
55 | function join(address, uint) external payable;
56 | function exit(address, uint) external;
57 | }
58 |
59 | interface HopeLike {
60 | function hope(address) external;
61 | function nope(address) external;
62 | }
63 |
64 | interface EndLike {
65 | function fix(bytes32) external view returns (uint);
66 | function cash(bytes32, uint) external;
67 | function free(bytes32) external;
68 | function pack(uint) external;
69 | function skim(bytes32, address) external;
70 | }
71 |
72 | interface JugLike {
73 | function drip(bytes32) external returns (uint);
74 | }
75 |
76 | interface PotLike {
77 | function pie(address) external view returns (uint);
78 | function drip() external returns (uint);
79 | function join(uint) external;
80 | function exit(uint) external;
81 | }
82 |
83 | interface SpotLike {
84 | function ilks(bytes32) external view returns (address, uint);
85 | }
86 |
87 | interface OSMedianizer {
88 | function read() external view returns (uint, bool);
89 | function foresight() external view returns (uint, bool);
90 | }
91 |
--------------------------------------------------------------------------------
/packages/vaults/contracts/test/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.6.12;
3 |
4 | import "@openzeppelin/contracts/math/SafeMath.sol";
5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6 |
7 | contract Token is ERC20 {
8 | mapping(address => bool) public _blocked;
9 |
10 | constructor(uint8 _decimals) public ERC20("yearn.finance test token", "TEST") {
11 | _setupDecimals(_decimals);
12 | _mint(msg.sender, 30000 * 10**uint256(_decimals));
13 | }
14 |
15 | function _setBlocked(address user, bool value) public virtual {
16 | _blocked[user] = value;
17 | }
18 |
19 | function _beforeTokenTransfer(
20 | address from,
21 | address to,
22 | uint256 amount
23 | ) internal virtual override(ERC20) {
24 | require(!_blocked[to], "Token transfer refused. Receiver is on blacklist");
25 | super._beforeTokenTransfer(from, to, amount);
26 | }
27 | }
28 |
29 | contract TokenNoReturn {
30 | using SafeMath for uint256;
31 |
32 | string public name;
33 | string public symbol;
34 | uint8 public decimals;
35 |
36 | uint256 public totalSupply;
37 | mapping(address => uint256) public balanceOf;
38 | mapping(address => mapping(address => uint256)) public allowance;
39 |
40 | event Transfer(address indexed from, address indexed to, uint256 value);
41 | event Approval(address indexed owner, address indexed spender, uint256 value);
42 |
43 | mapping(address => bool) public _blocked;
44 |
45 | constructor(uint8 _decimals) public {
46 | name = "yearn.finance test token";
47 | symbol = "TEST";
48 | decimals = _decimals;
49 | balanceOf[msg.sender] = 30000 * 10**uint256(_decimals);
50 | totalSupply = 30000 * 10**uint256(_decimals);
51 | }
52 |
53 | function _setBlocked(address user, bool value) public virtual {
54 | _blocked[user] = value;
55 | }
56 |
57 | function transfer(address receiver, uint256 amount) external {
58 | require(!_blocked[receiver], "Token transfer refused. Receiver is on blacklist");
59 | balanceOf[msg.sender] = balanceOf[msg.sender].sub(amount);
60 | balanceOf[receiver] = balanceOf[receiver].add(amount);
61 | emit Transfer(msg.sender, receiver, amount);
62 | }
63 |
64 | function approve(address spender, uint256 amount) external {
65 | allowance[msg.sender][spender] = amount;
66 | emit Approval(msg.sender, spender, amount);
67 | }
68 |
69 | function transferFrom(
70 | address sender,
71 | address receiver,
72 | uint256 amount
73 | ) external {
74 | require(!_blocked[receiver], "Token transfer refused. Receiver is on blacklist");
75 | allowance[sender][msg.sender] = allowance[sender][msg.sender].sub(amount);
76 | balanceOf[sender] = balanceOf[sender].sub(amount);
77 | balanceOf[receiver] = balanceOf[receiver].add(amount);
78 | emit Transfer(sender, receiver, amount);
79 | }
80 | }
81 |
82 | contract TokenFalseReturn is Token {
83 | constructor(uint8 _decimals) public Token(_decimals) {}
84 |
85 | function transfer(address receiver, uint256 amount) public virtual override returns (bool) {
86 | return false;
87 | }
88 |
89 | function transferFrom(
90 | address sender,
91 | address receiver,
92 | uint256 amount
93 | ) public virtual override returns (bool) {
94 | return false;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/packages/protocol/truffle-config.js:
--------------------------------------------------------------------------------
1 | // Truffle Configuration
2 | require("dotenv").config();
3 |
4 | const Web3 = require("web3");
5 | // @openzeppelinV2/=$(pwd)/node_modules/@openzeppelin/ contracts/*.sol
6 | const web3 = new Web3();
7 | const HDWalletProvider = require("@truffle/hdwallet-provider");
8 |
9 | // defaults
10 | const DEFAULT_ADDRESS_COUNT = 10;
11 | const DEFAULT_GAS_WEI = "4600000";
12 | const DEFAULT_ADDRESS_INDEX = "0";
13 | const DEFAULT_GAS_GWEI_PRICE = "20";
14 |
15 | const mnemonic = process.env.MNEMONIC;
16 | const infuraKey = process.env.INFURA_KEY;
17 | const etherscanApiKey = process.env.ETHERSCAN_API_KEY;
18 | const addressCount = process.env.ADDRESS_COUNT || DEFAULT_ADDRESS_COUNT;
19 | const gas = process.env.GAS_WEI || DEFAULT_GAS_WEI;
20 | const gasPrice = process.env.GAS_PRICE_GWEI || DEFAULT_GAS_GWEI_PRICE;
21 | const defaultAddressIndex = process.env.DEFAULT_ADDRESS_INDEX || DEFAULT_ADDRESS_INDEX;
22 |
23 | module.exports = {
24 | web3: Web3,
25 | mocha: {
26 | enableTimeouts: false,
27 | },
28 | compilers: {
29 | solc: {
30 | version: "0.5.17",
31 | settings: {
32 | metadata: {
33 | bytecodeHash: "none",
34 | },
35 | optimizer: {
36 | enabled: true,
37 | runs: 200,
38 | },
39 | },
40 | },
41 | },
42 | api_keys: {
43 | etherscan: etherscanApiKey,
44 | },
45 | plugins: ["truffle-plugin-verify", "@chainsafe/truffle-plugin-abigen"],
46 | networks: {
47 | geth: {
48 | host: "localhost",
49 | port: 8045,
50 | network_id: "*",
51 | },
52 | ganache: {
53 | host: "127.0.0.1",
54 | port: 8545,
55 | network_id: 5777,
56 | function () {
57 | return new HDWalletProvider(
58 | mnemonic,
59 | `http://localhost:8545`,
60 | defaultAddressIndex,
61 | addressCount
62 | );
63 | },
64 | confirmations: 0,
65 | timeoutBlocks: 50,
66 | skipDryRun: true,
67 | },
68 | coverage: {
69 | host: "127.0.0.1",
70 | network_id: "*",
71 | port: 8555, // <-- If you change this, also set the port option in .solcover.js.
72 | gas: 0xfffffffffff, // <-- Use this high gas value
73 | gasPrice: 0x01, // <-- Use this low gas price
74 | },
75 | infuraRinkeby: {
76 | provider: function () {
77 | return new HDWalletProvider(
78 | mnemonic,
79 | `https://rinkeby.infura.io/v3/${infuraKey}`,
80 | defaultAddressIndex,
81 | addressCount
82 | );
83 | },
84 | gas: gas,
85 | gasPrice: web3.utils.toWei(gasPrice, "gwei"),
86 | network_id: "4",
87 | skipDryRun: true,
88 | },
89 | infuraMainnet: {
90 | provider: function () {
91 | return new HDWalletProvider(
92 | mnemonic,
93 | `https://mainnet.infura.io/v3/${infuraKey}`,
94 | defaultAddressIndex,
95 | addressCount
96 | );
97 | },
98 | gas: gas,
99 | gasPrice: web3.utils.toWei(gasPrice, "gwei"),
100 | network_id: "1",
101 | skipDryRun: false,
102 | },
103 | },
104 | };
105 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/strategy/test_startup.py:
--------------------------------------------------------------------------------
1 | DAY = 86400 # seconds
2 |
3 |
4 | def test_startup(token, gov, vault, strategy, keeper, chain):
5 | debt_per_harvest = (
6 | (vault.totalAssets() - vault.totalDebt()) * (vault.debtRatio() / 10_000)
7 | ) // 10 # 10 harvests, or about 8 loop iterations
8 | vault.updateStrategyMaxDebtPerHarvest(strategy, debt_per_harvest, {"from": gov})
9 | expectedReturn = lambda: vault.expectedReturn(strategy)
10 |
11 | # Never reported yet (no data points)
12 | # NOTE: done for coverage
13 | assert expectedReturn() == 0
14 |
15 | # Check accounting is maintained everywhere
16 | assert token.balanceOf(vault) > 0
17 | assert vault.totalAssets() == token.balanceOf(vault)
18 | assert (
19 | vault.totalDebt()
20 | == vault.strategies(strategy).dict()["totalDebt"]
21 | == strategy.estimatedTotalAssets()
22 | == token.balanceOf(strategy)
23 | == 0
24 | )
25 |
26 | # Take on debt
27 | chain.mine(timestamp=chain.time() + DAY)
28 | assert vault.expectedReturn(strategy) == 0
29 | chain.sleep(1)
30 | strategy.harvest({"from": keeper})
31 |
32 | # Check balance is increasing
33 | assert token.balanceOf(strategy) > 0
34 | balance = token.balanceOf(strategy)
35 |
36 | # Check accounting is maintained everywhere
37 | assert vault.totalAssets() == token.balanceOf(vault) + balance
38 | assert (
39 | vault.totalDebt()
40 | == vault.strategies(strategy).dict()["totalDebt"]
41 | == strategy.estimatedTotalAssets()
42 | == balance
43 | )
44 |
45 | # We have 1 data point for E[R] calc w/ no profits, so E[R] = 0
46 | chain.mine(timestamp=chain.time() + DAY)
47 | assert expectedReturn() == 0
48 |
49 | profit = token.balanceOf(strategy) // 101
50 | assert profit > 0
51 | token.transfer(strategy, profit, {"from": gov})
52 | chain.sleep(1)
53 | strategy.harvest({"from": keeper})
54 | assert vault.strategies(strategy).dict()["totalGain"] == profit
55 |
56 | # Check balance is increasing
57 | assert token.balanceOf(strategy) > balance
58 | balance = token.balanceOf(strategy)
59 |
60 | # Check accounting is maintained everywhere
61 | assert vault.totalAssets() == token.balanceOf(vault) + balance
62 | assert (
63 | vault.totalDebt()
64 | == vault.strategies(strategy).dict()["totalDebt"]
65 | == strategy.estimatedTotalAssets()
66 | == balance
67 | )
68 |
69 | # Ramp up debt (Should execute at least once)
70 | debt_limit_hit = lambda: (
71 | vault.strategies(strategy).dict()["totalDebt"] / vault.totalAssets()
72 | # NOTE: Needs to hit at least 99% of the debt ratio, because 100% is unobtainable
73 | # (Strategy increases it's absolute debt every harvest)
74 | >= 0.99 * vault.strategies(strategy).dict()["debtRatio"] / 10_000
75 | )
76 | assert not debt_limit_hit()
77 | while not debt_limit_hit():
78 |
79 | chain.mine(timestamp=chain.time() + DAY)
80 | assert expectedReturn() > 0
81 | token.transfer(strategy, expectedReturn(), {"from": gov})
82 | chain.sleep(1)
83 | strategy.harvest({"from": keeper})
84 |
85 | # Check balance is increasing
86 | assert token.balanceOf(strategy) > balance
87 | balance = token.balanceOf(strategy)
88 |
89 | # Check accounting is maintained everywhere
90 | assert vault.totalAssets() == token.balanceOf(vault) + balance
91 | assert (
92 | vault.totalDebt()
93 | == vault.strategies(strategy).dict()["totalDebt"]
94 | == strategy.estimatedTotalAssets()
95 | == balance
96 | )
97 |
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from brownie import Token, TokenNoReturn
4 |
5 |
6 | @pytest.fixture
7 | def gov(accounts):
8 | yield accounts[0]
9 |
10 |
11 | @pytest.fixture
12 | def rewards(accounts):
13 | yield accounts[1]
14 |
15 |
16 | @pytest.fixture
17 | def guardian(accounts):
18 | yield accounts[2]
19 |
20 |
21 | @pytest.fixture
22 | def management(accounts):
23 | yield accounts[3]
24 |
25 |
26 | @pytest.fixture
27 | def common_health_check(gov, CommonHealthCheck):
28 | yield gov.deploy(CommonHealthCheck)
29 |
30 |
31 | @pytest.fixture
32 | def create_token(gov):
33 | def create_token(decimal=18, behaviour="Normal"):
34 | assert behaviour in ("Normal", "NoReturn")
35 | return gov.deploy(Token if behaviour == "Normal" else TokenNoReturn, decimal)
36 |
37 | yield create_token
38 |
39 |
40 | @pytest.fixture(params=[("Normal", 18), ("NoReturn", 18), ("Normal", 8), ("Normal", 2)])
41 | def token(create_token, request):
42 | # NOTE: Run our test suite using both compliant and non-compliant ERC20 Token
43 | (behaviour, decimal) = request.param
44 | yield create_token(decimal=decimal, behaviour=behaviour)
45 |
46 |
47 | @pytest.fixture
48 | def create_vault(
49 | gov, guardian, rewards, create_token, patch_vault_version, common_health_check
50 | ):
51 | def create_vault(token=None, version=None, governance=gov):
52 | if token is None:
53 | token = create_token()
54 | vault = patch_vault_version(version).deploy({"from": guardian})
55 | vault.initialize(
56 | token,
57 | governance,
58 | rewards,
59 | "",
60 | "",
61 | guardian,
62 | governance,
63 | common_health_check,
64 | )
65 | vault.setDepositLimit(2 ** 256 - 1, {"from": governance})
66 | return vault
67 |
68 | yield create_vault
69 |
70 |
71 | @pytest.fixture
72 | def vault(gov, management, token, create_vault):
73 | vault = create_vault(token=token, governance=gov)
74 | vault.setManagement(management, {"from": gov})
75 |
76 | # Make it so vault has some AUM to start
77 | token.approve(vault, token.balanceOf(gov) // 2, {"from": gov})
78 | vault.deposit(token.balanceOf(gov) // 2, {"from": gov})
79 | yield vault
80 |
81 |
82 | @pytest.fixture
83 | def strategist(accounts):
84 | yield accounts[4]
85 |
86 |
87 | @pytest.fixture
88 | def keeper(accounts):
89 | yield accounts[5]
90 |
91 |
92 | @pytest.fixture(params=["RegularStrategy", "ClonedStrategy"])
93 | def strategy(gov, strategist, keeper, rewards, vault, TestStrategy, request):
94 | strategy = strategist.deploy(TestStrategy, vault)
95 |
96 | if request.param == "ClonedStrategy":
97 | # deploy the proxy using as logic the original strategy
98 | tx = strategy.clone(vault, strategist, rewards, keeper, {"from": strategist})
99 | # strategy proxy address is returned in the event `Cloned`
100 | strategyAddress = tx.events["Cloned"]["clone"]
101 | # redefine strategy as the new proxy deployed
102 | strategy = TestStrategy.at(strategyAddress, owner=strategist)
103 |
104 | strategy.setKeeper(keeper, {"from": strategist})
105 | vault.addStrategy(
106 | strategy,
107 | 4_000, # 40% of Vault
108 | 0, # Minimum debt increase per harvest
109 | 2 ** 256 - 1, # maximum debt increase per harvest
110 | 1000, # 10% performance fee for Strategist
111 | {"from": gov},
112 | )
113 | yield strategy
114 |
115 |
116 | @pytest.fixture
117 | def rando(accounts):
118 | yield accounts[9]
119 |
120 |
121 | @pytest.fixture
122 | def registry(gov, Registry):
123 | yield gov.deploy(Registry)
124 |
--------------------------------------------------------------------------------
/packages/protocol/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yearn-protocol",
3 | "version": "0.6.0",
4 | "description": "Reference version of the Yearn Protocol for NodeJS/TS",
5 | "files": [
6 | "/contracts/**/*.sol",
7 | "/build/contracts/*.json",
8 | "!/contracts/mocks/**/*",
9 | "/typings/**/*",
10 | "/ethers/**/*",
11 | "/web3/**/*",
12 | "packages/vaults/**/*"
13 | ],
14 | "scripts": {
15 | "setup": "npx hardhat compile",
16 | "compile": "npx truffle compile",
17 | "lint:prettier": "npx prettier --write contracts/**/**/**/**/**.sol",
18 | "lint:fix": "npm run lint:js:fix",
19 | "lint:js": "npx eslint --ignore-path .gitignore .",
20 | "lint:js:fix": "npx eslint --ignore-path .gitignore . --fix",
21 | "test": "npx truffle test",
22 | "test:ganache": "npx truffle test --network ganache",
23 | "deploy:ganache": "npx truffle migrate --reset --network ganache",
24 | "deploy:rinkeby": "npx truffle migrate --reset --network infuraRinkeby",
25 | "deploy:mainnet-dryrun": "npx truffle migrate --reset --network --dry-run infuraMainnet",
26 | "deploy:mainnet": "npx truffle migrate --reset --network infuraMainnet",
27 | "ethers-abi": "abi-types-generator './build/contracts/*.json' --output='./ethers/generated-typings' --name=yearn --provider=ethers_v5",
28 | "web3-abi": "abi-types-generator './build/contracts/*.json' --output='./web3/generated-typings' --name=yearn",
29 | "postinstall": "mv node_modules/@openzeppelin node_modules/@openzeppelinV2"
30 | },
31 | "dependencies": {
32 | "@openzeppelin/contracts": "^2.5.1"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.13.10",
36 | "@babel/plugin-proposal-class-properties": "^7.13.0",
37 | "@babel/plugin-transform-runtime": "^7.13.10",
38 | "@babel/preset-env": "^7.13.12",
39 | "@babel/preset-typescript": "^7.13.0",
40 | "@chainsafe/truffle-plugin-abigen": "0.0.2",
41 | "@nomiclabs/hardhat-etherscan": "^2.1.0",
42 | "@nomiclabs/hardhat-truffle5": "^2.0.0",
43 | "@nomiclabs/hardhat-vyper": "^2.0.1",
44 | "@nomiclabs/hardhat-web3": "^2.0.0",
45 | "@openzeppelin/test-environment": "^0.1.4",
46 | "@truffle/debug-utils": "^5.0.7",
47 | "@truffle/hdwallet-provider": "^1.0.35",
48 | "@typechain/ethers-v5": "^6.0.5",
49 | "@typechain/hardhat": "^1.0.1",
50 | "@types/fs-extra": "^9.0.8",
51 | "@typescript-eslint/parser": "^4.19.0",
52 | "chai": "^4.2.0",
53 | "chai-as-promised": "^7.1.1",
54 | "chai-bignumber": "^2.0.2",
55 | "codecov": "^3.8.1",
56 | "decimal.js": "^10.2.1",
57 | "dotenv": "^8.2.0",
58 | "eslint": "^7.13.0",
59 | "eslint-config-prettier": "^6.10.0",
60 | "eslint-config-standard": "^12.0.0",
61 | "eslint-plugin-import": "^2.17.2",
62 | "eslint-plugin-node": "^8.0.1",
63 | "eslint-plugin-prettier": "^3.1.2",
64 | "eslint-plugin-promise": "^4.1.1",
65 | "eslint-plugin-standard": "^4.0.0",
66 | "fs-extra": "^7.0.1",
67 | "hardhat": "^2.5.0",
68 | "hardhat-contract-sizer": "^2.0.3",
69 | "hardhat-dependency-compiler": "^1.1.1",
70 | "hardhat-deploy": "^0.7.0-beta.49",
71 | "hardhat-deploy-ethers": "^0.3.0-beta.7",
72 | "mocha": "^8.2.1",
73 | "prettier": "^2.2.1",
74 | "prettier-config-solidity": "1.4.0",
75 | "prettier-plugin-solidity": "1.0.0-beta.6",
76 | "solc": "^0.5.17",
77 | "solhint": "^3.3.3",
78 | "solhint-plugin-prettier": "^0.0.5",
79 | "solidity-coverage": "^0.7.13",
80 | "solium": "^1.2.5",
81 | "truffle": "^5.1.21",
82 | "ts-generator": "^0.1.1",
83 | "ts-node": "^9.1.1",
84 | "typechain": "^4.0.1",
85 | "typescript": "^4.2.3"
86 | },
87 | "keywords": [
88 | "solidity",
89 | "ethereum",
90 | "smart",
91 | "contracts",
92 | "yearn",
93 | "defi",
94 | "yearn finance",
95 | "yfi",
96 | "blockchain",
97 | "web3",
98 | "ethers"
99 | ],
100 | "peerDependencies": {
101 | "web3": "^1.3.0"
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/packages/core/contracts/abi/interfaces/Mintr.json:
--------------------------------------------------------------------------------
1 | {
2 | "abi": [
3 | {
4 | "constant": false,
5 | "inputs": [
6 | {
7 | "internalType": "address",
8 | "name": "",
9 | "type": "address"
10 | }
11 | ],
12 | "name": "mint",
13 | "outputs": [],
14 | "payable": false,
15 | "stateMutability": "nonpayable",
16 | "type": "function"
17 | }
18 | ],
19 | "ast": {
20 | "absolutePath": "interfaces/curve/Mintr.sol",
21 | "exportedSymbols": {
22 | "Mintr": [
23 | 367
24 | ]
25 | },
26 | "id": 368,
27 | "nodeType": "SourceUnit",
28 | "nodes": [
29 | {
30 | "id": 361,
31 | "literals": [
32 | "solidity",
33 | "^",
34 | "0.5",
35 | ".17"
36 | ],
37 | "nodeType": "PragmaDirective",
38 | "src": "33:24:9"
39 | },
40 | {
41 | "baseContracts": [],
42 | "contractDependencies": [],
43 | "contractKind": "interface",
44 | "documentation": null,
45 | "fullyImplemented": false,
46 | "id": 367,
47 | "linearizedBaseContracts": [
48 | 367
49 | ],
50 | "name": "Mintr",
51 | "nodeType": "ContractDefinition",
52 | "nodes": [
53 | {
54 | "body": null,
55 | "documentation": null,
56 | "id": 366,
57 | "implemented": false,
58 | "kind": "function",
59 | "modifiers": [],
60 | "name": "mint",
61 | "nodeType": "FunctionDefinition",
62 | "parameters": {
63 | "id": 364,
64 | "nodeType": "ParameterList",
65 | "parameters": [
66 | {
67 | "constant": false,
68 | "id": 363,
69 | "name": "",
70 | "nodeType": "VariableDeclaration",
71 | "scope": 366,
72 | "src": "95:7:9",
73 | "stateVariable": false,
74 | "storageLocation": "default",
75 | "typeDescriptions": {
76 | "typeIdentifier": "t_address",
77 | "typeString": "address"
78 | },
79 | "typeName": {
80 | "id": 362,
81 | "name": "address",
82 | "nodeType": "ElementaryTypeName",
83 | "src": "95:7:9",
84 | "stateMutability": "nonpayable",
85 | "typeDescriptions": {
86 | "typeIdentifier": "t_address",
87 | "typeString": "address"
88 | }
89 | },
90 | "value": null,
91 | "visibility": "internal"
92 | }
93 | ],
94 | "src": "94:9:9"
95 | },
96 | "returnParameters": {
97 | "id": 365,
98 | "nodeType": "ParameterList",
99 | "parameters": [],
100 | "src": "112:0:9"
101 | },
102 | "scope": 367,
103 | "src": "81:32:9",
104 | "stateMutability": "nonpayable",
105 | "superFunction": null,
106 | "visibility": "external"
107 | }
108 | ],
109 | "scope": 368,
110 | "src": "59:56:9"
111 | }
112 | ],
113 | "src": "33:83:9"
114 | },
115 | "contractName": "Mintr",
116 | "dependencies": [],
117 | "offset": [
118 | 59,
119 | 115
120 | ],
121 | "sha1": "21fa872ac3fb68eda76ab98831381f950b13f347",
122 | "source": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.5.17;\n\ninterface Mintr {\n function mint(address) external;\n}\n",
123 | "type": "interface"
124 | }
--------------------------------------------------------------------------------
/packages/core/contracts/abi/interfaces/AaveToken.json:
--------------------------------------------------------------------------------
1 | {
2 | "abi": [
3 | {
4 | "constant": true,
5 | "inputs": [],
6 | "name": "underlyingAssetAddress",
7 | "outputs": [
8 | {
9 | "internalType": "address",
10 | "name": "",
11 | "type": "address"
12 | }
13 | ],
14 | "payable": false,
15 | "stateMutability": "view",
16 | "type": "function"
17 | }
18 | ],
19 | "ast": {
20 | "absolutePath": "interfaces/aave/AaveToken.sol",
21 | "exportedSymbols": {
22 | "AaveToken": [
23 | 85
24 | ]
25 | },
26 | "id": 86,
27 | "nodeType": "SourceUnit",
28 | "nodes": [
29 | {
30 | "id": 79,
31 | "literals": [
32 | "solidity",
33 | "^",
34 | "0.5",
35 | ".17"
36 | ],
37 | "nodeType": "PragmaDirective",
38 | "src": "0:24:1"
39 | },
40 | {
41 | "baseContracts": [],
42 | "contractDependencies": [],
43 | "contractKind": "interface",
44 | "documentation": null,
45 | "fullyImplemented": false,
46 | "id": 85,
47 | "linearizedBaseContracts": [
48 | 85
49 | ],
50 | "name": "AaveToken",
51 | "nodeType": "ContractDefinition",
52 | "nodes": [
53 | {
54 | "body": null,
55 | "documentation": null,
56 | "id": 84,
57 | "implemented": false,
58 | "kind": "function",
59 | "modifiers": [],
60 | "name": "underlyingAssetAddress",
61 | "nodeType": "FunctionDefinition",
62 | "parameters": {
63 | "id": 80,
64 | "nodeType": "ParameterList",
65 | "parameters": [],
66 | "src": "83:2:1"
67 | },
68 | "returnParameters": {
69 | "id": 83,
70 | "nodeType": "ParameterList",
71 | "parameters": [
72 | {
73 | "constant": false,
74 | "id": 82,
75 | "name": "",
76 | "nodeType": "VariableDeclaration",
77 | "scope": 84,
78 | "src": "109:7:1",
79 | "stateVariable": false,
80 | "storageLocation": "default",
81 | "typeDescriptions": {
82 | "typeIdentifier": "t_address",
83 | "typeString": "address"
84 | },
85 | "typeName": {
86 | "id": 81,
87 | "name": "address",
88 | "nodeType": "ElementaryTypeName",
89 | "src": "109:7:1",
90 | "stateMutability": "nonpayable",
91 | "typeDescriptions": {
92 | "typeIdentifier": "t_address",
93 | "typeString": "address"
94 | }
95 | },
96 | "value": null,
97 | "visibility": "internal"
98 | }
99 | ],
100 | "src": "108:9:1"
101 | },
102 | "scope": 85,
103 | "src": "52:66:1",
104 | "stateMutability": "view",
105 | "superFunction": null,
106 | "visibility": "external"
107 | }
108 | ],
109 | "scope": 86,
110 | "src": "26:94:1"
111 | }
112 | ],
113 | "src": "0:121:1"
114 | },
115 | "contractName": "AaveToken",
116 | "dependencies": [],
117 | "offset": [
118 | 26,
119 | 120
120 | ],
121 | "sha1": "dc3d8697ad30b5c4b35de353651e7e4d862a19d1",
122 | "source": "pragma solidity ^0.5.17;\n\ninterface AaveToken {\n function underlyingAssetAddress() external view returns (address);\n}\n",
123 | "type": "interface"
124 | }
--------------------------------------------------------------------------------
/packages/core/contracts/abi/interfaces/Creamtroller.json:
--------------------------------------------------------------------------------
1 | {
2 | "abi": [
3 | {
4 | "constant": false,
5 | "inputs": [
6 | {
7 | "internalType": "address",
8 | "name": "holder",
9 | "type": "address"
10 | }
11 | ],
12 | "name": "claimComp",
13 | "outputs": [],
14 | "payable": false,
15 | "stateMutability": "nonpayable",
16 | "type": "function"
17 | }
18 | ],
19 | "ast": {
20 | "absolutePath": "interfaces/cream/Controller.sol",
21 | "exportedSymbols": {
22 | "Creamtroller": [
23 | 181
24 | ]
25 | },
26 | "id": 182,
27 | "nodeType": "SourceUnit",
28 | "nodes": [
29 | {
30 | "id": 175,
31 | "literals": [
32 | "solidity",
33 | "^",
34 | "0.5",
35 | ".17"
36 | ],
37 | "nodeType": "PragmaDirective",
38 | "src": "33:24:5"
39 | },
40 | {
41 | "baseContracts": [],
42 | "contractDependencies": [],
43 | "contractKind": "interface",
44 | "documentation": null,
45 | "fullyImplemented": false,
46 | "id": 181,
47 | "linearizedBaseContracts": [
48 | 181
49 | ],
50 | "name": "Creamtroller",
51 | "nodeType": "ContractDefinition",
52 | "nodes": [
53 | {
54 | "body": null,
55 | "documentation": null,
56 | "id": 180,
57 | "implemented": false,
58 | "kind": "function",
59 | "modifiers": [],
60 | "name": "claimComp",
61 | "nodeType": "FunctionDefinition",
62 | "parameters": {
63 | "id": 178,
64 | "nodeType": "ParameterList",
65 | "parameters": [
66 | {
67 | "constant": false,
68 | "id": 177,
69 | "name": "holder",
70 | "nodeType": "VariableDeclaration",
71 | "scope": 180,
72 | "src": "107:14:5",
73 | "stateVariable": false,
74 | "storageLocation": "default",
75 | "typeDescriptions": {
76 | "typeIdentifier": "t_address",
77 | "typeString": "address"
78 | },
79 | "typeName": {
80 | "id": 176,
81 | "name": "address",
82 | "nodeType": "ElementaryTypeName",
83 | "src": "107:7:5",
84 | "stateMutability": "nonpayable",
85 | "typeDescriptions": {
86 | "typeIdentifier": "t_address",
87 | "typeString": "address"
88 | }
89 | },
90 | "value": null,
91 | "visibility": "internal"
92 | }
93 | ],
94 | "src": "106:16:5"
95 | },
96 | "returnParameters": {
97 | "id": 179,
98 | "nodeType": "ParameterList",
99 | "parameters": [],
100 | "src": "131:0:5"
101 | },
102 | "scope": 181,
103 | "src": "88:44:5",
104 | "stateMutability": "nonpayable",
105 | "superFunction": null,
106 | "visibility": "external"
107 | }
108 | ],
109 | "scope": 182,
110 | "src": "59:75:5"
111 | }
112 | ],
113 | "src": "33:102:5"
114 | },
115 | "contractName": "Creamtroller",
116 | "dependencies": [],
117 | "offset": [
118 | 59,
119 | 134
120 | ],
121 | "sha1": "3f32289d73f2b5f07649b77507f47e038b429953",
122 | "source": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.5.17;\n\ninterface Creamtroller {\n function claimComp(address holder) external;\n}\n",
123 | "type": "interface"
124 | }
--------------------------------------------------------------------------------
/packages/vaults/tests/integration/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture
5 | def andre(accounts):
6 | # Andre, giver of tokens, and maker of yield
7 | yield accounts[0]
8 |
9 |
10 | @pytest.fixture
11 | def create_token(andre, Token):
12 | yield lambda: andre.deploy(Token, 18)
13 |
14 |
15 | @pytest.fixture
16 | def token(create_token):
17 | yield create_token()
18 |
19 |
20 | @pytest.fixture
21 | def gov(accounts):
22 | # yearn multis... I mean YFI governance. I swear!
23 | yield accounts[1]
24 |
25 |
26 | @pytest.fixture
27 | def registry(gov, Registry):
28 | yield gov.deploy(Registry)
29 |
30 |
31 | @pytest.fixture
32 | def common_health_check(gov, CommonHealthCheck):
33 | yield gov.deploy(CommonHealthCheck)
34 |
35 |
36 | @pytest.fixture
37 | def rewards(gov):
38 | yield gov # TODO: Add rewards contract
39 |
40 |
41 | @pytest.fixture
42 | def guardian(accounts):
43 | # YFI Whale, probably
44 | yield accounts[2]
45 |
46 |
47 | @pytest.fixture
48 | def create_vault(
49 | gov, rewards, guardian, create_token, patch_vault_version, common_health_check
50 | ):
51 | def create_vault(token=None, version=None):
52 | if token is None:
53 | token = create_token()
54 | vault = patch_vault_version(version).deploy({"from": guardian})
55 | vault.initialize(
56 | token,
57 | gov,
58 | rewards,
59 | token.symbol() + " yVault",
60 | "yv" + token.symbol(),
61 | guardian,
62 | guardian,
63 | common_health_check,
64 | )
65 | vault.setDepositLimit(2 ** 256 - 1, {"from": gov})
66 | assert vault.depositLimit() == 2 ** 256 - 1
67 | assert vault.token() == token
68 | return vault
69 |
70 | yield create_vault
71 |
72 |
73 | @pytest.fixture
74 | def vault(token, create_vault):
75 | yield create_vault(token)
76 |
77 |
78 | @pytest.fixture
79 | def strategist(accounts):
80 | # You! Our new Strategist!
81 | yield accounts[3]
82 |
83 |
84 | @pytest.fixture
85 | def keeper(accounts):
86 | # This is our trusty bot!
87 | yield accounts[4]
88 |
89 |
90 | @pytest.fixture
91 | def strategy(gov, strategist, keeper, vault, TestStrategy):
92 | strategy = strategist.deploy(TestStrategy, vault)
93 | strategy.setKeeper(keeper)
94 | yield strategy
95 |
96 |
97 | @pytest.fixture
98 | def nocoiner(accounts):
99 | # Has no tokens (DeFi is a ponzi scheme!)
100 | yield accounts[5]
101 |
102 |
103 | @pytest.fixture
104 | def pleb(accounts, andre, token, vault):
105 | # Small fish in a big pond
106 | a = accounts[6]
107 | # Has 0.01% of tokens (heard about this new DeFi thing!)
108 | bal = token.totalSupply() // 10000
109 | token.transfer(a, bal, {"from": andre})
110 | # Unlimited Approvals
111 | token.approve(vault, 2 ** 256 - 1, {"from": a})
112 | # Deposit half their stack
113 | vault.deposit(bal // 2, {"from": a})
114 | yield a
115 |
116 |
117 | @pytest.fixture
118 | def chad(accounts, andre, token, vault):
119 | # Just here to have fun!
120 | a = accounts[7]
121 | # Has 0.1% of tokens (somehow makes money trying every new thing)
122 | bal = token.totalSupply() // 1000
123 | token.transfer(a, bal, {"from": andre})
124 | # Unlimited Approvals
125 | token.approve(vault, 2 ** 256 - 1, {"from": a})
126 | # Deposit half their stack
127 | vault.deposit(bal // 2, {"from": a})
128 | yield a
129 |
130 |
131 | @pytest.fixture
132 | def greyhat(accounts, andre, token, vault):
133 | # Chaotic evil, will eat you alive
134 | a = accounts[8]
135 | # Has 1% of tokens (earned them the *hard way*)
136 | bal = token.totalSupply() // 100
137 | token.transfer(a, bal, {"from": andre})
138 | # Unlimited Approvals
139 | token.approve(vault, 2 ** 256 - 1, {"from": a})
140 | # Deposit half their stack
141 | vault.deposit(bal // 2, {"from": a})
142 | yield a
143 |
144 |
145 | @pytest.fixture
146 | def whale(accounts, andre, token, vault):
147 | # Totally in it for the tech
148 | a = accounts[9]
149 | # Has 10% of tokens (was in the ICO)
150 | bal = token.totalSupply() // 10
151 | token.transfer(a, bal, {"from": andre})
152 | # Unlimited Approvals
153 | token.approve(vault, 2 ** 256 - 1, {"from": a})
154 | # Deposit half their stack
155 | vault.deposit(bal // 2, {"from": a})
156 | yield a
157 |
--------------------------------------------------------------------------------
/packages/core/interfaces/maker/Maker.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | interface GemLike {
4 | function approve(address, uint256) external;
5 |
6 | function transfer(address, uint256) external;
7 |
8 | function transferFrom(
9 | address,
10 | address,
11 | uint256
12 | ) external;
13 |
14 | function deposit() external payable;
15 |
16 | function withdraw(uint256) external;
17 | }
18 |
19 | interface ManagerLike {
20 | function cdpCan(
21 | address,
22 | uint256,
23 | address
24 | ) external view returns (uint256);
25 |
26 | function ilks(uint256) external view returns (bytes32);
27 |
28 | function owns(uint256) external view returns (address);
29 |
30 | function urns(uint256) external view returns (address);
31 |
32 | function vat() external view returns (address);
33 |
34 | function open(bytes32, address) external returns (uint256);
35 |
36 | function give(uint256, address) external;
37 |
38 | function cdpAllow(
39 | uint256,
40 | address,
41 | uint256
42 | ) external;
43 |
44 | function urnAllow(address, uint256) external;
45 |
46 | function frob(
47 | uint256,
48 | int256,
49 | int256
50 | ) external;
51 |
52 | function flux(
53 | uint256,
54 | address,
55 | uint256
56 | ) external;
57 |
58 | function move(
59 | uint256,
60 | address,
61 | uint256
62 | ) external;
63 |
64 | function exit(
65 | address,
66 | uint256,
67 | address,
68 | uint256
69 | ) external;
70 |
71 | function quit(uint256, address) external;
72 |
73 | function enter(address, uint256) external;
74 |
75 | function shift(uint256, uint256) external;
76 | }
77 |
78 | interface VatLike {
79 | function can(address, address) external view returns (uint256);
80 |
81 | function ilks(bytes32)
82 | external
83 | view
84 | returns (
85 | uint256,
86 | uint256,
87 | uint256,
88 | uint256,
89 | uint256
90 | );
91 |
92 | function dai(address) external view returns (uint256);
93 |
94 | function urns(bytes32, address) external view returns (uint256, uint256);
95 |
96 | function frob(
97 | bytes32,
98 | address,
99 | address,
100 | address,
101 | int256,
102 | int256
103 | ) external;
104 |
105 | function hope(address) external;
106 |
107 | function move(
108 | address,
109 | address,
110 | uint256
111 | ) external;
112 | }
113 |
114 | interface GemJoinLike {
115 | function dec() external returns (uint256);
116 |
117 | function gem() external returns (GemLike);
118 |
119 | function join(address, uint256) external payable;
120 |
121 | function exit(address, uint256) external;
122 | }
123 |
124 | interface GNTJoinLike {
125 | function bags(address) external view returns (address);
126 |
127 | function make(address) external returns (address);
128 | }
129 |
130 | interface DaiJoinLike {
131 | function vat() external returns (VatLike);
132 |
133 | function dai() external returns (GemLike);
134 |
135 | function join(address, uint256) external payable;
136 |
137 | function exit(address, uint256) external;
138 | }
139 |
140 | interface HopeLike {
141 | function hope(address) external;
142 |
143 | function nope(address) external;
144 | }
145 |
146 | interface EndLike {
147 | function fix(bytes32) external view returns (uint256);
148 |
149 | function cash(bytes32, uint256) external;
150 |
151 | function free(bytes32) external;
152 |
153 | function pack(uint256) external;
154 |
155 | function skim(bytes32, address) external;
156 | }
157 |
158 | interface JugLike {
159 | function drip(bytes32) external returns (uint256);
160 | }
161 |
162 | interface PotLike {
163 | function pie(address) external view returns (uint256);
164 |
165 | function drip() external returns (uint256);
166 |
167 | function join(uint256) external;
168 |
169 | function exit(uint256) external;
170 | }
171 |
172 | interface SpotLike {
173 | function ilks(bytes32) external view returns (address, uint256);
174 | }
175 |
176 | interface OSMedianizer {
177 | function read() external view returns (uint256, bool);
178 |
179 | function foresight() external view returns (uint256, bool);
180 | }
181 |
--------------------------------------------------------------------------------
/packages/vaults/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | pull_request:
9 |
10 | jobs:
11 |
12 | functional:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | group: [1, 2, 3, 4, 5, 6]
17 |
18 | steps:
19 | - uses: actions/checkout@v1
20 |
21 | - name: Cache compiler installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm/vyper-*
27 | key: ${{ runner.os }}-compiler-cache
28 |
29 | - name: Setup node.js
30 | uses: actions/setup-node@v1
31 | with:
32 | node-version: '12.x'
33 |
34 | - name: Install ganache
35 | run: npm install -g ganache-cli@6.12.1
36 |
37 | - name: Set up python 3.8
38 | uses: actions/setup-python@v2
39 | with:
40 | python-version: 3.8
41 |
42 | - name: Set pip cache directory path
43 | id: pip-cache-dir-path
44 | run: |
45 | echo "::set-output name=dir::$(pip cache dir)"
46 |
47 | - name: Restore pip cache
48 | uses: actions/cache@v2
49 | id: pip-cache
50 | with:
51 | path: |
52 | ${{ steps.pip-cache-dir-path.outputs.dir }}
53 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
54 | restore-keys: |
55 | ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
56 | ${{ runner.os }}-pip-
57 |
58 | - name: Get Month
59 | id: get-month
60 | run: echo "::set-output name=date::$(/bin/date -u "+%Y%m")"
61 | shell: bash
62 |
63 | - name: Restore duration cache
64 | uses: actions/cache@v2
65 | id: test_durations_cache
66 | with:
67 | path: .test_durations
68 | key: ${{ runner.os }}-${{ steps.get-month.outputs.date }}-test-durations-cache-
69 |
70 | - name: Check file existence
71 | id: check_test_durations
72 | uses: andstor/file-existence-action@v1
73 | with:
74 | files: .test_durations
75 |
76 | - name: cat
77 | if: steps.check_test_durations.outputs.files_exists == 'true'
78 | run: cat .test_durations
79 |
80 | - name: Install python dependencies
81 | run: pip install -r requirements-dev.txt
82 |
83 | - name: Install pytest-split
84 | run: pip install pytest-split
85 |
86 | - name: Compile Code
87 | run: brownie compile --size
88 |
89 | - name: Run Splitted Tests
90 | if: steps.check_test_durations.outputs.files_exists == 'true'
91 | run: brownie test tests/functional --gas --coverage --splits 6 --group ${{ matrix.group }};
92 |
93 | - name: Run build test_duration
94 | if: steps.check_test_durations.outputs.files_exists == 'false' # has to run on all, otherwise the first one that finishes creates an empty cache and lock the cache for others
95 | id: build_cache_duration
96 | run: brownie test tests/functional --store-durations --gas --coverage
97 |
98 | integration:
99 | runs-on: ubuntu-latest
100 |
101 | steps:
102 | - uses: actions/checkout@v1
103 |
104 | - name: Cache compiler installations
105 | uses: actions/cache@v2
106 | with:
107 | path: |
108 | ~/.solcx
109 | ~/.vvm/vyper-*
110 | key: ${{ runner.os }}-compiler-cache
111 |
112 | - name: Setup node.js
113 | uses: actions/setup-node@v1
114 | with:
115 | node-version: '12.x'
116 |
117 | - name: Install ganache
118 | run: npm install -g ganache-cli@6.12.1
119 |
120 | - name: Set up python 3.8
121 | uses: actions/setup-python@v2
122 | with:
123 | python-version: 3.8
124 |
125 | - name: Set pip cache directory path
126 | id: pip-cache-dir-path
127 | run: |
128 | echo "::set-output name=dir::$(pip cache dir)"
129 |
130 | - name: Restore pip cache
131 | uses: actions/cache@v2
132 | id: pip-cache
133 | with:
134 | path: |
135 | ${{ steps.pip-cache-dir-path.outputs.dir }}
136 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
137 | restore-keys: |
138 | ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
139 | ${{ runner.os }}-pip-
140 |
141 | - name: Install python dependencies
142 | run: pip install -r requirements-dev.txt
143 |
144 | - name: Run Tests
145 | run: brownie test tests/integration/ -s --revert-tb
146 |
--------------------------------------------------------------------------------
/packages/vaults/scripts/keep.py:
--------------------------------------------------------------------------------
1 | from brownie import accounts, network, interface, Vault, Token
2 | from brownie.network.gas.strategies import GasNowScalingStrategy
3 | from decimal import Decimal
4 | from eth_utils import is_checksum_address
5 | import requests
6 | from time import sleep
7 |
8 |
9 | GAS_BUFFER = 1.2
10 |
11 | gas_strategy = GasNowScalingStrategy()
12 |
13 |
14 | def get_address(msg: str) -> str:
15 | while True:
16 | addr = input(msg)
17 | if is_checksum_address(addr):
18 | return addr
19 | print(f"I'm sorry, but '{addr}' is not a checksummed address")
20 |
21 |
22 | def main():
23 | print(f"You are using the '{network.show_active()}' network")
24 | bot = accounts.load("bot")
25 | print(f"You are using: 'bot' [{bot.address}]")
26 | # TODO: Allow adding/removing strategies during operation
27 | strategies = [interface.StrategyAPI(get_address("Strategy to farm: "))]
28 | while input("Add another strategy? (y/[N]): ").lower() == "y":
29 | strategies.append(interface.StrategyAPI(get_address("Strategy to farm: ")))
30 |
31 | vault = Vault.at(strategies[0].vault())
32 | want = Token.at(vault.token())
33 |
34 | for strategy in strategies:
35 | assert (
36 | strategy.keeper() == bot.address
37 | ), "Bot is not set as keeper! [{strategy.address}]"
38 | assert (
39 | strategy.vault() == vault.address
40 | ), f"Vault mismatch! [{strategy.address}]"
41 |
42 | while True:
43 | starting_balance = bot.balance()
44 |
45 | calls_made = 0
46 | total_gas_estimate = 0
47 | for strategy in strategies:
48 | # Display some relevant statistics
49 | symbol = want.symbol()
50 | credit = vault.creditAvailable(strategy) / 10 ** vault.decimals()
51 | print(f"[{strategy.address}] Credit Available: {credit:0.3f} {symbol}")
52 | debt = vault.debtOutstanding(strategy) / 10 ** vault.decimals()
53 | print(f"[{strategy.address}] Debt Outstanding: {debt:0.3f} {symbol}")
54 |
55 | starting_gas_price = next(gas_strategy.get_gas_price())
56 |
57 | try:
58 | tend_gas_estimate = int(
59 | GAS_BUFFER * strategy.tend.estimate_gas({"from": bot})
60 | )
61 | total_gas_estimate += tend_gas_estimate
62 | except ValueError:
63 | print(f"[{strategy.address}] `tend` estimate fails")
64 | tend_gas_estimate = None
65 |
66 | try:
67 | harvest_gas_estimate = int(
68 | GAS_BUFFER * strategy.harvest.estimate_gas({"from": bot})
69 | )
70 | total_gas_estimate += harvest_gas_estimate
71 | except ValueError:
72 | print(f"[{strategy.address}] `harvest` estimate fails")
73 | harvest_gas_estimate = None
74 |
75 | if harvest_gas_estimate and strategy.harvestTrigger(
76 | harvest_gas_estimate * starting_gas_price
77 | ):
78 | try:
79 | strategy.harvest({"from": bot, "gas_price": gas_strategy})
80 | calls_made += 1
81 | except:
82 | print(f"[{strategy.address}] `harvest` call fails")
83 |
84 | elif tend_gas_estimate and strategy.tendTrigger(
85 | tend_gas_estimate * starting_gas_price
86 | ):
87 | try:
88 | strategy.tend({"from": bot, "gas_price": gas_strategy})
89 | calls_made += 1
90 | except:
91 | print(f"[{strategy.address}] `tend` call fails")
92 |
93 | # Check running 10 `tend`s & `harvest`s per strategy at estimated gas price
94 | # would empty the balance of the bot account
95 | if bot.balance() < 10 * total_gas_estimate * starting_gas_price:
96 | print(f"Need more ether please! {bot.address}")
97 |
98 | # Wait a minute if we didn't make any calls
99 | if calls_made > 0:
100 | gas_cost = (starting_balance - bot.balance()) / 10 ** 18
101 | num_harvests = bot.balance() // (starting_balance - bot.balance())
102 | print(f"Made {calls_made} calls, spent {gas_cost} ETH on gas.")
103 | print(
104 | f"At this rate, it'll take {num_harvests} harvests to run out of gas."
105 | )
106 | else:
107 | print("Sleeping for 60 seconds...")
108 | sleep(60)
109 |
--------------------------------------------------------------------------------
/packages/vaults/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution guide
2 |
3 | If you are new to [Yearn Finance](https://yearn.finance/), you might want to familiarize yourself with its [core concepts and products](https://docs.yearn.finance/). You can also join the [discord channel](https://discord.com/invite/6PNv2nF/) if you have questions or to keep up with updates.
4 |
5 | ## Setting up your environment
6 |
7 | Before proceeding, please set up your environment by following these installation, building and testing [instructions](https://github.com/iearn-finance/yearn-vaults/blob/main/README.md).
8 |
9 | ## Making your first contribution
10 |
11 | Each time you begin a set of changes, ensure that you are working on a new branch that you have created as opposed to the `main` of your local repository. By keeping your changes segregated in this branch, merging your changes into the main repository later will be much simpler for the team.
12 |
13 | To create a local branch for `git` to checkout, issue the command:
14 |
15 | ```bash
16 | git checkout -b feature-in-progress-branch
17 | ```
18 |
19 | To checkout a branch you have already created:
20 |
21 | ```bash
22 | git checkout feature-in-progress-branch
23 | ```
24 |
25 | ### Preparing your commit
26 |
27 | The official yearn-vaults repository may have changed since the time you cloned it. To fetch changes to the yearn-vaults repository since your last session:
28 |
29 | ```bash
30 | git fetch origin
31 | ```
32 |
33 | Then synchronize your main branch:
34 |
35 | ```bash
36 | git pull origin main
37 | ```
38 |
39 | To stage the changed files that are be committed, issue the command:
40 |
41 | ```bash
42 | git add --all
43 | ```
44 |
45 | Once you are ready to make a commit, you can do so with:
46 |
47 | ```bash
48 | git commit -m “fix: message to explain what the commit covers”
49 | ```
50 |
51 | **NOTE**: commit message must follow Conventional Commits [standard](https://www.conventionalcommits.org/en/v1.0.0/), otherwise your pull requests (discussed further below below) will not pass validation tests. You can use the [`--amend` flag](https://git-scm.com/docs/git-commit) to effectively change your commit message.
52 |
53 | ### Handling conflicts
54 |
55 | If there are conflicts between your edits and those made by others since you started work Git will ask you to resolve them. To find out which files have conflicts, run:
56 |
57 | ```bash
58 | git status
59 | ```
60 |
61 | Open those files, and you will see lines inserted by Git that identify the conflicts:
62 |
63 | ```text
64 | <<<<<< HEAD
65 | Other developers’ version of the conflicting code
66 | ======
67 | Your version of the conflicting code
68 | '>>>>> Your Commit
69 | ```
70 |
71 | The code from the yearn-vaults repository is inserted between `<<<` and `===` while the change you have made is inserted between `===` and `>>>>`. Remove everything between `<<<<` and `>>>` and replace it with code that resolves the conflict. Repeat the process for all files listed by Git status to have conflicts.
72 |
73 | When you are ready, use git push to move your local copy of the changes to your fork of the repository on Github.
74 |
75 | ```bash
76 | git push git@github.com:/yearn-vaults.git feature-in-progress-branch
77 | ```
78 |
79 | ### Opening a pull request
80 |
81 | Navigate to your fork of the repository on Github. In the upper left where the current branch is listed, change the branch to your newly created one (feature-in-progress-branch). Open the files that you have worked on and ensure they include your changes.
82 |
83 | Navigate to yearn-vault [repository](https://github.com/iearn-finance/yearn-vaults) and click on the new pull request button. In the “base” box on the left, leave the default selection “base main”, the branch that you want your changes to be applied to. In the “compare” box on the right, select the branch containing the changes you want to apply. You will then be asked to answer a few questions about your pull request. Pull requests should have enough context about what you are working on, how you are solving a problem, and reference all necessary information for your reviewers to help.
84 |
85 | After you complete the questionnaire, the pull request will appear in the [list](https://github.com/iearn-finance/yearn-vaults/pulls) of pull requests.
86 |
87 | ### Following up
88 |
89 | Core developers may ask questions and request that you make edits. If you set notifications at the top of the page to “not watching,” you will still be notified by email whenever someone comments on the page of a pull request you have created. If you are asked to modify your pull request, edit your local branch, push up your fixes, then leave a comment to notify the original reviewer that the pull request is ready for further review.
90 |
--------------------------------------------------------------------------------
/packages/core/contracts/vaults/yVault.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";
4 | import "@openzeppelinV2/contracts/math/SafeMath.sol";
5 | import "@openzeppelinV2/contracts/utils/Address.sol";
6 | import "@openzeppelinV2/contracts/token/ERC20/SafeERC20.sol";
7 | import "@openzeppelinV2/contracts/token/ERC20/ERC20.sol";
8 | import "@openzeppelinV2/contracts/token/ERC20/ERC20Detailed.sol";
9 | import "@openzeppelinV2/contracts/ownership/Ownable.sol";
10 |
11 | import "../../interfaces/yearn/IController.sol";
12 |
13 | contract yVault is ERC20, ERC20Detailed {
14 | using SafeERC20 for IERC20;
15 | using Address for address;
16 | using SafeMath for uint256;
17 |
18 | IERC20 public token;
19 |
20 | uint256 public min = 9500;
21 | uint256 public constant max = 10000;
22 |
23 | address public governance;
24 | address public controller;
25 |
26 | constructor(address _token, address _controller)
27 | public
28 | ERC20Detailed(
29 | string(abi.encodePacked("yearn ", ERC20Detailed(_token).name())),
30 | string(abi.encodePacked("y", ERC20Detailed(_token).symbol())),
31 | ERC20Detailed(_token).decimals()
32 | )
33 | {
34 | token = IERC20(_token);
35 | governance = msg.sender;
36 | controller = _controller;
37 | }
38 |
39 | function balance() public view returns (uint256) {
40 | return token.balanceOf(address(this)).add(IController(controller).balanceOf(address(token)));
41 | }
42 |
43 | function setMin(uint256 _min) external {
44 | require(msg.sender == governance, "!governance");
45 | min = _min;
46 | }
47 |
48 | function setGovernance(address _governance) public {
49 | require(msg.sender == governance, "!governance");
50 | governance = _governance;
51 | }
52 |
53 | function setController(address _controller) public {
54 | require(msg.sender == governance, "!governance");
55 | controller = _controller;
56 | }
57 |
58 | // Custom logic in here for how much the vault allows to be borrowed
59 | // Sets minimum required on-hand to keep small withdrawals cheap
60 | function available() public view returns (uint256) {
61 | return token.balanceOf(address(this)).mul(min).div(max);
62 | }
63 |
64 | function earn() public {
65 | uint256 _bal = available();
66 | token.safeTransfer(controller, _bal);
67 | IController(controller).earn(address(token), _bal);
68 | }
69 |
70 | function depositAll() external {
71 | deposit(token.balanceOf(msg.sender));
72 | }
73 |
74 | function deposit(uint256 _amount) public {
75 | uint256 _pool = balance();
76 | uint256 _before = token.balanceOf(address(this));
77 | token.safeTransferFrom(msg.sender, address(this), _amount);
78 | uint256 _after = token.balanceOf(address(this));
79 | _amount = _after.sub(_before); // Additional check for deflationary tokens
80 | uint256 shares = 0;
81 | if (totalSupply() == 0) {
82 | shares = _amount;
83 | } else {
84 | shares = (_amount.mul(totalSupply())).div(_pool);
85 | }
86 | _mint(msg.sender, shares);
87 | }
88 |
89 | function withdrawAll() external {
90 | withdraw(balanceOf(msg.sender));
91 | }
92 |
93 | // Used to swap any borrowed reserve over the debt limit to liquidate to 'token'
94 | function harvest(address reserve, uint256 amount) external {
95 | require(msg.sender == controller, "!controller");
96 | require(reserve != address(token), "token");
97 | IERC20(reserve).safeTransfer(controller, amount);
98 | }
99 |
100 | // No rebalance implementation for lower fees and faster swaps
101 | function withdraw(uint256 _shares) public {
102 | uint256 r = (balance().mul(_shares)).div(totalSupply());
103 | _burn(msg.sender, _shares);
104 |
105 | // Check balance
106 | uint256 b = token.balanceOf(address(this));
107 | if (b < r) {
108 | uint256 _withdraw = r.sub(b);
109 | IController(controller).withdraw(address(token), _withdraw);
110 | uint256 _after = token.balanceOf(address(this));
111 | uint256 _diff = _after.sub(b);
112 | if (_diff < _withdraw) {
113 | r = b.add(_diff);
114 | }
115 | }
116 |
117 | token.safeTransfer(msg.sender, r);
118 | }
119 |
120 | function getPricePerFullShare() public view returns (uint256) {
121 | return balance().mul(1e18).div(totalSupply());
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/vaults/yVault.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.17;
2 |
3 | import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";
4 | import "@openzeppelinV2/contracts/math/SafeMath.sol";
5 | import "@openzeppelinV2/contracts/utils/Address.sol";
6 | import "@openzeppelinV2/contracts/token/ERC20/SafeERC20.sol";
7 | import "@openzeppelinV2/contracts/token/ERC20/ERC20.sol";
8 | import "@openzeppelinV2/contracts/token/ERC20/ERC20Detailed.sol";
9 | import "@openzeppelinV2/contracts/ownership/Ownable.sol";
10 |
11 | import "../../interfaces/yearn/IController.sol";
12 |
13 | contract yVault is ERC20, ERC20Detailed {
14 | using SafeERC20 for IERC20;
15 | using Address for address;
16 | using SafeMath for uint256;
17 |
18 | IERC20 public token;
19 |
20 | uint256 public min = 9500;
21 | uint256 public constant max = 10000;
22 |
23 | address public governance;
24 | address public controller;
25 |
26 | constructor(address _token, address _controller)
27 | public
28 | ERC20Detailed(
29 | string(abi.encodePacked("yearn ", ERC20Detailed(_token).name())),
30 | string(abi.encodePacked("y", ERC20Detailed(_token).symbol())),
31 | ERC20Detailed(_token).decimals()
32 | )
33 | {
34 | token = IERC20(_token);
35 | governance = msg.sender;
36 | controller = _controller;
37 | }
38 |
39 | function balance() public view returns (uint256) {
40 | return token.balanceOf(address(this)).add(IController(controller).balanceOf(address(token)));
41 | }
42 |
43 | function setMin(uint256 _min) external {
44 | require(msg.sender == governance, "!governance");
45 | min = _min;
46 | }
47 |
48 | function setGovernance(address _governance) public {
49 | require(msg.sender == governance, "!governance");
50 | governance = _governance;
51 | }
52 |
53 | function setController(address _controller) public {
54 | require(msg.sender == governance, "!governance");
55 | controller = _controller;
56 | }
57 |
58 | // Custom logic in here for how much the vault allows to be borrowed
59 | // Sets minimum required on-hand to keep small withdrawals cheap
60 | function available() public view returns (uint256) {
61 | return token.balanceOf(address(this)).mul(min).div(max);
62 | }
63 |
64 | function earn() public {
65 | uint256 _bal = available();
66 | token.safeTransfer(controller, _bal);
67 | IController(controller).earn(address(token), _bal);
68 | }
69 |
70 | function depositAll() external {
71 | deposit(token.balanceOf(msg.sender));
72 | }
73 |
74 | function deposit(uint256 _amount) public {
75 | uint256 _pool = balance();
76 | uint256 _before = token.balanceOf(address(this));
77 | token.safeTransferFrom(msg.sender, address(this), _amount);
78 | uint256 _after = token.balanceOf(address(this));
79 | _amount = _after.sub(_before); // Additional check for deflationary tokens
80 | uint256 shares = 0;
81 | if (totalSupply() == 0) {
82 | shares = _amount;
83 | } else {
84 | shares = (_amount.mul(totalSupply())).div(_pool);
85 | }
86 | _mint(msg.sender, shares);
87 | }
88 |
89 | function withdrawAll() external {
90 | withdraw(balanceOf(msg.sender));
91 | }
92 |
93 | // Used to swap any borrowed reserve over the debt limit to liquidate to 'token'
94 | function harvest(address reserve, uint256 amount) external {
95 | require(msg.sender == controller, "!controller");
96 | require(reserve != address(token), "token");
97 | IERC20(reserve).safeTransfer(controller, amount);
98 | }
99 |
100 | // No rebalance implementation for lower fees and faster swaps
101 | function withdraw(uint256 _shares) public {
102 | uint256 r = (balance().mul(_shares)).div(totalSupply());
103 | _burn(msg.sender, _shares);
104 |
105 | // Check balance
106 | uint256 b = token.balanceOf(address(this));
107 | if (b < r) {
108 | uint256 _withdraw = r.sub(b);
109 | IController(controller).withdraw(address(token), _withdraw);
110 | uint256 _after = token.balanceOf(address(this));
111 | uint256 _diff = _after.sub(b);
112 | if (_diff < _withdraw) {
113 | r = b.add(_diff);
114 | }
115 | }
116 |
117 | token.safeTransfer(msg.sender, r);
118 | }
119 |
120 | function getPricePerFullShare() public view returns (uint256) {
121 | return balance().mul(1e18).div(totalSupply());
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/packages/vaults/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yearn-protocol-vaults",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@tsconfig/node10": {
8 | "version": "1.0.8",
9 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
10 | "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
11 | "dev": true
12 | },
13 | "@tsconfig/node12": {
14 | "version": "1.0.9",
15 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
16 | "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
17 | "dev": true
18 | },
19 | "@tsconfig/node14": {
20 | "version": "1.0.1",
21 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
22 | "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
23 | "dev": true
24 | },
25 | "@tsconfig/node16": {
26 | "version": "1.0.2",
27 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
28 | "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
29 | "dev": true
30 | },
31 | "arg": {
32 | "version": "4.1.3",
33 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
34 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
35 | "dev": true
36 | },
37 | "buffer-from": {
38 | "version": "1.1.1",
39 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
40 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
41 | "dev": true
42 | },
43 | "create-require": {
44 | "version": "1.1.1",
45 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
46 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
47 | "dev": true
48 | },
49 | "diff": {
50 | "version": "4.0.2",
51 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
52 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
53 | "dev": true
54 | },
55 | "make-error": {
56 | "version": "1.3.6",
57 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
58 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
59 | "dev": true
60 | },
61 | "source-map": {
62 | "version": "0.6.1",
63 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
64 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
65 | "dev": true
66 | },
67 | "source-map-support": {
68 | "version": "0.5.19",
69 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
70 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
71 | "dev": true,
72 | "requires": {
73 | "buffer-from": "^1.0.0",
74 | "source-map": "^0.6.0"
75 | }
76 | },
77 | "ts-node": {
78 | "version": "10.1.0",
79 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz",
80 | "integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
81 | "dev": true,
82 | "requires": {
83 | "@tsconfig/node10": "^1.0.7",
84 | "@tsconfig/node12": "^1.0.7",
85 | "@tsconfig/node14": "^1.0.0",
86 | "@tsconfig/node16": "^1.0.1",
87 | "arg": "^4.1.0",
88 | "create-require": "^1.1.0",
89 | "diff": "^4.0.1",
90 | "make-error": "^1.1.1",
91 | "source-map-support": "^0.5.17",
92 | "yn": "3.1.1"
93 | }
94 | },
95 | "typescript": {
96 | "version": "4.3.5",
97 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
98 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
99 | "dev": true
100 | },
101 | "yn": {
102 | "version": "3.1.1",
103 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
104 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
105 | "dev": true
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/packages/vaults/tests/integration/test_wrapper.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from collections import namedtuple
4 | from semantic_version import Version
5 |
6 | from brownie import yToken, AffiliateToken, Vault
7 | from brownie.test import strategy
8 |
9 |
10 | class Migration:
11 | st_ratio = strategy("decimal", min_value="0.1", max_value="0.9")
12 | st_index = strategy("uint256")
13 |
14 | def __init__(
15 | self, chain, registry, token, create_vault, wrapper, first_vault, user, gov
16 | ):
17 | self.chain = chain
18 | self.registry = registry
19 | self.token = token
20 | # NOTE: Have to fakeout this call because it thinks it's a method
21 | self.create_vault = lambda self, version: create_vault(
22 | token=token, version=version
23 | )
24 | self.wrapper = wrapper
25 | self.user = user
26 | self.gov = gov
27 | self.vaults = []
28 | self._first_vault = first_vault
29 | self.starting_balance = token.balanceOf(user) # Don't touch!
30 |
31 | def setup(self):
32 | # NOTE: Approve wrapper for all future deposits (only once)
33 | self.token.approve(self.wrapper, 2 ** 256 - 1, {"from": self.user})
34 |
35 | # NOTE: Deposit a little bit to start (so we don't just always skip)
36 | self.wrapper.deposit(self.starting_balance // 10, {"from": self.user})
37 | self.vaults = [self._first_vault]
38 |
39 | def rule_new_release(self):
40 | next_version = str(Version(self.registry.latestRelease()).next_major())
41 | vault = self.create_vault(next_version)
42 | print(f" Registry.newRelease({vault})")
43 |
44 | self.registry.newRelease(vault, {"from": self.gov})
45 | self.registry.endorseVault(vault, {"from": self.gov})
46 |
47 | # NOTE: yToken's are non-custodial, so you need to authorize them
48 | if self.wrapper._name == "yToken":
49 | vault.approve(self.wrapper, 2 ** 256 - 1, {"from": self.user})
50 |
51 | self.vaults.append(vault)
52 |
53 | def rule_deposit(self, ratio="st_ratio"):
54 | amount = int(ratio * self.token.balanceOf(self.user))
55 | if amount > 0:
56 | print(f" Wrapper.deposit({amount})")
57 | self.wrapper.deposit(amount, {"from": self.user})
58 |
59 | else:
60 | print(" Wrapper.deposit: Nothing to deposit...")
61 |
62 | def rule_harvest(self, index="st_index"):
63 | # Give some profit to one of the Vaults we are invested in
64 | vaults_in_use = [v for v in self.vaults if v.totalSupply() > 0]
65 | if len(vaults_in_use) > 0:
66 | vault = vaults_in_use[index % len(vaults_in_use)]
67 | amount = int(1e17)
68 | print(f" {vault}.harvest({amount})")
69 | self.token.transfer(vault, amount, {"from": self.user})
70 | # NOTE: Wait enough time where "profit locking" isn't a problem (about a day)
71 | self.chain.mine(timedelta=24 * 60 * 60)
72 |
73 | else:
74 | print(" vaults.harvest: No Vaults in use...")
75 |
76 | def rule_migrate(self):
77 | print(" Wrapper.migrate()")
78 | self.wrapper.migrate({"from": self.gov})
79 |
80 | def rule_withdraw(self, ratio="st_ratio"):
81 | amount = int(ratio * self.wrapper.balanceOf(self.user))
82 | if amount > 0:
83 | print(f" Wrapper.withdraw({amount})")
84 | self.wrapper.withdraw(amount, {"from": self.user}).return_value
85 |
86 | else:
87 | print(" Wrapper.withdraw: Nothing to withdraw...")
88 |
89 | def invariant_balances(self):
90 | actual_deposits = sum(v.totalAssets() for v in self.vaults)
91 | expected_deposits = self.starting_balance - self.token.balanceOf(self.user)
92 | assert actual_deposits == expected_deposits
93 |
94 |
95 | @pytest.mark.parametrize("Wrapper", (AffiliateToken, yToken))
96 | def test_migration_wrapper(
97 | chain, state_machine, token, create_vault, whale, gov, registry, Wrapper
98 | ):
99 | if Wrapper._name == "AffiliateToken":
100 | wrapper = gov.deploy(
101 | Wrapper, token, registry, "Test Affiliate Token", "afToken"
102 | )
103 | else:
104 | wrapper = gov.deploy(Wrapper, token, registry)
105 |
106 | # NOTE: Must start with at least one vault in registry (for token)
107 | vault = create_vault(token=token, version="1.0.0")
108 | registry.newRelease(vault, {"from": gov})
109 | registry.endorseVault(vault, {"from": gov})
110 |
111 | # NOTE: yToken's are non-custodial, so you need to authorize them
112 | if wrapper._name == "yToken":
113 | vault.approve(wrapper, 2 ** 256 - 1, {"from": whale})
114 |
115 | state_machine(
116 | Migration, chain, registry, token, create_vault, wrapper, vault, whale, gov
117 | )
118 |
--------------------------------------------------------------------------------
/packages/vaults/tests/integration/test_release.py:
--------------------------------------------------------------------------------
1 | from semantic_version import Version
2 |
3 | from brownie.test import strategy
4 |
5 | from brownie import Vault
6 |
7 |
8 | class ReleaseTest:
9 |
10 | st_bool = strategy("bool")
11 |
12 | def __init__(self, gov, registry, create_token, create_vault):
13 | self.gov = gov
14 | self.registry = registry
15 | self.create_token = lambda s: create_token()
16 |
17 | def create_vault_adaptor(self, *args, **kwargs):
18 | return create_vault(*args, **kwargs)
19 |
20 | self.create_vault = create_vault_adaptor
21 |
22 | def setup(self):
23 | self.latest_version = Version("1.0.0")
24 | token = self.create_token()
25 | vault = self.create_vault(token, version=str(self.latest_version))
26 | self.vaults = {token: [vault]}
27 | self.registry.newRelease(vault, {"from": self.gov})
28 | self.experiments = {}
29 |
30 | def rule_new_release(self, new_token="st_bool"):
31 | if new_token or len(self.vaults.keys()) == 0:
32 | token = self.create_token()
33 | else:
34 | token = list(self.vaults.keys())[-1]
35 |
36 | self.latest_version = self.latest_version.next_patch()
37 |
38 | vault = self.create_vault(token, version=str(self.latest_version))
39 | print(f"Registry.newRelease({token}, {self.latest_version})")
40 | self.registry.newRelease(vault, {"from": self.gov})
41 |
42 | if token in self.vaults:
43 | self.vaults[token].append(vault)
44 | else:
45 | self.vaults[token] = [vault]
46 |
47 | def rule_new_vault(self, new_token="st_bool"):
48 | tokens_with_stale_deployments = [
49 | token
50 | for token, deployments in self.vaults.items()
51 | if Version(deployments[-1].apiVersion()) < self.latest_version
52 | ]
53 | if new_token or len(tokens_with_stale_deployments) == 0:
54 | token = self.create_token()
55 | else:
56 | token = tokens_with_stale_deployments[-1]
57 |
58 | print(f"Registry.newVault({token}, {self.latest_version})")
59 | vault = Vault.at(
60 | self.registry.newVault(
61 | token, self.gov, self.gov, "", "", {"from": self.gov}
62 | ).return_value
63 | )
64 |
65 | if token in self.vaults:
66 | self.vaults[token].append(vault)
67 | else:
68 | self.vaults[token] = [vault]
69 |
70 | def rule_new_experiment(self):
71 | token = self.create_token()
72 | print(f"Registry.newExperimentalVault({token}, {self.latest_version})")
73 |
74 | vault = Vault.at(
75 | self.registry.newExperimentalVault(
76 | token,
77 | self.gov,
78 | self.gov,
79 | self.gov,
80 | "",
81 | "",
82 | {"from": self.gov},
83 | ).return_value
84 | )
85 |
86 | self.experiments[token] = [vault]
87 |
88 | def rule_endorse_experiment(self):
89 | experiments_with_latest_api = [
90 | (token, deployments[-1])
91 | for token, deployments in self.experiments.items()
92 | if (
93 | Version(deployments[-1].apiVersion()) == self.latest_version
94 | and (
95 | token not in self.vaults
96 | or Version(self.vaults[token][-1].apiVersion())
97 | < Version(deployments[-1].apiVersion())
98 | )
99 | )
100 | ]
101 | if len(experiments_with_latest_api) > 0:
102 | token, vault = experiments_with_latest_api[-1]
103 | print(f"Registry.endorseVault({token}, {self.latest_version})")
104 | self.registry.endorseVault(vault, {"from": self.gov})
105 |
106 | if token in self.vaults:
107 | self.vaults[token].append(vault)
108 | else:
109 | self.vaults[token] = [vault]
110 |
111 | def invariant(self):
112 | for token, deployments in self.vaults.items():
113 | # Check that token matches up
114 | assert deployments[0].token() == token
115 | # Strictly linearly increasing versions
116 | last_version = Version(deployments[0].apiVersion())
117 | assert last_version <= self.latest_version
118 |
119 | for vault in deployments[1:]:
120 | # Check that token matches up
121 | assert vault.token() == token
122 | # Strictly linearly increasing versions
123 | assert last_version < Version(vault.apiVersion()) <= self.latest_version
124 |
125 |
126 | def test_releases(gov, registry, create_token, create_vault, state_machine):
127 | state_machine(
128 | ReleaseTest,
129 | gov,
130 | registry,
131 | create_token,
132 | create_vault,
133 | # NOTE: Taking too long with default settings
134 | settings={"max_examples": 20, "stateful_step_count": 6},
135 | )
136 |
--------------------------------------------------------------------------------
/packages/core/contracts/abi/interfaces/IConverter.json:
--------------------------------------------------------------------------------
1 | {
2 | "abi": [
3 | {
4 | "constant": false,
5 | "inputs": [
6 | {
7 | "internalType": "address",
8 | "name": "",
9 | "type": "address"
10 | }
11 | ],
12 | "name": "convert",
13 | "outputs": [
14 | {
15 | "internalType": "uint256",
16 | "name": "",
17 | "type": "uint256"
18 | }
19 | ],
20 | "payable": false,
21 | "stateMutability": "nonpayable",
22 | "type": "function"
23 | }
24 | ],
25 | "ast": {
26 | "absolutePath": "interfaces/yearn/IConverter.sol",
27 | "exportedSymbols": {
28 | "IConverter": [
29 | 972
30 | ]
31 | },
32 | "id": 973,
33 | "nodeType": "SourceUnit",
34 | "nodes": [
35 | {
36 | "id": 964,
37 | "literals": [
38 | "solidity",
39 | "^",
40 | "0.5",
41 | ".17"
42 | ],
43 | "nodeType": "PragmaDirective",
44 | "src": "33:24:18"
45 | },
46 | {
47 | "baseContracts": [],
48 | "contractDependencies": [],
49 | "contractKind": "interface",
50 | "documentation": null,
51 | "fullyImplemented": false,
52 | "id": 972,
53 | "linearizedBaseContracts": [
54 | 972
55 | ],
56 | "name": "IConverter",
57 | "nodeType": "ContractDefinition",
58 | "nodes": [
59 | {
60 | "body": null,
61 | "documentation": null,
62 | "id": 971,
63 | "implemented": false,
64 | "kind": "function",
65 | "modifiers": [],
66 | "name": "convert",
67 | "nodeType": "FunctionDefinition",
68 | "parameters": {
69 | "id": 967,
70 | "nodeType": "ParameterList",
71 | "parameters": [
72 | {
73 | "constant": false,
74 | "id": 966,
75 | "name": "",
76 | "nodeType": "VariableDeclaration",
77 | "scope": 971,
78 | "src": "103:7:18",
79 | "stateVariable": false,
80 | "storageLocation": "default",
81 | "typeDescriptions": {
82 | "typeIdentifier": "t_address",
83 | "typeString": "address"
84 | },
85 | "typeName": {
86 | "id": 965,
87 | "name": "address",
88 | "nodeType": "ElementaryTypeName",
89 | "src": "103:7:18",
90 | "stateMutability": "nonpayable",
91 | "typeDescriptions": {
92 | "typeIdentifier": "t_address",
93 | "typeString": "address"
94 | }
95 | },
96 | "value": null,
97 | "visibility": "internal"
98 | }
99 | ],
100 | "src": "102:9:18"
101 | },
102 | "returnParameters": {
103 | "id": 970,
104 | "nodeType": "ParameterList",
105 | "parameters": [
106 | {
107 | "constant": false,
108 | "id": 969,
109 | "name": "",
110 | "nodeType": "VariableDeclaration",
111 | "scope": 971,
112 | "src": "130:7:18",
113 | "stateVariable": false,
114 | "storageLocation": "default",
115 | "typeDescriptions": {
116 | "typeIdentifier": "t_uint256",
117 | "typeString": "uint256"
118 | },
119 | "typeName": {
120 | "id": 968,
121 | "name": "uint256",
122 | "nodeType": "ElementaryTypeName",
123 | "src": "130:7:18",
124 | "typeDescriptions": {
125 | "typeIdentifier": "t_uint256",
126 | "typeString": "uint256"
127 | }
128 | },
129 | "value": null,
130 | "visibility": "internal"
131 | }
132 | ],
133 | "src": "129:9:18"
134 | },
135 | "scope": 972,
136 | "src": "86:53:18",
137 | "stateMutability": "nonpayable",
138 | "superFunction": null,
139 | "visibility": "external"
140 | }
141 | ],
142 | "scope": 973,
143 | "src": "59:82:18"
144 | }
145 | ],
146 | "src": "33:109:18"
147 | },
148 | "contractName": "IConverter",
149 | "dependencies": [],
150 | "offset": [
151 | 59,
152 | 141
153 | ],
154 | "sha1": "d8ce5d57b8fafc2cf9cb11e22c9fc1b4ca5fd509",
155 | "source": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.5.17;\n\ninterface IConverter {\n function convert(address) external returns (uint256);\n}\n",
156 | "type": "interface"
157 | }
--------------------------------------------------------------------------------
/packages/vaults/tests/functional/vault/test_losses.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import brownie
3 |
4 | DAY = 86400 # seconds
5 | MAX_UINT256 = 2 ** 256 - 1
6 |
7 |
8 | @pytest.fixture
9 | def vault(gov, token, Vault, common_health_check):
10 | # NOTE: Because the fixture has tokens in it already
11 | vault = gov.deploy(Vault)
12 | vault.initialize(
13 | token,
14 | gov,
15 | gov,
16 | token.symbol() + " yVault",
17 | "yv" + token.symbol(),
18 | gov,
19 | gov,
20 | common_health_check,
21 | )
22 | vault.setDepositLimit(2 ** 256 - 1, {"from": gov})
23 | yield vault
24 |
25 |
26 | @pytest.fixture
27 | def strategy(gov, vault, TestStrategy):
28 | # NOTE: Because the fixture has tokens in it already
29 | yield gov.deploy(TestStrategy, vault)
30 |
31 |
32 | def test_losses_updates_less_and_debt(
33 | chain, vault, strategy, gov, token, common_health_check
34 | ):
35 | vault.addStrategy(strategy, 1000, 0, 1000, 0, {"from": gov})
36 | token.approve(vault, 2 ** 256 - 1, {"from": gov})
37 | vault.deposit(5000, {"from": gov})
38 |
39 | chain.sleep(DAY // 10)
40 | strategy.harvest({"from": gov})
41 | assert token.balanceOf(strategy) == 500
42 |
43 | # First loss
44 | chain.sleep(DAY // 10)
45 | strategy._takeFunds(100, {"from": gov})
46 | vault.deposit(100, {"from": gov}) # NOTE: total assets doesn't change
47 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
48 | strategy.harvest({"from": gov})
49 | params = vault.strategies(strategy).dict()
50 | assert params["totalLoss"] == 100
51 | assert params["totalDebt"] == 400
52 |
53 | # Harder second loss
54 | chain.sleep(DAY // 10)
55 | strategy._takeFunds(300, {"from": gov})
56 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
57 | vault.deposit(300, {"from": gov}) # NOTE: total assets doesn't change
58 | chain.sleep(1)
59 | strategy.harvest({"from": gov})
60 | params = vault.strategies(strategy).dict()
61 | assert params["totalLoss"] == 400
62 | assert params["totalDebt"] == 100
63 |
64 | # Strike three
65 | chain.sleep(DAY // 10)
66 | assert token.balanceOf(strategy) == 100
67 | strategy._takeFunds(100, {"from": gov})
68 | vault.deposit(100, {"from": gov}) # NOTE: total assets doesn't change
69 | assert token.balanceOf(strategy) == 0
70 | chain.sleep(1)
71 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
72 | strategy.harvest({"from": gov})
73 | params = vault.strategies(strategy).dict()
74 | assert params["totalLoss"] == 500
75 | assert params["totalDebt"] == 0
76 |
77 |
78 | def test_total_loss(chain, vault, strategy, gov, token, common_health_check):
79 | vault.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 1_000, {"from": gov})
80 | token.approve(vault, 2 ** 256 - 1, {"from": gov})
81 | vault.deposit(5000, {"from": gov})
82 | strategy.harvest({"from": gov})
83 | assert token.balanceOf(strategy) == 5000
84 |
85 | # send all our tokens back to the token contract
86 | token.transfer(token, token.balanceOf(strategy), {"from": strategy})
87 |
88 | chain.sleep(1)
89 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
90 | strategy.harvest({"from": gov})
91 | params = vault.strategies(strategy)
92 | assert params["totalLoss"] == 5000
93 | assert params["totalDebt"] == 0
94 | assert params["debtRatio"] == 0
95 |
96 |
97 | def test_loss_should_be_removed_from_locked_profit(
98 | chain, vault, strategy, gov, token, common_health_check
99 | ):
100 | vault.setLockedProfitDegradation(1e10, {"from": gov})
101 |
102 | vault.addStrategy(strategy, 1000, 0, 1000, 0, {"from": gov})
103 | token.approve(vault, 2 ** 256 - 1, {"from": gov})
104 | vault.deposit(5000, {"from": gov})
105 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
106 | strategy.harvest({"from": gov})
107 | assert token.balanceOf(strategy) == 500
108 | token.transfer(strategy, 100, {"from": gov})
109 | chain.sleep(1)
110 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
111 | strategy.harvest({"from": gov})
112 |
113 | assert vault.lockedProfit() == 90 # 100 - performance fees
114 |
115 | token.transfer(token, 40, {"from": strategy})
116 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
117 | chain.sleep(1)
118 | strategy.harvest({"from": gov})
119 | assert vault.lockedProfit() == 50
120 |
121 |
122 | def test_report_loss(chain, token, gov, vault, strategy, common_health_check):
123 | token.approve(vault, MAX_UINT256, {"from": gov})
124 | vault.deposit({"from": gov})
125 | vault.addStrategy(strategy, 1000, 0, 1000, 0, {"from": gov})
126 | chain.sleep(1)
127 | strategy.harvest()
128 | strategy._takeFunds(token.balanceOf(strategy), {"from": gov})
129 | assert token.balanceOf(strategy) == 0
130 |
131 | # Make sure we do not send more funds to the strategy.
132 | chain.sleep(1)
133 | with brownie.reverts():
134 | strategy.harvest()
135 | common_health_check.setDisabledCheck(strategy, True, {"from": gov})
136 | strategy.harvest()
137 | assert token.balanceOf(strategy) == 0
138 |
139 | assert vault.debtRatio() == 0
140 |
--------------------------------------------------------------------------------
/packages/core/contracts/strategies/CurveYCRVVoter.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";
6 | import "@openzeppelinV2/contracts/math/SafeMath.sol";
7 | import "@openzeppelinV2/contracts/utils/Address.sol";
8 | import "@openzeppelinV2/contracts/token/ERC20/SafeERC20.sol";
9 |
10 | import "../../interfaces/curve/Curve.sol";
11 | import "../../interfaces/curve/Gauge.sol";
12 | import "../../interfaces/curve/Mintr.sol";
13 | import "../../interfaces/curve/VoteEscrow.sol";
14 | import "../../interfaces/uniswap/Uni.sol";
15 | import "../../interfaces/yearn/IToken.sol";
16 |
17 | contract CurveYCRVVoter {
18 | using SafeERC20 for IERC20;
19 | using Address for address;
20 | using SafeMath for uint256;
21 |
22 | address public constant want = address(0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8);
23 | address public constant pool = address(0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1);
24 | address public constant mintr = address(0xd061D61a4d941c39E5453435B6345Dc261C2fcE0);
25 | address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52);
26 |
27 | address public constant escrow = address(0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2);
28 |
29 | address public governance;
30 | address public strategy;
31 |
32 | constructor() public {
33 | governance = msg.sender;
34 | }
35 |
36 | function getName() external pure returns (string memory) {
37 | return "CurveYCRVVoter";
38 | }
39 |
40 | function setStrategy(address _strategy) external {
41 | require(msg.sender == governance, "!governance");
42 | strategy = _strategy;
43 | }
44 |
45 | function deposit() public {
46 | uint256 _want = IERC20(want).balanceOf(address(this));
47 | if (_want > 0) {
48 | IERC20(want).safeApprove(pool, 0);
49 | IERC20(want).safeApprove(pool, _want);
50 | Gauge(pool).deposit(_want);
51 | }
52 | }
53 |
54 | // Controller only function for creating additional rewards from dust
55 | function withdraw(IERC20 _asset) external returns (uint256 balance) {
56 | require(msg.sender == strategy, "!controller");
57 | balance = _asset.balanceOf(address(this));
58 | _asset.safeTransfer(strategy, balance);
59 | }
60 |
61 | // Withdraw partial funds, normally used with a vault withdrawal
62 | function withdraw(uint256 _amount) external {
63 | require(msg.sender == strategy, "!controller");
64 | uint256 _balance = IERC20(want).balanceOf(address(this));
65 | if (_balance < _amount) {
66 | _amount = _withdrawSome(_amount.sub(_balance));
67 | _amount = _amount.add(_balance);
68 | }
69 | IERC20(want).safeTransfer(strategy, _amount);
70 | }
71 |
72 | // Withdraw all funds, normally used when migrating strategies
73 | function withdrawAll() external returns (uint256 balance) {
74 | require(msg.sender == strategy, "!controller");
75 | _withdrawAll();
76 |
77 | balance = IERC20(want).balanceOf(address(this));
78 | IERC20(want).safeTransfer(strategy, balance);
79 | }
80 |
81 | function _withdrawAll() internal {
82 | Gauge(pool).withdraw(Gauge(pool).balanceOf(address(this)));
83 | }
84 |
85 | function createLock(uint256 _value, uint256 _unlockTime) external {
86 | require(msg.sender == strategy || msg.sender == governance, "!authorized");
87 | IERC20(crv).safeApprove(escrow, 0);
88 | IERC20(crv).safeApprove(escrow, _value);
89 | VoteEscrow(escrow).create_lock(_value, _unlockTime);
90 | }
91 |
92 | function increaseAmount(uint256 _value) external {
93 | require(msg.sender == strategy || msg.sender == governance, "!authorized");
94 | IERC20(crv).safeApprove(escrow, 0);
95 | IERC20(crv).safeApprove(escrow, _value);
96 | VoteEscrow(escrow).increase_amount(_value);
97 | }
98 |
99 | function release() external {
100 | require(msg.sender == strategy || msg.sender == governance, "!authorized");
101 | VoteEscrow(escrow).withdraw();
102 | }
103 |
104 | function _withdrawSome(uint256 _amount) internal returns (uint256) {
105 | Gauge(pool).withdraw(_amount);
106 | return _amount;
107 | }
108 |
109 | function balanceOfWant() public view returns (uint256) {
110 | return IERC20(want).balanceOf(address(this));
111 | }
112 |
113 | function balanceOfPool() public view returns (uint256) {
114 | return Gauge(pool).balanceOf(address(this));
115 | }
116 |
117 | function balanceOf() public view returns (uint256) {
118 | return balanceOfWant().add(balanceOfPool());
119 | }
120 |
121 | function setGovernance(address _governance) external {
122 | require(msg.sender == governance, "!governance");
123 | governance = _governance;
124 | }
125 |
126 | function execute(
127 | address to,
128 | uint256 value,
129 | bytes calldata data
130 | ) external returns (bool, bytes memory) {
131 | require(msg.sender == strategy || msg.sender == governance, "!governance");
132 | (bool success, bytes memory result) = to.call.value(value)(data);
133 |
134 | return (success, result);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/packages/protocol/contracts/strategies/CurveYCRVVoter.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.5.17;
4 |
5 | import "@openzeppelinV2/contracts/token/ERC20/IERC20.sol";
6 | import "@openzeppelinV2/contracts/math/SafeMath.sol";
7 | import "@openzeppelinV2/contracts/utils/Address.sol";
8 | import "@openzeppelinV2/contracts/token/ERC20/SafeERC20.sol";
9 |
10 | import "../../interfaces/curve/Curve.sol";
11 | import "../../interfaces/curve/Gauge.sol";
12 | import "../../interfaces/curve/Mintr.sol";
13 | import "../../interfaces/curve/VoteEscrow.sol";
14 | import "../../interfaces/uniswap/Uni.sol";
15 | import "../../interfaces/yearn/IToken.sol";
16 |
17 | contract CurveYCRVVoter {
18 | using SafeERC20 for IERC20;
19 | using Address for address;
20 | using SafeMath for uint256;
21 |
22 | address public constant want = address(0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8);
23 | address public constant pool = address(0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1);
24 | address public constant mintr = address(0xd061D61a4d941c39E5453435B6345Dc261C2fcE0);
25 | address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52);
26 |
27 | address public constant escrow = address(0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2);
28 |
29 | address public governance;
30 | address public strategy;
31 |
32 | constructor() public {
33 | governance = msg.sender;
34 | }
35 |
36 | function getName() external pure returns (string memory) {
37 | return "CurveYCRVVoter";
38 | }
39 |
40 | function setStrategy(address _strategy) external {
41 | require(msg.sender == governance, "!governance");
42 | strategy = _strategy;
43 | }
44 |
45 | function deposit() public {
46 | uint256 _want = IERC20(want).balanceOf(address(this));
47 | if (_want > 0) {
48 | IERC20(want).safeApprove(pool, 0);
49 | IERC20(want).safeApprove(pool, _want);
50 | Gauge(pool).deposit(_want);
51 | }
52 | }
53 |
54 | // Controller only function for creating additional rewards from dust
55 | function withdraw(IERC20 _asset) external returns (uint256 balance) {
56 | require(msg.sender == strategy, "!controller");
57 | balance = _asset.balanceOf(address(this));
58 | _asset.safeTransfer(strategy, balance);
59 | }
60 |
61 | // Withdraw partial funds, normally used with a vault withdrawal
62 | function withdraw(uint256 _amount) external {
63 | require(msg.sender == strategy, "!controller");
64 | uint256 _balance = IERC20(want).balanceOf(address(this));
65 | if (_balance < _amount) {
66 | _amount = _withdrawSome(_amount.sub(_balance));
67 | _amount = _amount.add(_balance);
68 | }
69 | IERC20(want).safeTransfer(strategy, _amount);
70 | }
71 |
72 | // Withdraw all funds, normally used when migrating strategies
73 | function withdrawAll() external returns (uint256 balance) {
74 | require(msg.sender == strategy, "!controller");
75 | _withdrawAll();
76 |
77 | balance = IERC20(want).balanceOf(address(this));
78 | IERC20(want).safeTransfer(strategy, balance);
79 | }
80 |
81 | function _withdrawAll() internal {
82 | Gauge(pool).withdraw(Gauge(pool).balanceOf(address(this)));
83 | }
84 |
85 | function createLock(uint256 _value, uint256 _unlockTime) external {
86 | require(msg.sender == strategy || msg.sender == governance, "!authorized");
87 | IERC20(crv).safeApprove(escrow, 0);
88 | IERC20(crv).safeApprove(escrow, _value);
89 | VoteEscrow(escrow).create_lock(_value, _unlockTime);
90 | }
91 |
92 | function increaseAmount(uint256 _value) external {
93 | require(msg.sender == strategy || msg.sender == governance, "!authorized");
94 | IERC20(crv).safeApprove(escrow, 0);
95 | IERC20(crv).safeApprove(escrow, _value);
96 | VoteEscrow(escrow).increase_amount(_value);
97 | }
98 |
99 | function release() external {
100 | require(msg.sender == strategy || msg.sender == governance, "!authorized");
101 | VoteEscrow(escrow).withdraw();
102 | }
103 |
104 | function _withdrawSome(uint256 _amount) internal returns (uint256) {
105 | Gauge(pool).withdraw(_amount);
106 | return _amount;
107 | }
108 |
109 | function balanceOfWant() public view returns (uint256) {
110 | return IERC20(want).balanceOf(address(this));
111 | }
112 |
113 | function balanceOfPool() public view returns (uint256) {
114 | return Gauge(pool).balanceOf(address(this));
115 | }
116 |
117 | function balanceOf() public view returns (uint256) {
118 | return balanceOfWant().add(balanceOfPool());
119 | }
120 |
121 | function setGovernance(address _governance) external {
122 | require(msg.sender == governance, "!governance");
123 | governance = _governance;
124 | }
125 |
126 | function execute(
127 | address to,
128 | uint256 value,
129 | bytes calldata data
130 | ) external returns (bool, bytes memory) {
131 | require(msg.sender == strategy || msg.sender == governance, "!governance");
132 | (bool success, bytes memory result) = to.call.value(value)(data);
133 |
134 | return (success, result);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/packages/vaults/contracts/test/TestStrategy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 | pragma solidity 0.6.12;
3 | pragma experimental ABIEncoderV2;
4 |
5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6 | import {BaseStrategyInitializable, StrategyParams, VaultAPI} from "../BaseStrategy.sol";
7 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
8 |
9 | /*
10 | * This Strategy serves as both a mock Strategy for testing, and an example
11 | * for integrators on how to use BaseStrategy
12 | */
13 |
14 | contract TestStrategy is BaseStrategyInitializable {
15 | bool public doReentrancy;
16 | bool public delegateEverything;
17 |
18 | // Some token that needs to be protected for some reason
19 | // Initialize this to some fake address, because we're just using it
20 | // to test `BaseStrategy.protectedTokens()`
21 | address public constant protectedToken = address(0xbad);
22 |
23 | constructor(address _vault) public BaseStrategyInitializable(_vault) {}
24 |
25 | function name() external view override returns (string memory) {
26 | return string(abi.encodePacked("TestStrategy ", apiVersion()));
27 | }
28 |
29 | // NOTE: This is a test-only function to simulate delegation
30 | function _toggleDelegation() public {
31 | delegateEverything = !delegateEverything;
32 | }
33 |
34 | function delegatedAssets() external view override returns (uint256) {
35 | if (delegateEverything) {
36 | return vault.strategies(address(this)).totalDebt;
37 | } else {
38 | return 0;
39 | }
40 | }
41 |
42 | // NOTE: This is a test-only function to simulate losses
43 | function _takeFunds(uint256 amount) public {
44 | SafeERC20.safeTransfer(want, msg.sender, amount);
45 | }
46 |
47 | // NOTE: This is a test-only function to enable reentrancy on withdraw
48 | function _toggleReentrancyExploit() public {
49 | doReentrancy = !doReentrancy;
50 | }
51 |
52 | // NOTE: This is a test-only function to simulate a wrong want token
53 | function _setWant(IERC20 _want) public {
54 | want = _want;
55 | }
56 |
57 | function ethToWant(uint256 amtInWei) public view override returns (uint256) {
58 | return amtInWei; // 1:1 conversion for testing
59 | }
60 |
61 | function estimatedTotalAssets() public view override returns (uint256) {
62 | // For mock, this is just everything we have
63 | return want.balanceOf(address(this));
64 | }
65 |
66 | function prepareReturn(uint256 _debtOutstanding)
67 | internal
68 | override
69 | returns (
70 | uint256 _profit,
71 | uint256 _loss,
72 | uint256 _debtPayment
73 | )
74 | {
75 | // During testing, send this contract some tokens to simulate "Rewards"
76 | uint256 totalAssets = want.balanceOf(address(this));
77 | uint256 totalDebt = vault.strategies(address(this)).totalDebt;
78 | if (totalAssets > _debtOutstanding) {
79 | _debtPayment = _debtOutstanding;
80 | totalAssets = totalAssets.sub(_debtOutstanding);
81 | } else {
82 | _debtPayment = totalAssets;
83 | totalAssets = 0;
84 | }
85 | totalDebt = totalDebt.sub(_debtPayment);
86 |
87 | if (totalAssets > totalDebt) {
88 | _profit = totalAssets.sub(totalDebt);
89 | } else {
90 | _loss = totalDebt.sub(totalAssets);
91 | }
92 | }
93 |
94 | function adjustPosition(uint256 _debtOutstanding) internal override {
95 | // Whatever we have "free", consider it "invested" now
96 | }
97 |
98 | function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss) {
99 | if (doReentrancy) {
100 | // simulate a malicious protocol or reentrancy situation triggered by strategy withdraw interactions
101 | uint256 stratBalance = VaultAPI(address(vault)).balanceOf(address(this));
102 | VaultAPI(address(vault)).withdraw(stratBalance, address(this));
103 | }
104 |
105 | uint256 totalDebt = vault.strategies(address(this)).totalDebt;
106 | uint256 totalAssets = want.balanceOf(address(this));
107 | if (_amountNeeded > totalAssets) {
108 | _liquidatedAmount = totalAssets;
109 | _loss = _amountNeeded.sub(totalAssets);
110 | } else {
111 | // NOTE: Just in case something was stolen from this contract
112 | if (totalDebt > totalAssets) {
113 | _loss = totalDebt.sub(totalAssets);
114 | if (_loss > _amountNeeded) _loss = _amountNeeded;
115 | }
116 | _liquidatedAmount = _amountNeeded;
117 | }
118 | }
119 |
120 | function prepareMigration(address _newStrategy) internal override {
121 | // Nothing needed here because no additional tokens/tokenized positions for mock
122 | }
123 |
124 | function protectedTokens() internal view override returns (address[] memory) {
125 | address[] memory protected = new address[](1);
126 | protected[0] = protectedToken;
127 | return protected;
128 | }
129 |
130 | function liquidateAllPositions() internal override returns (uint256 amountFreed) {
131 | uint256 totalAssets = want.balanceOf(address(this));
132 | amountFreed = totalAssets;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/packages/vaults/contracts/CommonHealthCheck.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 | pragma solidity >=0.6.0 <0.7.0;
3 |
4 | import {ICustomHealthCheck} from "./interfaces/ICustomHealthCheck.sol";
5 | import {StrategyAPI} from "./BaseStrategy.sol";
6 |
7 | struct Limits {
8 | uint256 profitLimitRatio;
9 | uint256 lossLimitRatio;
10 | bool exists;
11 | }
12 |
13 | contract CommonHealthCheck {
14 | // Default Settings for all strategies
15 | uint256 constant MAX_BPS = 10_000;
16 | uint256 public profitLimitRatio;
17 | uint256 public lossLimitRatio;
18 | mapping(address => Limits) public strategiesLimits;
19 |
20 | address public governance;
21 | address public management;
22 |
23 | mapping(address => address) public checks;
24 | mapping(address => bool) public disabledCheck;
25 |
26 | modifier onlyGovernance() {
27 | require(msg.sender == governance, "!authorized");
28 | _;
29 | }
30 |
31 | modifier onlyAuthorized() {
32 | require(msg.sender == governance || msg.sender == management, "!authorized");
33 | _;
34 | }
35 |
36 | modifier onlyVault(address strategy) {
37 | require(msg.sender == StrategyAPI(strategy).vault(), "!authorized");
38 | _;
39 | }
40 |
41 | constructor() public {
42 | governance = msg.sender;
43 | management = msg.sender;
44 | profitLimitRatio = 100;
45 | lossLimitRatio = 1;
46 | }
47 |
48 | function setGovernance(address _governance) external onlyGovernance {
49 | require(_governance != address(0));
50 | governance = _governance;
51 | }
52 |
53 | function setManagement(address _management) external onlyGovernance {
54 | require(_management != address(0));
55 | management = _management;
56 | }
57 |
58 | function setProfitLimitRatio(uint256 _profitLimitRatio) external onlyAuthorized {
59 | require(_profitLimitRatio < MAX_BPS);
60 | profitLimitRatio = _profitLimitRatio;
61 | }
62 |
63 | function setlossLimitRatio(uint256 _lossLimitRatio) external onlyAuthorized {
64 | require(_lossLimitRatio < MAX_BPS);
65 | lossLimitRatio = _lossLimitRatio;
66 | }
67 |
68 | function setStrategyLimits(
69 | address _strategy,
70 | uint256 _profitLimitRatio,
71 | uint256 _lossLimitRatio
72 | ) external onlyAuthorized {
73 | require(_lossLimitRatio < MAX_BPS);
74 | require(_profitLimitRatio < MAX_BPS);
75 | strategiesLimits[_strategy] = Limits(_profitLimitRatio, _lossLimitRatio, true);
76 | }
77 |
78 | function setCheck(address _strategy, address _check) external onlyAuthorized {
79 | checks[_strategy] = _check;
80 | }
81 |
82 | function enableCheck(address _strategy) external onlyVault(_strategy) {
83 | disabledCheck[_strategy] = false;
84 | }
85 |
86 | function setDisabledCheck(address _strategy, bool disabled) external onlyAuthorized {
87 | disabledCheck[_strategy] = disabled;
88 | }
89 |
90 | function doHealthCheck(address _strategy) external view returns (bool) {
91 | return !disabledCheck[_strategy];
92 | }
93 |
94 | function check(
95 | uint256 profit,
96 | uint256 loss,
97 | uint256 debtPayment,
98 | uint256 debtOutstanding,
99 | uint256 totalDebt
100 | ) external view returns (bool) {
101 | address strategy = msg.sender;
102 |
103 | return _runChecks(strategy, profit, loss, debtPayment, debtOutstanding, totalDebt);
104 | }
105 |
106 | function check(
107 | address strategy,
108 | uint256 profit,
109 | uint256 loss,
110 | uint256 debtPayment,
111 | uint256 debtOutstanding,
112 | uint256 totalDebt
113 | ) external view returns (bool) {
114 | require(strategy != address(0));
115 |
116 | return _runChecks(strategy, profit, loss, debtPayment, debtOutstanding, totalDebt);
117 | }
118 |
119 | function _runChecks(
120 | address strategy,
121 | uint256 profit,
122 | uint256 loss,
123 | uint256 debtPayment,
124 | uint256 debtOutstanding,
125 | uint256 totalDebt
126 | ) internal view returns (bool) {
127 | address customCheck = checks[strategy];
128 |
129 | if (customCheck == address(0)) {
130 | return _executeDefaultCheck(strategy, profit, loss, totalDebt);
131 | }
132 |
133 | return ICustomHealthCheck(customCheck).check(strategy, profit, loss, debtPayment, debtOutstanding);
134 | }
135 |
136 | function _executeDefaultCheck(
137 | address strategy,
138 | uint256 _profit,
139 | uint256 _loss,
140 | uint256 _totalDebt
141 | ) internal view returns (bool) {
142 | Limits memory limits = strategiesLimits[strategy];
143 | uint256 _profitLimitRatio;
144 | uint256 _lossLimitRatio;
145 | if (limits.exists) {
146 | _profitLimitRatio = limits.profitLimitRatio;
147 | _lossLimitRatio = limits.lossLimitRatio;
148 | } else {
149 | _profitLimitRatio = profitLimitRatio;
150 | _lossLimitRatio = lossLimitRatio;
151 | }
152 |
153 | if (_profit > ((_totalDebt * _profitLimitRatio) / MAX_BPS)) {
154 | return false;
155 | }
156 | if (_loss > ((_totalDebt * _lossLimitRatio) / MAX_BPS)) {
157 | return false;
158 | }
159 | return true;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------