├── 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 | [![Twitter Follow](https://img.shields.io/twitter/follow/iearnfinance.svg?label=iearnfinance&style=social)](https://twitter.com/iearnfinance) [![Discord](https://img.shields.io/discord/734804446353031319.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discordapp.com/channels/734804446353031319/) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](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 | [![GitHub license](https://img.shields.io/badge/license-AGPL-blue.svg)](https://github.com/iearn-finance/yearn-protocol/blob/master/LICENSE) 4 | ![Lint](https://github.com/iearn-finance/yearn-protocol/workflows/Lint/badge.svg) 5 | ![Test](https://github.com/iearn-finance/yearn-protocol/workflows/Test/badge.svg) 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 | --------------------------------------------------------------------------------