From 7567e3271f8baee17905b7a6fb659a0bfcb20f46 Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 17:50:23 -0700 Subject: [PATCH 01/19] yearn vaults --- contracts/interfaces/external/IYearnVault.sol | 24 + .../mocks/external/YearnStrategyMock.sol | 124 ++ .../protocol/integration/YearnWrapAdapter.sol | 132 ++ .../integration/oracles/YearnVaultOracle.sol | 94 + external/abi/yearn/Registry.json | 589 ++++++ external/abi/yearn/Vault.json | 1666 ++++++++++++++++ external/contracts/yearn/BaseStrategy.sol | 861 +++++++++ external/contracts/yearn/Registry.vy | 330 ++++ external/contracts/yearn/Vault.vy | 1667 +++++++++++++++++ test/fixtures/yearn.spec.ts | 69 + .../oracles/yearnVaultOracle.spec.ts | 134 ++ test/integration/yearnWrapModule.spec.ts | 195 ++ utils/contracts/index.ts | 3 + utils/contracts/yearn.ts | 3 + utils/deploys/deployAdapters.ts | 6 + utils/deploys/deployExternal.ts | 22 + utils/deploys/deployMocks.ts | 6 + utils/deploys/deployOracles.ts | 25 + utils/deploys/index.ts | 5 +- utils/fixtures/index.ts | 1 + utils/fixtures/yearnFixture.ts | 65 + utils/test/index.ts | 3 +- 22 files changed, 6021 insertions(+), 3 deletions(-) create mode 100644 contracts/interfaces/external/IYearnVault.sol create mode 100644 contracts/mocks/external/YearnStrategyMock.sol create mode 100644 contracts/protocol/integration/YearnWrapAdapter.sol create mode 100644 contracts/protocol/integration/oracles/YearnVaultOracle.sol create mode 100644 external/abi/yearn/Registry.json create mode 100644 external/abi/yearn/Vault.json create mode 100644 external/contracts/yearn/BaseStrategy.sol create mode 100644 external/contracts/yearn/Registry.vy create mode 100644 external/contracts/yearn/Vault.vy create mode 100644 test/fixtures/yearn.spec.ts create mode 100644 test/integration/oracles/yearnVaultOracle.spec.ts create mode 100644 test/integration/yearnWrapModule.spec.ts create mode 100644 utils/contracts/yearn.ts create mode 100644 utils/deploys/deployOracles.ts create mode 100644 utils/fixtures/yearnFixture.ts diff --git a/contracts/interfaces/external/IYearnVault.sol b/contracts/interfaces/external/IYearnVault.sol new file mode 100644 index 000000000..0592fb811 --- /dev/null +++ b/contracts/interfaces/external/IYearnVault.sol @@ -0,0 +1,24 @@ +/* + Copyright 2021 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; + +interface IYearnVault { + function token() external view returns(address); + function pricePerShare() external view returns(uint256); +} diff --git a/contracts/mocks/external/YearnStrategyMock.sol b/contracts/mocks/external/YearnStrategyMock.sol new file mode 100644 index 000000000..27758a0f9 --- /dev/null +++ b/contracts/mocks/external/YearnStrategyMock.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.6.10; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {BaseStrategyInitializable, StrategyParams, VaultAPI} from "../../../external/contracts/yearn/BaseStrategy.sol"; + +/* + * This Strategy serves as both a mock Strategy for testing, and an example + * for integrators on how to use BaseStrategy + */ + +contract YearnStrategyMock is BaseStrategyInitializable { + bool public doReentrancy; + bool public delegateEverything; + + // Some token that needs to be protected for some reason + // Initialize this to some fake address, because we're just using it + // to test `BaseStrategy.protectedTokens()` + address public constant protectedToken = address(0xbad); + + constructor(address _vault) public BaseStrategyInitializable(_vault) {} + + function name() external override view returns (string memory) { + return string(abi.encodePacked("YearnStrategyMock ", apiVersion())); + } + + // NOTE: This is a test-only function to simulate delegation + function _toggleDelegation() public { + delegateEverything = !delegateEverything; + } + + function delegatedAssets() external override view returns (uint256) { + if (delegateEverything) { + return vault.strategies(address(this)).totalDebt; + } else { + return 0; + } + } + + // NOTE: This is a test-only function to simulate losses + function _takeFunds(uint256 amount) public { + want.safeTransfer(msg.sender, amount); + } + + // NOTE: This is a test-only function to enable reentrancy on withdraw + function _toggleReentrancyExploit() public { + doReentrancy = !doReentrancy; + } + + // NOTE: This is a test-only function to simulate a wrong want token + function _setWant(IERC20 _want) public { + want = _want; + } + + function estimatedTotalAssets() public override view returns (uint256) { + // For mock, this is just everything we have + return want.balanceOf(address(this)); + } + + function prepareReturn(uint256 _debtOutstanding) + internal + override + returns ( + uint256 _profit, + uint256 _loss, + uint256 _debtPayment + ) + { + // During testing, send this contract some tokens to simulate "Rewards" + uint256 totalAssets = want.balanceOf(address(this)); + uint256 totalDebt = vault.strategies(address(this)).totalDebt; + if (totalAssets > _debtOutstanding) { + _debtPayment = _debtOutstanding; + totalAssets = totalAssets.sub(_debtOutstanding); + } else { + _debtPayment = totalAssets; + totalAssets = 0; + } + totalDebt = totalDebt.sub(_debtPayment); + + if (totalAssets > totalDebt) { + _profit = totalAssets.sub(totalDebt); + } else { + _loss = totalDebt.sub(totalAssets); + } + } + + function adjustPosition(uint256 _debtOutstanding) internal override { + // Whatever we have "free", consider it "invested" now + } + + function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss) { + if (doReentrancy) { + // simulate a malicious protocol or reentrancy situation triggered by strategy withdraw interactions + uint256 stratBalance = VaultAPI(address(vault)).balanceOf(address(this)); + VaultAPI(address(vault)).withdraw(stratBalance, address(this)); + } + + uint256 totalDebt = vault.strategies(address(this)).totalDebt; + uint256 totalAssets = want.balanceOf(address(this)); + if (_amountNeeded > totalAssets) { + _liquidatedAmount = totalAssets; + _loss = _amountNeeded.sub(totalAssets); + } else { + // NOTE: Just in case something was stolen from this contract + if (totalDebt > totalAssets) { + _loss = totalDebt.sub(totalAssets); + if (_loss > _amountNeeded) _loss = _amountNeeded; + } + _liquidatedAmount = _amountNeeded; + } + } + + function prepareMigration(address _newStrategy) internal override { + // Nothing needed here because no additional tokens/tokenized positions for mock + } + + function protectedTokens() internal override view returns (address[] memory) { + address[] memory protected = new address[](1); + protected[0] = protectedToken; + return protected; + } +} diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol new file mode 100644 index 000000000..227fc0ba2 --- /dev/null +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -0,0 +1,132 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; +pragma experimental "ABIEncoderV2"; + +import { IYearnVault } from "../../interfaces/external/IYearnVault.sol"; + +/** + * @title YearnWrapAdapter + * @author Set Protocol + * + * Wrap adapter for Yearn that returns data for wraps/unwraps of tokens + */ +contract YearnWrapAdapter { + + /* ============ Modifiers ============ */ + + /** + * Throws if the underlying/wrapped token pair is not valid + */ + modifier onlyValidTokenPair(address _underlyingToken, address _wrappedToken) { + require(validTokenPair(_underlyingToken, _wrappedToken), "Must be a valid token pair"); + _; + } + + /* ============ Constants ============ */ + + // Mock address to indicate ETH. + address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /* ============ Constructor ============ */ + + constructor() public { } + + /* ============ External Getter Functions ============ */ + + /** + * Generates the calldata to wrap an underlying asset into a wrappedToken. + * + * @param _underlyingToken Address of the component to be wrapped + * @param _wrappedToken Address of the desired wrapped token + * @param _underlyingUnits Total quantity of underlying units to wrap + * + * @return address Target contract address + * @return uint256 Total quantity of underlying units (if underlying is ETH) + * @return bytes Wrap calldata + */ + function getWrapCallData( + address _underlyingToken, + address _wrappedToken, + uint256 _underlyingUnits + ) + external + view + onlyValidTokenPair(_underlyingToken, _wrappedToken) + returns (address, uint256, bytes memory) + { + uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0; + + // deposit(address _reserve, uint256 _amount, uint16 _referralCode) + bytes memory callData = abi.encodeWithSignature("deposit(uint256)", _underlyingUnits); + + return (address(_wrappedToken), value, callData); + } + + /** + * Generates the calldata to unwrap a wrapped asset into its underlying. + * + * @param _underlyingToken Address of the underlying asset + * @param _wrappedToken Address of the component to be unwrapped + * @param _wrappedTokenUnits Total quantity of wrapped token units to unwrap + * + * @return address Target contract address + * @return uint256 Total quantity of wrapped token units to unwrap. This will always be 0 for unwrapping + * @return bytes Unwrap calldata + */ + function getUnwrapCallData( + address _underlyingToken, + address _wrappedToken, + uint256 _wrappedTokenUnits + ) + external + view + onlyValidTokenPair(_underlyingToken, _wrappedToken) + returns (address, uint256, bytes memory) + { + // redeem(uint256 _amount) + bytes memory callData = abi.encodeWithSignature("withdraw(uint256)", _wrappedTokenUnits); + + return (address(_wrappedToken), 0, callData); + } + + /** + * Returns the address to approve source tokens for wrapping. + * + * @return address Address of the contract to approve tokens to + */ + function getSpenderAddress(address /* _underlyingToken */, address _wrappedToken) external view returns(address) { + return address(_wrappedToken); + } + + /* ============ Internal Functions ============ */ + + /** + * Validates the underlying and wrapped token pair + * + * @param _underlyingToken Address of the underlying asset + * @param _wrappedToken Address of the wrapped asset + * + * @return bool Whether or not the wrapped token accepts the underlying token as collateral + */ + function validTokenPair(address _underlyingToken, address _wrappedToken) internal view returns(bool) { + address unwrappedToken = IYearnVault(_wrappedToken).token(); + return unwrappedToken == _underlyingToken; + } +} diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol new file mode 100644 index 000000000..ed09d791d --- /dev/null +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -0,0 +1,94 @@ +/* + Copyright 2021 Set Labs Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.6.10; + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol"; +import { IYearnVault } from "../../../interfaces/external/IYearnVault.sol"; +import { IOracle } from "../../../interfaces/IOracle.sol"; + + +/** + * @title YearnVaultOracle + * @author Set Protocol, Ember Fund + * + * Oracle built to retrieve the Yearn vault price + */ +contract YearnVaultOracle is IOracle +{ + using SafeMath for uint256; + using PreciseUnitMath for uint256; + + + /* ============ State Variables ============ */ + IYearnVault public vault; + IOracle public underlyingOracle; // Underlying token oracle + string public dataDescription; + + // Price per share values are scaled by 1e18 + uint256 internal constant scalingFactor = 10 ** 18; + + // CToken Full Unit + uint256 public cTokenFullUnit; + + // Underlying Asset Full Unit + uint256 public underlyingFullUnit; + + /* ============ Constructor ============ */ + + /* + * @param _vault The address of Yearn Vault Token + * @param _underlyingOracle The address of the underlying oracle + * @param _underlyingFullUnit The full unit of the underlying asset + * @param _dataDescription Human readable description of oracle + */ + constructor( + IYearnVault _vault, + IOracle _underlyingOracle, + uint256 _underlyingFullUnit, + string memory _dataDescription + ) + public + { + vault = _vault; + underlyingFullUnit = _underlyingFullUnit; + underlyingOracle = _underlyingOracle; + dataDescription = _dataDescription; + } + + /** + * Returns the price value of a full vault token denominated in underlyingOracle value + & + * The underlying oracle is assumed to return a price of 18 decimal + * for a single full token of the underlying asset. The derived price + * of the vault token is then the price of a unit of underlying multiplied + * by the exchangeRate, adjusted for decimal differences, and descaled. + */ + function read() + external + override + view + returns (uint256) + { + // Retrieve the price of the underlying + uint256 underlyingPrice = underlyingOracle.read(); + + // Retrieve price per share + uint256 pricePerShare = vault.pricePerShare(); + uint256 normalizedPricePerShare = pricePerShare.preciseDiv(underlyingFullUnit); + + return normalizedPricePerShare.preciseMul(underlyingPrice); + } +} diff --git a/external/abi/yearn/Registry.json b/external/abi/yearn/Registry.json new file mode 100644 index 000000000..44603c974 --- /dev/null +++ b/external/abi/yearn/Registry.json @@ -0,0 +1,589 @@ +{ + "_format": "hh-vyper-artifact-1", + "contractName": "Registry", + "abi": [ + { + "name": "NewRelease", + "inputs": [ + { + "name": "release_id", + "type": "uint256", + "indexed": true + }, + { + "name": "template", + "type": "address", + "indexed": false + }, + { + "name": "api_version", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewVault", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true + }, + { + "name": "vault_id", + "type": "uint256", + "indexed": true + }, + { + "name": "vault", + "type": "address", + "indexed": false + }, + { + "name": "api_version", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewExperimentalVault", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true + }, + { + "name": "deployer", + "type": "address", + "indexed": true + }, + { + "name": "vault", + "type": "address", + "indexed": false + }, + { + "name": "api_version", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewGovernance", + "inputs": [ + { + "name": "governance", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "VaultTagged", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": false + }, + { + "name": "tag", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "constructor", + "inputs": [], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGovernance", + "inputs": [ + { + "name": "governance", + "type": "address" + } + ], + "outputs": [], + "gas": 36245 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "acceptGovernance", + "inputs": [], + "outputs": [], + "gas": 37517 + }, + { + "stateMutability": "view", + "type": "function", + "name": "latestRelease", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 6831 + }, + { + "stateMutability": "view", + "type": "function", + "name": "latestVault", + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2587 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newRelease", + "inputs": [ + { + "name": "vault", + "type": "address" + } + ], + "outputs": [], + "gas": 82588 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "releaseDelta", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newExperimentalVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newExperimentalVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "releaseDelta", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "endorseVault", + "inputs": [ + { + "name": "vault", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "endorseVault", + "inputs": [ + { + "name": "vault", + "type": "address" + }, + { + "name": "releaseDelta", + "type": "uint256" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setBanksy", + "inputs": [ + { + "name": "tagger", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setBanksy", + "inputs": [ + { + "name": "tagger", + "type": "address" + }, + { + "name": "allowed", + "type": "bool" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "tagVault", + "inputs": [ + { + "name": "vault", + "type": "address" + }, + { + "name": "tag", + "type": "string" + } + ], + "outputs": [], + "gas": 186064 + }, + { + "stateMutability": "view", + "type": "function", + "name": "numReleases", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1388 + }, + { + "stateMutability": "view", + "type": "function", + "name": "releases", + "inputs": [ + { + "name": "arg0", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1533 + }, + { + "stateMutability": "view", + "type": "function", + "name": "numVaults", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1663 + }, + { + "stateMutability": "view", + "type": "function", + "name": "vaults", + "inputs": [ + { + "name": "arg0", + "type": "address" + }, + { + "name": "arg1", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1808 + }, + { + "stateMutability": "view", + "type": "function", + "name": "tokens", + "inputs": [ + { + "name": "arg0", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1623 + }, + { + "stateMutability": "view", + "type": "function", + "name": "numTokens", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1538 + }, + { + "stateMutability": "view", + "type": "function", + "name": "isRegistered", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 1783 + }, + { + "stateMutability": "view", + "type": "function", + "name": "governance", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1598 + }, + { + "stateMutability": "view", + "type": "function", + "name": "pendingGovernance", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1628 + }, + { + "stateMutability": "view", + "type": "function", + "name": "tags", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 10229 + }, + { + "stateMutability": "view", + "type": "function", + "name": "banksy", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 1903 + } + ], + "bytecode": "0x336007556115eb56600436101561000d576110f9565b600035601c52600051341561002157600080fd5b63ab033ea98114156100535760043560a01c1561003d57600080fd5b600754331461004b57600080fd5b600435600855005b63238efcbc81141561009f57600854331461006d57600080fd5b3360075533610140527f4f386975ea1c2f7cf1845b08bee00626fbb624ecad16254d63d9bb9ba86526de6020610140a1005b637be0ca5e8114156101735760606101a0600463258294106101405261015c60016000546001808210156100d257600080fd5b8082039050905060e05260c052604060c020545afa6100f057600080fd5b603f3d116100fd57600080fd5b601d6101a06101a05101511061011257600080fd5b6000506101c08051602001806102408284600060045af161013257600080fd5b5050610240518061026001818260206001820306601f82010390500336823750506020610220526040610240510160206001820306601f8201039050610220f35b63e177dc708114156101db5760043560a01c1561018f57600080fd5b600360043560e05260c052604060c020600260043560e05260c052604060c020546001808210156101bf57600080fd5b8082039050905060e05260c052604060c0205460005260206000f35b6333990d4b8114156104315760043560a01c156101f757600080fd5b600754331461020557600080fd5b6000546101405260006101405111156102e1576060610400600463258294106103a0526103bc6004355afa61023957600080fd5b603f3d1161024657600080fd5b601d6104006104005101511061025b57600080fd5b600050610420602001516060610340600463258294106102e0526102fc60016101405160018082101561028d57600080fd5b8082039050905060e05260c052604060c020545afa6102ab57600080fd5b603f3d116102b857600080fd5b601d610340610340510151106102cd57600080fd5b60005061036060200151186102e157600080fd5b60043560016101405160e05260c052604060c0205561014051600181818301101561030b57600080fd5b8082019050905060005560606101c0600463258294106101605261017c6004355afa61033657600080fd5b603f3d1161034357600080fd5b601d6101c06101c05101511061035857600080fd5b6000506101e08051602001806102208284600060045af161037857600080fd5b50506004356102c052604061028052610280516102e052610220805160200180610280516102c0018284600060045af16103b157600080fd5b5050610280516102c00151806020610280516102c0010101818260206001820306601f82010390500336823750506020610280516102c0015160206001820306601f820103905061028051010161028052610140517fa6fbd216b6734f34092f1be6b7247e1551a6d4f2d5000c53721cfdc119a5b7cf610280516102c0a2005b63108ca11e81141561044857600061022052610469565b63b0b40fce81141561046457602060a461022037600050610469565b6106f4565b60043560a01c1561047957600080fd5b60243560a01c1561048957600080fd5b60443560a01c1561049957600080fd5b60606064356004016101403760406064356004013511156104b957600080fd5b60406084356004016101c03760206084356004013511156104d957600080fd5b60075433146104e757600080fd5b6000546001808210156104f957600080fd5b80820390509050610220518082101561051157600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e060043561028052336102a0526044356102c0526024356102e05280610300526101408080516020018084610280018284600060045af161058557600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af16105c457600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156106065761061b565b610440515160206104405103610440526105f4565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a05261018052610160526101405261046051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160043561028052610260516102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506102605160005260206000f35b635b73aa0d81141561070b5760006102205261072c565b635bd4b0f281141561072757602060c46102203760005061072c565b610a64565b60043560a01c1561073c57600080fd5b60243560a01c1561074c57600080fd5b60443560a01c1561075c57600080fd5b60643560a01c1561076c57600080fd5b606060843560040161014037604060843560040135111561078c57600080fd5b604060a4356004016101c037602060a4356004013511156107ac57600080fd5b6000546001808210156107be57600080fd5b8082039050905061022051808210156107d657600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e0600435610280526024356102a0526064356102c0526044356102e05280610300526101408080516020018084610280018284600060045af161084c57600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af161088b57600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156108cd576108e2565b610440515160206104405103610440526108bb565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052610460516102605260606102e0600463258294106102805261029c610260515afa61095d57600080fd5b603f3d1161096a57600080fd5b601d6102e06102e05101511061097f57600080fd5b6000506103008051602001806103408284600060045af161099f57600080fd5b5050610260516103e05260406103a0526103a051610400526103408051602001806103a0516103e0018284600060045af16109d957600080fd5b50506103a0516103e001518060206103a0516103e0010101818260206001820306601f820103905003368237505060206103a0516103e0015160206001820306601f82010390506103a05101016103a052336004357f57a9cdc2a05e05f66e76769bdbe88e21ec45d9ee0f97d4cb60395d4c75dcbcda6103a0516103e0a36102605160005260206000f35b6329b2e0c6811415610a7b57600061014052610a9c565b63b366a35c811415610a97576020602461014037600050610a9c565b610cac565b60043560a01c15610aac57600080fd5b6007543314610aba57600080fd5b3360206101c06004635aa6e6756101605261017c6004355afa610adc57600080fd5b601f3d11610ae957600080fd5b6000506101c05114610afa57600080fd5b600054600180821015610b0c57600080fd5b808203905090506101405180821015610b2457600080fd5b80820390509050610160526060610240600463258294106101e0526101fc60016101605160e05260c052604060c020545afa610b5f57600080fd5b603f3d11610b6c57600080fd5b601d61024061024051015110610b8157600080fd5b6000506102608051602001806101808284600060045af1610ba157600080fd5b50506101a0516060610300600463258294106102a0526102bc6004355afa610bc857600080fd5b603f3d11610bd557600080fd5b601d61030061030051015110610bea57600080fd5b6000506103206020015114610bfe57600080fd5b6020610240600463fc0c546a6101e0526101fc6004355afa610c1f57600080fd5b601f3d11610c2c57600080fd5b60005061024051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605161026051610280526004356102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052600050005b632cad8f9f811415610cc357600161014052610cf4565b635e05f6af811415610cef5760243560011c15610cdf57600080fd5b6020602461014037600050610cf4565b610d29565b60043560a01c15610d0457600080fd5b6007543314610d1257600080fd5b61014051600a60043560e05260c052604060c02055005b6360bd68f8811415610ea25760043560a01c15610d4557600080fd5b6098602435600401610140376078602435600401351115610d6557600080fd5b600754331815610d8757600a3360e05260c052604060c02054610d8757600080fd5b61014080600960043560e05260c052604060c02060c052602060c020602082510161012060006005818352015b82610120516020021115610dc757610de9565b61012051602002850151610120518501555b8151600101808352811415610db4575b50505050505060043561024052604061020052610200516102605261014080516020018061020051610240018284600060045af1610e2657600080fd5b505061020051610240015180602061020051610240010101818260206001820306601f8201039050033682375050602061020051610240015160206001820306601f8201039050610200510101610200527f07bd58794c2ca0ae152f7719eb5f02c654476de972cadec0e8191ae8be42096d61020051610240a1005b6356e0a94b811415610eba5760005460005260206000f35b63b6a9f40f811415610ee057600160043560e05260c052604060c0205460005260206000f35b63f9c7bba5811415610f165760043560a01c15610efc57600080fd5b600260043560e05260c052604060c0205460005260206000f35b637bbfc69e811415610f5a5760043560a01c15610f3257600080fd5b600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b634f64b2be811415610f8057600460043560e05260c052604060c0205460005260206000f35b638e499bcf811415610f985760055460005260206000f35b63c3c5a547811415610fce5760043560a01c15610fb457600080fd5b600660043560e05260c052604060c0205460005260206000f35b635aa6e675811415610fe65760075460005260206000f35b63f39c38a0811415610ffe5760085460005260206000f35b6358b8f8428114156110c15760043560a01c1561101a57600080fd5b600960043560e05260c052604060c0208060c052602060c020610180602082540161012060006005818352015b8261012051602002111561105a5761107c565b61012051850154610120516020028501525b8151600101808352811415611047575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63ee711ed58114156110f75760043560a01c156110dd57600080fd5b600a60043560e05260c052604060c0205460005260206000f35b505b60006000fd5b610220526101405261016052610180526101a0526101c0526101e05261020052610240526000610320525b6102405160206001820306601f82010390506103205110151561114c57611165565b610320516102600152610320516020016103205261112a565b6102c0526000610320525b6102c05160206001820306601f820103905061032051101515611192576111ab565b610320516102e001526103205160200161032052611170565b60005060016102005160e05260c052604060c0205461034052600061034051186111d457600080fd5b7f602d3d8160093d39f3363d3d373d3d3d363d7300000000000000000000000000610380526103405160601b610393527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006103a75260366103806000f061036052610360513b61124357600080fd5b6000600061016460c063a5b81fdf61038052610140516103a052610160516103c052610180516103e052806104005261024080805160200180846103a0018284600060045af161129257600080fd5b50508051820160206001820306601f820103905060200191505080610420526102c080805160200180846103a0018284600060045af16112d157600080fd5b5050506101a0516104405261039c90506000610360515af16112f257600080fd5b6103605160005260005161022051565b61018052610140526101605260026101405160e05260c052604060c020546101a05260006101a0511115611409576060610460600463258294106104005261041c610160515afa61135257600080fd5b603f3d1161135f57600080fd5b601d6104606104605101511061137457600080fd5b6000506104806020015160606103a0600463258294106103405261035c60036101405160e05260c052604060c0206101a0516001808210156113b557600080fd5b8082039050905060e05260c052604060c020545afa6113d357600080fd5b603f3d116113e057600080fd5b601d6103a06103a0510151106113f557600080fd5b6000506103c0602001511861140957600080fd5b6101605160036101405160e05260c052604060c0206101a05160e05260c052604060c020556101a051600181818301101561144357600080fd5b8082019050905060026101405160e05260c052604060c0205560066101405160e05260c052604060c0205415156114bd57600160066101405160e05260c052604060c0205561014051600460055460e05260c052604060c020556005805460018181830110156114b257600080fd5b808201905090508155505b6060610220600463258294106101c0526101dc610160515afa6114df57600080fd5b603f3d116114ec57600080fd5b601d6102206102205101511061150157600080fd5b6000506102408051602001806102808284600060045af161152157600080fd5b5050610160516103205260406102e0526102e051610340526102808051602001806102e051610320018284600060045af161155b57600080fd5b50506102e05161032001518060206102e051610320010101818260206001820306601f820103905003368237505060206102e051610320015160206001820306601f82010390506102e05101016102e0526101a051610140517fce089905ba4a4d622553bcb5646fd23e895c256f0376eee04e99e61cec1dc7e86102e051610320a361018051565b6100086115eb036100086000396100086115eb036000f3", + "deployedBytecode": "0x600436101561000d576110f9565b600035601c52600051341561002157600080fd5b63ab033ea98114156100535760043560a01c1561003d57600080fd5b600754331461004b57600080fd5b600435600855005b63238efcbc81141561009f57600854331461006d57600080fd5b3360075533610140527f4f386975ea1c2f7cf1845b08bee00626fbb624ecad16254d63d9bb9ba86526de6020610140a1005b637be0ca5e8114156101735760606101a0600463258294106101405261015c60016000546001808210156100d257600080fd5b8082039050905060e05260c052604060c020545afa6100f057600080fd5b603f3d116100fd57600080fd5b601d6101a06101a05101511061011257600080fd5b6000506101c08051602001806102408284600060045af161013257600080fd5b5050610240518061026001818260206001820306601f82010390500336823750506020610220526040610240510160206001820306601f8201039050610220f35b63e177dc708114156101db5760043560a01c1561018f57600080fd5b600360043560e05260c052604060c020600260043560e05260c052604060c020546001808210156101bf57600080fd5b8082039050905060e05260c052604060c0205460005260206000f35b6333990d4b8114156104315760043560a01c156101f757600080fd5b600754331461020557600080fd5b6000546101405260006101405111156102e1576060610400600463258294106103a0526103bc6004355afa61023957600080fd5b603f3d1161024657600080fd5b601d6104006104005101511061025b57600080fd5b600050610420602001516060610340600463258294106102e0526102fc60016101405160018082101561028d57600080fd5b8082039050905060e05260c052604060c020545afa6102ab57600080fd5b603f3d116102b857600080fd5b601d610340610340510151106102cd57600080fd5b60005061036060200151186102e157600080fd5b60043560016101405160e05260c052604060c0205561014051600181818301101561030b57600080fd5b8082019050905060005560606101c0600463258294106101605261017c6004355afa61033657600080fd5b603f3d1161034357600080fd5b601d6101c06101c05101511061035857600080fd5b6000506101e08051602001806102208284600060045af161037857600080fd5b50506004356102c052604061028052610280516102e052610220805160200180610280516102c0018284600060045af16103b157600080fd5b5050610280516102c00151806020610280516102c0010101818260206001820306601f82010390500336823750506020610280516102c0015160206001820306601f820103905061028051010161028052610140517fa6fbd216b6734f34092f1be6b7247e1551a6d4f2d5000c53721cfdc119a5b7cf610280516102c0a2005b63108ca11e81141561044857600061022052610469565b63b0b40fce81141561046457602060a461022037600050610469565b6106f4565b60043560a01c1561047957600080fd5b60243560a01c1561048957600080fd5b60443560a01c1561049957600080fd5b60606064356004016101403760406064356004013511156104b957600080fd5b60406084356004016101c03760206084356004013511156104d957600080fd5b60075433146104e757600080fd5b6000546001808210156104f957600080fd5b80820390509050610220518082101561051157600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e060043561028052336102a0526044356102c0526024356102e05280610300526101408080516020018084610280018284600060045af161058557600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af16105c457600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156106065761061b565b610440515160206104405103610440526105f4565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a05261018052610160526101405261046051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160043561028052610260516102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506102605160005260206000f35b635b73aa0d81141561070b5760006102205261072c565b635bd4b0f281141561072757602060c46102203760005061072c565b610a64565b60043560a01c1561073c57600080fd5b60243560a01c1561074c57600080fd5b60443560a01c1561075c57600080fd5b60643560a01c1561076c57600080fd5b606060843560040161014037604060843560040135111561078c57600080fd5b604060a4356004016101c037602060a4356004013511156107ac57600080fd5b6000546001808210156107be57600080fd5b8082039050905061022051808210156107d657600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e0600435610280526024356102a0526064356102c0526044356102e05280610300526101408080516020018084610280018284600060045af161084c57600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af161088b57600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156108cd576108e2565b610440515160206104405103610440526108bb565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052610460516102605260606102e0600463258294106102805261029c610260515afa61095d57600080fd5b603f3d1161096a57600080fd5b601d6102e06102e05101511061097f57600080fd5b6000506103008051602001806103408284600060045af161099f57600080fd5b5050610260516103e05260406103a0526103a051610400526103408051602001806103a0516103e0018284600060045af16109d957600080fd5b50506103a0516103e001518060206103a0516103e0010101818260206001820306601f820103905003368237505060206103a0516103e0015160206001820306601f82010390506103a05101016103a052336004357f57a9cdc2a05e05f66e76769bdbe88e21ec45d9ee0f97d4cb60395d4c75dcbcda6103a0516103e0a36102605160005260206000f35b6329b2e0c6811415610a7b57600061014052610a9c565b63b366a35c811415610a97576020602461014037600050610a9c565b610cac565b60043560a01c15610aac57600080fd5b6007543314610aba57600080fd5b3360206101c06004635aa6e6756101605261017c6004355afa610adc57600080fd5b601f3d11610ae957600080fd5b6000506101c05114610afa57600080fd5b600054600180821015610b0c57600080fd5b808203905090506101405180821015610b2457600080fd5b80820390509050610160526060610240600463258294106101e0526101fc60016101605160e05260c052604060c020545afa610b5f57600080fd5b603f3d11610b6c57600080fd5b601d61024061024051015110610b8157600080fd5b6000506102608051602001806101808284600060045af1610ba157600080fd5b50506101a0516060610300600463258294106102a0526102bc6004355afa610bc857600080fd5b603f3d11610bd557600080fd5b601d61030061030051015110610bea57600080fd5b6000506103206020015114610bfe57600080fd5b6020610240600463fc0c546a6101e0526101fc6004355afa610c1f57600080fd5b601f3d11610c2c57600080fd5b60005061024051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605161026051610280526004356102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052600050005b632cad8f9f811415610cc357600161014052610cf4565b635e05f6af811415610cef5760243560011c15610cdf57600080fd5b6020602461014037600050610cf4565b610d29565b60043560a01c15610d0457600080fd5b6007543314610d1257600080fd5b61014051600a60043560e05260c052604060c02055005b6360bd68f8811415610ea25760043560a01c15610d4557600080fd5b6098602435600401610140376078602435600401351115610d6557600080fd5b600754331815610d8757600a3360e05260c052604060c02054610d8757600080fd5b61014080600960043560e05260c052604060c02060c052602060c020602082510161012060006005818352015b82610120516020021115610dc757610de9565b61012051602002850151610120518501555b8151600101808352811415610db4575b50505050505060043561024052604061020052610200516102605261014080516020018061020051610240018284600060045af1610e2657600080fd5b505061020051610240015180602061020051610240010101818260206001820306601f8201039050033682375050602061020051610240015160206001820306601f8201039050610200510101610200527f07bd58794c2ca0ae152f7719eb5f02c654476de972cadec0e8191ae8be42096d61020051610240a1005b6356e0a94b811415610eba5760005460005260206000f35b63b6a9f40f811415610ee057600160043560e05260c052604060c0205460005260206000f35b63f9c7bba5811415610f165760043560a01c15610efc57600080fd5b600260043560e05260c052604060c0205460005260206000f35b637bbfc69e811415610f5a5760043560a01c15610f3257600080fd5b600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b634f64b2be811415610f8057600460043560e05260c052604060c0205460005260206000f35b638e499bcf811415610f985760055460005260206000f35b63c3c5a547811415610fce5760043560a01c15610fb457600080fd5b600660043560e05260c052604060c0205460005260206000f35b635aa6e675811415610fe65760075460005260206000f35b63f39c38a0811415610ffe5760085460005260206000f35b6358b8f8428114156110c15760043560a01c1561101a57600080fd5b600960043560e05260c052604060c0208060c052602060c020610180602082540161012060006005818352015b8261012051602002111561105a5761107c565b61012051850154610120516020028501525b8151600101808352811415611047575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63ee711ed58114156110f75760043560a01c156110dd57600080fd5b600a60043560e05260c052604060c0205460005260206000f35b505b60006000fd5b610220526101405261016052610180526101a0526101c0526101e05261020052610240526000610320525b6102405160206001820306601f82010390506103205110151561114c57611165565b610320516102600152610320516020016103205261112a565b6102c0526000610320525b6102c05160206001820306601f820103905061032051101515611192576111ab565b610320516102e001526103205160200161032052611170565b60005060016102005160e05260c052604060c0205461034052600061034051186111d457600080fd5b7f602d3d8160093d39f3363d3d373d3d3d363d7300000000000000000000000000610380526103405160601b610393527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006103a75260366103806000f061036052610360513b61124357600080fd5b6000600061016460c063a5b81fdf61038052610140516103a052610160516103c052610180516103e052806104005261024080805160200180846103a0018284600060045af161129257600080fd5b50508051820160206001820306601f820103905060200191505080610420526102c080805160200180846103a0018284600060045af16112d157600080fd5b5050506101a0516104405261039c90506000610360515af16112f257600080fd5b6103605160005260005161022051565b61018052610140526101605260026101405160e05260c052604060c020546101a05260006101a0511115611409576060610460600463258294106104005261041c610160515afa61135257600080fd5b603f3d1161135f57600080fd5b601d6104606104605101511061137457600080fd5b6000506104806020015160606103a0600463258294106103405261035c60036101405160e05260c052604060c0206101a0516001808210156113b557600080fd5b8082039050905060e05260c052604060c020545afa6113d357600080fd5b603f3d116113e057600080fd5b601d6103a06103a0510151106113f557600080fd5b6000506103c0602001511861140957600080fd5b6101605160036101405160e05260c052604060c0206101a05160e05260c052604060c020556101a051600181818301101561144357600080fd5b8082019050905060026101405160e05260c052604060c0205560066101405160e05260c052604060c0205415156114bd57600160066101405160e05260c052604060c0205561014051600460055460e05260c052604060c020556005805460018181830110156114b257600080fd5b808201905090508155505b6060610220600463258294106101c0526101dc610160515afa6114df57600080fd5b603f3d116114ec57600080fd5b601d6102206102205101511061150157600080fd5b6000506102408051602001806102808284600060045af161152157600080fd5b5050610160516103205260406102e0526102e051610340526102808051602001806102e051610320018284600060045af161155b57600080fd5b50506102e05161032001518060206102e051610320010101818260206001820306601f820103905003368237505060206102e051610320015160206001820306601f82010390506102e05101016102e0526101a051610140517fce089905ba4a4d622553bcb5646fd23e895c256f0376eee04e99e61cec1dc7e86102e051610320a36101805156", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/external/abi/yearn/Vault.json b/external/abi/yearn/Vault.json new file mode 100644 index 000000000..cd9256e8e --- /dev/null +++ b/external/abi/yearn/Vault.json @@ -0,0 +1,1666 @@ +{ + "_format": "hh-vyper-artifact-1", + "contractName": "Vault", + "abi": [ + { + "name": "Transfer", + "inputs": [ + { + "name": "sender", + "type": "address", + "indexed": true + }, + { + "name": "receiver", + "type": "address", + "indexed": true + }, + { + "name": "value", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true + }, + { + "name": "spender", + "type": "address", + "indexed": true + }, + { + "name": "value", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyAdded", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "debtRatio", + "type": "uint256", + "indexed": false + }, + { + "name": "minDebtPerHarvest", + "type": "uint256", + "indexed": false + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256", + "indexed": false + }, + { + "name": "performanceFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyReported", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "gain", + "type": "uint256", + "indexed": false + }, + { + "name": "loss", + "type": "uint256", + "indexed": false + }, + { + "name": "debtPaid", + "type": "uint256", + "indexed": false + }, + { + "name": "totalGain", + "type": "uint256", + "indexed": false + }, + { + "name": "totalLoss", + "type": "uint256", + "indexed": false + }, + { + "name": "totalDebt", + "type": "uint256", + "indexed": false + }, + { + "name": "debtAdded", + "type": "uint256", + "indexed": false + }, + { + "name": "debtRatio", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateGovernance", + "inputs": [ + { + "name": "governance", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateManagement", + "inputs": [ + { + "name": "management", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateGuestList", + "inputs": [ + { + "name": "guestList", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateRewards", + "inputs": [ + { + "name": "rewards", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateDepositLimit", + "inputs": [ + { + "name": "depositLimit", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdatePerformanceFee", + "inputs": [ + { + "name": "performanceFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateManagementFee", + "inputs": [ + { + "name": "managementFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateGuardian", + "inputs": [ + { + "name": "guardian", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "EmergencyShutdown", + "inputs": [ + { + "name": "active", + "type": "bool", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateWithdrawalQueue", + "inputs": [ + { + "name": "queue", + "type": "address[20]", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdateDebtRatio", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "debtRatio", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdateMinDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "minDebtPerHarvest", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdateMaxDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdatePerformanceFee", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "performanceFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyMigrated", + "inputs": [ + { + "name": "oldVersion", + "type": "address", + "indexed": true + }, + { + "name": "newVersion", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyRevoked", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyRemovedFromQueue", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyAddedToQueue", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "nameOverride", + "type": "string" + }, + { + "name": "symbolOverride", + "type": "string" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "nameOverride", + "type": "string" + }, + { + "name": "symbolOverride", + "type": "string" + }, + { + "name": "guardian", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "pure", + "type": "function", + "name": "apiVersion", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 4546 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setName", + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "outputs": [], + "gas": 107044 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setSymbol", + "inputs": [ + { + "name": "symbol", + "type": "string" + } + ], + "outputs": [], + "gas": 71894 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGovernance", + "inputs": [ + { + "name": "governance", + "type": "address" + } + ], + "outputs": [], + "gas": 36365 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "acceptGovernance", + "inputs": [], + "outputs": [], + "gas": 37637 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setManagement", + "inputs": [ + { + "name": "management", + "type": "address" + } + ], + "outputs": [], + "gas": 37775 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGuestList", + "inputs": [ + { + "name": "guestList", + "type": "address" + } + ], + "outputs": [], + "gas": 37805 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setRewards", + "inputs": [ + { + "name": "rewards", + "type": "address" + } + ], + "outputs": [], + "gas": 37835 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setLockedProfitDegration", + "inputs": [ + { + "name": "degration", + "type": "uint256" + } + ], + "outputs": [], + "gas": 36519 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setDepositLimit", + "inputs": [ + { + "name": "limit", + "type": "uint256" + } + ], + "outputs": [], + "gas": 37795 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setPerformanceFee", + "inputs": [ + { + "name": "fee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 37929 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setManagementFee", + "inputs": [ + { + "name": "fee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 37959 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGuardian", + "inputs": [ + { + "name": "guardian", + "type": "address" + } + ], + "outputs": [], + "gas": 39203 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setEmergencyShutdown", + "inputs": [ + { + "name": "active", + "type": "bool" + } + ], + "outputs": [], + "gas": 39274 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setWithdrawalQueue", + "inputs": [ + { + "name": "queue", + "type": "address[20]" + } + ], + "outputs": [], + "gas": 763950 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 76768 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 116531 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 38271 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "increaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 40312 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "decreaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 40336 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "permit", + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + }, + { + "name": "expiry", + "type": "uint256" + }, + { + "name": "signature", + "type": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 81264 + }, + { + "stateMutability": "view", + "type": "function", + "name": "totalAssets", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 4098 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "deposit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_amount", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "maxAvailableShares", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 366010 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "maxShares", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "maxShares", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "maxShares", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + }, + { + "name": "maxLoss", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "pricePerShare", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 17041 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "addStrategy", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "debtRatio", + "type": "uint256" + }, + { + "name": "minDebtPerHarvest", + "type": "uint256" + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256" + }, + { + "name": "performanceFee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 1485796 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyDebtRatio", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "debtRatio", + "type": "uint256" + } + ], + "outputs": [], + "gas": 115193 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyMinDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "minDebtPerHarvest", + "type": "uint256" + } + ], + "outputs": [], + "gas": 42441 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyMaxDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256" + } + ], + "outputs": [], + "gas": 42471 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyPerformanceFee", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "performanceFee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 41251 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "migrateStrategy", + "inputs": [ + { + "name": "oldVersion", + "type": "address" + }, + { + "name": "newVersion", + "type": "address" + } + ], + "outputs": [], + "gas": 1141468 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "revokeStrategy", + "inputs": [], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "revokeStrategy", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "addStrategyToQueue", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [], + "gas": 1199804 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "removeStrategyFromQueue", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [], + "gas": 23088703 + }, + { + "stateMutability": "view", + "type": "function", + "name": "debtOutstanding", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "debtOutstanding", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "creditAvailable", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "creditAvailable", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "availableDepositLimit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 9551 + }, + { + "stateMutability": "view", + "type": "function", + "name": "expectedReturn", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "expectedReturn", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "report", + "inputs": [ + { + "name": "gain", + "type": "uint256" + }, + { + "name": "loss", + "type": "uint256" + }, + { + "name": "_debtPayment", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1117355 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 8750 + }, + { + "stateMutability": "view", + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 7803 + }, + { + "stateMutability": "view", + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2408 + }, + { + "stateMutability": "view", + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2653 + }, + { + "stateMutability": "view", + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "arg0", + "type": "address" + }, + { + "name": "arg1", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2898 + }, + { + "stateMutability": "view", + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2498 + }, + { + "stateMutability": "view", + "type": "function", + "name": "token", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2528 + }, + { + "stateMutability": "view", + "type": "function", + "name": "governance", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2558 + }, + { + "stateMutability": "view", + "type": "function", + "name": "management", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2588 + }, + { + "stateMutability": "view", + "type": "function", + "name": "guardian", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2618 + }, + { + "stateMutability": "view", + "type": "function", + "name": "guestList", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2648 + }, + { + "stateMutability": "view", + "type": "function", + "name": "strategies", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "performanceFee", + "type": "uint256" + }, + { + "name": "activation", + "type": "uint256" + }, + { + "name": "debtRatio", + "type": "uint256" + }, + { + "name": "minDebtPerHarvest", + "type": "uint256" + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256" + }, + { + "name": "lastReport", + "type": "uint256" + }, + { + "name": "totalDebt", + "type": "uint256" + }, + { + "name": "totalGain", + "type": "uint256" + }, + { + "name": "totalLoss", + "type": "uint256" + } + ], + "gas": 11001 + }, + { + "stateMutability": "view", + "type": "function", + "name": "withdrawalQueue", + "inputs": [ + { + "name": "arg0", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2817 + }, + { + "stateMutability": "view", + "type": "function", + "name": "emergencyShutdown", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 2738 + }, + { + "stateMutability": "view", + "type": "function", + "name": "depositLimit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2768 + }, + { + "stateMutability": "view", + "type": "function", + "name": "debtRatio", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2798 + }, + { + "stateMutability": "view", + "type": "function", + "name": "totalDebt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2828 + }, + { + "stateMutability": "view", + "type": "function", + "name": "delegatedAssets", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2858 + }, + { + "stateMutability": "view", + "type": "function", + "name": "lastReport", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2888 + }, + { + "stateMutability": "view", + "type": "function", + "name": "activation", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2918 + }, + { + "stateMutability": "view", + "type": "function", + "name": "lockedProfit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2948 + }, + { + "stateMutability": "view", + "type": "function", + "name": "lockedProfitDegration", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2978 + }, + { + "stateMutability": "view", + "type": "function", + "name": "rewards", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 3008 + }, + { + "stateMutability": "view", + "type": "function", + "name": "managementFee", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 3038 + }, + { + "stateMutability": "view", + "type": "function", + "name": "performanceFee", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 3068 + }, + { + "stateMutability": "view", + "type": "function", + "name": "nonces", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 3313 + }, + { + "stateMutability": "view", + "type": "function", + "name": "DOMAIN_SEPARATOR", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "gas": 3128 + } + ], + "bytecode": "0x61511956600436101561000d57613ced565b600035601c52600051341561002157600080fd5b6383b43589811415610037573361022052610068565b63a5b81fdf8114156100635760a43560a01c1561005357600080fd5b602060a461022037600050610068565b6106ab565b60043560a01c1561007857600080fd5b60243560a01c1561008857600080fd5b60443560a01c1561009857600080fd5b60606064356004016101403760406064356004013511156100b857600080fd5b60406084356004016101c03760206084356004013511156100d857600080fd5b601554156100e557600080fd5b600435600655600061028052610280805160208201209050610140805160208201209050141561022c576000606061032060046395d89b416102c0526102dc6004355afa61013257600080fd5b603f3d1161013f57600080fd5b60156103206103205101511061015457600080fd5b6000506103406014806020846103e001018260208501600060045af15050805182019150506007610380527f20795661756c74000000000000000000000000000000000000000000000000006103a0526103806007806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600060c052602060c020602082510161012060006002818352015b826101205160200211156101ff57610221565b61012051602002850151610120518501555b81516001018083528114156101ec575b505050505050610287565b61014080600060c052602060c020602082510161012060006003818352015b8261012051602002111561025e57610280565b61012051602002850151610120518501555b815160010180835281141561024b575b5050505050505b6000610280526102808051602082012090506101c080516020820120905014156103c857600060026102c0527f79760000000000000000000000000000000000000000000000000000000000006102e0526102c06002806020846103e001018260208501600060045af1505080518201915050606061038060046395d89b416103205261033c6004355afa61031b57600080fd5b603f3d1161032857600080fd5b60156103806103805101511061033d57600080fd5b6000506103a06014806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600160c052602060c020602082510161012060006002818352015b8261012051602002111561039b576103bd565b61012051602002850151610120518501555b8151600101808352811415610388575b505050505050610423565b6101c080600160c052602060c020602082510161012060006002818352015b826101205160200211156103fa5761041c565b61012051602002850151610120518501555b81516001018083528114156103e7575b5050505050505b60206102a0600463313ce5676102405261025c6004355afa61044457600080fd5b601f3d1161045157600080fd5b6000506102a051600255602435600755602435610240527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610240a1602435600855602435610240527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610240a1604435601855604435610240527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610240a16102205160095561022051610240527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610240a16103e8601a556103e86102405261024051610260527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610260a160c860195560c86102405261024051610260527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610260a142601455426015556529d635a8e00060175560007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f602082610620010152602081019050600b610500527f596561726e205661756c740000000000000000000000000000000000000000006105205261050080516020820120905060208261062001015260208101905060056105c0527f302e332e340000000000000000000000000000000000000000000000000000006105e0526105c0805160208201209050602082610620010152602081019050466020826106200101526020810190503060208261062001015260208101905080610620526106209050805160208201209050601c55005b6325829410811415610740576005610140527f302e332e34000000000000000000000000000000000000000000000000000000610160526101408051602001806101e08284600060045af16106ff57600080fd5b50506101e0518061020001818260206001820306601f820103905003368237505060206101c05260406101e0510160206001820306601f82010390506101c0f35b63c47f00278114156107d657604a60043560040161014037602a60043560040135111561076c57600080fd5b600754331461077a57600080fd5b61014080600060c052602060c020602082510161012060006003818352015b826101205160200211156107ac576107ce565b61012051602002850151610120518501555b8151600101808352811415610799575b505050505050005b63b84c824681141561086c57603460043560040161014037601460043560040135111561080257600080fd5b600754331461081057600080fd5b61014080600160c052602060c020602082510161012060006002818352015b8261012051602002111561084257610864565b61012051602002850151610120518501555b815160010180835281141561082f575b505050505050005b63ab033ea981141561089e5760043560a01c1561088857600080fd5b600754331461089657600080fd5b600435600a55005b63238efcbc8114156108ea57600a5433146108b857600080fd5b3360075533610140527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610140a1005b63d4a22bde81141561094a5760043560a01c1561090657600080fd5b600754331461091457600080fd5b600435600855600435610140527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610140a1005b630b5b78eb8114156109aa5760043560a01c1561096657600080fd5b600754331461097457600080fd5b600435600b55600435610140527f6d674c311329fb38bbc96dc33d2aad03b9bf9fcfdd8f5e5054fda291a5b3c1f86020610140a1005b63ec38a862811415610a0a5760043560a01c156109c657600080fd5b60075433146109d457600080fd5b600435601855600435610140527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610140a1005b638402a84f811415610a43576007543314610a2457600080fd5b670de0b6b3a76400006004351115610a3b57600080fd5b600435601755005b63bdc8144b811415610a93576007543314610a5d57600080fd5b600435600f55600435610140527fae565aab888bca5e19e25a13db7b0c9144305bf55cb0f3f4d724f730e5acdd626020610140a1005b6370897b23811415610af4576007543314610aad57600080fd5b6127106004351115610abe57600080fd5b600435601a55600435610140527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610140a1005b63fe56e232811415610b55576007543314610b0e57600080fd5b6127106004351115610b1f57600080fd5b600435601955600435610140527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610140a1005b638a0dac4a811415610c065760043560a01c15610b7157600080fd5b600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610bb05760018352610bc0565b8151600101808352811415610b94575b50505061014051610bd057600080fd5b600435600955600435610140527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610140a1005b6314c64402811415610cd25760043560011c15610c2257600080fd5b60043515610c8e57600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610c695760018352610c79565b8151600101808352811415610c4d575b50505061014051610c8957600080fd5b610c9c565b6007543314610c9c57600080fd5b600435600e55600435610140527fba40372a3a724dca3c57156128ef1e896724b65b37a17f190b1ad5de68f3a4f36020610140a1005b6394148415811415610f17576000610120525b610120516004013560a01c15610cfa57600080fd5b6020610120510161012052610280610120511015610d1757610ce5565b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610d565760018352610d66565b8151600101808352811415610d3a575b50505061014051610d7657600080fd5b61014060006014818352015b60046101405160148110610d9557600080fd5b60200201351515610dc3576101405160148110610db157600080fd5b600d60c052602060c020015415610dc6565b60005b15610dd057610e53565b60006001600c60046101405160148110610de957600080fd5b602002013560e05260c052604060c02060c052602060c020015411610e0d57600080fd5b60046101405160148110610e2057600080fd5b60200201356101405160148110610e3657600080fd5b600d60c052602060c02001555b8151600101808352811415610d82575b50506004356101405260243561016052604435610180526064356101a0526084356101c05260a4356101e05260c4356102005260e43561022052610104356102405261012435610260526101443561028052610164356102a052610184356102c0526101a4356102e0526101c435610300526101e43561032052610204356103405261022435610360526102443561038052610264356103a0527f695ac3ac73f08f2002284ffe563cefe798ee2878a5e04219522e2e99eb89d168610280610140a1005b63a9059cbb811415610f695760043560a01c15610f3357600080fd5b336101405260043561016052602435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b6323b872dd81141561109e5760043560a01c15610f8557600080fd5b60243560a01c15610f9557600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460043560e05260c052604060c0203360e05260c052604060c02054101561106657600460043560e05260c052604060c0203360e05260c052604060c020546044358082101561100657600080fd5b808203905090506101405261014051600460043560e05260c052604060c0203360e05260c052604060c020556101405161016052336004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610160a35b6004356101405260243561016052604435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b63095ea7b38114156111175760043560a01c156110ba57600080fd5b60243560043360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63395093518114156111c45760043560a01c1561113357600080fd5b60043360e05260c052604060c02060043560e05260c052604060c020805460243581818301101561116357600080fd5b8082019050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63a457c2d781141561126f5760043560a01c156111e057600080fd5b60043360e05260c052604060c02060043560e05260c052604060c02080546024358082101561120e57600080fd5b8082039050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b639fd5a6cf81141561166c5760043560a01c1561128b57600080fd5b60243560a01c1561129b57600080fd5b60616084356004016101403760416084356004013511156112bb57600080fd5b6000600435186112ca57600080fd5b60643515156112da5760016112e1565b4260643510155b6112ea57600080fd5b601b60043560e05260c052604060c020546101e05260006002610520527f19010000000000000000000000000000000000000000000000000000000000006105405261052060028060208461078001018260208501600060045af1505080518201915050601c5460208261078001015260208101905060007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c96020826106800101526020810190506004356020826106800101526020810190506024356020826106800101526020810190506044356020826106800101526020810190506101e05160208261068001015260208101905060643560208261068001015260208101905080610680526106809050805160208201209050602082610780010152602081019050806107805261078090508051602082012090506102005260006020602082066103000161014051828401111561144457600080fd5b6041806103208260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561148257600080fd5b809190121561149057600080fd5b806020036101000a8204905090509050610220526020602060208206610320016101405182840111156114c257600080fd5b6041806103408260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561150057600080fd5b809190121561150e57600080fd5b806020036101000a82049050905090506102405260406001602082066103400161014051828401111561154057600080fd5b6041806103608260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561157e57600080fd5b809190121561158c57600080fd5b806020036101000a8204905090509050610260526004356102005161028052610260516102a052610220516102c052610240516102e052602060c0608061028060015afa5060c051146115de57600080fd5b604435600460043560e05260c052604060c02060243560e05260c052604060c020556101e051600181818301101561161557600080fd5b80820190509050601b60043560e05260c052604060c02055604435610280526024356004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610280a3600160005260206000f35b6301e1d1148114156116925760065801614149565b610140526101405160005260206000f35b63d0e30db08114156116cd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052611727565b63b6b55f258114156116ee5733610160526020600461014037600050611727565b636e553f6581141561172257602060046101403760243560a01c1561171257600080fd5b6020602461016037600050611727565b61197b565b601d541561173457600080fd5b6001601d55600e541561174657600080fd5b61014051610180527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61018051141561181057600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051808210156117b257600080fd5b80820390509050602061024060246370a082316101c052336101e0526101dc6006545afa6117df57600080fd5b601f3d116117ec57600080fd5b60005061024051808211156118015780611803565b815b9050905061018052611861565b600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a0516101805181818301101561184f57600080fd5b80820190509050111561186157600080fd5b6000610180511161187157600080fd5b6000600b5418156118c75760206102406044635ed7660e6101a052336101c052610180516101e0526101bc600b545afa6118aa57600080fd5b601f3d116118b757600080fd5b600050610240516118c757600080fd5b6101405161016051610180516101a051610160516101c052610180516101e0526101e0516101c051600658016141ac565b610240526101a052610180526101605261014052610240516101a0526101405161016051610180516101a0516006546101c052336101e0523061020052610180516102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000506101a0516000526000601d5560206000f35b6375de2902811415611ad95760206101e060246370a0823161016052306101805261017c6006545afa6119ad57600080fd5b601f3d116119ba57600080fd5b6000506101e051610200526101405161016051610180516101a0516101c0516101e051610200516102005161022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515611a4d57611aca565b61014080516101405161016051610180516006600c6101605160e05260c052604060c02060c052602060c02001546101a0526101a0516006580161445a565b6102005261018052610160526101405261020051818183011015611aaf57600080fd5b808201905090508152505b8151600101808352811415611a2a575b50506101405160005260206000f35b633ccfd60b811415611b1a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052600161018052611bbb565b632e1a7d4d811415611b415733610160526001610180526020600461014037600050611bbb565b62f714ce811415611b7a57600161018052602060046101403760243560a01c15611b6a57600080fd5b6020602461016037600050611bbb565b63e63697c8811415611bb657602060046101403760243560a01c15611b9e57600080fd5b60206024610160376020604461018037600050611bbb565b612214565b601d5415611bc857600080fd5b6001601d55610140516101a052612710610180511115611be757600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101a0511415611c265760033360e05260c052604060c020546101a0525b60033360e05260c052604060c020546101a0511115611c4457600080fd5b60006101a05111611c5457600080fd5b6101405161016051610180516101a0516101c0516101a0516101e0526101e051600658016142e6565b610240526101c0526101a052610180526101605261014052610240516101c05260006101e052602061028060246370a0823161020052306102205261021c6006545afa611cc957600080fd5b601f3d11611cd657600080fd5b600050610280516101c0511115612001576102c060006014818352015b6102c051600d60c052602060c02001546102a0526102a0511515611d1657611ffe565b602061038060246370a0823161030052306103205261031c6006545afa611d3c57600080fd5b601f3d11611d4957600080fd5b600050610380516102e0526102e0516101c051111515611d6857611ffe565b6101c0516102e05180821015611d7d57600080fd5b8082039050905061030052610300516006600c6102a05160e05260c052604060c02060c052602060c020015480821115611db75780611db9565b815b9050905061030052610300511515611dd057611fee565b60206103c06024632e1a7d4d61034052610300516103605261035c60006102a0515af1611dfc57600080fd5b601f3d11611e0957600080fd5b6000506103c0516103205260206103e060246370a0823161036052306103805261037c6006545afa611e3a57600080fd5b601f3d11611e4757600080fd5b6000506103e0516102e05180821015611e5f57600080fd5b80820390509050610340526000610320511115611ef4576101c080516103205180821015611e8c57600080fd5b808203905090508152506101e0805161032051818183011015611eae57600080fd5b808201905090508152506008600c6102a05160e05260c052604060c02060c052602060c02001805461032051818183011015611ee957600080fd5b808201905090508155505b6006600c6102a05160e05260c052604060c02060c052602060c0200180546103405161032051818183011015611f2957600080fd5b8082019050905080821015611f3d57600080fd5b80820390509050815550601180546103405161032051818183011015611f6257600080fd5b8082019050905080821015611f7657600080fd5b80820390509050815550610140610360525b61036051516020610360510161036052610360610360511015611faa57611f88565b6102a05161038052610380516006580161452f565b610340610360525b6103605152602061036051036103605261014061036051101515611fea57611fc7565b6000505b8151600101808352811415611cf3575b50505b60206102a060246370a0823161022052306102405261023c6006545afa61202757600080fd5b601f3d1161203457600080fd5b6000506102a05161020052610200516101c05111156120c957610200516101c0526101405161016051610180516101a0516101c0516101e051610200516101c0516101e05181818301101561208857600080fd5b8082019050905061022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101a0525b610180516101c0516101e0518181830110156120e457600080fd5b8082019050905080820282158284830414176120ff57600080fd5b80905090509050612710808204905090506101e051111561211f57600080fd5b600580546101a0518082101561213457600080fd5b8082039050905081555060033360e05260c052604060c02080546101a0518082101561215f57600080fd5b808203905090508155506101a051610220526000337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610220a36101405161016051610180516101a0516101c0516101e051610200516006546102205261016051610240526101c0516102605261026051610240516102205160065801613cf3565b610200526101e0526101c0526101a0526101805261016052610140526000506101c0516000526000601d5560206000f35b6399530b0681141561225757604e6002541061222f57600080fd5b600254600a0a6101405261014051600658016142e6565b6101a0526101a05160005260206000f35b6314b4e26e81141561249c5760043560a01c1561227357600080fd5b6013600d60c052602060c02001541561228b57600080fd5b600e541561229857600080fd5b60075433146122a657600080fd5b6000600435186122b557600080fd5b6001600c60043560e05260c052604060c02060c052602060c0200154156122db57600080fd5b60206101a0600463fbfa77cf6101405261015c6004355afa6122fc57600080fd5b601f3d1161230957600080fd5b6000506101a051301461231b57600080fd5b60206101a06004631f1fcd516101405261015c6004355afa61233c57600080fd5b601f3d1161234957600080fd5b6000506101a0516006541461235d57600080fd5b61271060105460243581818301101561237557600080fd5b80820190509050111561238757600080fd5b606435604435111561239857600080fd5b612710601a54808210156123ab57600080fd5b8082039050905060843511156123c057600080fd5b600c60043560e05260c052604060c02060c052602060c0206084358155426001820155602435600282015560443560038201556064356004820155426005820155600060068201556000600782015560006008820155506024356101405260443561016052606435610180526084356101a0526004357f5a6abd2af9fe6c0554fa08649e2d86e4393ff19dc304d072d38d295c9291d4dc6080610140a26010805460243581818301101561247357600080fd5b808201905090508155506004356013600d60c052602060c02001556006580161460c565b600050005b637c6a4f248114156125f95760043560a01c156124b857600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156124f75760018352612507565b81516001018083528114156124db575b5050506101405161251757600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161253f57600080fd5b601080546002600c60043560e05260c052604060c02060c052602060c02001548082101561256c57600080fd5b808203905090508155506024356002600c60043560e05260c052604060c02060c052602060c0200155601080546024358181830110156125ab57600080fd5b8082019050905081555061271060105411156125c657600080fd5b602435610140526004357fbda9398315c83ccef012bcaa318a2ff7b680f36429d36597bd4bc25ac11ead596020610140a2005b63e722befe8114156127185760043560a01c1561261557600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156126545760018352612664565b8151600101808352811415612638575b5050506101405161267457600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161269c57600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c020015410156126c657600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f0b728ad785976532c4aaadde09b1cba5f262a7090e83c62d2377bc405678b29c6020610140a2005b634757a1568114156128375760043560a01c1561273457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156127735760018352612783565b8151600101808352811415612757575b5050506101405161279357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116127bb57600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c020015411156127e557600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f1796a8e0760e2de5b72e7bf64fccb7666c48ceab94cb6cae7cb7eff4b6f641ab6020610140a2005b63d0194ed68114156129005760043560a01c1561285357600080fd5b600754331461286157600080fd5b612710601a548082101561287457600080fd5b80820390509050602435111561288957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116128b157600080fd5b602435600c60043560e05260c052604060c02060c052602060c02055602435610140526004357fe57488a65fa53066d4c25bac90db47dda4e5de3025ac12bf76ff07211cf7f39e6020610140a2005b636cb56d19811415612c185760043560a01c1561291c57600080fd5b60243560a01c1561292c57600080fd5b600754331461293a57600080fd5b60006024351861294957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161297157600080fd5b6001600c60243560e05260c052604060c02060c052602060c02001541561299757600080fd5b610140600c60043560e05260c052604060c0208060c052602060c02054825260018160c052602060c0200154826020015260028160c052602060c0200154826040015260038160c052602060c0200154826060015260048160c052602060c0200154826080015260058160c052602060c02001548260a0015260068160c052602060c02001548260c0015260078160c052602060c02001548260e0015260088160c052602060c020015482610100015250506101405161016051610180516101a0516101c0516101e0516102005161022051610240516004356102605261026051600658016146ef565b6102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506010805461018051818183011015612abf57600080fd5b8082019050905081555060006006600c60043560e05260c052604060c02060c052602060c0200155600c60243560e05260c052604060c02060c052602060c0206101405181556101e05160018201556101805160028201556101a05160038201556101c05160048201556101e05160058201556102005160068201556000600782015560006008820155506004353b612b5757600080fd5b60006000602463ce5494bb610260526024356102805261027c60006004355af1612b8057600080fd5b6024356004357f100b69bb6b504e1252e36b375233158edee64d071b399e2f81473a695fd1b02160006000a361026060006014818352015b6004356102605160148110612bcc57600080fd5b600d60c052602060c02001541415612c04576024356102605160148110612bf257600080fd5b600d60c052602060c020015560006000f35b8151600101808352811415612bb8575b5050005b63a0e4af9a811415612c2e573361014052612c5f565b63bb994d48811415612c5a5760043560a01c15612c4a57600080fd5b6020600461014037600050612c5f565b612ce8565b61014051610180526007546101a0526009546101c05260006101605261016061012060006003818352015b610120516020026101800151331415612ca65760018352612cb6565b8151600101808352811415612c8a575b50505061016051612cc657600080fd5b61014051610140516101605261016051600658016146ef565b61014052600050005b63f76e4caa811415612e645760043560a01c15612d0457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612d435760018352612d53565b8151600101808352811415612d27575b50505061014051612d6357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c020015411612d8b57600080fd5b60006101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515612dc057612e02565b6004356101605118612dd157600080fd5b61014080516001818183011015612de757600080fd5b808201905090508152505b8151600101808352811415612d9d575b505060146101405110612e1457600080fd5b6004356013600d60c052602060c0200155610140516006580161460c565b610140526000506004357fa8727d412c6fa1e2497d6d6f275e2d9fe4d9318d5b793632e60ad9d38ee8f1fa60006000a2005b63b22439f5811415612f8b5760043560a01c15612e8057600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612ebf5760018352612ecf565b8151600101808352811415612ea3575b50505061014051612edf57600080fd5b61014060006014818352015b6004356101405160148110612eff57600080fd5b600d60c052602060c02001541415612f735760006101405160148110612f2457600080fd5b600d60c052602060c0200155610140516006580161460c565b610140526000506004357f8e1ec3c16d6a67ea8effe2ac7adef9c2de0bc0dc47c49cdf18f6a8b0048085be60006000a260006000f35b8151600101808352811415612eeb575b505060006000fd5b63bf3759b5811415612fa1573361014052612fd2565b63bdcf36bb811415612fcd5760043560a01c15612fbd57600080fd5b6020600461014037600050612fd2565b613000565b610140516101405161016052610160516006580161477e565b6101c052610140526101c05160005260206000f35b63112c1f9b811415613016573361014052613047565b63d76480138114156130425760043560a01c1561303257600080fd5b6020600461014037600050613047565b613075565b610140516101405161016052610160516006580161487d565b6101c052610140526101c05160005260206000f35b63153c27c48114156130e55760065801614149565b6101405261014051600f5411156130d857600f546101405160065801614149565b610160526101405261016051808210156130c457600080fd5b8082039050905060005260206000f36130e3565b600060005260206000f35b005b63d3406abd8114156130fb57336101405261312c565b6333586b678114156131275760043560a01c1561311757600080fd5b602060046101403760005061312c565b61315a565b6101405161014051610160526101605160065801614ad8565b6101c052610140526101c05160005260206000f35b63a1d9bafc81141561367f5760006001600c3360e05260c052604060c02060c052602060c02001541161318c57600080fd5b6004356044358181830110156131a157600080fd5b8082019050905060206101c060246370a0823161014052336101605261015c6006545afa6131ce57600080fd5b601f3d116131db57600080fd5b6000506101c05110156131ed57600080fd5b6000602435111561321957336101405260243561016052610160516101405160065801614c2b565b6000505b336101405260043561016052610160516101405160065801614e0d565b6000506007600c3360e05260c052604060c02060c052602060c02001805460043581818301101561326657600080fd5b80820190509050815550610140513361016052610160516006580161477e565b6101c052610140526101c0516101405260443561014051808211156132ab57806132ad565b815b90509050610160526000610160511115613337576006600c3360e05260c052604060c02060c052602060c02001805461016051808210156132ed57600080fd5b8082039050905081555060118054610160518082101561330c57600080fd5b808203905090508155506101408051610160518082101561332c57600080fd5b808203905090508152505b610140516101605161018051336101a0526101a0516006580161487d565b61020052610180526101605261014052610200516101805260006101805111156133d3576006600c3360e05260c052604060c02060c052602060c020018054610180518181830110156133a757600080fd5b8082019050905081555060118054610180518181830110156133c857600080fd5b808201905090508155505b600435610160518181830110156133e957600080fd5b808201905090506101a052610180516101a051101561346b576101405161016051610180516101a0516006546101c052336101e052610180516101a0518082101561343357600080fd5b8082039050905061020052610200516101e0516101c05160065801613cf3565b6101a0526101805261016052610140526000506134e7565b610180516101a05111156134e7576101405161016051610180516101a0516006546101c052336101e05230610200526101a05161018051808210156134af57600080fd5b808203905090506102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000505b6101405161016051610180516101a051336101c0526101c0516006580161452f565b6101a052610180526101605261014052600050426005600c3360e05260c052604060c02060c052602060c0200155426014556004356016556004356101c0526024356101e05261016051610200526007600c3360e05260c052604060c02060c052602060c0200154610220526008600c3360e05260c052604060c02060c052602060c0200154610240526006600c3360e05260c052604060c02060c052602060c02001546102605261018051610280526002600c3360e05260c052604060c02060c052602060c02001546102a052337f67f96d2854a335a4cadb49f84fd3ca6f990744ddb3feceeb4b349d2d53d32ad36101006101c0a26002600c3360e05260c052604060c02060c052602060c0200154151561362757600161362b565b600e545b15613670576020610220600463efbb5cb06101c0526101dc335afa61364f57600080fd5b601f3d1161365c57600080fd5b6000506102205160005260206000f361367d565b6101405160005260206000f35b005b6301681a628114156136b5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140526136d6565b636ea056a98114156136d15760206024610140376000506136d6565b6137b6565b60043560a01c156136e657600080fd5b60075433146136f457600080fd5b6006546004351861370457600080fd5b61014051610160527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61016051141561377657602061020060246370a0823161018052306101a05261019c6004355afa61375d57600080fd5b601f3d1161376a57600080fd5b60005061020051610160525b6101405161016051600435610180526007546101a052610160516101c0526101c0516101a0516101805160065801613cf3565b6101605261014052600050005b6306fdde0381141561385b5760008060c052602060c020610180602082540161012060006003818352015b826101205160200211156137f457613816565b61012051850154610120516020028501525b81516001018083528114156137e1575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b6395d89b418114156139005760018060c052602060c020610180602082540161012060006002818352015b82610120516020021115613899576138bb565b61012051850154610120516020028501525b8151600101808352811415613886575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63313ce5678114156139185760025460005260206000f35b6370a0823181141561394e5760043560a01c1561393457600080fd5b600360043560e05260c052604060c0205460005260206000f35b63dd62ed3e8114156139a25760043560a01c1561396a57600080fd5b60243560a01c1561397a57600080fd5b600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b6318160ddd8114156139ba5760055460005260206000f35b63fc0c546a8114156139d25760065460005260206000f35b635aa6e6758114156139ea5760075460005260206000f35b6388a8d602811415613a025760085460005260206000f35b63452a9320811415613a1a5760095460005260206000f35b6346d55875811415613a3257600b5460005260206000f35b6339ebf823811415613b4c5760043560a01c15613a4e57600080fd5b600c60043560e05260c052604060c0206101408080808460c052602060c0205481525050602081019050808060018560c052602060c020015481525050602081019050808060028560c052602060c020015481525050602081019050808060038560c052602060c020015481525050602081019050808060048560c052602060c020015481525050602081019050808060058560c052602060c020015481525050602081019050808060068560c052602060c020015481525050602081019050808060078560c052602060c020015481525050602081019050808060088560c052602060c0200154815250506101209050905060c05260c051610140f35b63c822adda811415613b7d5760043560148110613b6857600080fd5b600d60c052602060c020015460005260206000f35b633403c2fc811415613b9557600e5460005260206000f35b63ecf70858811415613bad57600f5460005260206000f35b63cea55f57811415613bc55760105460005260206000f35b63fc7b9c18811415613bdd5760115460005260206000f35b638e6350e2811415613bf55760125460005260206000f35b63c3535b52811415613c0d5760145460005260206000f35b633629c8de811415613c255760155460005260206000f35b6344b81396811415613c3d5760165460005260206000f35b632140254d811415613c555760175460005260206000f35b639ec5a894811415613c6d5760185460005260206000f35b63a6f7f5d6811415613c855760195460005260206000f35b6387788782811415613c9d57601a5460005260206000f35b637ecebe00811415613cd35760043560a01c15613cb957600080fd5b601b60043560e05260c052604060c0205460005260206000f35b633644e515811415613ceb57601c5460005260206000f35b505b60006000fd5b6101a05261014052610160526101805260006004610220527fa9059cbb000000000000000000000000000000000000000000000000000000006102405261022060048060208461028001018260208501600060045af15050805182019150506101605160208261028001015260208101905061018051602082610280010152602081019050806102805261028090508051602001806103208284600060045af1613d9c57600080fd5b505060206103e0610320516103406000610140515af1613dbb57600080fd5b60203d80821115613dcc5780613dce565b815b905090506103c0526103c08051602001806101c08284600060045af1613df357600080fd5b505060006101c0511115613e87576101c0806020015160008251806020901315613e1c57600080fd5b8091901215613e2a57600080fd5b806020036101000a820490509050905015151515613e87576308c379a0610220526020610240526010610260527f5472616e73666572206661696c656421000000000000000000000000000000006102805261026050606461023cfd5b6101a051565b6101c0526101405261016052610180526101a05260006004610240527f23b872dd00000000000000000000000000000000000000000000000000000000610260526102406004806020846102a001018260208501600060045af1505080518201915050610160516020826102a0010152602081019050610180516020826102a00101526020810190506101a0516020826102a0010152602081019050806102a0526102a090508051602001806103608284600060045af1613f4d57600080fd5b50506020610440610360516103806000610140515af1613f6c57600080fd5b60203d80821115613f7d5780613f7f565b815b90509050610420526104208051602001806101e08284600060045af1613fa457600080fd5b505060006101e0511115614038576101e0806020015160008251806020901315613fcd57600080fd5b8091901215613fdb57600080fd5b806020036101000a820490509050905015151515614038576308c379a0610240526020610260526010610280527f5472616e73666572206661696c656421000000000000000000000000000000006102a05261028050606461025cfd5b6101c051565b6101a052610140526101605261018052306101e05260006102005260006101c0526101c061012060006002818352015b610120516020026101e0015161016051141561408d576001835261409d565b815160010180835281141561406e575b5050506101c051156140ae57600080fd5b60036101405160e05260c052604060c020805461018051808210156140d257600080fd5b8082039050905081555060036101605160e05260c052604060c02080546101805181818301101561410257600080fd5b80820190509050815550610180516101c05261016051610140517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101c0a36101a051565b6101405260206101e060246370a0823161016052306101805261017c6006545afa61417357600080fd5b601f3d1161418057600080fd5b6000506101e05160115481818301101561419957600080fd5b8082019050905060005260005161014051565b61018052610140526101605260006101a0526005546101c05260006101c051111561424757610160516101c05180820282158284830414176141ed57600080fd5b809050905090506101405161016051610180516101a0516101c05160065801614149565b6101e0526101c0526101a0526101805261016052610140526101e051808061423857600080fd5b8204905090506101a052614250565b610160516101a0525b6101c0516101a05181818301101561426757600080fd5b8082019050905060055560036101405160e05260c052604060c02080546101a05181818301101561429757600080fd5b808201905090508155506101a0516101e0526101405160007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101e0a36101a05160005260005161018051565b61016052610140526005541515614307576101405160005260005161016051565b426014548082101561431857600080fd5b80820390509050601754808202821582848304141761433657600080fd5b80905090509050610180526101405161016051610180516101a05160065801614149565b6101c0526101a0526101805261016052610140526101c0516101a052670de0b6b3a76400006101805110156143ef576101a080516016546101805160165480820282158284830414176143ac57600080fd5b80905090509050670de0b6b3a764000080820490509050808210156143d057600080fd5b80820390509050808210156143e457600080fd5b808203905090508152505b6103e8610140516101a051808202821582848304141761440e57600080fd5b80905090509050808202821582848304141761442957600080fd5b80905090509050600554808061443e57600080fd5b8204905090506103e88082049050905060005260005161016051565b61016052610140526000610140516101605160065801614149565b61018052610160526101405261018051111561451f576103e86101405160055480820282158284830414176144a957600080fd5b8090509050905080820282158284830414176144c457600080fd5b8090509050905061014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180806144ff57600080fd5b8204905090506103e880820490509050600052600051610160515661452d565b600060005260005161016051565b005b61016052610140526012805460136101405160e05260c052604060c020548082101561455a57600080fd5b808203905090508155506006600c6101405160e05260c052604060c02060c052602060c020015460206102006004638e6350e26101a0526101bc610140515afa6145a357600080fd5b601f3d116145b057600080fd5b60005061020051808211156145c557806145c7565b815b905090506101805260128054610180518181830110156145e657600080fd5b808201905090508155506101805160136101405160e05260c052604060c0205561016051565b6101405260006101605261018060006014818352015b610180516014811061463357600080fd5b600d60c052602060c02001546101a0526101a0511515614672576101608051600181818301101561466357600080fd5b808201905090508152506146d7565b60006101605111156146d7576101a05161018051610160518082101561469757600080fd5b80820390509050601481106146ab57600080fd5b600d60c052602060c0200155600061018051601481106146ca57600080fd5b600d60c052602060c02001555b8151600101808352811415614622575b505061014051565b6101605261014052601080546002600c6101405160e05260c052604060c02060c052602060c02001548082101561472557600080fd5b8082039050905081555060006002600c6101405160e05260c052604060c02060c052602060c0200155610140517f4201c688d84c01154d321afa0c72f1bffe9eef53005c9de9d035074e71e9b32a60006000a261016051565b61016052610140526002600c6101405160e05260c052604060c02060c052602060c020015461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180820282158284830414176147e057600080fd5b8090509050905061271080820490509050610180526006600c6101405160e05260c052604060c02060c052602060c02001546101a052600e5415614832576101a051600052600051610160515661487b565b610180516101a051111515614853576000600052600051610160515661487b565b6101a051610180518082101561486857600080fd5b8082039050905060005260005161016051565b005b6101605261014052600e541561489b57600060005260005161016051565b61014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051610180526010546101805180820282158284830414176148e357600080fd5b80905090509050612710808204905090506101a0526011546101c0526002600c6101405160e05260c052604060c02060c052602060c020015461018051808202821582848304141761493457600080fd5b80905090509050612710808204905090506101e0526006600c6101405160e05260c052604060c02060c052602060c0200154610200526003600c6101405160e05260c052604060c02060c052602060c0200154610220526004600c6101405160e05260c052604060c02060c052602060c020015461024052610200516101e0511115156149c25760016149cd565b6101c0516101a05111155b156149e057600060005260005161016051565b6101e05161020051808210156149f557600080fd5b8082039050905061026052610260516101a0516101c05180821015614a1957600080fd5b8082039050905080821115614a2e5780614a30565b815b905090506102605261026051602061030060246370a0823161028052306102a05261029c6006545afa614a6257600080fd5b601f3d11614a6f57600080fd5b6000506103005180821115614a845780614a86565b815b905090506102605261022051610260511015614aae5760006000526000516101605156614ad6565b610260516102405180821115614ac45780614ac6565b815b9050905060005260005161016051565b005b61016052610140526005600c6101405160e05260c052604060c02060c052602060c020015461018052426101805180821015614b1357600080fd5b808203905090506101a052610180516001600c6101405160e05260c052604060c02060c052602060c020015480821015614b4c57600080fd5b808203905090506101c05260006101a0511115614bb25760006101c0511115614baa5760206102c060046322f3e2d46102605261027c610140515afa614b9157600080fd5b601f3d11614b9e57600080fd5b6000506102c051614bad565b60005b614bb5565b60005b15614c1b576007600c6101405160e05260c052604060c02060c052602060c02001546101a0518082028215828483041417614bef57600080fd5b809050905090506101c0518080614c0557600080fd5b8204905090506000526000516101605156614c29565b600060005260005161016051565b005b6101805261014052610160526006600c6101405160e05260c052604060c02060c052602060c02001546101a052610160516101a0511015614c6b57600080fd5b6008600c6101405160e05260c052604060c02060c052602060c02001805461016051818183011015614c9c57600080fd5b808201905090508155506101a0516101605180821015614cbb57600080fd5b808203905090506006600c6101405160e05260c052604060c02060c052602060c0200155601180546101605180821015614cf457600080fd5b808203905090508155506002600c6101405160e05260c052604060c02060c052602060c02001546101c052610160516127108082028215828483041417614d3a57600080fd5b809050905090506101405161016051610180516101a0516101c0516101e05160065801614149565b610200526101e0526101c0526101a052610180526101605261014052610200518080614d8d57600080fd5b8204905090506101c05180821115614da55780614da7565b815b905090506101e0526002600c6101405160e05260c052604060c02060c052602060c0200180546101e05180821015614dde57600080fd5b80820390509050815550601080546101e05180821015614dfd57600080fd5b8082039050905081555061018051565b61018052610140526101605260115460125480821015614e2c57600080fd5b808203905090504260145480821015614e4457600080fd5b808203905090508082028215828483041417614e5f57600080fd5b809050905090506019548082028215828483041417614e7d57600080fd5b80905090509050612710808204905090506301e18558808204905090506101a05260006101c0526000610160511115614f425761016051600c6101405160e05260c052604060c02060c052602060c020548082028215828483041417614ee257600080fd5b80905090509050612710808204905090506101c0526101a0805161016051601a548082028215828483041417614f1757600080fd5b8090509050905061271080820490509050818183011015614f3757600080fd5b808201905090508152505b6101a0516101c051818183011015614f5957600080fd5b808201905090506101e05260006101e051111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526101e051610240526102405161022051600658016141ac565b6102a052610200526101e0526101c0526101a0526101805261016052610140526102a0516102005260006101c0511115615088576101c051610200518082028215828483041417614ffa57600080fd5b809050905090506101e051808061501057600080fd5b820490509050610220526101405161016051610180516101a0516101c0516101e05161020051610220513061024052610140516102605261022051610280526102805161026051610240516006580161403e565b61022052610200526101e0526101c0526101a0526101805261016052610140526000505b600060033060e05260c052604060c02054111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526018546102405260033060e05260c052604060c02054610260526102605161024051610220516006580161403e565b610200526101e0526101c0526101a0526101805261016052610140526000505b61018051565b61000461511903610004600039610004615119036000f3", + "deployedBytecode": "0x600436101561000d57613ced565b600035601c52600051341561002157600080fd5b6383b43589811415610037573361022052610068565b63a5b81fdf8114156100635760a43560a01c1561005357600080fd5b602060a461022037600050610068565b6106ab565b60043560a01c1561007857600080fd5b60243560a01c1561008857600080fd5b60443560a01c1561009857600080fd5b60606064356004016101403760406064356004013511156100b857600080fd5b60406084356004016101c03760206084356004013511156100d857600080fd5b601554156100e557600080fd5b600435600655600061028052610280805160208201209050610140805160208201209050141561022c576000606061032060046395d89b416102c0526102dc6004355afa61013257600080fd5b603f3d1161013f57600080fd5b60156103206103205101511061015457600080fd5b6000506103406014806020846103e001018260208501600060045af15050805182019150506007610380527f20795661756c74000000000000000000000000000000000000000000000000006103a0526103806007806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600060c052602060c020602082510161012060006002818352015b826101205160200211156101ff57610221565b61012051602002850151610120518501555b81516001018083528114156101ec575b505050505050610287565b61014080600060c052602060c020602082510161012060006003818352015b8261012051602002111561025e57610280565b61012051602002850151610120518501555b815160010180835281141561024b575b5050505050505b6000610280526102808051602082012090506101c080516020820120905014156103c857600060026102c0527f79760000000000000000000000000000000000000000000000000000000000006102e0526102c06002806020846103e001018260208501600060045af1505080518201915050606061038060046395d89b416103205261033c6004355afa61031b57600080fd5b603f3d1161032857600080fd5b60156103806103805101511061033d57600080fd5b6000506103a06014806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600160c052602060c020602082510161012060006002818352015b8261012051602002111561039b576103bd565b61012051602002850151610120518501555b8151600101808352811415610388575b505050505050610423565b6101c080600160c052602060c020602082510161012060006002818352015b826101205160200211156103fa5761041c565b61012051602002850151610120518501555b81516001018083528114156103e7575b5050505050505b60206102a0600463313ce5676102405261025c6004355afa61044457600080fd5b601f3d1161045157600080fd5b6000506102a051600255602435600755602435610240527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610240a1602435600855602435610240527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610240a1604435601855604435610240527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610240a16102205160095561022051610240527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610240a16103e8601a556103e86102405261024051610260527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610260a160c860195560c86102405261024051610260527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610260a142601455426015556529d635a8e00060175560007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f602082610620010152602081019050600b610500527f596561726e205661756c740000000000000000000000000000000000000000006105205261050080516020820120905060208261062001015260208101905060056105c0527f302e332e340000000000000000000000000000000000000000000000000000006105e0526105c0805160208201209050602082610620010152602081019050466020826106200101526020810190503060208261062001015260208101905080610620526106209050805160208201209050601c55005b6325829410811415610740576005610140527f302e332e34000000000000000000000000000000000000000000000000000000610160526101408051602001806101e08284600060045af16106ff57600080fd5b50506101e0518061020001818260206001820306601f820103905003368237505060206101c05260406101e0510160206001820306601f82010390506101c0f35b63c47f00278114156107d657604a60043560040161014037602a60043560040135111561076c57600080fd5b600754331461077a57600080fd5b61014080600060c052602060c020602082510161012060006003818352015b826101205160200211156107ac576107ce565b61012051602002850151610120518501555b8151600101808352811415610799575b505050505050005b63b84c824681141561086c57603460043560040161014037601460043560040135111561080257600080fd5b600754331461081057600080fd5b61014080600160c052602060c020602082510161012060006002818352015b8261012051602002111561084257610864565b61012051602002850151610120518501555b815160010180835281141561082f575b505050505050005b63ab033ea981141561089e5760043560a01c1561088857600080fd5b600754331461089657600080fd5b600435600a55005b63238efcbc8114156108ea57600a5433146108b857600080fd5b3360075533610140527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610140a1005b63d4a22bde81141561094a5760043560a01c1561090657600080fd5b600754331461091457600080fd5b600435600855600435610140527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610140a1005b630b5b78eb8114156109aa5760043560a01c1561096657600080fd5b600754331461097457600080fd5b600435600b55600435610140527f6d674c311329fb38bbc96dc33d2aad03b9bf9fcfdd8f5e5054fda291a5b3c1f86020610140a1005b63ec38a862811415610a0a5760043560a01c156109c657600080fd5b60075433146109d457600080fd5b600435601855600435610140527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610140a1005b638402a84f811415610a43576007543314610a2457600080fd5b670de0b6b3a76400006004351115610a3b57600080fd5b600435601755005b63bdc8144b811415610a93576007543314610a5d57600080fd5b600435600f55600435610140527fae565aab888bca5e19e25a13db7b0c9144305bf55cb0f3f4d724f730e5acdd626020610140a1005b6370897b23811415610af4576007543314610aad57600080fd5b6127106004351115610abe57600080fd5b600435601a55600435610140527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610140a1005b63fe56e232811415610b55576007543314610b0e57600080fd5b6127106004351115610b1f57600080fd5b600435601955600435610140527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610140a1005b638a0dac4a811415610c065760043560a01c15610b7157600080fd5b600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610bb05760018352610bc0565b8151600101808352811415610b94575b50505061014051610bd057600080fd5b600435600955600435610140527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610140a1005b6314c64402811415610cd25760043560011c15610c2257600080fd5b60043515610c8e57600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610c695760018352610c79565b8151600101808352811415610c4d575b50505061014051610c8957600080fd5b610c9c565b6007543314610c9c57600080fd5b600435600e55600435610140527fba40372a3a724dca3c57156128ef1e896724b65b37a17f190b1ad5de68f3a4f36020610140a1005b6394148415811415610f17576000610120525b610120516004013560a01c15610cfa57600080fd5b6020610120510161012052610280610120511015610d1757610ce5565b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610d565760018352610d66565b8151600101808352811415610d3a575b50505061014051610d7657600080fd5b61014060006014818352015b60046101405160148110610d9557600080fd5b60200201351515610dc3576101405160148110610db157600080fd5b600d60c052602060c020015415610dc6565b60005b15610dd057610e53565b60006001600c60046101405160148110610de957600080fd5b602002013560e05260c052604060c02060c052602060c020015411610e0d57600080fd5b60046101405160148110610e2057600080fd5b60200201356101405160148110610e3657600080fd5b600d60c052602060c02001555b8151600101808352811415610d82575b50506004356101405260243561016052604435610180526064356101a0526084356101c05260a4356101e05260c4356102005260e43561022052610104356102405261012435610260526101443561028052610164356102a052610184356102c0526101a4356102e0526101c435610300526101e43561032052610204356103405261022435610360526102443561038052610264356103a0527f695ac3ac73f08f2002284ffe563cefe798ee2878a5e04219522e2e99eb89d168610280610140a1005b63a9059cbb811415610f695760043560a01c15610f3357600080fd5b336101405260043561016052602435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b6323b872dd81141561109e5760043560a01c15610f8557600080fd5b60243560a01c15610f9557600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460043560e05260c052604060c0203360e05260c052604060c02054101561106657600460043560e05260c052604060c0203360e05260c052604060c020546044358082101561100657600080fd5b808203905090506101405261014051600460043560e05260c052604060c0203360e05260c052604060c020556101405161016052336004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610160a35b6004356101405260243561016052604435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b63095ea7b38114156111175760043560a01c156110ba57600080fd5b60243560043360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63395093518114156111c45760043560a01c1561113357600080fd5b60043360e05260c052604060c02060043560e05260c052604060c020805460243581818301101561116357600080fd5b8082019050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63a457c2d781141561126f5760043560a01c156111e057600080fd5b60043360e05260c052604060c02060043560e05260c052604060c02080546024358082101561120e57600080fd5b8082039050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b639fd5a6cf81141561166c5760043560a01c1561128b57600080fd5b60243560a01c1561129b57600080fd5b60616084356004016101403760416084356004013511156112bb57600080fd5b6000600435186112ca57600080fd5b60643515156112da5760016112e1565b4260643510155b6112ea57600080fd5b601b60043560e05260c052604060c020546101e05260006002610520527f19010000000000000000000000000000000000000000000000000000000000006105405261052060028060208461078001018260208501600060045af1505080518201915050601c5460208261078001015260208101905060007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c96020826106800101526020810190506004356020826106800101526020810190506024356020826106800101526020810190506044356020826106800101526020810190506101e05160208261068001015260208101905060643560208261068001015260208101905080610680526106809050805160208201209050602082610780010152602081019050806107805261078090508051602082012090506102005260006020602082066103000161014051828401111561144457600080fd5b6041806103208260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561148257600080fd5b809190121561149057600080fd5b806020036101000a8204905090509050610220526020602060208206610320016101405182840111156114c257600080fd5b6041806103408260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561150057600080fd5b809190121561150e57600080fd5b806020036101000a82049050905090506102405260406001602082066103400161014051828401111561154057600080fd5b6041806103608260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561157e57600080fd5b809190121561158c57600080fd5b806020036101000a8204905090509050610260526004356102005161028052610260516102a052610220516102c052610240516102e052602060c0608061028060015afa5060c051146115de57600080fd5b604435600460043560e05260c052604060c02060243560e05260c052604060c020556101e051600181818301101561161557600080fd5b80820190509050601b60043560e05260c052604060c02055604435610280526024356004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610280a3600160005260206000f35b6301e1d1148114156116925760065801614149565b610140526101405160005260206000f35b63d0e30db08114156116cd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052611727565b63b6b55f258114156116ee5733610160526020600461014037600050611727565b636e553f6581141561172257602060046101403760243560a01c1561171257600080fd5b6020602461016037600050611727565b61197b565b601d541561173457600080fd5b6001601d55600e541561174657600080fd5b61014051610180527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61018051141561181057600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051808210156117b257600080fd5b80820390509050602061024060246370a082316101c052336101e0526101dc6006545afa6117df57600080fd5b601f3d116117ec57600080fd5b60005061024051808211156118015780611803565b815b9050905061018052611861565b600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a0516101805181818301101561184f57600080fd5b80820190509050111561186157600080fd5b6000610180511161187157600080fd5b6000600b5418156118c75760206102406044635ed7660e6101a052336101c052610180516101e0526101bc600b545afa6118aa57600080fd5b601f3d116118b757600080fd5b600050610240516118c757600080fd5b6101405161016051610180516101a051610160516101c052610180516101e0526101e0516101c051600658016141ac565b610240526101a052610180526101605261014052610240516101a0526101405161016051610180516101a0516006546101c052336101e0523061020052610180516102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000506101a0516000526000601d5560206000f35b6375de2902811415611ad95760206101e060246370a0823161016052306101805261017c6006545afa6119ad57600080fd5b601f3d116119ba57600080fd5b6000506101e051610200526101405161016051610180516101a0516101c0516101e051610200516102005161022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515611a4d57611aca565b61014080516101405161016051610180516006600c6101605160e05260c052604060c02060c052602060c02001546101a0526101a0516006580161445a565b6102005261018052610160526101405261020051818183011015611aaf57600080fd5b808201905090508152505b8151600101808352811415611a2a575b50506101405160005260206000f35b633ccfd60b811415611b1a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052600161018052611bbb565b632e1a7d4d811415611b415733610160526001610180526020600461014037600050611bbb565b62f714ce811415611b7a57600161018052602060046101403760243560a01c15611b6a57600080fd5b6020602461016037600050611bbb565b63e63697c8811415611bb657602060046101403760243560a01c15611b9e57600080fd5b60206024610160376020604461018037600050611bbb565b612214565b601d5415611bc857600080fd5b6001601d55610140516101a052612710610180511115611be757600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101a0511415611c265760033360e05260c052604060c020546101a0525b60033360e05260c052604060c020546101a0511115611c4457600080fd5b60006101a05111611c5457600080fd5b6101405161016051610180516101a0516101c0516101a0516101e0526101e051600658016142e6565b610240526101c0526101a052610180526101605261014052610240516101c05260006101e052602061028060246370a0823161020052306102205261021c6006545afa611cc957600080fd5b601f3d11611cd657600080fd5b600050610280516101c0511115612001576102c060006014818352015b6102c051600d60c052602060c02001546102a0526102a0511515611d1657611ffe565b602061038060246370a0823161030052306103205261031c6006545afa611d3c57600080fd5b601f3d11611d4957600080fd5b600050610380516102e0526102e0516101c051111515611d6857611ffe565b6101c0516102e05180821015611d7d57600080fd5b8082039050905061030052610300516006600c6102a05160e05260c052604060c02060c052602060c020015480821115611db75780611db9565b815b9050905061030052610300511515611dd057611fee565b60206103c06024632e1a7d4d61034052610300516103605261035c60006102a0515af1611dfc57600080fd5b601f3d11611e0957600080fd5b6000506103c0516103205260206103e060246370a0823161036052306103805261037c6006545afa611e3a57600080fd5b601f3d11611e4757600080fd5b6000506103e0516102e05180821015611e5f57600080fd5b80820390509050610340526000610320511115611ef4576101c080516103205180821015611e8c57600080fd5b808203905090508152506101e0805161032051818183011015611eae57600080fd5b808201905090508152506008600c6102a05160e05260c052604060c02060c052602060c02001805461032051818183011015611ee957600080fd5b808201905090508155505b6006600c6102a05160e05260c052604060c02060c052602060c0200180546103405161032051818183011015611f2957600080fd5b8082019050905080821015611f3d57600080fd5b80820390509050815550601180546103405161032051818183011015611f6257600080fd5b8082019050905080821015611f7657600080fd5b80820390509050815550610140610360525b61036051516020610360510161036052610360610360511015611faa57611f88565b6102a05161038052610380516006580161452f565b610340610360525b6103605152602061036051036103605261014061036051101515611fea57611fc7565b6000505b8151600101808352811415611cf3575b50505b60206102a060246370a0823161022052306102405261023c6006545afa61202757600080fd5b601f3d1161203457600080fd5b6000506102a05161020052610200516101c05111156120c957610200516101c0526101405161016051610180516101a0516101c0516101e051610200516101c0516101e05181818301101561208857600080fd5b8082019050905061022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101a0525b610180516101c0516101e0518181830110156120e457600080fd5b8082019050905080820282158284830414176120ff57600080fd5b80905090509050612710808204905090506101e051111561211f57600080fd5b600580546101a0518082101561213457600080fd5b8082039050905081555060033360e05260c052604060c02080546101a0518082101561215f57600080fd5b808203905090508155506101a051610220526000337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610220a36101405161016051610180516101a0516101c0516101e051610200516006546102205261016051610240526101c0516102605261026051610240516102205160065801613cf3565b610200526101e0526101c0526101a0526101805261016052610140526000506101c0516000526000601d5560206000f35b6399530b0681141561225757604e6002541061222f57600080fd5b600254600a0a6101405261014051600658016142e6565b6101a0526101a05160005260206000f35b6314b4e26e81141561249c5760043560a01c1561227357600080fd5b6013600d60c052602060c02001541561228b57600080fd5b600e541561229857600080fd5b60075433146122a657600080fd5b6000600435186122b557600080fd5b6001600c60043560e05260c052604060c02060c052602060c0200154156122db57600080fd5b60206101a0600463fbfa77cf6101405261015c6004355afa6122fc57600080fd5b601f3d1161230957600080fd5b6000506101a051301461231b57600080fd5b60206101a06004631f1fcd516101405261015c6004355afa61233c57600080fd5b601f3d1161234957600080fd5b6000506101a0516006541461235d57600080fd5b61271060105460243581818301101561237557600080fd5b80820190509050111561238757600080fd5b606435604435111561239857600080fd5b612710601a54808210156123ab57600080fd5b8082039050905060843511156123c057600080fd5b600c60043560e05260c052604060c02060c052602060c0206084358155426001820155602435600282015560443560038201556064356004820155426005820155600060068201556000600782015560006008820155506024356101405260443561016052606435610180526084356101a0526004357f5a6abd2af9fe6c0554fa08649e2d86e4393ff19dc304d072d38d295c9291d4dc6080610140a26010805460243581818301101561247357600080fd5b808201905090508155506004356013600d60c052602060c02001556006580161460c565b600050005b637c6a4f248114156125f95760043560a01c156124b857600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156124f75760018352612507565b81516001018083528114156124db575b5050506101405161251757600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161253f57600080fd5b601080546002600c60043560e05260c052604060c02060c052602060c02001548082101561256c57600080fd5b808203905090508155506024356002600c60043560e05260c052604060c02060c052602060c0200155601080546024358181830110156125ab57600080fd5b8082019050905081555061271060105411156125c657600080fd5b602435610140526004357fbda9398315c83ccef012bcaa318a2ff7b680f36429d36597bd4bc25ac11ead596020610140a2005b63e722befe8114156127185760043560a01c1561261557600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156126545760018352612664565b8151600101808352811415612638575b5050506101405161267457600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161269c57600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c020015410156126c657600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f0b728ad785976532c4aaadde09b1cba5f262a7090e83c62d2377bc405678b29c6020610140a2005b634757a1568114156128375760043560a01c1561273457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156127735760018352612783565b8151600101808352811415612757575b5050506101405161279357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116127bb57600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c020015411156127e557600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f1796a8e0760e2de5b72e7bf64fccb7666c48ceab94cb6cae7cb7eff4b6f641ab6020610140a2005b63d0194ed68114156129005760043560a01c1561285357600080fd5b600754331461286157600080fd5b612710601a548082101561287457600080fd5b80820390509050602435111561288957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116128b157600080fd5b602435600c60043560e05260c052604060c02060c052602060c02055602435610140526004357fe57488a65fa53066d4c25bac90db47dda4e5de3025ac12bf76ff07211cf7f39e6020610140a2005b636cb56d19811415612c185760043560a01c1561291c57600080fd5b60243560a01c1561292c57600080fd5b600754331461293a57600080fd5b60006024351861294957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161297157600080fd5b6001600c60243560e05260c052604060c02060c052602060c02001541561299757600080fd5b610140600c60043560e05260c052604060c0208060c052602060c02054825260018160c052602060c0200154826020015260028160c052602060c0200154826040015260038160c052602060c0200154826060015260048160c052602060c0200154826080015260058160c052602060c02001548260a0015260068160c052602060c02001548260c0015260078160c052602060c02001548260e0015260088160c052602060c020015482610100015250506101405161016051610180516101a0516101c0516101e0516102005161022051610240516004356102605261026051600658016146ef565b6102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506010805461018051818183011015612abf57600080fd5b8082019050905081555060006006600c60043560e05260c052604060c02060c052602060c0200155600c60243560e05260c052604060c02060c052602060c0206101405181556101e05160018201556101805160028201556101a05160038201556101c05160048201556101e05160058201556102005160068201556000600782015560006008820155506004353b612b5757600080fd5b60006000602463ce5494bb610260526024356102805261027c60006004355af1612b8057600080fd5b6024356004357f100b69bb6b504e1252e36b375233158edee64d071b399e2f81473a695fd1b02160006000a361026060006014818352015b6004356102605160148110612bcc57600080fd5b600d60c052602060c02001541415612c04576024356102605160148110612bf257600080fd5b600d60c052602060c020015560006000f35b8151600101808352811415612bb8575b5050005b63a0e4af9a811415612c2e573361014052612c5f565b63bb994d48811415612c5a5760043560a01c15612c4a57600080fd5b6020600461014037600050612c5f565b612ce8565b61014051610180526007546101a0526009546101c05260006101605261016061012060006003818352015b610120516020026101800151331415612ca65760018352612cb6565b8151600101808352811415612c8a575b50505061016051612cc657600080fd5b61014051610140516101605261016051600658016146ef565b61014052600050005b63f76e4caa811415612e645760043560a01c15612d0457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612d435760018352612d53565b8151600101808352811415612d27575b50505061014051612d6357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c020015411612d8b57600080fd5b60006101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515612dc057612e02565b6004356101605118612dd157600080fd5b61014080516001818183011015612de757600080fd5b808201905090508152505b8151600101808352811415612d9d575b505060146101405110612e1457600080fd5b6004356013600d60c052602060c0200155610140516006580161460c565b610140526000506004357fa8727d412c6fa1e2497d6d6f275e2d9fe4d9318d5b793632e60ad9d38ee8f1fa60006000a2005b63b22439f5811415612f8b5760043560a01c15612e8057600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612ebf5760018352612ecf565b8151600101808352811415612ea3575b50505061014051612edf57600080fd5b61014060006014818352015b6004356101405160148110612eff57600080fd5b600d60c052602060c02001541415612f735760006101405160148110612f2457600080fd5b600d60c052602060c0200155610140516006580161460c565b610140526000506004357f8e1ec3c16d6a67ea8effe2ac7adef9c2de0bc0dc47c49cdf18f6a8b0048085be60006000a260006000f35b8151600101808352811415612eeb575b505060006000fd5b63bf3759b5811415612fa1573361014052612fd2565b63bdcf36bb811415612fcd5760043560a01c15612fbd57600080fd5b6020600461014037600050612fd2565b613000565b610140516101405161016052610160516006580161477e565b6101c052610140526101c05160005260206000f35b63112c1f9b811415613016573361014052613047565b63d76480138114156130425760043560a01c1561303257600080fd5b6020600461014037600050613047565b613075565b610140516101405161016052610160516006580161487d565b6101c052610140526101c05160005260206000f35b63153c27c48114156130e55760065801614149565b6101405261014051600f5411156130d857600f546101405160065801614149565b610160526101405261016051808210156130c457600080fd5b8082039050905060005260206000f36130e3565b600060005260206000f35b005b63d3406abd8114156130fb57336101405261312c565b6333586b678114156131275760043560a01c1561311757600080fd5b602060046101403760005061312c565b61315a565b6101405161014051610160526101605160065801614ad8565b6101c052610140526101c05160005260206000f35b63a1d9bafc81141561367f5760006001600c3360e05260c052604060c02060c052602060c02001541161318c57600080fd5b6004356044358181830110156131a157600080fd5b8082019050905060206101c060246370a0823161014052336101605261015c6006545afa6131ce57600080fd5b601f3d116131db57600080fd5b6000506101c05110156131ed57600080fd5b6000602435111561321957336101405260243561016052610160516101405160065801614c2b565b6000505b336101405260043561016052610160516101405160065801614e0d565b6000506007600c3360e05260c052604060c02060c052602060c02001805460043581818301101561326657600080fd5b80820190509050815550610140513361016052610160516006580161477e565b6101c052610140526101c0516101405260443561014051808211156132ab57806132ad565b815b90509050610160526000610160511115613337576006600c3360e05260c052604060c02060c052602060c02001805461016051808210156132ed57600080fd5b8082039050905081555060118054610160518082101561330c57600080fd5b808203905090508155506101408051610160518082101561332c57600080fd5b808203905090508152505b610140516101605161018051336101a0526101a0516006580161487d565b61020052610180526101605261014052610200516101805260006101805111156133d3576006600c3360e05260c052604060c02060c052602060c020018054610180518181830110156133a757600080fd5b8082019050905081555060118054610180518181830110156133c857600080fd5b808201905090508155505b600435610160518181830110156133e957600080fd5b808201905090506101a052610180516101a051101561346b576101405161016051610180516101a0516006546101c052336101e052610180516101a0518082101561343357600080fd5b8082039050905061020052610200516101e0516101c05160065801613cf3565b6101a0526101805261016052610140526000506134e7565b610180516101a05111156134e7576101405161016051610180516101a0516006546101c052336101e05230610200526101a05161018051808210156134af57600080fd5b808203905090506102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000505b6101405161016051610180516101a051336101c0526101c0516006580161452f565b6101a052610180526101605261014052600050426005600c3360e05260c052604060c02060c052602060c0200155426014556004356016556004356101c0526024356101e05261016051610200526007600c3360e05260c052604060c02060c052602060c0200154610220526008600c3360e05260c052604060c02060c052602060c0200154610240526006600c3360e05260c052604060c02060c052602060c02001546102605261018051610280526002600c3360e05260c052604060c02060c052602060c02001546102a052337f67f96d2854a335a4cadb49f84fd3ca6f990744ddb3feceeb4b349d2d53d32ad36101006101c0a26002600c3360e05260c052604060c02060c052602060c0200154151561362757600161362b565b600e545b15613670576020610220600463efbb5cb06101c0526101dc335afa61364f57600080fd5b601f3d1161365c57600080fd5b6000506102205160005260206000f361367d565b6101405160005260206000f35b005b6301681a628114156136b5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140526136d6565b636ea056a98114156136d15760206024610140376000506136d6565b6137b6565b60043560a01c156136e657600080fd5b60075433146136f457600080fd5b6006546004351861370457600080fd5b61014051610160527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61016051141561377657602061020060246370a0823161018052306101a05261019c6004355afa61375d57600080fd5b601f3d1161376a57600080fd5b60005061020051610160525b6101405161016051600435610180526007546101a052610160516101c0526101c0516101a0516101805160065801613cf3565b6101605261014052600050005b6306fdde0381141561385b5760008060c052602060c020610180602082540161012060006003818352015b826101205160200211156137f457613816565b61012051850154610120516020028501525b81516001018083528114156137e1575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b6395d89b418114156139005760018060c052602060c020610180602082540161012060006002818352015b82610120516020021115613899576138bb565b61012051850154610120516020028501525b8151600101808352811415613886575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63313ce5678114156139185760025460005260206000f35b6370a0823181141561394e5760043560a01c1561393457600080fd5b600360043560e05260c052604060c0205460005260206000f35b63dd62ed3e8114156139a25760043560a01c1561396a57600080fd5b60243560a01c1561397a57600080fd5b600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b6318160ddd8114156139ba5760055460005260206000f35b63fc0c546a8114156139d25760065460005260206000f35b635aa6e6758114156139ea5760075460005260206000f35b6388a8d602811415613a025760085460005260206000f35b63452a9320811415613a1a5760095460005260206000f35b6346d55875811415613a3257600b5460005260206000f35b6339ebf823811415613b4c5760043560a01c15613a4e57600080fd5b600c60043560e05260c052604060c0206101408080808460c052602060c0205481525050602081019050808060018560c052602060c020015481525050602081019050808060028560c052602060c020015481525050602081019050808060038560c052602060c020015481525050602081019050808060048560c052602060c020015481525050602081019050808060058560c052602060c020015481525050602081019050808060068560c052602060c020015481525050602081019050808060078560c052602060c020015481525050602081019050808060088560c052602060c0200154815250506101209050905060c05260c051610140f35b63c822adda811415613b7d5760043560148110613b6857600080fd5b600d60c052602060c020015460005260206000f35b633403c2fc811415613b9557600e5460005260206000f35b63ecf70858811415613bad57600f5460005260206000f35b63cea55f57811415613bc55760105460005260206000f35b63fc7b9c18811415613bdd5760115460005260206000f35b638e6350e2811415613bf55760125460005260206000f35b63c3535b52811415613c0d5760145460005260206000f35b633629c8de811415613c255760155460005260206000f35b6344b81396811415613c3d5760165460005260206000f35b632140254d811415613c555760175460005260206000f35b639ec5a894811415613c6d5760185460005260206000f35b63a6f7f5d6811415613c855760195460005260206000f35b6387788782811415613c9d57601a5460005260206000f35b637ecebe00811415613cd35760043560a01c15613cb957600080fd5b601b60043560e05260c052604060c0205460005260206000f35b633644e515811415613ceb57601c5460005260206000f35b505b60006000fd5b6101a05261014052610160526101805260006004610220527fa9059cbb000000000000000000000000000000000000000000000000000000006102405261022060048060208461028001018260208501600060045af15050805182019150506101605160208261028001015260208101905061018051602082610280010152602081019050806102805261028090508051602001806103208284600060045af1613d9c57600080fd5b505060206103e0610320516103406000610140515af1613dbb57600080fd5b60203d80821115613dcc5780613dce565b815b905090506103c0526103c08051602001806101c08284600060045af1613df357600080fd5b505060006101c0511115613e87576101c0806020015160008251806020901315613e1c57600080fd5b8091901215613e2a57600080fd5b806020036101000a820490509050905015151515613e87576308c379a0610220526020610240526010610260527f5472616e73666572206661696c656421000000000000000000000000000000006102805261026050606461023cfd5b6101a051565b6101c0526101405261016052610180526101a05260006004610240527f23b872dd00000000000000000000000000000000000000000000000000000000610260526102406004806020846102a001018260208501600060045af1505080518201915050610160516020826102a0010152602081019050610180516020826102a00101526020810190506101a0516020826102a0010152602081019050806102a0526102a090508051602001806103608284600060045af1613f4d57600080fd5b50506020610440610360516103806000610140515af1613f6c57600080fd5b60203d80821115613f7d5780613f7f565b815b90509050610420526104208051602001806101e08284600060045af1613fa457600080fd5b505060006101e0511115614038576101e0806020015160008251806020901315613fcd57600080fd5b8091901215613fdb57600080fd5b806020036101000a820490509050905015151515614038576308c379a0610240526020610260526010610280527f5472616e73666572206661696c656421000000000000000000000000000000006102a05261028050606461025cfd5b6101c051565b6101a052610140526101605261018052306101e05260006102005260006101c0526101c061012060006002818352015b610120516020026101e0015161016051141561408d576001835261409d565b815160010180835281141561406e575b5050506101c051156140ae57600080fd5b60036101405160e05260c052604060c020805461018051808210156140d257600080fd5b8082039050905081555060036101605160e05260c052604060c02080546101805181818301101561410257600080fd5b80820190509050815550610180516101c05261016051610140517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101c0a36101a051565b6101405260206101e060246370a0823161016052306101805261017c6006545afa61417357600080fd5b601f3d1161418057600080fd5b6000506101e05160115481818301101561419957600080fd5b8082019050905060005260005161014051565b61018052610140526101605260006101a0526005546101c05260006101c051111561424757610160516101c05180820282158284830414176141ed57600080fd5b809050905090506101405161016051610180516101a0516101c05160065801614149565b6101e0526101c0526101a0526101805261016052610140526101e051808061423857600080fd5b8204905090506101a052614250565b610160516101a0525b6101c0516101a05181818301101561426757600080fd5b8082019050905060055560036101405160e05260c052604060c02080546101a05181818301101561429757600080fd5b808201905090508155506101a0516101e0526101405160007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101e0a36101a05160005260005161018051565b61016052610140526005541515614307576101405160005260005161016051565b426014548082101561431857600080fd5b80820390509050601754808202821582848304141761433657600080fd5b80905090509050610180526101405161016051610180516101a05160065801614149565b6101c0526101a0526101805261016052610140526101c0516101a052670de0b6b3a76400006101805110156143ef576101a080516016546101805160165480820282158284830414176143ac57600080fd5b80905090509050670de0b6b3a764000080820490509050808210156143d057600080fd5b80820390509050808210156143e457600080fd5b808203905090508152505b6103e8610140516101a051808202821582848304141761440e57600080fd5b80905090509050808202821582848304141761442957600080fd5b80905090509050600554808061443e57600080fd5b8204905090506103e88082049050905060005260005161016051565b61016052610140526000610140516101605160065801614149565b61018052610160526101405261018051111561451f576103e86101405160055480820282158284830414176144a957600080fd5b8090509050905080820282158284830414176144c457600080fd5b8090509050905061014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180806144ff57600080fd5b8204905090506103e880820490509050600052600051610160515661452d565b600060005260005161016051565b005b61016052610140526012805460136101405160e05260c052604060c020548082101561455a57600080fd5b808203905090508155506006600c6101405160e05260c052604060c02060c052602060c020015460206102006004638e6350e26101a0526101bc610140515afa6145a357600080fd5b601f3d116145b057600080fd5b60005061020051808211156145c557806145c7565b815b905090506101805260128054610180518181830110156145e657600080fd5b808201905090508155506101805160136101405160e05260c052604060c0205561016051565b6101405260006101605261018060006014818352015b610180516014811061463357600080fd5b600d60c052602060c02001546101a0526101a0511515614672576101608051600181818301101561466357600080fd5b808201905090508152506146d7565b60006101605111156146d7576101a05161018051610160518082101561469757600080fd5b80820390509050601481106146ab57600080fd5b600d60c052602060c0200155600061018051601481106146ca57600080fd5b600d60c052602060c02001555b8151600101808352811415614622575b505061014051565b6101605261014052601080546002600c6101405160e05260c052604060c02060c052602060c02001548082101561472557600080fd5b8082039050905081555060006002600c6101405160e05260c052604060c02060c052602060c0200155610140517f4201c688d84c01154d321afa0c72f1bffe9eef53005c9de9d035074e71e9b32a60006000a261016051565b61016052610140526002600c6101405160e05260c052604060c02060c052602060c020015461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180820282158284830414176147e057600080fd5b8090509050905061271080820490509050610180526006600c6101405160e05260c052604060c02060c052602060c02001546101a052600e5415614832576101a051600052600051610160515661487b565b610180516101a051111515614853576000600052600051610160515661487b565b6101a051610180518082101561486857600080fd5b8082039050905060005260005161016051565b005b6101605261014052600e541561489b57600060005260005161016051565b61014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051610180526010546101805180820282158284830414176148e357600080fd5b80905090509050612710808204905090506101a0526011546101c0526002600c6101405160e05260c052604060c02060c052602060c020015461018051808202821582848304141761493457600080fd5b80905090509050612710808204905090506101e0526006600c6101405160e05260c052604060c02060c052602060c0200154610200526003600c6101405160e05260c052604060c02060c052602060c0200154610220526004600c6101405160e05260c052604060c02060c052602060c020015461024052610200516101e0511115156149c25760016149cd565b6101c0516101a05111155b156149e057600060005260005161016051565b6101e05161020051808210156149f557600080fd5b8082039050905061026052610260516101a0516101c05180821015614a1957600080fd5b8082039050905080821115614a2e5780614a30565b815b905090506102605261026051602061030060246370a0823161028052306102a05261029c6006545afa614a6257600080fd5b601f3d11614a6f57600080fd5b6000506103005180821115614a845780614a86565b815b905090506102605261022051610260511015614aae5760006000526000516101605156614ad6565b610260516102405180821115614ac45780614ac6565b815b9050905060005260005161016051565b005b61016052610140526005600c6101405160e05260c052604060c02060c052602060c020015461018052426101805180821015614b1357600080fd5b808203905090506101a052610180516001600c6101405160e05260c052604060c02060c052602060c020015480821015614b4c57600080fd5b808203905090506101c05260006101a0511115614bb25760006101c0511115614baa5760206102c060046322f3e2d46102605261027c610140515afa614b9157600080fd5b601f3d11614b9e57600080fd5b6000506102c051614bad565b60005b614bb5565b60005b15614c1b576007600c6101405160e05260c052604060c02060c052602060c02001546101a0518082028215828483041417614bef57600080fd5b809050905090506101c0518080614c0557600080fd5b8204905090506000526000516101605156614c29565b600060005260005161016051565b005b6101805261014052610160526006600c6101405160e05260c052604060c02060c052602060c02001546101a052610160516101a0511015614c6b57600080fd5b6008600c6101405160e05260c052604060c02060c052602060c02001805461016051818183011015614c9c57600080fd5b808201905090508155506101a0516101605180821015614cbb57600080fd5b808203905090506006600c6101405160e05260c052604060c02060c052602060c0200155601180546101605180821015614cf457600080fd5b808203905090508155506002600c6101405160e05260c052604060c02060c052602060c02001546101c052610160516127108082028215828483041417614d3a57600080fd5b809050905090506101405161016051610180516101a0516101c0516101e05160065801614149565b610200526101e0526101c0526101a052610180526101605261014052610200518080614d8d57600080fd5b8204905090506101c05180821115614da55780614da7565b815b905090506101e0526002600c6101405160e05260c052604060c02060c052602060c0200180546101e05180821015614dde57600080fd5b80820390509050815550601080546101e05180821015614dfd57600080fd5b8082039050905081555061018051565b61018052610140526101605260115460125480821015614e2c57600080fd5b808203905090504260145480821015614e4457600080fd5b808203905090508082028215828483041417614e5f57600080fd5b809050905090506019548082028215828483041417614e7d57600080fd5b80905090509050612710808204905090506301e18558808204905090506101a05260006101c0526000610160511115614f425761016051600c6101405160e05260c052604060c02060c052602060c020548082028215828483041417614ee257600080fd5b80905090509050612710808204905090506101c0526101a0805161016051601a548082028215828483041417614f1757600080fd5b8090509050905061271080820490509050818183011015614f3757600080fd5b808201905090508152505b6101a0516101c051818183011015614f5957600080fd5b808201905090506101e05260006101e051111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526101e051610240526102405161022051600658016141ac565b6102a052610200526101e0526101c0526101a0526101805261016052610140526102a0516102005260006101c0511115615088576101c051610200518082028215828483041417614ffa57600080fd5b809050905090506101e051808061501057600080fd5b820490509050610220526101405161016051610180516101a0516101c0516101e05161020051610220513061024052610140516102605261022051610280526102805161026051610240516006580161403e565b61022052610200526101e0526101c0526101a0526101805261016052610140526000505b600060033060e05260c052604060c02054111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526018546102405260033060e05260c052604060c02054610260526102605161024051610220516006580161403e565b610200526101e0526101c0526101a0526101805261016052610140526000505b6101805156", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/external/contracts/yearn/BaseStrategy.sol b/external/contracts/yearn/BaseStrategy.sol new file mode 100644 index 000000000..fa6261007 --- /dev/null +++ b/external/contracts/yearn/BaseStrategy.sol @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.6.0 <0.7.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; + +struct StrategyParams { + uint256 performanceFee; + uint256 activation; + uint256 debtRatio; + uint256 minDebtPerHarvest; + uint256 maxDebtPerHarvest; + uint256 lastReport; + uint256 totalDebt; + uint256 totalGain; + uint256 totalLoss; +} + +interface VaultAPI is IERC20 { + function name() external view returns (string calldata); + + function symbol() external view returns (string calldata); + + function decimals() external view returns (uint256); + + function apiVersion() external pure returns (string memory); + + function permit( + address owner, + address spender, + uint256 amount, + uint256 expiry, + bytes calldata signature + ) external returns (bool); + + // NOTE: Vyper produces multiple signatures for a given function with "default" args + function deposit() external returns (uint256); + + function deposit(uint256 amount) external returns (uint256); + + function deposit(uint256 amount, address recipient) external returns (uint256); + + // NOTE: Vyper produces multiple signatures for a given function with "default" args + function withdraw() external returns (uint256); + + function withdraw(uint256 maxShares) external returns (uint256); + + function withdraw(uint256 maxShares, address recipient) external returns (uint256); + + function token() external view returns (address); + + function strategies(address _strategy) external view returns (StrategyParams memory); + + function pricePerShare() external view returns (uint256); + + function totalAssets() external view returns (uint256); + + function depositLimit() external view returns (uint256); + + function maxAvailableShares() external view returns (uint256); + + /** + * View how much the Vault would increase this Strategy's borrow limit, + * based on its present performance (since its last report). Can be used to + * determine expectedReturn in your Strategy. + */ + function creditAvailable() external view returns (uint256); + + /** + * View how much the Vault would like to pull back from the Strategy, + * based on its present performance (since its last report). Can be used to + * determine expectedReturn in your Strategy. + */ + function debtOutstanding() external view returns (uint256); + + /** + * View how much the Vault expect this Strategy to return at the current + * block, based on its present performance (since its last report). Can be + * used to determine expectedReturn in your Strategy. + */ + function expectedReturn() external view returns (uint256); + + /** + * This is the main contact point where the Strategy interacts with the + * Vault. It is critical that this call is handled as intended by the + * Strategy. Therefore, this function will be called by BaseStrategy to + * make sure the integration is correct. + */ + function report( + uint256 _gain, + uint256 _loss, + uint256 _debtPayment + ) external returns (uint256); + + /** + * This function should only be used in the scenario where the Strategy is + * being retired but no migration of the positions are possible, or in the + * extreme scenario that the Strategy needs to be put into "Emergency Exit" + * mode in order for it to exit as quickly as possible. The latter scenario + * could be for any reason that is considered "critical" that the Strategy + * exits its position as fast as possible, such as a sudden change in + * market conditions leading to losses, or an imminent failure in an + * external dependency. + */ + function revokeStrategy() external; + + /** + * View the governance address of the Vault to assert privileged functions + * can only be called by governance. The Strategy serves the Vault, so it + * is subject to governance defined by the Vault. + */ + function governance() external view returns (address); + + /** + * View the management address of the Vault to assert privileged functions + * can only be called by management. The Strategy serves the Vault, so it + * is subject to management defined by the Vault. + */ + function management() external view returns (address); + + /** + * View the guardian address of the Vault to assert privileged functions + * can only be called by guardian. The Strategy serves the Vault, so it + * is subject to guardian defined by the Vault. + */ + function guardian() external view returns (address); +} + +/** + * This interface is here for the keeper bot to use. + */ +interface StrategyAPI { + function name() external view returns (string memory); + + function vault() external view returns (address); + + function want() external view returns (address); + + function apiVersion() external pure returns (string memory); + + function keeper() external view returns (address); + + function isActive() external view returns (bool); + + function delegatedAssets() external view returns (uint256); + + function estimatedTotalAssets() external view returns (uint256); + + function tendTrigger(uint256 callCost) external view returns (bool); + + function tend() external; + + function harvestTrigger(uint256 callCost) external view returns (bool); + + function harvest() external; + + event Harvested(uint256 profit, uint256 loss, uint256 debtPayment, uint256 debtOutstanding); +} + +/** + * @title Yearn Base Strategy + * @author yearn.finance + * @notice + * BaseStrategy implements all of the required functionality to interoperate + * closely with the Vault contract. This contract should be inherited and the + * abstract methods implemented to adapt the Strategy to the particular needs + * it has to create a return. + * + * Of special interest is the relationship between `harvest()` and + * `vault.report()'. `harvest()` may be called simply because enough time has + * elapsed since the last report, and not because any funds need to be moved + * or positions adjusted. This is critical so that the Vault may maintain an + * accurate picture of the Strategy's performance. See `vault.report()`, + * `harvest()`, and `harvestTrigger()` for further details. + */ +abstract contract BaseStrategy { + using SafeMath for uint256; + using SafeERC20 for IERC20; + string public metadataURI; + + /** + * @notice + * Used to track which version of `StrategyAPI` this Strategy + * implements. + * @dev The Strategy's version must match the Vault's `API_VERSION`. + * @return A string which holds the current API version of this contract. + */ + function apiVersion() public pure returns (string memory) { + return "0.3.4"; + } + + /** + * @notice This Strategy's name. + * @dev + * You can use this field to manage the "version" of this Strategy, e.g. + * `StrategySomethingOrOtherV1`. However, "API Version" is managed by + * `apiVersion()` function above. + * @return This Strategy's name. + */ + function name() external virtual view returns (string memory); + + /** + * @notice + * The amount (priced in want) of the total assets managed by this strategy should not count + * towards Yearn's TVL calculations. + * @dev + * You can override this field to set it to a non-zero value if some of the assets of this + * Strategy is somehow delegated inside another part of of Yearn's ecosystem e.g. another Vault. + * Note that this value must be strictly less than or equal to the amount provided by + * `estimatedTotalAssets()` below, as the TVL calc will be total assets minus delegated assets. + * Also note that this value is used to determine the total assets under management by this + * strategy, for the purposes of computing the management fee in `Vault` + * @return + * The amount of assets this strategy manages that should not be included in Yearn's Total Value + * Locked (TVL) calculation across it's ecosystem. + */ + function delegatedAssets() external virtual view returns (uint256) { + return 0; + } + + VaultAPI public vault; + address public strategist; + address public rewards; + address public keeper; + + IERC20 public want; + + // So indexers can keep track of this + event Harvested(uint256 profit, uint256 loss, uint256 debtPayment, uint256 debtOutstanding); + + event UpdatedStrategist(address newStrategist); + + event UpdatedKeeper(address newKeeper); + + event UpdatedRewards(address rewards); + + event UpdatedMinReportDelay(uint256 delay); + + event UpdatedMaxReportDelay(uint256 delay); + + event UpdatedProfitFactor(uint256 profitFactor); + + event UpdatedDebtThreshold(uint256 debtThreshold); + + event EmergencyExitEnabled(); + + event UpdatedMetadataURI(string metadataURI); + + // The minimum number of seconds between harvest calls. See + // `setMinReportDelay()` for more details. + uint256 public minReportDelay; + + // The maximum number of seconds between harvest calls. See + // `setMaxReportDelay()` for more details. + uint256 public maxReportDelay; + + // The minimum multiple that `callCost` must be above the credit/profit to + // be "justifiable". See `setProfitFactor()` for more details. + uint256 public profitFactor; + + // Use this to adjust the threshold at which running a debt causes a + // harvest trigger. See `setDebtThreshold()` for more details. + uint256 public debtThreshold; + + // See note on `setEmergencyExit()`. + bool public emergencyExit; + + // modifiers + modifier onlyAuthorized() { + require(msg.sender == strategist || msg.sender == governance(), "!authorized"); + _; + } + + modifier onlyStrategist() { + require(msg.sender == strategist, "!strategist"); + _; + } + + modifier onlyGovernance() { + require(msg.sender == governance(), "!authorized"); + _; + } + + modifier onlyKeepers() { + require( + msg.sender == keeper || + msg.sender == strategist || + msg.sender == governance() || + msg.sender == vault.guardian() || + msg.sender == vault.management(), + "!authorized" + ); + _; + } + + constructor(address _vault) public { + _initialize(_vault, msg.sender, msg.sender, msg.sender); + } + + /** + * @notice + * Initializes the Strategy, this is called only once, when the + * contract is deployed. + * @dev `_vault` should implement `VaultAPI`. + * @param _vault The address of the Vault responsible for this Strategy. + */ + function _initialize( + address _vault, + address _strategist, + address _rewards, + address _keeper + ) internal { + require(address(want) == address(0), "Strategy already initialized"); + + vault = VaultAPI(_vault); + want = IERC20(vault.token()); + want.safeApprove(_vault, uint256(-1)); // Give Vault unlimited access (might save gas) + strategist = _strategist; + rewards = _rewards; + keeper = _keeper; + + // initialize variables + minReportDelay = 0; + maxReportDelay = 86400; + profitFactor = 100; + debtThreshold = 0; + + vault.approve(rewards, uint256(-1)); // Allow rewards to be pulled + } + + /** + * @notice + * Used to change `strategist`. + * + * This may only be called by governance or the existing strategist. + * @param _strategist The new address to assign as `strategist`. + */ + function setStrategist(address _strategist) external onlyAuthorized { + require(_strategist != address(0)); + strategist = _strategist; + emit UpdatedStrategist(_strategist); + } + + /** + * @notice + * Used to change `keeper`. + * + * `keeper` is the only address that may call `tend()` or `harvest()`, + * other than `governance()` or `strategist`. However, unlike + * `governance()` or `strategist`, `keeper` may *only* call `tend()` + * and `harvest()`, and no other authorized functions, following the + * principle of least privilege. + * + * This may only be called by governance or the strategist. + * @param _keeper The new address to assign as `keeper`. + */ + function setKeeper(address _keeper) external onlyAuthorized { + require(_keeper != address(0)); + keeper = _keeper; + emit UpdatedKeeper(_keeper); + } + + /** + * @notice + * Used to change `rewards`. EOA or smart contract which has the permission + * to pull rewards from the vault. + * + * This may only be called by the strategist. + * @param _rewards The address to use for pulling rewards. + */ + function setRewards(address _rewards) external onlyStrategist { + require(_rewards != address(0)); + vault.approve(rewards, 0); + rewards = _rewards; + vault.approve(rewards, uint256(-1)); + emit UpdatedRewards(_rewards); + } + + /** + * @notice + * Used to change `minReportDelay`. `minReportDelay` is the minimum number + * of blocks that should pass for `harvest()` to be called. + * + * For external keepers (such as the Keep3r network), this is the minimum + * time between jobs to wait. (see `harvestTrigger()` + * for more details.) + * + * This may only be called by governance or the strategist. + * @param _delay The minimum number of seconds to wait between harvests. + */ + function setMinReportDelay(uint256 _delay) external onlyAuthorized { + minReportDelay = _delay; + emit UpdatedMinReportDelay(_delay); + } + + /** + * @notice + * Used to change `maxReportDelay`. `maxReportDelay` is the maximum number + * of blocks that should pass for `harvest()` to be called. + * + * For external keepers (such as the Keep3r network), this is the maximum + * time between jobs to wait. (see `harvestTrigger()` + * for more details.) + * + * This may only be called by governance or the strategist. + * @param _delay The maximum number of seconds to wait between harvests. + */ + function setMaxReportDelay(uint256 _delay) external onlyAuthorized { + maxReportDelay = _delay; + emit UpdatedMaxReportDelay(_delay); + } + + /** + * @notice + * Used to change `profitFactor`. `profitFactor` is used to determine + * if it's worthwhile to harvest, given gas costs. (See `harvestTrigger()` + * for more details.) + * + * This may only be called by governance or the strategist. + * @param _profitFactor A ratio to multiply anticipated + * `harvest()` gas cost against. + */ + function setProfitFactor(uint256 _profitFactor) external onlyAuthorized { + profitFactor = _profitFactor; + emit UpdatedProfitFactor(_profitFactor); + } + + /** + * @notice + * Sets how far the Strategy can go into loss without a harvest and report + * being required. + * + * By default this is 0, meaning any losses would cause a harvest which + * will subsequently report the loss to the Vault for tracking. (See + * `harvestTrigger()` for more details.) + * + * This may only be called by governance or the strategist. + * @param _debtThreshold How big of a loss this Strategy may carry without + * being required to report to the Vault. + */ + function setDebtThreshold(uint256 _debtThreshold) external onlyAuthorized { + debtThreshold = _debtThreshold; + emit UpdatedDebtThreshold(_debtThreshold); + } + + /** + * @notice + * Used to change `metadataURI`. `metadataURI` is used to store the URI + * of the file describing the strategy. + * + * This may only be called by governance or the strategist. + * @param _metadataURI The URI that describe the strategy. + */ + function setMetadataURI(string calldata _metadataURI) external onlyAuthorized { + metadataURI = _metadataURI; + emit UpdatedMetadataURI(_metadataURI); + } + + /** + * Resolve governance address from Vault contract, used to make assertions + * on protected functions in the Strategy. + */ + function governance() internal view returns (address) { + return vault.governance(); + } + + /** + * @notice + * Provide an accurate estimate for the total amount of assets + * (principle + return) that this Strategy is currently managing, + * denominated in terms of `want` tokens. + * + * This total should be "realizable" e.g. the total value that could + * *actually* be obtained from this Strategy if it were to divest its + * entire position based on current on-chain conditions. + * @dev + * Care must be taken in using this function, since it relies on external + * systems, which could be manipulated by the attacker to give an inflated + * (or reduced) value produced by this function, based on current on-chain + * conditions (e.g. this function is possible to influence through + * flashloan attacks, oracle manipulations, or other DeFi attack + * mechanisms). + * + * It is up to governance to use this function to correctly order this + * Strategy relative to its peers in the withdrawal queue to minimize + * losses for the Vault based on sudden withdrawals. This value should be + * higher than the total debt of the Strategy and higher than its expected + * value to be "safe". + * @return The estimated total assets in this Strategy. + */ + function estimatedTotalAssets() public virtual view returns (uint256); + + /* + * @notice + * Provide an indication of whether this strategy is currently "active" + * in that it is managing an active position, or will manage a position in + * the future. This should correlate to `harvest()` activity, so that Harvest + * events can be tracked externally by indexing agents. + * @return True if the strategy is actively managing a position. + */ + function isActive() public view returns (bool) { + return vault.strategies(address(this)).debtRatio > 0 || estimatedTotalAssets() > 0; + } + + /** + * Perform any Strategy unwinding or other calls necessary to capture the + * "free return" this Strategy has generated since the last time its core + * position(s) were adjusted. Examples include unwrapping extra rewards. + * This call is only used during "normal operation" of a Strategy, and + * should be optimized to minimize losses as much as possible. + * + * This method returns any realized profits and/or realized losses + * incurred, and should return the total amounts of profits/losses/debt + * payments (in `want` tokens) for the Vault's accounting (e.g. + * `want.balanceOf(this) >= _debtPayment + _profit - _loss`). + * + * `_debtOutstanding` will be 0 if the Strategy is not past the configured + * debt limit, otherwise its value will be how far past the debt limit + * the Strategy is. The Strategy's debt limit is configured in the Vault. + * + * NOTE: `_debtPayment` should be less than or equal to `_debtOutstanding`. + * It is okay for it to be less than `_debtOutstanding`, as that + * should only used as a guide for how much is left to pay back. + * Payments should be made to minimize loss from slippage, debt, + * withdrawal fees, etc. + * + * See `vault.debtOutstanding()`. + */ + function prepareReturn(uint256 _debtOutstanding) + internal + virtual + returns ( + uint256 _profit, + uint256 _loss, + uint256 _debtPayment + ); + + /** + * Perform any adjustments to the core position(s) of this Strategy given + * what change the Vault made in the "investable capital" available to the + * Strategy. Note that all "free capital" in the Strategy after the report + * was made is available for reinvestment. Also note that this number + * could be 0, and you should handle that scenario accordingly. + * + * See comments regarding `_debtOutstanding` on `prepareReturn()`. + */ + function adjustPosition(uint256 _debtOutstanding) internal virtual; + + /** + * Liquidate up to `_amountNeeded` of `want` of this strategy's positions, + * irregardless of slippage. Any excess will be re-invested with `adjustPosition()`. + * This function should return the amount of `want` tokens made available by the + * liquidation. If there is a difference between them, `_loss` indicates whether the + * difference is due to a realized loss, or if there is some other sitution at play + * (e.g. locked funds) where the amount made available is less than what is needed. + * This function is used during emergency exit instead of `prepareReturn()` to + * liquidate all of the Strategy's positions back to the Vault. + * + * NOTE: The invariant `_liquidatedAmount + _loss <= _amountNeeded` should always be maintained + */ + function liquidatePosition(uint256 _amountNeeded) internal virtual returns (uint256 _liquidatedAmount, uint256 _loss); + + /** + * @notice + * Provide a signal to the keeper that `tend()` should be called. The + * keeper will provide the estimated gas cost that they would pay to call + * `tend()`, and this function should use that estimate to make a + * determination if calling it is "worth it" for the keeper. This is not + * the only consideration into issuing this trigger, for example if the + * position would be negatively affected if `tend()` is not called + * shortly, then this can return `true` even if the keeper might be + * "at a loss" (keepers are always reimbursed by Yearn). + * @dev + * `callCost` must be priced in terms of `want`. + * + * This call and `harvestTrigger()` should never return `true` at the same + * time. + * @param callCost The keeper's estimated cast cost to call `tend()`. + * @return `true` if `tend()` should be called, `false` otherwise. + */ + function tendTrigger(uint256 callCost) public virtual view returns (bool) { + // We usually don't need tend, but if there are positions that need + // active maintainence, overriding this function is how you would + // signal for that. + return false; + } + + /** + * @notice + * Adjust the Strategy's position. The purpose of tending isn't to + * realize gains, but to maximize yield by reinvesting any returns. + * + * See comments on `adjustPosition()`. + * + * This may only be called by governance, the strategist, or the keeper. + */ + function tend() external onlyKeepers { + // Don't take profits with this call, but adjust for better gains + adjustPosition(vault.debtOutstanding()); + } + + /** + * @notice + * Provide a signal to the keeper that `harvest()` should be called. The + * keeper will provide the estimated gas cost that they would pay to call + * `harvest()`, and this function should use that estimate to make a + * determination if calling it is "worth it" for the keeper. This is not + * the only consideration into issuing this trigger, for example if the + * position would be negatively affected if `harvest()` is not called + * shortly, then this can return `true` even if the keeper might be "at a + * loss" (keepers are always reimbursed by Yearn). + * @dev + * `callCost` must be priced in terms of `want`. + * + * This call and `tendTrigger` should never return `true` at the + * same time. + * + * See `min/maxReportDelay`, `profitFactor`, `debtThreshold` to adjust the + * strategist-controlled parameters that will influence whether this call + * returns `true` or not. These parameters will be used in conjunction + * with the parameters reported to the Vault (see `params`) to determine + * if calling `harvest()` is merited. + * + * It is expected that an external system will check `harvestTrigger()`. + * This could be a script run off a desktop or cloud bot (e.g. + * https://github.com/iearn-finance/yearn-vaults/blob/master/scripts/keep.py), + * or via an integration with the Keep3r network (e.g. + * https://github.com/Macarse/GenericKeep3rV2/blob/master/contracts/keep3r/GenericKeep3rV2.sol). + * @param callCost The keeper's estimated cast cost to call `harvest()`. + * @return `true` if `harvest()` should be called, `false` otherwise. + */ + function harvestTrigger(uint256 callCost) public virtual view returns (bool) { + StrategyParams memory params = vault.strategies(address(this)); + + // Should not trigger if Strategy is not activated + if (params.activation == 0) return false; + + // Should not trigger if we haven't waited long enough since previous harvest + if (block.timestamp.sub(params.lastReport) < minReportDelay) return false; + + // Should trigger if hasn't been called in a while + if (block.timestamp.sub(params.lastReport) >= maxReportDelay) return true; + + // If some amount is owed, pay it back + // NOTE: Since debt is based on deposits, it makes sense to guard against large + // changes to the value from triggering a harvest directly through user + // behavior. This should ensure reasonable resistance to manipulation + // from user-initiated withdrawals as the outstanding debt fluctuates. + uint256 outstanding = vault.debtOutstanding(); + if (outstanding > debtThreshold) return true; + + // Check for profits and losses + uint256 total = estimatedTotalAssets(); + // Trigger if we have a loss to report + if (total.add(debtThreshold) < params.totalDebt) return true; + + uint256 profit = 0; + if (total > params.totalDebt) profit = total.sub(params.totalDebt); // We've earned a profit! + + // Otherwise, only trigger if it "makes sense" economically (gas cost + // is debtOutstanding ? totalAssets : debtOutstanding); + // NOTE: take up any remainder here as profit + if (debtPayment > debtOutstanding) { + profit = debtPayment.sub(debtOutstanding); + debtPayment = debtOutstanding; + } + } else { + // Free up returns for Vault to pull + (profit, loss, debtPayment) = prepareReturn(debtOutstanding); + } + + // Allow Vault to take up to the "harvested" balance of this contract, + // which is the amount it has earned since the last time it reported to + // the Vault. + debtOutstanding = vault.report(profit, loss, debtPayment); + + // Check if free returns are left, and re-invest them + adjustPosition(debtOutstanding); + + emit Harvested(profit, loss, debtPayment, debtOutstanding); + } + + /** + * @notice + * Withdraws `_amountNeeded` to `vault`. + * + * This may only be called by the Vault. + * @param _amountNeeded How much `want` to withdraw. + * @return _loss Any realized losses + */ + function withdraw(uint256 _amountNeeded) external returns (uint256 _loss) { + require(msg.sender == address(vault), "!vault"); + // Liquidate as much as possible to `want`, up to `_amountNeeded` + uint256 amountFreed; + (amountFreed, _loss) = liquidatePosition(_amountNeeded); + // Send it directly back (NOTE: Using `msg.sender` saves some gas here) + want.safeTransfer(msg.sender, amountFreed); + // NOTE: Reinvest anything leftover on next `tend`/`harvest` + } + + /** + * Do anything necessary to prepare this Strategy for migration, such as + * transferring any reserve or LP tokens, CDPs, or other tokens or stores of + * value. + */ + function prepareMigration(address _newStrategy) internal virtual; + + /** + * @notice + * Transfers all `want` from this Strategy to `_newStrategy`. + * + * This may only be called by governance or the Vault. + * @dev + * The new Strategy's Vault must be the same as this Strategy's Vault. + * @param _newStrategy The Strategy to migrate to. + */ + function migrate(address _newStrategy) external { + require(msg.sender == address(vault) || msg.sender == governance()); + require(BaseStrategy(_newStrategy).vault() == vault); + prepareMigration(_newStrategy); + want.safeTransfer(_newStrategy, want.balanceOf(address(this))); + } + + /** + * @notice + * Activates emergency exit. Once activated, the Strategy will exit its + * position upon the next harvest, depositing all funds into the Vault as + * quickly as is reasonable given on-chain conditions. + * + * This may only be called by governance or the strategist. + * @dev + * See `vault.setEmergencyShutdown()` and `harvest()` for further details. + */ + function setEmergencyExit() external onlyAuthorized { + emergencyExit = true; + vault.revokeStrategy(); + + emit EmergencyExitEnabled(); + } + + /** + * Override this to add all tokens/tokenized positions this contract + * manages on a *persistent* basis (e.g. not just for swapping back to + * want ephemerally). + * + * NOTE: Do *not* include `want`, already included in `sweep` below. + * + * Example: + * + * function protectedTokens() internal override view returns (address[] memory) { + * address[] memory protected = new address[](3); + * protected[0] = tokenA; + * protected[1] = tokenB; + * protected[2] = tokenC; + * return protected; + * } + */ + function protectedTokens() internal virtual view returns (address[] memory); + + /** + * @notice + * Removes tokens from this Strategy that are not the type of tokens + * managed by this Strategy. This may be used in case of accidentally + * sending the wrong kind of token to this Strategy. + * + * Tokens will be sent to `governance()`. + * + * This will fail if an attempt is made to sweep `want`, or any tokens + * that are protected by this Strategy. + * + * This may only be called by governance. + * @dev + * Implement `protectedTokens()` to specify any additional tokens that + * should be protected from sweeping in addition to `want`. + * @param _token The token to transfer out of this vault. + */ + function sweep(address _token) external onlyGovernance { + require(_token != address(want), "!want"); + require(_token != address(vault), "!shares"); + + address[] memory _protectedTokens = protectedTokens(); + for (uint256 i; i < _protectedTokens.length; i++) require(_token != _protectedTokens[i], "!protected"); + + IERC20(_token).safeTransfer(governance(), IERC20(_token).balanceOf(address(this))); + } +} + +abstract contract BaseStrategyInitializable is BaseStrategy { + event Cloned(address indexed clone); + + constructor(address _vault) public BaseStrategy(_vault) {} + + function initialize( + address _vault, + address _strategist, + address _rewards, + address _keeper + ) external virtual { + _initialize(_vault, _strategist, _rewards, _keeper); + } + + function clone(address _vault) external returns (address) { + return this.clone(_vault, msg.sender, msg.sender, msg.sender); + } + + function clone( + address _vault, + address _strategist, + address _rewards, + address _keeper + ) external returns (address newStrategy) { + // Copied from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol + bytes20 addressBytes = bytes20(address(this)); + + assembly { + // EIP-1167 bytecode + let clone_code := mload(0x40) + mstore(clone_code, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(clone_code, 0x14), addressBytes) + mstore(add(clone_code, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + newStrategy := create(0, clone_code, 0x37) + } + + BaseStrategyInitializable(newStrategy).initialize(_vault, _strategist, _rewards, _keeper); + + emit Cloned(newStrategy); + } +} diff --git a/external/contracts/yearn/Registry.vy b/external/contracts/yearn/Registry.vy new file mode 100644 index 000000000..de2cfb222 --- /dev/null +++ b/external/contracts/yearn/Registry.vy @@ -0,0 +1,330 @@ +# @version 0.2.11 + + +interface Vault: + def token() -> address: view + def apiVersion() -> String[28]: view + def governance() -> address: view + def initialize( + token: address, + governance: address, + rewards: address, + name: String[64], + symbol: String[32], + guardian: address, + ): nonpayable + + +# len(releases) +numReleases: public(uint256) +releases: public(HashMap[uint256, address]) + +# Token => len(vaults) +numVaults: public(HashMap[address, uint256]) +vaults: public(HashMap[address, HashMap[uint256, address]]) + +# Index of token added => token address +tokens: public(HashMap[uint256, address]) +# len(tokens) +numTokens: public(uint256) +# Inclusion check for token +isRegistered: public(HashMap[address, bool]) + +# 2-phase commit +governance: public(address) +pendingGovernance: public(address) + +tags: public(HashMap[address, String[120]]) +banksy: public(HashMap[address, bool]) # could be anyone + +event NewRelease: + release_id: indexed(uint256) + template: address + api_version: String[28] + +event NewVault: + token: indexed(address) + vault_id: indexed(uint256) + vault: address + api_version: String[28] + +event NewExperimentalVault: + token: indexed(address) + deployer: indexed(address) + vault: address + api_version: String[28] + +event NewGovernance: + governance: address + +event VaultTagged: + vault: address + tag: String[120] + +@external +def __init__(): + self.governance = msg.sender + + +@external +def setGovernance(governance: address): + """ + @notice Starts the 1st phase of the governance transfer. + @dev Throws if the caller is not current governance. + @param governance The next governance address + """ + assert msg.sender == self.governance # dev: unauthorized + self.pendingGovernance = governance + + +@external +def acceptGovernance(): + """ + @notice Completes the 2nd phase of the governance transfer. + @dev + Throws if the caller is not the pending caller. + Emits a `NewGovernance` event. + """ + assert msg.sender == self.pendingGovernance # dev: unauthorized + self.governance = msg.sender + log NewGovernance(msg.sender) + + +@view +@external +def latestRelease() -> String[28]: + """ + @notice Returns the api version of the latest release. + @dev Throws if no releases are registered yet. + @return The api version of the latest release. + """ + # NOTE: Throws if there has not been a release yet + return Vault(self.releases[self.numReleases - 1]).apiVersion() # dev: no release + + +@view +@external +def latestVault(token: address) -> address: + """ + @notice Returns the latest deployed vault for the given token. + @dev Throws if no vaults are endorsed yet for the given token. + @param token The token address to find the latest vault for. + @return The address of the latest vault for the given token. + """ + # NOTE: Throws if there has not been a deployed vault yet for this token + return self.vaults[token][self.numVaults[token] - 1] # dev: no vault for token + + +@external +def newRelease(vault: address): + """ + @notice + Add a previously deployed Vault as the template contract for the latest release, + to be used by further "forwarder-style" delegatecall proxy contracts that can be + deployed from the registry throw other methods (to save gas). + @dev + Throws if caller isn't `self.governance`. + Throws if `vault`'s governance isn't `self.governance`. + Throws if the api version is the same as the previous release. + Emits a `NewVault` event. + @param vault The vault that will be used as the template contract for the next release. + """ + assert msg.sender == self.governance # dev: unauthorized + + # Check if the release is different from the current one + # NOTE: This doesn't check for strict semver-style linearly increasing release versions + release_id: uint256 = self.numReleases # Next id in series + if release_id > 0: + assert ( + Vault(self.releases[release_id - 1]).apiVersion() + != Vault(vault).apiVersion() + ) # dev: same api version + # else: we are adding the first release to the Registry! + + # Update latest release + self.releases[release_id] = vault + self.numReleases = release_id + 1 + + # Log the release for external listeners (e.g. Graph) + log NewRelease(release_id, vault, Vault(vault).apiVersion()) + + +@internal +def _newProxyVault( + token: address, + governance: address, + rewards: address, + guardian: address, + name: String[64], + symbol: String[32], + releaseTarget: uint256, +) -> address: + release: address = self.releases[releaseTarget] + assert release != ZERO_ADDRESS # dev: unknown release + vault: address = create_forwarder_to(release) + + # NOTE: Must initialize the Vault atomically with deploying it + Vault(vault).initialize(token, governance, rewards, name, symbol, guardian) + + return vault + + +@internal +def _registerVault(token: address, vault: address): + # Check if there is an existing deployment for this token at the particular api version + # NOTE: This doesn't check for strict semver-style linearly increasing release versions + vault_id: uint256 = self.numVaults[token] # Next id in series + if vault_id > 0: + assert ( + Vault(self.vaults[token][vault_id - 1]).apiVersion() + != Vault(vault).apiVersion() + ) # dev: same api version + # else: we are adding a new token to the Registry + + # Update the latest deployment + self.vaults[token][vault_id] = vault + self.numVaults[token] = vault_id + 1 + + # Register tokens for endorsed vaults + if not self.isRegistered[token]: + self.isRegistered[token] = True + self.tokens[self.numTokens] = token + self.numTokens += 1 + + # Log the deployment for external listeners (e.g. Graph) + log NewVault(token, vault_id, vault, Vault(vault).apiVersion()) + + +@external +def newVault( + token: address, + guardian: address, + rewards: address, + name: String[64], + symbol: String[32], + releaseDelta: uint256 = 0, # NOTE: Uses latest by default +) -> address: + """ + @notice + Create a new vault for the given token using the latest release in the registry, + as a simple "forwarder-style" delegatecall proxy to the latest release. Also adds + the new vault to the list of "endorsed" vaults for that token. + @dev + `governance` is set in the new vault as `self.governance`, with no ability to override. + Throws if caller isn't `self.governance`. + Throws if no releases are registered yet. + Throws if there already is a registered vault for the given token with the latest api version. + Emits a `NewVault` event. + @param token The token that may be deposited into the new Vault. + @param guardian The address authorized for guardian interactions in the new Vault. + @param rewards The address to use for collecting rewards in the new Vault + @param name Specify a custom Vault name. Set to empty string for default choice. + @param symbol Specify a custom Vault symbol name. Set to empty string for default choice. + @param releaseDelta Specify the number of releases prior to the latest to use as a target. Default is latest. + @return The address of the newly-deployed vault + """ + assert msg.sender == self.governance # dev: unauthorized + + # NOTE: Underflow if no releases created yet, or targeting prior to release history + releaseTarget: uint256 = self.numReleases - 1 - releaseDelta # dev: no releases + vault: address = self._newProxyVault(token, msg.sender, rewards, guardian, name, symbol, releaseTarget) + + self._registerVault(token, vault) + + return vault + + +@external +def newExperimentalVault( + token: address, + governance: address, + guardian: address, + rewards: address, + name: String[64], + symbol: String[32], + releaseDelta: uint256 = 0, # NOTE: Uses latest by default +) -> address: + """ + @notice + Create a new vault for the given token using the latest release in the registry, + as a simple "forwarder-style" delegatecall proxy to the latest release. Does not add + the new vault to the list of "endorsed" vaults for that token. + @dev + Throws if no releases are registered yet. + Emits a `NewExperimentalVault` event. + @param token The token that may be deposited into the new Vault. + @param governance The address authorized for governance interactions in the new Vault. + @param guardian The address authorized for guardian interactions in the new Vault. + @param rewards The address to use for collecting rewards in the new Vault + @param name Specify a custom Vault name. Set to empty string for default choice. + @param symbol Specify a custom Vault symbol name. Set to empty string for default choice. + @param releaseDelta Specify the number of releases prior to the latest to use as a target. Default is latest. + @return The address of the newly-deployed vault + """ + # NOTE: Underflow if no releases created yet, or targeting prior to release history + releaseTarget: uint256 = self.numReleases - 1 - releaseDelta # dev: no releases + # NOTE: Anyone can call this method, as a convenience to Strategist' experiments + vault: address = self._newProxyVault(token, governance, rewards, guardian, name, symbol, releaseTarget) + + # NOTE: Not registered, so emit an "experiment" event here instead + log NewExperimentalVault(token, msg.sender, vault, Vault(vault).apiVersion()) + + return vault + + +@external +def endorseVault(vault: address, releaseDelta: uint256 = 0): + """ + @notice + Adds an existing vault to the list of "endorsed" vaults for that token. + @dev + `governance` is set in the new vault as `self.governance`, with no ability to override. + Throws if caller isn't `self.governance`. + Throws if `vault`'s governance isn't `self.governance`. + Throws if no releases are registered yet. + Throws if `vault`'s api version does not match latest release. + Throws if there already is a deployment for the vault's token with the latest api version. + Emits a `NewVault` event. + @param vault The vault that will be endorsed by the Registry. + @param releaseDelta Specify the number of releases prior to the latest to use as a target. Default is latest. + """ + assert msg.sender == self.governance # dev: unauthorized + assert Vault(vault).governance() == msg.sender # dev: not governed + + # NOTE: Underflow if no releases created yet, or targeting prior to release history + releaseTarget: uint256 = self.numReleases - 1 - releaseDelta # dev: no releases + api_version: String[28] = Vault(self.releases[releaseTarget]).apiVersion() + assert Vault(vault).apiVersion() == api_version # dev: not target release + + # Add to the end of the list of vaults for token + self._registerVault(Vault(vault).token(), vault) + + +@external +def setBanksy(tagger: address, allowed: bool = True): + """ + @notice Set the ability of a particular tagger to tag current vaults. + @dev Throws if caller is not `self.governance`. + @param tagger The address to approve or deny access to tagging. + @param allowed Whether to approve or deny `tagger`. Defaults to approve. + """ + assert msg.sender == self.governance # dev: unauthorized + self.banksy[tagger] = allowed + + +@external +def tagVault(vault: address, tag: String[120]): + """ + @notice Tag a Vault with a message. + @dev + Throws if caller is not `self.governance` or an approved tagger. + Emits a `VaultTagged` event. + @param vault The address to tag with the given `tag` message. + @param tag The message to tag `vault` with. + """ + if msg.sender != self.governance: + assert self.banksy[msg.sender] # dev: not banksy + # else: we are governance, we can do anything banksy can do + + self.tags[vault] = tag + log VaultTagged(vault, tag) diff --git a/external/contracts/yearn/Vault.vy b/external/contracts/yearn/Vault.vy new file mode 100644 index 000000000..fc88f3c0c --- /dev/null +++ b/external/contracts/yearn/Vault.vy @@ -0,0 +1,1667 @@ +# @version 0.2.11 +""" +@title Yearn Token Vault +@license GNU AGPLv3 +@author yearn.finance +@notice + Yearn Token Vault. Holds an underlying token, and allows users to interact + with the Yearn ecosystem through Strategies connected to the Vault. + Vaults are not limited to a single Strategy, they can have as many Strategies + as can be designed (however the withdrawal queue is capped at 20.) + Deposited funds are moved into the most impactful strategy that has not + already reached its limit for assets under management, regardless of which + Strategy a user's funds end up in, they receive their portion of yields + generated across all Strategies. + When a user withdraws, if there are no funds sitting undeployed in the + Vault, the Vault withdraws funds from Strategies in the order of least + impact. (Funds are taken from the Strategy that will disturb everyone's + gains the least, then the next least, etc.) In order to achieve this, the + withdrawal queue's order must be properly set and managed by the community + (through governance). + Vault Strategies are parameterized to pursue the highest risk-adjusted yield. + There is an "Emergency Shutdown" mode. When the Vault is put into emergency + shutdown, assets will be recalled from the Strategies as quickly as is + practical (given on-chain conditions), minimizing loss. Deposits are + halted, new Strategies may not be added, and each Strategy exits with the + minimum possible damage to position, while opening up deposits to be + withdrawn by users. There are no restrictions on withdrawals above what is + expected under Normal Operation. + For further details, please refer to the specification: + https://github.com/iearn-finance/yearn-vaults/blob/master/SPECIFICATION.md +""" + +API_VERSION: constant(String[28]) = "0.3.4" + +from vyper.interfaces import ERC20 + +implements: ERC20 + + +interface DetailedERC20: + def name() -> String[42]: view + def symbol() -> String[20]: view + def decimals() -> uint256: view + + +interface Strategy: + def want() -> address: view + def vault() -> address: view + def isActive() -> bool: view + def delegatedAssets() -> uint256: view + def estimatedTotalAssets() -> uint256: view + def withdraw(_amount: uint256) -> uint256: nonpayable + def migrate(_newStrategy: address): nonpayable + + +interface GuestList: + def authorized(guest: address, amount: uint256) -> bool: view + + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + value: uint256 + + +event Approval: + owner: indexed(address) + spender: indexed(address) + value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +totalSupply: public(uint256) + +token: public(ERC20) +governance: public(address) +management: public(address) +guardian: public(address) +pendingGovernance: address +guestList: public(GuestList) + +struct StrategyParams: + performanceFee: uint256 # Strategist's fee (basis points) + activation: uint256 # Activation block.timestamp + debtRatio: uint256 # Maximum borrow amount (in BPS of total assets) + minDebtPerHarvest: uint256 # Lower limit on the increase of debt since last harvest + maxDebtPerHarvest: uint256 # Upper limit on the increase of debt since last harvest + lastReport: uint256 # block.timestamp of the last time a report occured + totalDebt: uint256 # Total outstanding debt that Strategy has + totalGain: uint256 # Total returns that Strategy has realized for Vault + totalLoss: uint256 # Total losses that Strategy has realized for Vault + + +event StrategyAdded: + strategy: indexed(address) + debtRatio: uint256 # Maximum borrow amount (in BPS of total assets) + minDebtPerHarvest: uint256 # Lower limit on the increase of debt since last harvest + maxDebtPerHarvest: uint256 # Upper limit on the increase of debt since last harvest + performanceFee: uint256 # Strategist's fee (basis points) + + +event StrategyReported: + strategy: indexed(address) + gain: uint256 + loss: uint256 + debtPaid: uint256 + totalGain: uint256 + totalLoss: uint256 + totalDebt: uint256 + debtAdded: uint256 + debtRatio: uint256 + + +event UpdateGovernance: + governance: address # New active governance + + +event UpdateManagement: + management: address # New active manager + + +event UpdateGuestList: + guestList: address # Vault guest list address + + +event UpdateRewards: + rewards: address # New active rewards recipient + + +event UpdateDepositLimit: + depositLimit: uint256 # New active deposit limit + + +event UpdatePerformanceFee: + performanceFee: uint256 # New active performance fee + + +event UpdateManagementFee: + managementFee: uint256 # New active management fee + + +event UpdateGuardian: + guardian: address # Address of the active guardian + + +event EmergencyShutdown: + active: bool # New emergency shutdown state (if false, normal operation enabled) + + +event UpdateWithdrawalQueue: + queue: address[MAXIMUM_STRATEGIES] # New active withdrawal queue + + +event StrategyUpdateDebtRatio: + strategy: indexed(address) # Address of the strategy for the debt ratio adjustment + debtRatio: uint256 # The new debt limit for the strategy (in BPS of total assets) + + +event StrategyUpdateMinDebtPerHarvest: + strategy: indexed(address) # Address of the strategy for the rate limit adjustment + minDebtPerHarvest: uint256 # Lower limit on the increase of debt since last harvest + + +event StrategyUpdateMaxDebtPerHarvest: + strategy: indexed(address) # Address of the strategy for the rate limit adjustment + maxDebtPerHarvest: uint256 # Upper limit on the increase of debt since last harvest + + +event StrategyUpdatePerformanceFee: + strategy: indexed(address) # Address of the strategy for the performance fee adjustment + performanceFee: uint256 # The new performance fee for the strategy + + +event StrategyMigrated: + oldVersion: indexed(address) # Old version of the strategy to be migrated + newVersion: indexed(address) # New version of the strategy + + +event StrategyRevoked: + strategy: indexed(address) # Address of the strategy that is revoked + + +event StrategyRemovedFromQueue: + strategy: indexed(address) # Address of the strategy that is removed from the withdrawal queue + + +event StrategyAddedToQueue: + strategy: indexed(address) # Address of the strategy that is added to the withdrawal queue + + + +# NOTE: Track the total for overhead targeting purposes +strategies: public(HashMap[address, StrategyParams]) +MAXIMUM_STRATEGIES: constant(uint256) = 20 +DEGREDATION_COEFFICIENT: constant(uint256) = 10 ** 18 + +# Ordering that `withdraw` uses to determine which strategies to pull funds from +# NOTE: Does *NOT* have to match the ordering of all the current strategies that +# exist, but it is recommended that it does or else withdrawal depth is +# limited to only those inside the queue. +# NOTE: Ordering is determined by governance, and should be balanced according +# to risk, slippage, and/or volatility. Can also be ordered to increase the +# withdrawal speed of a particular Strategy. +# NOTE: The first time a ZERO_ADDRESS is encountered, it stops withdrawing +withdrawalQueue: public(address[MAXIMUM_STRATEGIES]) + +emergencyShutdown: public(bool) + +depositLimit: public(uint256) # Limit for totalAssets the Vault can hold +debtRatio: public(uint256) # Debt ratio for the Vault across all strategies (in BPS, <= 10k) +totalDebt: public(uint256) # Amount of tokens that all strategies have borrowed +delegatedAssets: public(uint256) # Amount of tokens that all strategies delegate to other Vaults +# NOTE: Cached value used solely for proper bookkeeping +_strategy_delegatedAssets: HashMap[address, uint256] +lastReport: public(uint256) # block.timestamp of last report +activation: public(uint256) # block.timestamp of contract deployment +lockedProfit: public(uint256) # how much profit is locked and cant be withdrawn + +lockedProfitDegration: public(uint256) # rate per block of degration. DEGREDATION_COEFFICIENT is 100% per block +rewards: public(address) # Rewards contract where Governance fees are sent to +# Governance Fee for management of Vault (given to `rewards`) +managementFee: public(uint256) +# Governance Fee for performance of Vault (given to `rewards`) +performanceFee: public(uint256) +MAX_BPS: constant(uint256) = 10_000 # 100%, or 10k basis points +# NOTE: A four-century period will be missing 3 of its 100 Julian leap years, leaving 97. +# So the average year has 365 + 97/400 = 365.2425 days +# ERROR(Julian): -0.0078 +# ERROR(Gregorian): -0.0003 +SECS_PER_YEAR: constant(uint256) = 31_556_952 # 365.2425 days +# `nonces` track `permit` approvals with signature. +nonces: public(HashMap[address, uint256]) +DOMAIN_SEPARATOR: public(bytes32) +DOMAIN_TYPE_HASH: constant(bytes32) = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)') +PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + + +@external +def initialize( + token: address, + governance: address, + rewards: address, + nameOverride: String[64], + symbolOverride: String[32], + guardian: address = msg.sender, +): + """ + @notice + Initializes the Vault, this is called only once, when the contract is + deployed. + The performance fee is set to 10% of yield, per Strategy. + The management fee is set to 2%, per year. + The initial deposit limit is set to 0 (deposits disabled); it must be + updated after initialization. + @dev + If `nameOverride` is not specified, the name will be 'yearn' + combined with the name of `token`. + If `symbolOverride` is not specified, the symbol will be 'y' + combined with the symbol of `token`. + @param token The token that may be deposited into this Vault. + @param governance The address authorized for governance interactions. + @param rewards The address to distribute rewards to. + @param nameOverride Specify a custom Vault name. Leave empty for default choice. + @param symbolOverride Specify a custom Vault symbol name. Leave empty for default choice. + @param guardian The address authorized for guardian interactions. Defaults to caller. + """ + assert self.activation == 0 # dev: no devops199 + self.token = ERC20(token) + if nameOverride == "": + self.name = concat(DetailedERC20(token).symbol(), " yVault") + else: + self.name = nameOverride + if symbolOverride == "": + self.symbol = concat("yv", DetailedERC20(token).symbol()) + else: + self.symbol = symbolOverride + self.decimals = DetailedERC20(token).decimals() + self.governance = governance + log UpdateGovernance(governance) + self.management = governance + log UpdateManagement(governance) + self.rewards = rewards + log UpdateRewards(rewards) + self.guardian = guardian + log UpdateGuardian(guardian) + self.performanceFee = 1000 # 10% of yield (per Strategy) + log UpdatePerformanceFee(convert(1000, uint256)) + self.managementFee = 200 # 2% per year + log UpdateManagementFee(convert(200, uint256)) + self.lastReport = block.timestamp + self.activation = block.timestamp + self.lockedProfitDegration = convert(DEGREDATION_COEFFICIENT * 46 /10 ** 6 , uint256) # 6 hours in blocks + # EIP-712 + self.DOMAIN_SEPARATOR = keccak256( + concat( + DOMAIN_TYPE_HASH, + keccak256(convert("Yearn Vault", Bytes[11])), + keccak256(convert(API_VERSION, Bytes[28])), + convert(chain.id, bytes32), + convert(self, bytes32) + ) + ) + + +@pure +@external +def apiVersion() -> String[28]: + """ + @notice + Used to track the deployed version of this contract. In practice you + can use this version number to compare with Yearn's GitHub and + determine which version of the source matches this deployed contract. + @dev + All strategies must have an `apiVersion()` that matches the Vault's + `API_VERSION`. + @return API_VERSION which holds the current version of this contract. + """ + return API_VERSION + + +@external +def setName(name: String[42]): + """ + @notice + Used to change the value of `name`. + This may only be called by governance. + @param name The new name to use. + """ + assert msg.sender == self.governance + self.name = name + + +@external +def setSymbol(symbol: String[20]): + """ + @notice + Used to change the value of `symbol`. + This may only be called by governance. + @param symbol The new symbol to use. + """ + assert msg.sender == self.governance + self.symbol = symbol + + +# 2-phase commit for a change in governance +@external +def setGovernance(governance: address): + """ + @notice + Nominate a new address to use as governance. + The change does not go into effect immediately. This function sets a + pending change, and the governance address is not updated until + the proposed governance address has accepted the responsibility. + This may only be called by the current governance address. + @param governance The address requested to take over Vault governance. + """ + assert msg.sender == self.governance + self.pendingGovernance = governance + + +@external +def acceptGovernance(): + """ + @notice + Once a new governance address has been proposed using setGovernance(), + this function may be called by the proposed address to accept the + responsibility of taking over governance for this contract. + This may only be called by the proposed governance address. + @dev + setGovernance() should be called by the existing governance address, + prior to calling this function. + """ + assert msg.sender == self.pendingGovernance + self.governance = msg.sender + log UpdateGovernance(msg.sender) + + +@external +def setManagement(management: address): + """ + @notice + Changes the management address. + Management is able to make some investment decisions adjusting parameters. + This may only be called by governance. + @param management The address to use for managing. + """ + assert msg.sender == self.governance + self.management = management + log UpdateManagement(management) + + +@external +def setGuestList(guestList: address): + """ + @notice + Used to set or change `guestList`. A guest list is another contract + that dictates who is allowed to participate in a Vault (and transfer + shares). + This may only be called by governance. + @param guestList The address of the `GuestList` contract to use. + """ + assert msg.sender == self.governance + self.guestList = GuestList(guestList) + log UpdateGuestList(guestList) + + +@external +def setRewards(rewards: address): + """ + @notice + Changes the rewards address. Any distributed rewards + will cease flowing to the old address and begin flowing + to this address once the change is in effect. + This will not change any Strategy reports in progress, only + new reports made after this change goes into effect. + This may only be called by governance. + @param rewards The address to use for collecting rewards. + """ + assert msg.sender == self.governance + self.rewards = rewards + log UpdateRewards(rewards) + +@external +def setLockedProfitDegration(degration: uint256): + """ + @notice + Changes the locked profit degration. + @param degration The rate of degration in percent per second scaled to 1e18. + """ + assert msg.sender == self.governance + # Since "degration" is of type uint256 it can never be less than zero + assert degration <= DEGREDATION_COEFFICIENT + self.lockedProfitDegration = degration + +@external +def setDepositLimit(limit: uint256): + """ + @notice + Changes the maximum amount of tokens that can be deposited in this Vault. + Note, this is not how much may be deposited by a single depositor, + but the maximum amount that may be deposited across all depositors. + This may only be called by governance. + @param limit The new deposit limit to use. + """ + assert msg.sender == self.governance + self.depositLimit = limit + log UpdateDepositLimit(limit) + + +@external +def setPerformanceFee(fee: uint256): + """ + @notice + Used to change the value of `performanceFee`. + Should set this value below the maximum strategist performance fee. + This may only be called by governance. + @param fee The new performance fee to use. + """ + assert msg.sender == self.governance + assert fee <= MAX_BPS + self.performanceFee = fee + log UpdatePerformanceFee(fee) + + +@external +def setManagementFee(fee: uint256): + """ + @notice + Used to change the value of `managementFee`. + This may only be called by governance. + @param fee The new management fee to use. + """ + assert msg.sender == self.governance + assert fee <= MAX_BPS + self.managementFee = fee + log UpdateManagementFee(fee) + + +@external +def setGuardian(guardian: address): + """ + @notice + Used to change the address of `guardian`. + This may only be called by governance or the existing guardian. + @param guardian The new guardian address to use. + """ + assert msg.sender in [self.guardian, self.governance] + self.guardian = guardian + log UpdateGuardian(guardian) + + +@external +def setEmergencyShutdown(active: bool): + """ + @notice + Activates or deactivates Vault mode where all Strategies go into full + withdrawal. + During Emergency Shutdown: + 1. No Users may deposit into the Vault (but may withdraw as usual.) + 2. Governance may not add new Strategies. + 3. Each Strategy must pay back their debt as quickly as reasonable to + minimally affect their position. + 4. Only Governance may undo Emergency Shutdown. + See contract level note for further details. + This may only be called by governance or the guardian. + @param active + If true, the Vault goes into Emergency Shutdown. If false, the Vault + goes back into Normal Operation. + """ + if active: + assert msg.sender in [self.guardian, self.governance] + else: + assert msg.sender == self.governance + self.emergencyShutdown = active + log EmergencyShutdown(active) + + +@external +def setWithdrawalQueue(queue: address[MAXIMUM_STRATEGIES]): + """ + @notice + Updates the withdrawalQueue to match the addresses and order specified + by `queue`. + There can be fewer strategies than the maximum, as well as fewer than + the total number of strategies active in the vault. `withdrawalQueue` + will be updated in a gas-efficient manner, assuming the input is well- + ordered with 0x0 only at the end. + This may only be called by governance or management. + @dev + This is order sensitive, specify the addresses in the order in which + funds should be withdrawn (so `queue`[0] is the first Strategy withdrawn + from, `queue`[1] is the second, etc.) + This means that the least impactful Strategy (the Strategy that will have + its core positions impacted the least by having funds removed) should be + at `queue`[0], then the next least impactful at `queue`[1], and so on. + @param queue + The array of addresses to use as the new withdrawal queue. This is + order sensitive. + """ + assert msg.sender in [self.management, self.governance] + # HACK: Temporary until Vyper adds support for Dynamic arrays + for i in range(MAXIMUM_STRATEGIES): + if queue[i] == ZERO_ADDRESS and self.withdrawalQueue[i] == ZERO_ADDRESS: + break + assert self.strategies[queue[i]].activation > 0 + self.withdrawalQueue[i] = queue[i] + log UpdateWithdrawalQueue(queue) + + +@internal +def erc20_safe_transfer(token: address, receiver: address, amount: uint256): + # Used only to send tokens that are not the type managed by this Vault. + # HACK: Used to handle non-compliant tokens like USDT + response: Bytes[32] = raw_call( + token, + concat( + method_id("transfer(address,uint256)"), + convert(receiver, bytes32), + convert(amount, bytes32), + ), + max_outsize=32, + ) + if len(response) > 0: + assert convert(response, bool), "Transfer failed!" + + +@internal +def erc20_safe_transferFrom(token: address, sender: address, receiver: address, amount: uint256): + # Used only to send tokens that are not the type managed by this Vault. + # HACK: Used to handle non-compliant tokens like USDT + response: Bytes[32] = raw_call( + token, + concat( + method_id("transferFrom(address,address,uint256)"), + convert(sender, bytes32), + convert(receiver, bytes32), + convert(amount, bytes32), + ), + max_outsize=32, + ) + if len(response) > 0: + assert convert(response, bool), "Transfer failed!" + + +@internal +def _transfer(sender: address, receiver: address, amount: uint256): + # See note on `transfer()`. + + # Protect people from accidentally sending their shares to bad places + assert not (receiver in [self, ZERO_ADDRESS]) + self.balanceOf[sender] -= amount + self.balanceOf[receiver] += amount + log Transfer(sender, receiver, amount) + + +@external +def transfer(receiver: address, amount: uint256) -> bool: + """ + @notice + Transfers shares from the caller's address to `receiver`. This function + will always return true, unless the user is attempting to transfer + shares to this contract's address, or to 0x0. + @param receiver + The address shares are being transferred to. Must not be this contract's + address, must not be 0x0. + @param amount The quantity of shares to transfer. + @return + True if transfer is sent to an address other than this contract's or + 0x0, otherwise the transaction will fail. + """ + self._transfer(msg.sender, receiver, amount) + return True + + +@external +def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: + """ + @notice + Transfers `amount` shares from `sender` to `receiver`. This operation will + always return true, unless the user is attempting to transfer shares + to this contract's address, or to 0x0. + Unless the caller has given this contract unlimited approval, + transfering shares will decrement the caller's `allowance` by `amount`. + @param sender The address shares are being transferred from. + @param receiver + The address shares are being transferred to. Must not be this contract's + address, must not be 0x0. + @param amount The quantity of shares to transfer. + @return + True if transfer is sent to an address other than this contract's or + 0x0, otherwise the transaction will fail. + """ + # Unlimited approval (saves an SSTORE) + if (self.allowance[sender][msg.sender] < MAX_UINT256): + allowance: uint256 = self.allowance[sender][msg.sender] - amount + self.allowance[sender][msg.sender] = allowance + # NOTE: Allows log filters to have a full accounting of allowance changes + log Approval(sender, msg.sender, allowance) + self._transfer(sender, receiver, amount) + return True + + +@external +def approve(spender: address, amount: uint256) -> bool: + """ + @dev Approve the passed address to spend the specified amount of tokens on behalf of + `msg.sender`. Beware that changing an allowance with this method brings the risk + that someone may use both the old and the new allowance by unfortunate transaction + ordering. See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param spender The address which will spend the funds. + @param amount The amount of tokens to be spent. + """ + self.allowance[msg.sender][spender] = amount + log Approval(msg.sender, spender, amount) + return True + + +@external +def increaseAllowance(spender: address, amount: uint256) -> bool: + """ + @dev Increase the allowance of the passed address to spend the total amount of tokens + on behalf of msg.sender. This method mitigates the risk that someone may use both + the old and the new allowance by unfortunate transaction ordering. + See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param spender The address which will spend the funds. + @param amount The amount of tokens to increase the allowance by. + """ + self.allowance[msg.sender][spender] += amount + log Approval(msg.sender, spender, self.allowance[msg.sender][spender]) + return True + + +@external +def decreaseAllowance(spender: address, amount: uint256) -> bool: + """ + @dev Decrease the allowance of the passed address to spend the total amount of tokens + on behalf of msg.sender. This method mitigates the risk that someone may use both + the old and the new allowance by unfortunate transaction ordering. + See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param spender The address which will spend the funds. + @param amount The amount of tokens to decrease the allowance by. + """ + self.allowance[msg.sender][spender] -= amount + log Approval(msg.sender, spender, self.allowance[msg.sender][spender]) + return True + + +@external +def permit(owner: address, spender: address, amount: uint256, expiry: uint256, signature: Bytes[65]) -> bool: + """ + @notice + Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @param owner The address which is a source of funds and has signed the Permit. + @param spender The address which is allowed to spend the funds. + @param amount The amount of tokens to be spent. + @param expiry The timestamp after which the Permit is no longer valid. + @param signature A valid secp256k1 signature of Permit by owner encoded as r, s, v. + @return True, if transaction completes successfully + """ + assert owner != ZERO_ADDRESS # dev: invalid owner + assert expiry == 0 or expiry >= block.timestamp # dev: permit expired + nonce: uint256 = self.nonces[owner] + digest: bytes32 = keccak256( + concat( + b'\x19\x01', + self.DOMAIN_SEPARATOR, + keccak256( + concat( + PERMIT_TYPE_HASH, + convert(owner, bytes32), + convert(spender, bytes32), + convert(amount, bytes32), + convert(nonce, bytes32), + convert(expiry, bytes32), + ) + ) + ) + ) + # NOTE: signature is packed as r, s, v + r: uint256 = convert(slice(signature, 0, 32), uint256) + s: uint256 = convert(slice(signature, 32, 32), uint256) + v: uint256 = convert(slice(signature, 64, 1), uint256) + assert ecrecover(digest, v, r, s) == owner # dev: invalid signature + self.allowance[owner][spender] = amount + self.nonces[owner] = nonce + 1 + log Approval(owner, spender, amount) + return True + + +@view +@internal +def _totalAssets() -> uint256: + # See note on `totalAssets()`. + return self.token.balanceOf(self) + self.totalDebt + + +@view +@external +def totalAssets() -> uint256: + """ + @notice + Returns the total quantity of all assets under control of this + Vault, whether they're loaned out to a Strategy, or currently held in + the Vault. + @return The total assets under control of this Vault. + """ + return self._totalAssets() + + +@internal +def _issueSharesForAmount(to: address, amount: uint256) -> uint256: + # Issues `amount` Vault shares to `to`. + # Shares must be issued prior to taking on new collateral, or + # calculation will be wrong. This means that only *trusted* tokens + # (with no capability for exploitative behavior) can be used. + shares: uint256 = 0 + # HACK: Saves 2 SLOADs (~4000 gas) + totalSupply: uint256 = self.totalSupply + if totalSupply > 0: + # Mint amount of shares based on what the Vault is managing overall + # NOTE: if sqrt(token.totalSupply()) > 1e39, this could potentially revert + shares = amount * totalSupply / self._totalAssets() + else: + # No existing shares, so mint 1:1 + shares = amount + + # Mint new shares + self.totalSupply = totalSupply + shares + self.balanceOf[to] += shares + log Transfer(ZERO_ADDRESS, to, shares) + + return shares + + +@external +@nonreentrant("withdraw") +def deposit(_amount: uint256 = MAX_UINT256, recipient: address = msg.sender) -> uint256: + """ + @notice + Deposits `_amount` `token`, issuing shares to `recipient`. If the + Vault is in Emergency Shutdown, deposits will not be accepted and this + call will fail. + @dev + Measuring quantity of shares to issues is based on the total + outstanding debt that this contract has ("expected value") instead + of the total balance sheet it has ("estimated value") has important + security considerations, and is done intentionally. If this value were + measured against external systems, it could be purposely manipulated by + an attacker to withdraw more assets than they otherwise should be able + to claim by redeeming their shares. + On deposit, this means that shares are issued against the total amount + that the deposited capital can be given in service of the debt that + Strategies assume. If that number were to be lower than the "expected + value" at some future point, depositing shares via this method could + entitle the depositor to *less* than the deposited value once the + "realized value" is updated from further reports by the Strategies + to the Vaults. + Care should be taken by integrators to account for this discrepancy, + by using the view-only methods of this contract (both off-chain and + on-chain) to determine if depositing into the Vault is a "good idea". + @param _amount The quantity of tokens to deposit, defaults to all. + @param recipient + The address to issue the shares in this Vault to. Defaults to the + caller's address. + @return The issued Vault shares. + """ + assert not self.emergencyShutdown # Deposits are locked out + + amount: uint256 = _amount + + # If _amount not specified, transfer the full token balance, + # up to deposit limit + if amount == MAX_UINT256: + amount = min( + self.depositLimit - self._totalAssets(), + self.token.balanceOf(msg.sender), + ) + else: + # Ensure deposit limit is respected + assert self._totalAssets() + amount <= self.depositLimit + + # Ensure we are depositing something + assert amount > 0 + + # Ensure deposit is permitted by guest list + if self.guestList.address != ZERO_ADDRESS: + assert self.guestList.authorized(msg.sender, amount) + + # Issue new shares (needs to be done before taking deposit to be accurate) + # Shares are issued to recipient (may be different from msg.sender) + # See @dev note, above. + shares: uint256 = self._issueSharesForAmount(recipient, amount) + + # Tokens are transferred from msg.sender (may be different from _recipient) + self.erc20_safe_transferFrom(self.token.address, msg.sender, self, amount) + + return shares # Just in case someone wants them + + +@view +@internal +def _shareValue(shares: uint256) -> uint256: + # Returns price = 1:1 if vault is empty + if self.totalSupply == 0: + return shares + + # Determines the current value of `shares`. + # NOTE: if sqrt(Vault.totalAssets()) >>> 1e39, this could potentially revert + lockedFundsRatio: uint256 = (block.timestamp - self.lastReport) * self.lockedProfitDegration + freeFunds: uint256 = self._totalAssets() + + if(lockedFundsRatio < DEGREDATION_COEFFICIENT): + freeFunds -= (self.lockedProfit - (lockedFundsRatio * self.lockedProfit / DEGREDATION_COEFFICIENT)) + # NOTE: using 1e3 for extra precision here, when decimals is low + return ((10 ** 3 * (shares * freeFunds)) / self.totalSupply) / 10 ** 3 + + +@view +@internal +def _sharesForAmount(amount: uint256) -> uint256: + # Determines how many shares `amount` of token would receive. + # See dev note on `deposit`. + if self._totalAssets() > 0: + # NOTE: if sqrt(token.totalSupply()) > 1e37, this could potentially revert + return ((10 ** 3 * (amount * self.totalSupply)) / self._totalAssets()) / 10 ** 3 + else: + return 0 + + +@view +@external +def maxAvailableShares() -> uint256: + """ + @notice + Determines the maximum quantity of shares this Vault can facilitate a + withdrawal for, factoring in assets currently residing in the Vault, + as well as those deployed to strategies on the Vault's balance sheet. + @dev + Regarding how shares are calculated, see dev note on `deposit`. + If you want to calculated the maximum a user could withdraw up to, + you want to use this function. + Note that the amount provided by this function is the theoretical + maximum possible from withdrawing, the real amount depends on the + realized losses incurred during withdrawal. + @return The total quantity of shares this Vault can provide. + """ + shares: uint256 = self._sharesForAmount(self.token.balanceOf(self)) + + for strategy in self.withdrawalQueue: + if strategy == ZERO_ADDRESS: + break + shares += self._sharesForAmount(self.strategies[strategy].totalDebt) + + return shares + + +@internal +def _updateDelegatedAssets(strategy: address): + self.delegatedAssets -= self._strategy_delegatedAssets[strategy] + # NOTE: Use `min(totalDebt, delegatedAssets)` as a guard against improper computation + delegatedAssets: uint256 = min( + self.strategies[strategy].totalDebt, + Strategy(strategy).delegatedAssets(), + ) + self.delegatedAssets += delegatedAssets + self._strategy_delegatedAssets[strategy] = delegatedAssets + + +@external +@nonreentrant("withdraw") +def withdraw( + maxShares: uint256 = MAX_UINT256, + recipient: address = msg.sender, + maxLoss: uint256 = 1, # 0.01% [BPS] +) -> uint256: + """ + @notice + Withdraws the calling account's tokens from this Vault, redeeming + amount `_shares` for an appropriate amount of tokens. + See note on `setWithdrawalQueue` for further details of withdrawal + ordering and behavior. + @dev + Measuring the value of shares is based on the total outstanding debt + that this contract has ("expected value") instead of the total balance + sheet it has ("estimated value") has important security considerations, + and is done intentionally. If this value were measured against external + systems, it could be purposely manipulated by an attacker to withdraw + more assets than they otherwise should be able to claim by redeeming + their shares. + On withdrawal, this means that shares are redeemed against the total + amount that the deposited capital had "realized" since the point it + was deposited, up until the point it was withdrawn. If that number + were to be higher than the "expected value" at some future point, + withdrawing shares via this method could entitle the depositor to + *more* than the expected value once the "realized value" is updated + from further reports by the Strategies to the Vaults. + Under exceptional scenarios, this could cause earlier withdrawals to + earn "more" of the underlying assets than Users might otherwise be + entitled to, if the Vault's estimated value were otherwise measured + through external means, accounting for whatever exceptional scenarios + exist for the Vault (that aren't covered by the Vault's own design.) + @param maxShares + How many shares to try and redeem for tokens, defaults to all. + @param recipient + The address to issue the shares in this Vault to. Defaults to the + caller's address. + @param maxLoss + The maximum acceptable loss to sustain on withdrawal. Defaults to 0.01%. + @return The quantity of tokens redeemed for `_shares`. + """ + shares: uint256 = maxShares # May reduce this number below + + # Max Loss is <=100%, revert otherwise + assert maxLoss <= MAX_BPS + + # If _shares not specified, transfer full share balance + if shares == MAX_UINT256: + shares = self.balanceOf[msg.sender] + + # Limit to only the shares they own + assert shares <= self.balanceOf[msg.sender] + + # Ensure we are withdrawing something + assert shares > 0 + + # See @dev note, above. + value: uint256 = self._shareValue(shares) + + totalLoss: uint256 = 0 + if value > self.token.balanceOf(self): + # We need to go get some from our strategies in the withdrawal queue + # NOTE: This performs forced withdrawals from each Strategy. During + # forced withdrawal, a Strategy may realize a loss. That loss + # is reported back to the Vault, and the will affect the amount + # of tokens that the withdrawer receives for their shares. They + # can optionally specify the maximum acceptable loss (in BPS) + # to prevent excessive losses on their withdrawals (which may + # happen in certain edge cases where Strategies realize a loss) + for strategy in self.withdrawalQueue: + if strategy == ZERO_ADDRESS: + break # We've exhausted the queue + + vault_balance: uint256 = self.token.balanceOf(self) + if value <= vault_balance: + break # We're done withdrawing + + amountNeeded: uint256 = value - vault_balance + + # NOTE: Don't withdraw more than the debt so that Strategy can still + # continue to work based on the profits it has + # NOTE: This means that user will lose out on any profits that each + # Strategy in the queue would return on next harvest, benefiting others + amountNeeded = min(amountNeeded, self.strategies[strategy].totalDebt) + if amountNeeded == 0: + continue # Nothing to withdraw from this Strategy, try the next one + + # Force withdraw amount from each Strategy in the order set by governance + loss: uint256 = Strategy(strategy).withdraw(amountNeeded) + withdrawn: uint256 = self.token.balanceOf(self) - vault_balance + + # NOTE: Withdrawer incurs any losses from liquidation + if loss > 0: + value -= loss + totalLoss += loss + self.strategies[strategy].totalLoss += loss + + # Reduce the Strategy's debt by the amount withdrawn ("realized returns") + # NOTE: This doesn't add to returns as it's not earned by "normal means" + self.strategies[strategy].totalDebt -= withdrawn + loss + self.totalDebt -= withdrawn + loss + + # Ensure that delegated asset cached value is kept up to date + self._updateDelegatedAssets(strategy) + + # NOTE: We have withdrawn everything possible out of the withdrawal queue + # but we still don't have enough to fully pay them back, so adjust + # to the total amount we've freed up through forced withdrawals + vault_balance: uint256 = self.token.balanceOf(self) + if value > vault_balance: + value = vault_balance + # NOTE: Burn # of shares that corresponds to what Vault has on-hand, + # including the losses that were incurred above during withdrawals + shares = self._sharesForAmount(value + totalLoss) + + # NOTE: This loss protection is put in place to revert if losses from + # withdrawing are more than what is considered acceptable. + assert totalLoss <= maxLoss * (value + totalLoss) / MAX_BPS + + # Burn shares (full value of what is being withdrawn) + self.totalSupply -= shares + self.balanceOf[msg.sender] -= shares + log Transfer(msg.sender, ZERO_ADDRESS, shares) + + # Withdraw remaining balance to _recipient (may be different to msg.sender) (minus fee) + self.erc20_safe_transfer(self.token.address, recipient, value) + + return value + + +@view +@external +def pricePerShare() -> uint256: + """ + @notice Gives the price for a single Vault share. + @dev See dev note on `withdraw`. + @return The value of a single share. + """ + return self._shareValue(10 ** self.decimals) + + +@internal +def _organizeWithdrawalQueue(): + # Reorganize `withdrawalQueue` based on premise that if there is an + # empty value between two actual values, then the empty value should be + # replaced by the later value. + # NOTE: Relative ordering of non-zero values is maintained. + offset: uint256 = 0 + for idx in range(MAXIMUM_STRATEGIES): + strategy: address = self.withdrawalQueue[idx] + if strategy == ZERO_ADDRESS: + offset += 1 # how many values we need to shift, always `<= idx` + elif offset > 0: + self.withdrawalQueue[idx - offset] = strategy + self.withdrawalQueue[idx] = ZERO_ADDRESS + + +@external +def addStrategy( + strategy: address, + debtRatio: uint256, + minDebtPerHarvest: uint256, + maxDebtPerHarvest: uint256, + performanceFee: uint256, +): + """ + @notice + Add a Strategy to the Vault. + This may only be called by governance. + @dev + The Strategy will be appended to `withdrawalQueue`, call + `setWithdrawalQueue` to change the order. + @param strategy The address of the Strategy to add. + @param debtRatio + The share of the total assets in the `vault that the `strategy` has access to. + @param minDebtPerHarvest + Lower limit on the increase of debt since last harvest + @param maxDebtPerHarvest + Upper limit on the increase of debt since last harvest + @param performanceFee + The fee the strategist will receive based on this Vault's performance. + """ + # Check if queue is full + assert self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] == ZERO_ADDRESS + + # Check calling conditions + assert not self.emergencyShutdown + assert msg.sender == self.governance + + # Check strategy configuration + assert strategy != ZERO_ADDRESS + assert self.strategies[strategy].activation == 0 + assert self == Strategy(strategy).vault() + assert self.token.address == Strategy(strategy).want() + + # Check strategy parameters + assert self.debtRatio + debtRatio <= MAX_BPS + assert minDebtPerHarvest <= maxDebtPerHarvest + assert performanceFee <= MAX_BPS - self.performanceFee + + # Add strategy to approved strategies + self.strategies[strategy] = StrategyParams({ + performanceFee: performanceFee, + activation: block.timestamp, + debtRatio: debtRatio, + minDebtPerHarvest: minDebtPerHarvest, + maxDebtPerHarvest: maxDebtPerHarvest, + lastReport: block.timestamp, + totalDebt: 0, + totalGain: 0, + totalLoss: 0, + }) + log StrategyAdded(strategy, debtRatio, minDebtPerHarvest, maxDebtPerHarvest, performanceFee) + + # Update Vault parameters + self.debtRatio += debtRatio + + # Add strategy to the end of the withdrawal queue + self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy + self._organizeWithdrawalQueue() + + +@external +def updateStrategyDebtRatio( + strategy: address, + debtRatio: uint256, +): + """ + @notice + Change the quantity of assets `strategy` may manage. + This may be called by governance or management. + @param strategy The Strategy to update. + @param debtRatio The quantity of assets `strategy` may now manage. + """ + assert msg.sender in [self.management, self.governance] + assert self.strategies[strategy].activation > 0 + self.debtRatio -= self.strategies[strategy].debtRatio + self.strategies[strategy].debtRatio = debtRatio + self.debtRatio += debtRatio + assert self.debtRatio <= MAX_BPS + log StrategyUpdateDebtRatio(strategy, debtRatio) + + +@external +def updateStrategyMinDebtPerHarvest( + strategy: address, + minDebtPerHarvest: uint256, +): + """ + @notice + Change the quantity assets per block this Vault may deposit to or + withdraw from `strategy`. + This may only be called by governance or management. + @param strategy The Strategy to update. + @param minDebtPerHarvest + Lower limit on the increase of debt since last harvest + """ + assert msg.sender in [self.management, self.governance] + assert self.strategies[strategy].activation > 0 + assert self.strategies[strategy].maxDebtPerHarvest >= minDebtPerHarvest + self.strategies[strategy].minDebtPerHarvest = minDebtPerHarvest + log StrategyUpdateMinDebtPerHarvest(strategy, minDebtPerHarvest) + + +@external +def updateStrategyMaxDebtPerHarvest( + strategy: address, + maxDebtPerHarvest: uint256, +): + """ + @notice + Change the quantity assets per block this Vault may deposit to or + withdraw from `strategy`. + This may only be called by governance or management. + @param strategy The Strategy to update. + @param maxDebtPerHarvest + Upper limit on the increase of debt since last harvest + """ + assert msg.sender in [self.management, self.governance] + assert self.strategies[strategy].activation > 0 + assert self.strategies[strategy].minDebtPerHarvest <= maxDebtPerHarvest + self.strategies[strategy].maxDebtPerHarvest = maxDebtPerHarvest + log StrategyUpdateMaxDebtPerHarvest(strategy, maxDebtPerHarvest) + + +@external +def updateStrategyPerformanceFee( + strategy: address, + performanceFee: uint256, +): + """ + @notice + Change the fee the strategist will receive based on this Vault's + performance. + This may only be called by governance. + @param strategy The Strategy to update. + @param performanceFee The new fee the strategist will receive. + """ + assert msg.sender == self.governance + assert performanceFee <= MAX_BPS - self.performanceFee + assert self.strategies[strategy].activation > 0 + self.strategies[strategy].performanceFee = performanceFee + log StrategyUpdatePerformanceFee(strategy, performanceFee) + + +@internal +def _revokeStrategy(strategy: address): + self.debtRatio -= self.strategies[strategy].debtRatio + self.strategies[strategy].debtRatio = 0 + log StrategyRevoked(strategy) + + +@external +def migrateStrategy(oldVersion: address, newVersion: address): + """ + @notice + Migrates a Strategy, including all assets from `oldVersion` to + `newVersion`. + This may only be called by governance. + @dev + Strategy must successfully migrate all capital and positions to new + Strategy, or else this will upset the balance of the Vault. + The new Strategy should be "empty" e.g. have no prior commitments to + this Vault, otherwise it could have issues. + @param oldVersion The existing Strategy to migrate from. + @param newVersion The new Strategy to migrate to. + """ + assert msg.sender == self.governance + assert newVersion != ZERO_ADDRESS + assert self.strategies[oldVersion].activation > 0 + assert self.strategies[newVersion].activation == 0 + + strategy: StrategyParams = self.strategies[oldVersion] + + self._revokeStrategy(oldVersion) + # _revokeStrategy will lower the debtRatio + self.debtRatio += strategy.debtRatio + # Debt is migrated to new strategy + self.strategies[oldVersion].totalDebt = 0 + + self.strategies[newVersion] = StrategyParams({ + performanceFee: strategy.performanceFee, + # NOTE: use last report for activation time, so E[R] calc works + activation: strategy.lastReport, + debtRatio: strategy.debtRatio, + minDebtPerHarvest: strategy.minDebtPerHarvest, + maxDebtPerHarvest: strategy.maxDebtPerHarvest, + lastReport: strategy.lastReport, + totalDebt: strategy.totalDebt, + totalGain: 0, + totalLoss: 0, + }) + + Strategy(oldVersion).migrate(newVersion) + log StrategyMigrated(oldVersion, newVersion) + + for idx in range(MAXIMUM_STRATEGIES): + if self.withdrawalQueue[idx] == oldVersion: + self.withdrawalQueue[idx] = newVersion + return # Don't need to reorder anything because we swapped + + +@external +def revokeStrategy(strategy: address = msg.sender): + """ + @notice + Revoke a Strategy, setting its debt limit to 0 and preventing any + future deposits. + This function should only be used in the scenario where the Strategy is + being retired but no migration of the positions are possible, or in the + extreme scenario that the Strategy needs to be put into "Emergency Exit" + mode in order for it to exit as quickly as possible. The latter scenario + could be for any reason that is considered "critical" that the Strategy + exits its position as fast as possible, such as a sudden change in market + conditions leading to losses, or an imminent failure in an external + dependency. + This may only be called by governance, the guardian, or the Strategy + itself. Note that a Strategy will only revoke itself during emergency + shutdown. + @param strategy The Strategy to revoke. + """ + assert msg.sender in [strategy, self.governance, self.guardian] + self._revokeStrategy(strategy) + + +@external +def addStrategyToQueue(strategy: address): + """ + @notice + Adds `strategy` to `withdrawalQueue`. + This may only be called by governance or management. + @dev + The Strategy will be appended to `withdrawalQueue`, call + `setWithdrawalQueue` to change the order. + @param strategy The Strategy to add. + """ + assert msg.sender in [self.management, self.governance] + # Must be a current Strategy + assert self.strategies[strategy].activation > 0 + # Can't already be in the queue + last_idx: uint256 = 0 + for s in self.withdrawalQueue: + if s == ZERO_ADDRESS: + break + assert s != strategy + last_idx += 1 + # Check if queue is full + assert last_idx < MAXIMUM_STRATEGIES + + self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy + self._organizeWithdrawalQueue() + log StrategyAddedToQueue(strategy) + + +@external +def removeStrategyFromQueue(strategy: address): + """ + @notice + Remove `strategy` from `withdrawalQueue`. + This may only be called by governance or management. + @dev + We don't do this with revokeStrategy because it should still + be possible to withdraw from the Strategy if it's unwinding. + @param strategy The Strategy to remove. + """ + assert msg.sender in [self.management, self.governance] + for idx in range(MAXIMUM_STRATEGIES): + if self.withdrawalQueue[idx] == strategy: + self.withdrawalQueue[idx] = ZERO_ADDRESS + self._organizeWithdrawalQueue() + log StrategyRemovedFromQueue(strategy) + return # We found the right location and cleared it + raise # We didn't find the Strategy in the queue + + +@view +@internal +def _debtOutstanding(strategy: address) -> uint256: + # See note on `debtOutstanding()`. + strategy_debtLimit: uint256 = self.strategies[strategy].debtRatio * self._totalAssets() / MAX_BPS + strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt + + if self.emergencyShutdown: + return strategy_totalDebt + elif strategy_totalDebt <= strategy_debtLimit: + return 0 + else: + return strategy_totalDebt - strategy_debtLimit + + +@view +@external +def debtOutstanding(strategy: address = msg.sender) -> uint256: + """ + @notice + Determines if `strategy` is past its debt limit and if any tokens + should be withdrawn to the Vault. + @param strategy The Strategy to check. Defaults to the caller. + @return The quantity of tokens to withdraw. + """ + return self._debtOutstanding(strategy) + + +@view +@internal +def _creditAvailable(strategy: address) -> uint256: + # See note on `creditAvailable()`. + if self.emergencyShutdown: + return 0 + + vault_totalAssets: uint256 = self._totalAssets() + vault_debtLimit: uint256 = self.debtRatio * vault_totalAssets / MAX_BPS + vault_totalDebt: uint256 = self.totalDebt + strategy_debtLimit: uint256 = self.strategies[strategy].debtRatio * vault_totalAssets / MAX_BPS + strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt + strategy_minDebtPerHarvest: uint256 = self.strategies[strategy].minDebtPerHarvest + strategy_maxDebtPerHarvest: uint256 = self.strategies[strategy].maxDebtPerHarvest + + # Exhausted credit line + if strategy_debtLimit <= strategy_totalDebt or vault_debtLimit <= vault_totalDebt: + return 0 + + # Start with debt limit left for the Strategy + available: uint256 = strategy_debtLimit - strategy_totalDebt + + # Adjust by the global debt limit left + available = min(available, vault_debtLimit - vault_totalDebt) + + # Can only borrow up to what the contract has in reserve + # NOTE: Running near 100% is discouraged + available = min(available, self.token.balanceOf(self)) + + # Adjust by min and max borrow limits (per harvest) + # NOTE: min increase can be used to ensure that if a strategy has a minimum + # amount of capital needed to purchase a position, it's not given capital + # it can't make use of yet. + # NOTE: max increase is used to make sure each harvest isn't bigger than what + # is authorized. This combined with adjusting min and max periods in + # `BaseStrategy` can be used to effect a "rate limit" on capital increase. + if available < strategy_minDebtPerHarvest: + return 0 + else: + return min(available, strategy_maxDebtPerHarvest) + +@view +@external +def creditAvailable(strategy: address = msg.sender) -> uint256: + """ + @notice + Amount of tokens in Vault a Strategy has access to as a credit line. + This will check the Strategy's debt limit, as well as the tokens + available in the Vault, and determine the maximum amount of tokens + (if any) the Strategy may draw on. + In the rare case the Vault is in emergency shutdown this will return 0. + @param strategy The Strategy to check. Defaults to caller. + @return The quantity of tokens available for the Strategy to draw on. + """ + return self._creditAvailable(strategy) + + +@view +@internal +def _expectedReturn(strategy: address) -> uint256: + # See note on `expectedReturn()`. + strategy_lastReport: uint256 = self.strategies[strategy].lastReport + timeSinceLastHarvest: uint256 = block.timestamp - strategy_lastReport + totalHarvestTime: uint256 = strategy_lastReport - self.strategies[strategy].activation + + # NOTE: If either `timeSinceLastHarvest` or `totalHarvestTime` is 0, we can short-circuit to `0` + if timeSinceLastHarvest > 0 and totalHarvestTime > 0 and Strategy(strategy).isActive(): + # NOTE: Unlikely to throw unless strategy accumalates >1e68 returns + # NOTE: Calculate average over period of time where harvests have occured in the past + return (self.strategies[strategy].totalGain * timeSinceLastHarvest) / totalHarvestTime + else: + return 0 # Covers the scenario when block.timestamp == activation + + +@view +@external +def availableDepositLimit() -> uint256: + if self.depositLimit > self._totalAssets(): + return self.depositLimit - self._totalAssets() + else: + return 0 + + +@view +@external +def expectedReturn(strategy: address = msg.sender) -> uint256: + """ + @notice + Provide an accurate expected value for the return this `strategy` + would provide to the Vault the next time `report()` is called + (since the last time it was called). + @param strategy The Strategy to determine the expected return for. Defaults to caller. + @return + The anticipated amount `strategy` should make on its investment + since its last report. + """ + return self._expectedReturn(strategy) + + +@internal +def _reportLoss(strategy: address, loss: uint256): + # Loss can only be up the amount of debt issued to strategy + totalDebt: uint256 = self.strategies[strategy].totalDebt + assert totalDebt >= loss + self.strategies[strategy].totalLoss += loss + self.strategies[strategy].totalDebt = totalDebt - loss + self.totalDebt -= loss + + # Also, make sure we reduce our trust with the strategy by the same amount + debtRatio: uint256 = self.strategies[strategy].debtRatio + ratio_change: uint256 = min(loss * MAX_BPS / self._totalAssets(), debtRatio) + self.strategies[strategy].debtRatio -= ratio_change + self.debtRatio -= ratio_change + +@internal +def _assessFees(strategy: address, gain: uint256): + # Issue new shares to cover fees + # NOTE: In effect, this reduces overall share price by the combined fee + # NOTE: may throw if Vault.totalAssets() > 1e64, or not called for more than a year + governance_fee: uint256 = ( + ( + (self.totalDebt - self.delegatedAssets) + * (block.timestamp - self.lastReport) + * self.managementFee + ) + / MAX_BPS + / SECS_PER_YEAR + ) + strategist_fee: uint256 = 0 # Only applies in certain conditions + + # NOTE: Applies if Strategy is not shutting down, or it is but all debt paid off + # NOTE: No fee is taken when a Strategy is unwinding it's position, until all debt is paid + if gain > 0: + # NOTE: Unlikely to throw unless strategy reports >1e72 harvest profit + strategist_fee = ( + gain * self.strategies[strategy].performanceFee + ) / MAX_BPS + # NOTE: Unlikely to throw unless strategy reports >1e72 harvest profit + governance_fee += gain * self.performanceFee / MAX_BPS + + # NOTE: This must be called prior to taking new collateral, + # or the calculation will be wrong! + # NOTE: This must be done at the same time, to ensure the relative + # ratio of governance_fee : strategist_fee is kept intact + total_fee: uint256 = governance_fee + strategist_fee + if total_fee > 0: # NOTE: If mgmt fee is 0% and no gains were realized, skip + reward: uint256 = self._issueSharesForAmount(self, total_fee) + + # Send the rewards out as new shares in this Vault + if strategist_fee > 0: # NOTE: Guard against DIV/0 fault + # NOTE: Unlikely to throw unless sqrt(reward) >>> 1e39 + strategist_reward: uint256 = (strategist_fee * reward) / total_fee + self._transfer(self, strategy, strategist_reward) + # NOTE: Strategy distributes rewards at the end of harvest() + # NOTE: Governance earns any dust leftover from flooring math above + if self.balanceOf[self] > 0: + self._transfer(self, self.rewards, self.balanceOf[self]) + + +@external +def report(gain: uint256, loss: uint256, _debtPayment: uint256) -> uint256: + """ + @notice + Reports the amount of assets the calling Strategy has free (usually in + terms of ROI). + The performance fee is determined here, off of the strategy's profits + (if any), and sent to governance. + The strategist's fee is also determined here (off of profits), to be + handled according to the strategist on the next harvest. + This may only be called by a Strategy managed by this Vault. + @dev + For approved strategies, this is the most efficient behavior. + The Strategy reports back what it has free, then Vault "decides" + whether to take some back or give it more. Note that the most it can + take is `gain + _debtPayment`, and the most it can give is all of the + remaining reserves. Anything outside of those bounds is abnormal behavior. + All approved strategies must have increased diligence around + calling this function, as abnormal behavior could become catastrophic. + @param gain + Amount Strategy has realized as a gain on it's investment since its + last report, and is free to be given back to Vault as earnings + @param loss + Amount Strategy has realized as a loss on it's investment since its + last report, and should be accounted for on the Vault's balance sheet + @param _debtPayment + Amount Strategy has made available to cover outstanding debt + @return Amount of debt outstanding (if totalDebt > debtLimit or emergency shutdown). + """ + + # Only approved strategies can call this function + assert self.strategies[msg.sender].activation > 0 + # No lying about total available to withdraw! + assert self.token.balanceOf(msg.sender) >= gain + _debtPayment + + # We have a loss to report, do it before the rest of the calculations + if loss > 0: + self._reportLoss(msg.sender, loss) + + # Assess both management fee and performance fee, and issue both as shares of the vault + self._assessFees(msg.sender, gain) + + # Returns are always "realized gains" + self.strategies[msg.sender].totalGain += gain + + # Outstanding debt the Strategy wants to take back from the Vault (if any) + # NOTE: debtOutstanding <= StrategyParams.totalDebt + debt: uint256 = self._debtOutstanding(msg.sender) + debtPayment: uint256 = min(_debtPayment, debt) + + if debtPayment > 0: + self.strategies[msg.sender].totalDebt -= debtPayment + self.totalDebt -= debtPayment + debt -= debtPayment + # NOTE: `debt` is being tracked for later + + # Compute the line of credit the Vault is able to offer the Strategy (if any) + credit: uint256 = self._creditAvailable(msg.sender) + + # Update the actual debt based on the full credit we are extending to the Strategy + # or the returns if we are taking funds back + # NOTE: credit + self.strategies[msg.sender].totalDebt is always < self.debtLimit + # NOTE: At least one of `credit` or `debt` is always 0 (both can be 0) + if credit > 0: + self.strategies[msg.sender].totalDebt += credit + self.totalDebt += credit + + # Give/take balance to Strategy, based on the difference between the reported gains + # (if any), the debt payment (if any), the credit increase we are offering (if any), + # and the debt needed to be paid off (if any) + # NOTE: This is just used to adjust the balance of tokens between the Strategy and + # the Vault based on the Strategy's debt limit (as well as the Vault's). + totalAvail: uint256 = gain + debtPayment + if totalAvail < credit: # credit surplus, give to Strategy + self.erc20_safe_transfer(self.token.address, msg.sender, credit - totalAvail) + elif totalAvail > credit: # credit deficit, take from Strategy + self.erc20_safe_transferFrom(self.token.address, msg.sender, self, totalAvail - credit) + # else, don't do anything because it is balanced + + # Update cached value of delegated assets + # (used to properly account for mgmt fee in `_assessFees`) + self._updateDelegatedAssets(msg.sender) + + # Update reporting time + self.strategies[msg.sender].lastReport = block.timestamp + self.lastReport = block.timestamp + self.lockedProfit = gain # profit is locked and gradually released per block + + log StrategyReported( + msg.sender, + gain, + loss, + debtPayment, + self.strategies[msg.sender].totalGain, + self.strategies[msg.sender].totalLoss, + self.strategies[msg.sender].totalDebt, + credit, + self.strategies[msg.sender].debtRatio, + ) + + if self.strategies[msg.sender].debtRatio == 0 or self.emergencyShutdown: + # Take every last penny the Strategy has (Emergency Exit/revokeStrategy) + # NOTE: This is different than `debt` in order to extract *all* of the returns + return Strategy(msg.sender).estimatedTotalAssets() + else: + # Otherwise, just return what we have as debt outstanding + return debt + + +@external +def sweep(token: address, amount: uint256 = MAX_UINT256): + """ + @notice + Removes tokens from this Vault that are not the type of token managed + by this Vault. This may be used in case of accidentally sending the + wrong kind of token to this Vault. + Tokens will be sent to `governance`. + This will fail if an attempt is made to sweep the tokens that this + Vault manages. + This may only be called by governance. + @param token The token to transfer out of this vault. + @param amount The quantity or tokenId to transfer out. + """ + assert msg.sender == self.governance + # Can't be used to steal what this Vault is protecting + assert token != self.token.address + value: uint256 = amount + if value == MAX_UINT256: + value = ERC20(token).balanceOf(self) + self.erc20_safe_transfer(token, self.governance, value) diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts new file mode 100644 index 000000000..13af643c1 --- /dev/null +++ b/test/fixtures/yearn.spec.ts @@ -0,0 +1,69 @@ +import "module-alias/register"; + +import { Account } from "@utils/test/types"; +import DeployHelper from "@utils/deploys"; +import { + ether, +} from "@utils/index"; +import { + addSnapshotBeforeRestoreAfterEach, + getAccounts, + getYearnFixture, + getWaffleExpect +} from "@utils/test/index"; +import { StandardTokenMock } from "../../typechain/StandardTokenMock"; +import { YearnFixture } from "@utils/fixtures"; +import { Vault } from "../../typechain/Vault"; + + +const expect = getWaffleExpect(); + +describe("YearnFixture", () => { + let owner: Account; + + // let setup: SystemFixture; + let yearnSetup: YearnFixture; + let dai: StandardTokenMock; + + before(async () => { + [ + owner, + ] = await getAccounts(); + + // setup = getSystemFixture(owner.address); + const deployer = new DeployHelper(owner.wallet); + dai = await deployer.mocks.deployTokenMock(owner.address, ether(10000), 18); + + yearnSetup = getYearnFixture(owner.address); + + // await setup.initialize(); + await yearnSetup.initialize(); + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#createAndEnableVaultWithStrategyMock", async () => { + async function subject(): Promise { + return await yearnSetup.createAndEnableVaultWithStrategyMock( + dai.address, + owner.address, + owner.address, + owner.address, + "MockStrategy", + "M", + ether(100) + ); + } + + it("should create and enable a vault", async () => { + const vault = await subject(); + + const governance = await vault.pricePerShare(); + expect(governance).to.eq(ether(1)); + + // const strategies = await vault.strategies(); + // console.log("strategies", strategies); + }); + }); + +}); diff --git a/test/integration/oracles/yearnVaultOracle.spec.ts b/test/integration/oracles/yearnVaultOracle.spec.ts new file mode 100644 index 000000000..822edc262 --- /dev/null +++ b/test/integration/oracles/yearnVaultOracle.spec.ts @@ -0,0 +1,134 @@ +import "module-alias/register"; +import { BigNumber } from "@ethersproject/bignumber"; + +import { Address } from "@utils/types"; +import { Account } from "@utils/test/types"; +import { OracleMock, YearnVaultOracle } from "@utils/contracts"; +import { Vault } from "../../../typechain/Vault"; + +import DeployHelper from "@utils/deploys"; + +import { + ether +} from "@utils/index"; +import { + getAccounts, + getWaffleExpect, + getSystemFixture, + getYearnFixture, + addSnapshotBeforeRestoreAfterEach, +} from "@utils/test/index"; +import { YearnFixture, SystemFixture } from "@utils/fixtures"; + +const expect = getWaffleExpect(); + +describe("CTokenOracle", () => { + let owner: Account; + let deployer: DeployHelper; + let setup: SystemFixture; + + let yearnSetup: YearnFixture; + let daiVault: Vault; + + let daiUsdcOracle: OracleMock; + let daiUsdcPrice: BigNumber; + let yearnVaultDaiOracle: YearnVaultOracle; + let daiFullUnit: BigNumber; + + before(async () => { + [ + owner, + ] = await getAccounts(); + + // System setup + deployer = new DeployHelper(owner.wallet); + setup = getSystemFixture(owner.address); + await setup.initialize(); + + // Yearn setup + yearnSetup = getYearnFixture(owner.address); + await yearnSetup.initialize(); + + daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( + setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) + ); + + daiUsdcPrice = ether(1); + daiUsdcOracle = await deployer.mocks.deployOracleMock(daiUsdcPrice); + daiFullUnit = BigNumber.from("1000000000000000000"); + yearnVaultDaiOracle = await deployer.oracles.deployYearnVaultOracle( + daiVault.address, + daiUsdcOracle.address, + daiFullUnit, + "yvDAIUSDC Oracle" + ); + + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#constructor", async () => { + let subjectVaultAddress: Address; + let subjectUnderlyingOracle: Address; + let subjectUnderlyingFullUnit: BigNumber; + let subjectDataDescription: string; + + before(async () => { + subjectVaultAddress = daiVault.address; + subjectUnderlyingFullUnit = BigNumber.from("1000000000000000000"); + subjectUnderlyingOracle = daiUsdcOracle.address; + subjectDataDescription = "yvDAI Oracle"; + }); + + async function subject(): Promise { + return deployer.oracles.deployYearnVaultOracle( + subjectVaultAddress, + subjectUnderlyingOracle, + subjectUnderlyingFullUnit, + subjectDataDescription + ); + } + + it("sets the correct vault address", async () => { + const yearVaultOracle = await subject(); + const vaultAddress = await yearVaultOracle.vault(); + expect(vaultAddress).to.equal(subjectVaultAddress); + }); + + it("sets the correct underlying full unit", async () => { + const yearVaultOracle = await subject(); + const underlyingFullUnit = await yearVaultOracle.underlyingFullUnit(); + expect(underlyingFullUnit).to.eq(subjectUnderlyingFullUnit); + }); + + it("sets the correct underlying oracle address", async () => { + const yearVaultOracle = await subject(); + const underlyingOracleAddress = await yearVaultOracle.underlyingOracle(); + expect(underlyingOracleAddress).to.eq(subjectUnderlyingOracle); + }); + + it("sets the correct data description", async () => { + const yearVaultOracle = await subject(); + const actualDataDescription = await yearVaultOracle.dataDescription(); + expect(actualDataDescription).to.eq(subjectDataDescription); + }); + + }); + + + describe("#read", async () => { + + async function subject(): Promise { + return yearnVaultDaiOracle.read(); + } + + it("returns the correct vault value", async () => { + const result = await subject(); + const expectedResult = ether(1) + .div(daiFullUnit) + .mul(daiUsdcPrice); + + expect(result).to.eq(expectedResult); + }); + }); +}); diff --git a/test/integration/yearnWrapModule.spec.ts b/test/integration/yearnWrapModule.spec.ts new file mode 100644 index 000000000..a3b26d4fd --- /dev/null +++ b/test/integration/yearnWrapModule.spec.ts @@ -0,0 +1,195 @@ +import "module-alias/register"; +import { BigNumber } from "@ethersproject/bignumber"; + +import { Address } from "@utils/types"; +import { Account } from "@utils/test/types"; +import { ADDRESS_ZERO } from "@utils/constants"; +import { YearnWrapAdapter, SetToken, WrapModule } from "@utils/contracts"; +import DeployHelper from "@utils/deploys"; +import { + ether, + preciseMul, +} from "@utils/index"; +import { + getAccounts, + getWaffleExpect, + getSystemFixture, + getYearnFixture, + addSnapshotBeforeRestoreAfterEach, +} from "@utils/test/index"; +import { YearnFixture, SystemFixture } from "@utils/fixtures"; +import { Vault } from "@utils/contracts/yearn"; + + +const expect = getWaffleExpect(); + +describe("yearnWrapModule", () => { + let owner: Account; + let deployer: DeployHelper; + let setup: SystemFixture; + + let yearnSetup: YearnFixture; + let daiVault: Vault; + + let wrapModule: WrapModule; + let yearnWrapAdapter: YearnWrapAdapter; + + const yearnWrapAdapterIntegrationName: string = "YEARN_WRAPPER"; + + before(async () => { + [ + owner, + ] = await getAccounts(); + + // System setup + deployer = new DeployHelper(owner.wallet); + setup = getSystemFixture(owner.address); + await setup.initialize(); + + // Yearn setup + yearnSetup = getYearnFixture(owner.address); + await yearnSetup.initialize(); + + daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( + setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) + ); + + // WrapModule setup + wrapModule = await deployer.modules.deployWrapModule(setup.controller.address, setup.weth.address); + await setup.controller.addModule(wrapModule.address); + + // CompoundWrapAdapter setup + yearnWrapAdapter = await deployer.adapters.deployYearnWrapAdapter(); + await setup.integrationRegistry.addIntegration(wrapModule.address, yearnWrapAdapterIntegrationName, yearnWrapAdapter.address); + }); + + addSnapshotBeforeRestoreAfterEach(); + + context("when a SetToken has been deployed and issued", async () => { + let setToken: SetToken; + let setTokensIssued: BigNumber; + + before(async () => { + setToken = await setup.createSetToken( + [setup.dai.address], + [ether(1)], + [setup.issuanceModule.address, wrapModule.address] + ); + + // Initialize modules + await setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO); + await wrapModule.initialize(setToken.address); + + // Issue some Sets + setTokensIssued = ether(10); + const underlyingRequired = setTokensIssued; + await setup.dai.approve(setup.issuanceModule.address, underlyingRequired); + await setup.issuanceModule.issue(setToken.address, setTokensIssued, owner.address); + }); + + describe("#wrap", async () => { + let subjectSetToken: Address; + let subjectUnderlyingToken: Address; + let subjectWrappedToken: Address; + let subjectUnderlyingUnits: BigNumber; + let subjectIntegrationName: string; + let subjectCaller: Account; + + beforeEach(async () => { + subjectSetToken = setToken.address; + subjectUnderlyingToken = setup.dai.address; + subjectWrappedToken = daiVault.address; + subjectUnderlyingUnits = ether(1); + subjectIntegrationName = yearnWrapAdapterIntegrationName; + subjectCaller = owner; + }); + + async function subject(): Promise { + return wrapModule.connect(subjectCaller.wallet).wrap( + subjectSetToken, + subjectUnderlyingToken, + subjectWrappedToken, + subjectUnderlyingUnits, + subjectIntegrationName, + ); + } + + it("should reduce the underlying quantity and mint the wrapped asset to the SetToken", async () => { + const previousUnderlyingBalance = await setup.dai.balanceOf(setToken.address); + const previousWrappedBalance = await daiVault.balanceOf(setToken.address); + + await subject(); + + const underlyingBalance = await setup.dai.balanceOf(setToken.address); + const wrappedBalance = await daiVault.balanceOf(setToken.address); + + const expectedUnderlyingBalance = previousUnderlyingBalance.sub(setTokensIssued); + expect(underlyingBalance).to.eq(expectedUnderlyingBalance); + + const expectedWrappedBalance = previousWrappedBalance.add(setTokensIssued); + expect(wrappedBalance).to.eq(expectedWrappedBalance); + }); + }); + + describe("#unwrap", () => { + let subjectSetToken: Address; + let subjectUnderlyingToken: Address; + let subjectWrappedToken: Address; + let subjectWrappedTokenUnits: BigNumber; + let subjectIntegrationName: string; + let subjectCaller: Account; + + let wrappedQuantity: BigNumber; + + beforeEach(async () => { + subjectSetToken = setToken.address; + subjectUnderlyingToken = setup.dai.address; + subjectWrappedToken = daiVault.address; + subjectWrappedTokenUnits = ether(0.5); + subjectIntegrationName = yearnWrapAdapterIntegrationName; + subjectCaller = owner; + + wrappedQuantity = ether(1); + + await wrapModule.wrap( + subjectSetToken, + subjectUnderlyingToken, + subjectWrappedToken, + wrappedQuantity, + subjectIntegrationName, + ); + }); + + async function subject(): Promise { + return wrapModule.connect(subjectCaller.wallet).unwrap( + subjectSetToken, + subjectUnderlyingToken, + subjectWrappedToken, + subjectWrappedTokenUnits, + subjectIntegrationName, + { + gasLimit: 5000000, + } + ); + } + + it("should burn the wrapped asset to the SetToken and increase the underlying quantity", async () => { + const previousUnderlyingBalance = await setup.dai.balanceOf(setToken.address); + const previousWrappedBalance = await daiVault.balanceOf(setToken.address); + + await subject(); + + const underlyingBalance = await setup.dai.balanceOf(setToken.address); + const wrappedBalance = await daiVault.balanceOf(setToken.address); + + const delta = preciseMul(setTokensIssued, wrappedQuantity.sub(subjectWrappedTokenUnits)); + + const expectedUnderlyingBalance = previousUnderlyingBalance.add(delta); + expect(underlyingBalance).to.eq(expectedUnderlyingBalance); + + const expectedWrappedBalance = previousWrappedBalance.sub(delta); + expect(wrappedBalance).to.eq(expectedWrappedBalance); + }); + }); + }); +}); diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index dc42b7feb..d31dc1526 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -4,6 +4,8 @@ export { AaveLendingPoolCoreMock } from "../../typechain/AaveLendingPoolCoreMock export { AaveLendingPoolMock } from "../../typechain/AaveLendingPoolMock"; export { AaveMigrationWrapAdapter } from "../../typechain/AaveMigrationWrapAdapter"; export { AaveWrapAdapter } from "../../typechain/AaveWrapAdapter"; +export { YearnWrapAdapter } from "../../typechain/YearnWrapAdapter"; +export { YearnStrategyMock } from "../../typechain/YearnStrategyMock"; export { AddressArrayUtilsMock } from "../../typechain/AddressArrayUtilsMock"; export { AirdropModule } from "../../typechain/AirdropModule"; export { AmmAdapterMock } from "../../typechain/AmmAdapterMock"; @@ -49,6 +51,7 @@ export { OneInchExchangeAdapter } from "../../typechain/OneInchExchangeAdapter"; export { OneInchExchangeMock } from "../../typechain/OneInchExchangeMock"; export { OracleAdapterMock } from "../../typechain/OracleAdapterMock"; export { OracleMock } from "../../typechain/OracleMock"; +export { YearnVaultOracle } from "../../typechain/YearnVaultOracle"; export { PositionMock } from "../../typechain/PositionMock"; export { PreciseUnitMathMock } from "../../typechain/PreciseUnitMathMock"; export { PriceOracle } from "../../typechain/PriceOracle"; diff --git a/utils/contracts/yearn.ts b/utils/contracts/yearn.ts new file mode 100644 index 000000000..02ea94ff1 --- /dev/null +++ b/utils/contracts/yearn.ts @@ -0,0 +1,3 @@ +// External Yearn Contracts +export { Vault } from "../../typechain/Vault"; +export { Registry } from "../../typechain/Registry"; diff --git a/utils/deploys/deployAdapters.ts b/utils/deploys/deployAdapters.ts index 2f68d896a..cf39a40da 100644 --- a/utils/deploys/deployAdapters.ts +++ b/utils/deploys/deployAdapters.ts @@ -9,6 +9,7 @@ import { OneInchExchangeAdapter, AaveMigrationWrapAdapter, AaveWrapAdapter, + YearnWrapAdapter, UniswapPairPriceAdapter, UniswapV2ExchangeAdapter, UniswapV2ExchangeAdapterV2, @@ -29,6 +30,7 @@ import { OneInchExchangeAdapter__factory } from "../../typechain/factories/OneIn import { ZeroExApiAdapter__factory } from "../../typechain/factories/ZeroExApiAdapter__factory"; import { AaveMigrationWrapAdapter__factory } from "../../typechain/factories/AaveMigrationWrapAdapter__factory"; import { AaveWrapAdapter__factory } from "../../typechain/factories/AaveWrapAdapter__factory"; +import { YearnWrapAdapter__factory } from "../../typechain/factories/YearnWrapAdapter__factory"; import { UniswapPairPriceAdapter__factory } from "../../typechain/factories/UniswapPairPriceAdapter__factory"; import { UniswapV2ExchangeAdapter__factory } from "../../typechain/factories/UniswapV2ExchangeAdapter__factory"; import { UniswapV2ExchangeAdapterV2__factory } from "../../typechain/factories/UniswapV2ExchangeAdapterV2__factory"; @@ -87,6 +89,10 @@ export default class DeployAdapters { return await new AaveWrapAdapter__factory(this._deployerSigner).deploy(aaveLendingPool); } + public async deployYearnWrapAdapter(): Promise { + return await new YearnWrapAdapter__factory(this._deployerSigner).deploy(); + } + public async deployCompoundLikeGovernanceAdapter(governanceAlpha: Address, governanceToken: Address): Promise { return await new CompoundLikeGovernanceAdapter__factory(this._deployerSigner).deploy(governanceAlpha, governanceToken); } diff --git a/utils/deploys/deployExternal.ts b/utils/deploys/deployExternal.ts index 29e54577d..015344cc4 100644 --- a/utils/deploys/deployExternal.ts +++ b/utils/deploys/deployExternal.ts @@ -124,6 +124,14 @@ import { ExchangeProxy__factory } from "../../typechain/factories/ExchangeProxy_ import { DelegateRegistry__factory } from "../../typechain/factories/DelegateRegistry__factory"; +import { + Vault, + Registry +} from "../contracts/yearn"; +import { Registry__factory } from "../../typechain/factories/Registry__factory"; +import { Vault__factory } from "../../typechain/factories/Vault__factory"; + + export default class DeployExternalContracts { private _deployerSigner: Signer; @@ -518,4 +526,18 @@ export default class DeployExternalContracts { public async deployDelegateRegistry(): Promise { return await new DelegateRegistry__factory(this._deployerSigner).deploy(); } + + // YEARN + public async deployVault(): Promise { + return await new Vault__factory(this._deployerSigner).deploy(); + } + + public async getVault(vaultAddress: Address): Promise { + return await new Vault__factory(this._deployerSigner).attach(vaultAddress); + } + + public async deployRegistry(): Promise { + return await new Registry__factory(this._deployerSigner).deploy(); + } + } diff --git a/utils/deploys/deployMocks.ts b/utils/deploys/deployMocks.ts index 16516caad..9b76dfabd 100644 --- a/utils/deploys/deployMocks.ts +++ b/utils/deploys/deployMocks.ts @@ -40,6 +40,7 @@ import { Uint256ArrayUtilsMock, WrapAdapterMock, ZeroExMock, + YearnStrategyMock } from "../contracts"; import { convertLibraryNameToLinkId, ether } from "../common"; @@ -81,6 +82,7 @@ import { WrapAdapterMock__factory } from "../../typechain/factories/WrapAdapterM import { ZeroExMock__factory } from "../../typechain/factories/ZeroExMock__factory"; import { SynthMock__factory } from "../../typechain/factories/SynthMock__factory"; import { SynthetixExchangerMock__factory } from "../../typechain/factories/SynthetixExchangerMock__factory"; +import { YearnStrategyMock__factory } from "../../typechain/factories/YearnStrategyMock__factory"; export default class DeployMocks { private _deployerSigner: Signer; @@ -315,6 +317,10 @@ export default class DeployMocks { return await new CustomSetValuerMock__factory(this._deployerSigner).deploy(); } + public async deployYearnStrategyMock(vault: Address): Promise { + return await new YearnStrategyMock__factory(this._deployerSigner).deploy(vault); + } + /************************************* * Instance getters ************************************/ diff --git a/utils/deploys/deployOracles.ts b/utils/deploys/deployOracles.ts new file mode 100644 index 000000000..e3a4cbba5 --- /dev/null +++ b/utils/deploys/deployOracles.ts @@ -0,0 +1,25 @@ +import { Signer } from "ethers"; +import { Address } from "../types"; +import { BigNumber } from "@ethersproject/bignumber"; + +import { + YearnVaultOracle, +} from "../contracts"; + +import { YearnVaultOracle__factory } from "../../typechain/factories/YearnVaultOracle__factory"; + +export default class DeployOracles { + private _deployerSigner: Signer; + + constructor(deployerSigner: Signer) { + this._deployerSigner = deployerSigner; + } + + public async deployYearnVaultOracle( + vault: Address, + underlyingOracle: Address, + underlyingFullUnit: BigNumber, + dataDescription: string): Promise { + return await new YearnVaultOracle__factory(this._deployerSigner).deploy(vault, underlyingOracle, underlyingFullUnit, dataDescription); + } +} diff --git a/utils/deploys/index.ts b/utils/deploys/index.ts index fb2ac5931..647a9a72f 100644 --- a/utils/deploys/index.ts +++ b/utils/deploys/index.ts @@ -8,6 +8,7 @@ import DeployExternalContracts from "./deployExternal"; import DeployAdapters from "./deployAdapters"; import DeployViewers from "./deployViewers"; import DeployProduct from "./deployProduct"; +import DeployOracles from "./deployOracles"; export default class DeployHelper { public libraries: DeployLibraries; @@ -18,6 +19,7 @@ export default class DeployHelper { public adapters: DeployAdapters; public viewers: DeployViewers; public product: DeployProduct; + public oracles: DeployOracles; constructor(deployerSigner: Signer) { this.libraries = new DeployLibraries(deployerSigner); @@ -28,7 +30,6 @@ export default class DeployHelper { this.adapters = new DeployAdapters(deployerSigner); this.viewers = new DeployViewers(deployerSigner); this.product = new DeployProduct(deployerSigner); + this.oracles = new DeployOracles(deployerSigner); } } - - diff --git a/utils/fixtures/index.ts b/utils/fixtures/index.ts index 90762cf86..dd4a01107 100644 --- a/utils/fixtures/index.ts +++ b/utils/fixtures/index.ts @@ -4,3 +4,4 @@ export { CompoundFixture } from "./compoundFixture"; export { CurveFixture } from "./curveFixture"; export { SystemFixture } from "./systemFixture"; export { UniswapFixture } from "./uniswapFixture"; +export { YearnFixture } from "./yearnFixture"; diff --git a/utils/fixtures/yearnFixture.ts b/utils/fixtures/yearnFixture.ts new file mode 100644 index 000000000..26d2cc910 --- /dev/null +++ b/utils/fixtures/yearnFixture.ts @@ -0,0 +1,65 @@ +import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers"; +import { BigNumber } from "@ethersproject/bignumber"; +import { Signer } from "ethers"; + +import { + Vault, + Registry +} from "../contracts/yearn"; +import DeployHelper from "../deploys"; +import { Address } from "../types"; + +export class YearnFixture { + private _deployer: DeployHelper; + private _ownerAddress: Address; + private _ownerSigner: Signer; + + public registry: Registry; + + constructor(provider: Web3Provider | JsonRpcProvider, ownerAddress: Address) { + this._ownerAddress = ownerAddress; + this._ownerSigner = provider.getSigner(ownerAddress); + this._deployer = new DeployHelper(this._ownerSigner); + } + + public async initialize(): Promise { + this.registry = await this._deployer.external.deployRegistry(); // self.governance = msg.sender == ownerAddress + } + + public async createAndEnableVaultWithStrategyMock( + underlying: Address, + governance: Address, + guardian: Address, + rewards: Address, + name: string, + symbol: string, + depositLimit: BigNumber + ): Promise { + // https://github.com/yearn/yearn-vaults/blob/master/docs/OPERATIONS.md + const emptyVault = await this._deployer.external.deployVault(); + await emptyVault["initialize(address,address,address,string,string,address)"] + (underlying, governance, rewards, name, symbol, guardian); + + await emptyVault.setGovernance(this._ownerAddress); + await emptyVault.acceptGovernance(); + + await this.registry.newRelease(emptyVault.address); + await this.registry["newVault(address,address,address,string,string)"](underlying, guardian, rewards, name, symbol); + + const vaultAddress = await this.registry.latestVault(underlying); + + const vault = await this._deployer.external.getVault(vaultAddress); + await vault.setDepositLimit(depositLimit); + await vault.setManagementFee(0); + + const strategy = await this._deployer.mocks.deployYearnStrategyMock(vault.address); + + await vault.addStrategy(strategy.address, 9800, 0, 1000, 0); + + await strategy.setKeeper(this._ownerAddress); + await strategy.setRewards(rewards); + + + return vault; + } +} diff --git a/utils/test/index.ts b/utils/test/index.ts index e03c75e76..66d6f1071 100644 --- a/utils/test/index.ts +++ b/utils/test/index.ts @@ -2,7 +2,7 @@ import { ethers } from "hardhat"; import { Address } from "../types"; -import { AaveFixture, BalancerFixture, CompoundFixture, CurveFixture, SystemFixture, UniswapFixture } from "../fixtures"; +import { AaveFixture, BalancerFixture, CompoundFixture, CurveFixture, SystemFixture, UniswapFixture, YearnFixture } from "../fixtures"; import { Blockchain, ProtocolUtils } from "../common"; // Hardhat-Provider Aware Exports @@ -15,6 +15,7 @@ export const getBalancerFixture = (ownerAddress: Address) => new BalancerFixture export const getCurveFixture = (ownerAddress: Address) => new CurveFixture(provider, ownerAddress); export const getCompoundFixture = (ownerAddress: Address) => new CompoundFixture(provider, ownerAddress); export const getUniswapFixture = (ownerAddress: Address) => new UniswapFixture(provider, ownerAddress); +export const getYearnFixture = (ownerAddress: Address) => new YearnFixture(provider, ownerAddress); export { getAccounts, From d7defb639b8cfcef766500f373452df5ff1c0f0b Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 18:05:42 -0700 Subject: [PATCH 02/19] update comment --- test/integration/yearnWrapModule.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/yearnWrapModule.spec.ts b/test/integration/yearnWrapModule.spec.ts index a3b26d4fd..7a777ea24 100644 --- a/test/integration/yearnWrapModule.spec.ts +++ b/test/integration/yearnWrapModule.spec.ts @@ -58,7 +58,7 @@ describe("yearnWrapModule", () => { wrapModule = await deployer.modules.deployWrapModule(setup.controller.address, setup.weth.address); await setup.controller.addModule(wrapModule.address); - // CompoundWrapAdapter setup + // YearnWrapAdapter setup yearnWrapAdapter = await deployer.adapters.deployYearnWrapAdapter(); await setup.integrationRegistry.addIntegration(wrapModule.address, yearnWrapAdapterIntegrationName, yearnWrapAdapter.address); }); From 39f6dc913fd2f97efcc876dbea515e083d1b4ada Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 19:43:59 -0700 Subject: [PATCH 03/19] improve oracle test readability --- contracts/protocol/integration/YearnWrapAdapter.sol | 1 - test/fixtures/yearn.spec.ts | 6 ------ test/integration/oracles/yearnVaultOracle.spec.ts | 9 +++++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol index 227fc0ba2..fc931ea51 100644 --- a/contracts/protocol/integration/YearnWrapAdapter.sol +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -73,7 +73,6 @@ contract YearnWrapAdapter { { uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0; - // deposit(address _reserve, uint256 _amount, uint16 _referralCode) bytes memory callData = abi.encodeWithSignature("deposit(uint256)", _underlyingUnits); return (address(_wrappedToken), value, callData); diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts index 13af643c1..b33aa5f21 100644 --- a/test/fixtures/yearn.spec.ts +++ b/test/fixtures/yearn.spec.ts @@ -20,8 +20,6 @@ const expect = getWaffleExpect(); describe("YearnFixture", () => { let owner: Account; - - // let setup: SystemFixture; let yearnSetup: YearnFixture; let dai: StandardTokenMock; @@ -30,13 +28,11 @@ describe("YearnFixture", () => { owner, ] = await getAccounts(); - // setup = getSystemFixture(owner.address); const deployer = new DeployHelper(owner.wallet); dai = await deployer.mocks.deployTokenMock(owner.address, ether(10000), 18); yearnSetup = getYearnFixture(owner.address); - // await setup.initialize(); await yearnSetup.initialize(); }); @@ -61,8 +57,6 @@ describe("YearnFixture", () => { const governance = await vault.pricePerShare(); expect(governance).to.eq(ether(1)); - // const strategies = await vault.strategies(); - // console.log("strategies", strategies); }); }); diff --git a/test/integration/oracles/yearnVaultOracle.spec.ts b/test/integration/oracles/yearnVaultOracle.spec.ts index 822edc262..3968ae2cf 100644 --- a/test/integration/oracles/yearnVaultOracle.spec.ts +++ b/test/integration/oracles/yearnVaultOracle.spec.ts @@ -53,7 +53,7 @@ describe("CTokenOracle", () => { setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) ); - daiUsdcPrice = ether(1); + daiUsdcPrice = BigNumber.from("1000000000000000000"); daiUsdcOracle = await deployer.mocks.deployOracleMock(daiUsdcPrice); daiFullUnit = BigNumber.from("1000000000000000000"); yearnVaultDaiOracle = await deployer.oracles.deployYearnVaultOracle( @@ -117,6 +117,11 @@ describe("CTokenOracle", () => { describe("#read", async () => { + let subjectUnderlyingPricePerShare: BigNumber; + + before(async () => { + subjectUnderlyingPricePerShare = BigNumber.from("1000000000000000000"); + }); async function subject(): Promise { return yearnVaultDaiOracle.read(); @@ -124,7 +129,7 @@ describe("CTokenOracle", () => { it("returns the correct vault value", async () => { const result = await subject(); - const expectedResult = ether(1) + const expectedResult = subjectUnderlyingPricePerShare .div(daiFullUnit) .mul(daiUsdcPrice); From ef8d9618f2d496f898d3d4a507fb481b2e62959e Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 19:52:00 -0700 Subject: [PATCH 04/19] remove eth support --- contracts/protocol/integration/YearnWrapAdapter.sol | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol index fc931ea51..523968056 100644 --- a/contracts/protocol/integration/YearnWrapAdapter.sol +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -39,11 +39,6 @@ contract YearnWrapAdapter { _; } - /* ============ Constants ============ */ - - // Mock address to indicate ETH. - address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - /* ============ Constructor ============ */ constructor() public { } @@ -71,11 +66,8 @@ contract YearnWrapAdapter { onlyValidTokenPair(_underlyingToken, _wrappedToken) returns (address, uint256, bytes memory) { - uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0; - bytes memory callData = abi.encodeWithSignature("deposit(uint256)", _underlyingUnits); - - return (address(_wrappedToken), value, callData); + return (address(_wrappedToken), 0, callData); } /** @@ -99,9 +91,7 @@ contract YearnWrapAdapter { onlyValidTokenPair(_underlyingToken, _wrappedToken) returns (address, uint256, bytes memory) { - // redeem(uint256 _amount) bytes memory callData = abi.encodeWithSignature("withdraw(uint256)", _wrappedTokenUnits); - return (address(_wrappedToken), 0, callData); } From c1f173e0460bccd2ad3b9cba2011a9b830350c32 Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 20:00:08 -0700 Subject: [PATCH 05/19] better commet --- test/fixtures/yearn.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts index b33aa5f21..7dec11895 100644 --- a/test/fixtures/yearn.spec.ts +++ b/test/fixtures/yearn.spec.ts @@ -54,8 +54,8 @@ describe("YearnFixture", () => { it("should create and enable a vault", async () => { const vault = await subject(); - const governance = await vault.pricePerShare(); - expect(governance).to.eq(ether(1)); + const pricePerShare = await vault.pricePerShare(); // No deposiit into the vault. 1 share = 1 underlying + expect(pricePerShare).to.eq(ether(1)); }); }); From 7b3a7afdf18cb4f4e07f1182167371a08aa62cbe Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 20:11:12 -0700 Subject: [PATCH 06/19] remove unused var --- contracts/protocol/integration/oracles/YearnVaultOracle.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol index ed09d791d..17b4a68b7 100644 --- a/contracts/protocol/integration/oracles/YearnVaultOracle.sol +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -40,9 +40,6 @@ contract YearnVaultOracle is IOracle // Price per share values are scaled by 1e18 uint256 internal constant scalingFactor = 10 ** 18; - // CToken Full Unit - uint256 public cTokenFullUnit; - // Underlying Asset Full Unit uint256 public underlyingFullUnit; From 98e4c709635e11543e2b13281db7d816e51d3ade Mon Sep 17 00:00:00 2001 From: mario Date: Tue, 6 Apr 2021 12:31:38 -0700 Subject: [PATCH 07/19] address PR comments --- contracts/mocks/external/YearnVaultMock.sol | 28 ++++++++++++++++ .../protocol/integration/YearnWrapAdapter.sol | 2 +- .../integration/oracles/YearnVaultOracle.sol | 25 ++++++--------- test/fixtures/yearn.spec.ts | 2 +- .../oracles/yearnVaultOracle.spec.ts | 32 +++++++------------ utils/contracts/index.ts | 1 + utils/deploys/deployMocks.ts | 7 ++++ 7 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 contracts/mocks/external/YearnVaultMock.sol diff --git a/contracts/mocks/external/YearnVaultMock.sol b/contracts/mocks/external/YearnVaultMock.sol new file mode 100644 index 000000000..c7874d0ff --- /dev/null +++ b/contracts/mocks/external/YearnVaultMock.sol @@ -0,0 +1,28 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; + +contract YearnVaultMock { + uint256 public pricePerShare; + + constructor(uint256 _pricePerShare) public { + pricePerShare = _pricePerShare; + } + +} diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol index 523968056..141147667 100644 --- a/contracts/protocol/integration/YearnWrapAdapter.sol +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -23,7 +23,7 @@ import { IYearnVault } from "../../interfaces/external/IYearnVault.sol"; /** * @title YearnWrapAdapter - * @author Set Protocol + * @author Set Protocol, Ember Fund * * Wrap adapter for Yearn that returns data for wraps/unwraps of tokens */ diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol index 17b4a68b7..601d79b34 100644 --- a/contracts/protocol/integration/oracles/YearnVaultOracle.sol +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -37,19 +37,16 @@ contract YearnVaultOracle is IOracle IOracle public underlyingOracle; // Underlying token oracle string public dataDescription; - // Price per share values are scaled by 1e18 - uint256 internal constant scalingFactor = 10 ** 18; - // Underlying Asset Full Unit uint256 public underlyingFullUnit; /* ============ Constructor ============ */ /* - * @param _vault The address of Yearn Vault Token - * @param _underlyingOracle The address of the underlying oracle - * @param _underlyingFullUnit The full unit of the underlying asset - * @param _dataDescription Human readable description of oracle + * @param _vault The address of Yearn Vault Token + * @param _underlyingOracle The address of the underlying oracle + * @param _underlyingFullUnit The full unit of the underlying asset + * @param _dataDescription Human readable description of oracle */ constructor( IYearnVault _vault, @@ -66,12 +63,9 @@ contract YearnVaultOracle is IOracle } /** - * Returns the price value of a full vault token denominated in underlyingOracle value - & - * The underlying oracle is assumed to return a price of 18 decimal - * for a single full token of the underlying asset. The derived price - * of the vault token is then the price of a unit of underlying multiplied - * by the exchangeRate, adjusted for decimal differences, and descaled. + * Returns the price value of a full vault token denominated in underlyingOracle value. + * The derived price of the vault token is the price of a share multiplied divided by + * underlying full unit and multiplied by the underlying price. */ function read() external @@ -82,10 +76,9 @@ contract YearnVaultOracle is IOracle // Retrieve the price of the underlying uint256 underlyingPrice = underlyingOracle.read(); - // Retrieve price per share + // Price per share is the amount of the underlying asset per 1 full vaultToken uint256 pricePerShare = vault.pricePerShare(); - uint256 normalizedPricePerShare = pricePerShare.preciseDiv(underlyingFullUnit); - return normalizedPricePerShare.preciseMul(underlyingPrice); + return pricePerShare.mul(underlyingPrice).div(underlyingFullUnit); } } diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts index 7dec11895..453ec2dc4 100644 --- a/test/fixtures/yearn.spec.ts +++ b/test/fixtures/yearn.spec.ts @@ -54,7 +54,7 @@ describe("YearnFixture", () => { it("should create and enable a vault", async () => { const vault = await subject(); - const pricePerShare = await vault.pricePerShare(); // No deposiit into the vault. 1 share = 1 underlying + const pricePerShare = await vault.pricePerShare(); // No deposit into the vault. 1 share = 1 underlying expect(pricePerShare).to.eq(ether(1)); }); diff --git a/test/integration/oracles/yearnVaultOracle.spec.ts b/test/integration/oracles/yearnVaultOracle.spec.ts index 3968ae2cf..a10f3b30a 100644 --- a/test/integration/oracles/yearnVaultOracle.spec.ts +++ b/test/integration/oracles/yearnVaultOracle.spec.ts @@ -3,8 +3,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import { Address } from "@utils/types"; import { Account } from "@utils/test/types"; -import { OracleMock, YearnVaultOracle } from "@utils/contracts"; -import { Vault } from "../../../typechain/Vault"; +import { OracleMock, YearnVaultOracle, YearnVaultMock } from "@utils/contracts"; import DeployHelper from "@utils/deploys"; @@ -22,18 +21,19 @@ import { YearnFixture, SystemFixture } from "@utils/fixtures"; const expect = getWaffleExpect(); -describe("CTokenOracle", () => { +describe("YearnVaultOracle", () => { let owner: Account; let deployer: DeployHelper; let setup: SystemFixture; let yearnSetup: YearnFixture; - let daiVault: Vault; + let daiVault: YearnVaultMock; let daiUsdcOracle: OracleMock; let daiUsdcPrice: BigNumber; let yearnVaultDaiOracle: YearnVaultOracle; let daiFullUnit: BigNumber; + let pricePerShare: BigNumber; before(async () => { [ @@ -49,13 +49,11 @@ describe("CTokenOracle", () => { yearnSetup = getYearnFixture(owner.address); await yearnSetup.initialize(); - daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( - setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) - ); - - daiUsdcPrice = BigNumber.from("1000000000000000000"); + pricePerShare = ether(1.5); + daiVault = await deployer.mocks.deployYearnVaultMock(pricePerShare); + daiUsdcPrice = ether(1); daiUsdcOracle = await deployer.mocks.deployOracleMock(daiUsdcPrice); - daiFullUnit = BigNumber.from("1000000000000000000"); + daiFullUnit = ether(1); yearnVaultDaiOracle = await deployer.oracles.deployYearnVaultOracle( daiVault.address, daiUsdcOracle.address, @@ -75,7 +73,7 @@ describe("CTokenOracle", () => { before(async () => { subjectVaultAddress = daiVault.address; - subjectUnderlyingFullUnit = BigNumber.from("1000000000000000000"); + subjectUnderlyingFullUnit = ether(1); subjectUnderlyingOracle = daiUsdcOracle.address; subjectDataDescription = "yvDAI Oracle"; }); @@ -117,11 +115,6 @@ describe("CTokenOracle", () => { describe("#read", async () => { - let subjectUnderlyingPricePerShare: BigNumber; - - before(async () => { - subjectUnderlyingPricePerShare = BigNumber.from("1000000000000000000"); - }); async function subject(): Promise { return yearnVaultDaiOracle.read(); @@ -129,10 +122,9 @@ describe("CTokenOracle", () => { it("returns the correct vault value", async () => { const result = await subject(); - const expectedResult = subjectUnderlyingPricePerShare - .div(daiFullUnit) - .mul(daiUsdcPrice); - + const expectedResult = pricePerShare + .mul(daiUsdcPrice) + .div(daiFullUnit); expect(result).to.eq(expectedResult); }); }); diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index d31dc1526..1071f1df6 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -51,6 +51,7 @@ export { OneInchExchangeAdapter } from "../../typechain/OneInchExchangeAdapter"; export { OneInchExchangeMock } from "../../typechain/OneInchExchangeMock"; export { OracleAdapterMock } from "../../typechain/OracleAdapterMock"; export { OracleMock } from "../../typechain/OracleMock"; +export { YearnVaultMock } from "../../typechain/YearnVaultMock"; export { YearnVaultOracle } from "../../typechain/YearnVaultOracle"; export { PositionMock } from "../../typechain/PositionMock"; export { PreciseUnitMathMock } from "../../typechain/PreciseUnitMathMock"; diff --git a/utils/deploys/deployMocks.ts b/utils/deploys/deployMocks.ts index 9b76dfabd..5aad45b66 100644 --- a/utils/deploys/deployMocks.ts +++ b/utils/deploys/deployMocks.ts @@ -28,6 +28,7 @@ import { OneInchExchangeMock, OracleAdapterMock, OracleMock, + YearnVaultMock, PositionMock, PreciseUnitMathMock, ResourceIdentifierMock, @@ -70,6 +71,7 @@ import { NAVIssuanceHookMock__factory } from "../../typechain/factories/NAVIssua import { OneInchExchangeMock__factory } from "../../typechain/factories/OneInchExchangeMock__factory"; import { OracleAdapterMock__factory } from "../../typechain/factories/OracleAdapterMock__factory"; import { OracleMock__factory } from "../../typechain/factories/OracleMock__factory"; +import { YearnVaultMock__factory } from "../../typechain/factories/YearnVaultMock__factory"; import { PositionMock__factory } from "../../typechain/factories/PositionMock__factory"; import { PreciseUnitMathMock__factory } from "../../typechain/factories/PreciseUnitMathMock__factory"; import { ResourceIdentifierMock__factory } from "../../typechain/factories/ResourceIdentifierMock__factory"; @@ -175,6 +177,11 @@ export default class DeployMocks { return await new OracleMock__factory(this._deployerSigner).deploy(initialValue); } + public async deployYearnVaultMock(pricePerShare: BigNumberish): Promise { + return await new YearnVaultMock__factory(this._deployerSigner).deploy(pricePerShare); + } + + public async deployOracleAdapterMock( asset: Address, dummyPrice: BigNumber From 28f0bf2fc764252eaf4f9d48b9cf424896b249a0 Mon Sep 17 00:00:00 2001 From: mario Date: Fri, 9 Apr 2021 16:02:08 -0700 Subject: [PATCH 08/19] state vars should be immutable --- contracts/protocol/integration/oracles/YearnVaultOracle.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol index 601d79b34..da541a8b3 100644 --- a/contracts/protocol/integration/oracles/YearnVaultOracle.sol +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -33,12 +33,12 @@ contract YearnVaultOracle is IOracle /* ============ State Variables ============ */ - IYearnVault public vault; - IOracle public underlyingOracle; // Underlying token oracle + IYearnVault public immutable vault; + IOracle public immutable underlyingOracle; // Underlying token oracle string public dataDescription; // Underlying Asset Full Unit - uint256 public underlyingFullUnit; + uint256 public immutable underlyingFullUnit; /* ============ Constructor ============ */ From 39a2707946b97f0f7346b453f62b2e628d136fc4 Mon Sep 17 00:00:00 2001 From: mario Date: Mon, 12 Apr 2021 15:29:33 -0700 Subject: [PATCH 09/19] add test --- test/integration/yearnWrapModule.spec.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/integration/yearnWrapModule.spec.ts b/test/integration/yearnWrapModule.spec.ts index 7a777ea24..1d4c4e5de 100644 --- a/test/integration/yearnWrapModule.spec.ts +++ b/test/integration/yearnWrapModule.spec.ts @@ -51,7 +51,7 @@ describe("yearnWrapModule", () => { await yearnSetup.initialize(); daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( - setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) + setup.dai.address, owner.address, owner.address, owner.address, "daiMockStrategy", "yvDAI", ether(100) ); // WrapModule setup @@ -129,6 +129,7 @@ describe("yearnWrapModule", () => { const expectedWrappedBalance = previousWrappedBalance.add(setTokensIssued); expect(wrappedBalance).to.eq(expectedWrappedBalance); }); + }); describe("#unwrap", () => { @@ -190,6 +191,17 @@ describe("yearnWrapModule", () => { const expectedWrappedBalance = previousWrappedBalance.sub(delta); expect(wrappedBalance).to.eq(expectedWrappedBalance); }); + + describe("when it is an invalid vault - underlying token", async () => { + beforeEach(async () => { + subjectUnderlyingToken = setup.usdc.address; + }); + + it("should revert as it the vault holds a different underlying token", async () => { + await expect(subject()).to.be.revertedWith("Must be a valid token pair"); + }); + }); + }); }); }); From eadd93b4b8d1ff18950a4246f377f51541234c1b Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 13 Apr 2021 11:17:55 -0700 Subject: [PATCH 10/19] Remove UniswapYieldStrategy and tests. (#68) Remove UniswapYieldStrategy and tests --- .../protocol/modules/UniswapYieldStrategy.sol | 443 ----------- .../modules/uniswapYieldStrategy.spec.ts | 715 ------------------ utils/contracts/index.ts | 1 - utils/deploys/deployModules.ts | 32 +- 4 files changed, 2 insertions(+), 1189 deletions(-) delete mode 100644 contracts/protocol/modules/UniswapYieldStrategy.sol delete mode 100644 test/protocol/modules/uniswapYieldStrategy.spec.ts diff --git a/contracts/protocol/modules/UniswapYieldStrategy.sol b/contracts/protocol/modules/UniswapYieldStrategy.sol deleted file mode 100644 index 7721b7a6e..000000000 --- a/contracts/protocol/modules/UniswapYieldStrategy.sol +++ /dev/null @@ -1,443 +0,0 @@ -/* - Copyright 2020 Set Labs Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - SPDX-License-Identifier: Apache License, Version 2.0 -*/ - -pragma solidity 0.6.10; -pragma experimental "ABIEncoderV2"; - -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { Math } from "@openzeppelin/contracts/math/Math.sol"; -import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; - -import { IController } from "../../interfaces/IController.sol"; -import { Invoke } from "../lib/Invoke.sol"; -import { ISetToken } from "../../interfaces/ISetToken.sol"; -import { IStakingRewards } from "../../interfaces/external/IStakingRewards.sol"; -import { IUniswapV2Pair } from "../../interfaces/external/IUniswapV2Pair.sol"; -import { IUniswapV2Router } from "../../interfaces/external/IUniswapV2Router.sol"; -import { ModuleBase } from "../lib/ModuleBase.sol"; -import { Position } from "../lib/Position.sol"; -import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol"; - - -contract UniswapYieldStrategy is ModuleBase, ReentrancyGuard { - using Position for ISetToken; - using Invoke for ISetToken; - using SafeMath for uint256; - using SafeCast for uint256; - using SafeCast for int256; - - /* ============ State Variables ============ */ - - IUniswapV2Router public uniswapRouter; - IUniswapV2Pair public lpToken; - IERC20 public assetOne; - IERC20 public assetTwo; - IERC20 public uni; - address public feeRecipient; - IStakingRewards public rewarder; - ISetToken public setToken; - uint256 public reservePercentage; // Precise percentage (e.g. 10^16 = 1%) - uint256 public slippageTolerance; // Precise percentage - uint256 public rewardFee; // Precise percentage - uint256 public withdrawalFee; // Precise percentage - uint256 public assetOneBaseUnit; - uint256 public assetTwoBaseUnit; - uint256 public lpTokenBaseUnit; - - /* ============ Constructor ============ */ - - constructor( - IController _controller, - IUniswapV2Router _uniswapRouter, - IUniswapV2Pair _lpToken, - IERC20 _assetOne, - IERC20 _assetTwo, - IERC20 _uni, - IStakingRewards _rewarder, - address _feeRecipient - ) - public - ModuleBase(_controller) - { - controller = _controller; - uniswapRouter = _uniswapRouter; - lpToken = _lpToken; - assetOne = _assetOne; - assetTwo = _assetTwo; - uni = _uni; - rewarder = _rewarder; - feeRecipient = _feeRecipient; - - uint256 tokenOneDecimals = ERC20(address(_assetOne)).decimals(); - assetOneBaseUnit = 10 ** tokenOneDecimals; - uint256 tokenTwoDecimals = ERC20(address(_assetTwo)).decimals(); - assetTwoBaseUnit = 10 ** tokenTwoDecimals; - uint256 lpTokenDecimals = ERC20(address(_lpToken)).decimals(); - lpTokenBaseUnit = 10 ** lpTokenDecimals; - } - - /* ============ External Functions ============ */ - - function engage() external nonReentrant { - _engage(); - } - - function disengage() external nonReentrant { - _rebalance(0); - - uint256 lpTokenQuantity = _calculateDisengageLPQuantity(); - - _unstake(lpTokenQuantity); - - _approveAndRemoveLiquidity(lpTokenQuantity); - - _updatePositions(); - } - - function reap() external nonReentrant { - _handleReward(); - - _engage(); - } - - function rebalance() external nonReentrant { - _rebalance(0); - - _updatePositions(); - } - - function rebalanceSome(uint256 _sellTokenQuantity) external nonReentrant { - _rebalance(_sellTokenQuantity); - - _updatePositions(); - } - - function unstakeAndRedeem(uint256 _setTokenQuantity) external nonReentrant { - require(setToken.balanceOf(msg.sender) >= _setTokenQuantity, "User must have sufficient SetToken"); - - setToken.burn(msg.sender, _setTokenQuantity); - - uint256 lpTokenUnit = setToken.getExternalPositionRealUnit(address(lpToken), address(this)).toUint256(); - - uint256 userLPBalance = lpTokenUnit.preciseMul(_setTokenQuantity); - - _unstake(userLPBalance); - - uint256 lpFees = userLPBalance.preciseMul(withdrawalFee); - setToken.invokeTransfer(address(lpToken), msg.sender, userLPBalance.sub(lpFees)); - setToken.invokeTransfer(address(lpToken), feeRecipient, lpFees); - - uint256 assetOneUnit = setToken.getDefaultPositionRealUnit(address(assetOne)).toUint256(); - uint256 assetOneNotional = assetOneUnit.preciseMul(_setTokenQuantity); - uint256 assetOneFee = assetOneNotional.preciseMul(withdrawalFee); - setToken.invokeTransfer(address(assetOne), msg.sender, assetOneNotional.sub(assetOneFee)); - setToken.invokeTransfer(address(assetOne), feeRecipient, assetOneFee); - - uint256 assetTwoUnit = setToken.getDefaultPositionRealUnit(address(assetTwo)).toUint256(); - uint256 assetTwoNotional = assetTwoUnit.preciseMul(_setTokenQuantity); - uint256 assetTwoFee = assetTwoNotional.preciseMul(withdrawalFee); - setToken.invokeTransfer(address(assetTwo), msg.sender, assetTwoNotional.sub(assetTwoFee)); - setToken.invokeTransfer(address(assetTwo), feeRecipient, assetTwoFee); - } - - function initialize( - ISetToken _setToken, - uint256 _reservePercentage, - uint256 _slippageTolerance, - uint256 _rewardFee, - uint256 _withdrawalFee - ) - external - onlySetManager(_setToken, msg.sender) - { - require(address(setToken) == address(0), "May only be called once"); - - setToken = _setToken; - reservePercentage = _reservePercentage; - slippageTolerance = _slippageTolerance; - rewardFee = _rewardFee; - withdrawalFee = _withdrawalFee; - - _setToken.initializeModule(); - } - - function removeModule() external override { - require(msg.sender == address(setToken), "Caller must be SetToken"); - - uint256 lpBalance = rewarder.balanceOf(address(setToken)); - - _unstake(lpBalance); - - _approveAndRemoveLiquidity(lpBalance); - - _updatePositions(); - } - - /* ============ Internal Functions ============ */ - - function _engage() internal { - _rebalance(0); - - (uint256 assetOneQuantity, uint256 assetTwoQuantity) = _calculateEngageQuantities(); - - uint256 lpBalance = _approveAndAddLiquidity(assetOneQuantity, assetTwoQuantity); - - _approveAndStake(lpBalance); - - _updatePositions(); - } - - // Rebalances reserve assets to achieve a 50/50 value split - // If a sellTokenQuantity is provided, then use this value - function _rebalance(uint256 _sellTokenQuantity) internal { - address assetToSell; - address assetToBuy; - uint256 quantityToSell; - uint256 minimumBuyToken; - - uint256 assetOneToTwoPrice = controller.getPriceOracle().getPrice(address(assetOne), address(assetTwo)); - - uint256 balanceAssetOne = assetOne.balanceOf(address(setToken)); - uint256 balanceAssetTwo = assetTwo.balanceOf(address(setToken)); - - // Convert Asset Two to One adjust for decimal differences - uint256 valueAssetTwoDenomOne = balanceAssetTwo.preciseDiv(assetOneToTwoPrice).mul(assetOneBaseUnit).div(assetTwoBaseUnit); - - if (balanceAssetOne > valueAssetTwoDenomOne) { - assetToSell = address(assetOne); - assetToBuy = address(assetTwo); - quantityToSell = balanceAssetOne.sub(valueAssetTwoDenomOne).div(2); - - // Base unit calculations are to normalize the values for different decimals - minimumBuyToken = quantityToSell.preciseMul(assetOneToTwoPrice).mul(assetTwoBaseUnit).div(assetOneBaseUnit); - } else { - assetToSell = address(assetTwo); - assetToBuy = address(assetOne); - quantityToSell = valueAssetTwoDenomOne - .sub(balanceAssetOne).div(2).preciseMul(assetOneToTwoPrice) - .mul(assetTwoBaseUnit).div(assetOneBaseUnit); - minimumBuyToken = quantityToSell.preciseDiv(assetOneToTwoPrice).mul(assetOneBaseUnit).div(assetTwoBaseUnit); - } - - if (_sellTokenQuantity > 0) { - require(_sellTokenQuantity <= quantityToSell, "Delta must be less than max"); - minimumBuyToken = minimumBuyToken.preciseMul(_sellTokenQuantity).preciseDiv(quantityToSell); - quantityToSell = _sellTokenQuantity; - } - - // Reduce the expected receive quantity - minimumBuyToken = minimumBuyToken - .preciseMul(PreciseUnitMath.preciseUnit().sub(slippageTolerance)); - - setToken.invokeApprove(assetToSell, address(uniswapRouter), quantityToSell); - if (quantityToSell > 0) { - _invokeUniswapTrade(assetToSell, assetToBuy, quantityToSell, minimumBuyToken); - } - } - - function _approveAndAddLiquidity(uint256 _assetOneQuantity, uint256 _assetTwoQuantity) internal returns (uint256) { - setToken.invokeApprove(address(assetOne), address(uniswapRouter), _assetOneQuantity); - setToken.invokeApprove(address(assetTwo), address(uniswapRouter), _assetTwoQuantity); - - bytes memory addLiquidityBytes = abi.encodeWithSignature( - "addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256)", - assetOne, - assetTwo, - _assetOneQuantity, - _assetTwoQuantity, - 1, - 1, - address(setToken), - now.add(60) // Valid for one minute - ); - - setToken.invoke(address(uniswapRouter), 0, addLiquidityBytes); - - return lpToken.balanceOf(address(setToken)); - } - - function _approveAndRemoveLiquidity(uint256 _liquidityQuantity) internal { - setToken.invokeApprove(address(lpToken), address(uniswapRouter), _liquidityQuantity); - - bytes memory removeLiquidityBytes = abi.encodeWithSignature( - "removeLiquidity(address,address,uint256,uint256,uint256,address,uint256)", - assetOne, - assetTwo, - _liquidityQuantity, - 1, - 1, - address(setToken), - now.add(60) // Valid for one minute - ); - - setToken.invoke(address(uniswapRouter), 0, removeLiquidityBytes); - } - - function _approveAndStake(uint256 _lpTokenQuantity) internal { - setToken.invokeApprove(address(lpToken), address(rewarder), _lpTokenQuantity); - bytes memory stakeBytes = abi.encodeWithSignature("stake(uint256)", _lpTokenQuantity); - - setToken.invoke(address(rewarder), 0, stakeBytes); - } - - function _unstake(uint256 _lpTokenQuantity) internal { - bytes memory unstakeBytes = abi.encodeWithSignature("withdraw(uint256)", _lpTokenQuantity); - - setToken.invoke(address(rewarder), 0, unstakeBytes); - } - - function _handleReward() internal { - setToken.invoke(address(rewarder), 0, abi.encodeWithSignature("getReward()")); - - uint256 uniBalance = uni.balanceOf(address(setToken)); - uint256 assetOneBalance = assetOne.balanceOf(address(setToken)); - - setToken.invokeApprove(address(uni), address(uniswapRouter), uniBalance); - _invokeUniswapTrade(address(uni), address(assetOne), uniBalance, 1); - - uint256 postTradeAssetOneBalance = assetOne.balanceOf(address(setToken)); - uint256 fee = postTradeAssetOneBalance.sub(assetOneBalance).preciseMul(rewardFee); - - setToken.strictInvokeTransfer(address(assetOne), feeRecipient, fee); - } - - function _invokeUniswapTrade( - address _sellToken, - address _buyToken, - uint256 _amountIn, - uint256 _amountOutMin - ) - internal - { - address[] memory path = new address[](2); - path[0] = _sellToken; - path[1] = _buyToken; - - bytes memory tradeBytes = abi.encodeWithSignature( - "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", - _amountIn, - _amountOutMin, - path, - address(setToken), - now.add(180) - ); - - setToken.invoke(address(uniswapRouter), 0, tradeBytes); - } - - function _calculateEngageQuantities() internal view returns(uint256 tokenAQuantity, uint256 tokenBQuantity) { - ( - uint256 desiredAssetOne, - uint256 desiredAssetTwo, - uint256 assetOneOnSetToken, - uint256 assetTwoOnSetToken - ) = _getDesiredSingleAssetReserve(); - - require(assetOneOnSetToken > desiredAssetOne && assetTwoOnSetToken > desiredAssetTwo, "SetToken assets must be > desired"); - - return ( - assetOneOnSetToken.sub(desiredAssetOne), - assetTwoOnSetToken.sub(desiredAssetTwo) - ); - } - - function _calculateDisengageLPQuantity() internal view returns(uint256 _lpTokenQuantity) { - (uint256 assetOneToLPRate, uint256 assetTwoToLPRate) = _getLPReserveExchangeRate(); - - ( - uint256 desiredOne, - uint256 desiredTwo, - uint256 assetOneOnSetToken, - uint256 assetTwoOnSetToken - ) = _getDesiredSingleAssetReserve(); - - require(assetOneOnSetToken < desiredOne && assetTwoOnSetToken < desiredTwo, "SetToken assets must be < desired"); - - // LP Rates already account for decimals - uint256 minLPForOneToRedeem = desiredOne.sub(assetOneOnSetToken).preciseDiv(assetOneToLPRate); - uint256 minLPForTwoToRedeem = desiredTwo.sub(assetTwoOnSetToken).preciseDiv(assetTwoToLPRate); - - return Math.max(minLPForOneToRedeem, minLPForTwoToRedeem); - } - - // Returns desiredOneReserve, desiredTwoReserve, tokenOne and tokenTwo balances - function _getDesiredSingleAssetReserve() - internal - view - returns(uint256, uint256, uint256, uint256) - { - (uint256 assetOneReserve, uint256 assetTwoReserve) = _getTotalLPReserves(); - uint256 balanceAssetOne = assetOne.balanceOf(address(setToken)); - uint256 balanceAssetTwo = assetTwo.balanceOf(address(setToken)); - - uint256 desiredOneReserve = assetOneReserve.add(balanceAssetOne).preciseMul(reservePercentage); - uint256 desiredTwoReserve = assetTwoReserve.add(balanceAssetTwo).preciseMul(reservePercentage); - - return(desiredOneReserve, desiredTwoReserve, balanceAssetOne, balanceAssetTwo); - } - - // Returns assetAToLPRate and assetBToLPRate - function _getLPReserveExchangeRate() internal view returns (uint256, uint256) { - (uint reserve0, uint reserve1) = _getReservesSafe(); - uint256 totalSupply = lpToken.totalSupply(); - return( - reserve0.preciseDiv(totalSupply), - reserve1.preciseDiv(totalSupply) - ); - } - - // Returns assetOneReserve and assetTwoReserve - function _getTotalLPReserves() internal view returns (uint256, uint256) { - (uint reserve0, uint reserve1) = _getReservesSafe(); - uint256 totalSupply = lpToken.totalSupply(); - uint256 lpTokenBalance = rewarder.balanceOf(address(setToken)); - return( - reserve0.mul(lpTokenBalance).div(totalSupply), - reserve1.mul(lpTokenBalance).div(totalSupply) - ); - } - - function _updatePositions() internal { - uint256 totalSupply = setToken.totalSupply(); - uint256 assetOneBalance = assetOne.balanceOf(address(setToken)); - uint256 assetTwoBalance = assetTwo.balanceOf(address(setToken)); - uint256 lpBalance = rewarder.balanceOf(address(setToken)); - - // Doesn't check to make sure unit is different, and no check for any LP token on Set - setToken.editDefaultPosition(address(assetOne), Position.getDefaultPositionUnit(totalSupply, assetOneBalance)); - setToken.editDefaultPosition(address(assetTwo), Position.getDefaultPositionUnit(totalSupply, assetTwoBalance)); - setToken.editExternalPosition( - address(lpToken), - address(this), - Position.getDefaultPositionUnit(totalSupply, lpBalance).toInt256(), - "" - ); - } - - // Code pulled to sort from UniswapV2Library - // https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol - function _getReservesSafe() internal view returns(uint256, uint256) { - address firstAsset = address(assetOne) < address(assetTwo) ? address(assetOne) : address(assetTwo); - (uint reserve0, uint reserve1,) = lpToken.getReserves(); - return address(assetOne) == firstAsset ? (reserve0, reserve1) : (reserve1, reserve0); - } -} \ No newline at end of file diff --git a/test/protocol/modules/uniswapYieldStrategy.spec.ts b/test/protocol/modules/uniswapYieldStrategy.spec.ts deleted file mode 100644 index f9d57843c..000000000 --- a/test/protocol/modules/uniswapYieldStrategy.spec.ts +++ /dev/null @@ -1,715 +0,0 @@ -import "module-alias/register"; -import { BigNumber } from "@ethersproject/bignumber"; - -import { Address, NAVIssuanceSettings } from "@utils/types"; -import { Account } from "@utils/test/types"; -import { GodModeMock, UniswapPairPriceAdapter, UniswapYieldStrategy, SetToken } from "@utils/contracts"; -import { MAX_UINT_256, ONE_HOUR_IN_SECONDS } from "@utils/constants"; -import DeployHelper from "@utils/deploys"; -import { - calculateRebalanceFlows, - calculateRebalanceQuantity, - calculateTokensInReserve, - ether, - min, - preciseMul, - preciseDiv, -} from "@utils/index"; -import { - addSnapshotBeforeRestoreAfterEach, - getAccounts, - getSystemFixture, - getWaffleExpect, - getUniswapFixture, - increaseTimeAsync, -} from "@utils/test/index"; -import { SystemFixture, UniswapFixture } from "@utils/fixtures"; -import { ContractTransaction } from "ethers"; -import { ADDRESS_ZERO, ZERO } from "@utils/constants"; - -const expect = getWaffleExpect(); - -/* - * Due to one off nature of module and flaky coverage tests, tests for disengage and unstakeAndRedeem - * are commented out. The module has successfully run in production from three weeks so feel confident - * of it's correctness. - */ - -describe("UniswapYieldStrategy", () => { - let owner: Account; - let manager: Account; - let feeRecipient: Account; - - let deployer: DeployHelper; - let setup: SystemFixture; - let uniswapSetup: UniswapFixture; - let uniswapPriceAdapter: UniswapPairPriceAdapter; - - let setToken: SetToken; - let yieldStrategy: UniswapYieldStrategy; - let godModule: GodModeMock; - - const reservePercentage = ether(.01); - const slippageTolerance = ether(.02); - const rewardFee = ether(.05); - const withdrawalFee = ether(.01); - - before(async () => { - [ - owner, - manager, - feeRecipient, - ] = await getAccounts(); - - deployer = new DeployHelper(owner.wallet); - setup = getSystemFixture(owner.address); - uniswapSetup = getUniswapFixture(owner.address); - - await setup.initialize(); - await uniswapSetup.initialize( - owner, - setup.weth.address, - setup.wbtc.address, - setup.dai.address - ); - - uniswapPriceAdapter = await deployer.adapters.deployUniswapPairPriceAdapter( - setup.controller.address, - uniswapSetup.factory.address, - [uniswapSetup.wethDaiPool.address, uniswapSetup.wethWbtcPool.address] - ); - - await setup.controller.addResource(uniswapPriceAdapter.address, BigNumber.from(3)); - await setup.priceOracle.addAdapter(uniswapPriceAdapter.address); - - yieldStrategy = await deployer.modules.deployUniswapYieldStrategy( - setup.controller.address, - uniswapSetup.router.address, - uniswapSetup.wethDaiPool.address, - setup.weth.address, - setup.dai.address, - uniswapSetup.uni.address, - uniswapSetup.wethDaiStakingRewards.address, - feeRecipient.address - ); - - godModule = await deployer.mocks.deployGodModeMock(setup.controller.address); - - await setup.controller.addModule(godModule.address); - await setup.controller.addModule(yieldStrategy.address); - - setToken = await setup.createSetToken( - [setup.weth.address, setup.dai.address], - [ether(1), preciseMul(setup.component1Price, ether(.99))], - [setup.navIssuanceModule.address, setup.issuanceModule.address, yieldStrategy.address, godModule.address], - manager.address - ); - - const navIssueSettings: NAVIssuanceSettings = { - managerIssuanceHook: ADDRESS_ZERO, - managerRedemptionHook: ADDRESS_ZERO, - reserveAssets: [setup.weth.address, setup.dai.address], - feeRecipient: feeRecipient.address, - managerFees: [ZERO, ether(.02)], - maxManagerFee: ether(.02), - premiumPercentage: ether(.01), - maxPremiumPercentage: ether(.01), - minSetTokenSupply: ether(1000), - }; - await setup.navIssuanceModule.connect(manager.wallet).initialize( - setToken.address, - navIssueSettings - ); - await godModule.initialize(setToken.address); - await setup.issuanceModule.connect(manager.wallet).initialize(setToken.address, ADDRESS_ZERO); - - await baseTestSetup(); - }); - - addSnapshotBeforeRestoreAfterEach(); - - describe("#engage", async () => { - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).engage(); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - await subject(); - - const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - setToken, - setup.weth, - setup.dai, - uniswapSetup.wethDaiPool, - uniswapSetup.wethDaiStakingRewards - ); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - expect(wethReserveRatio).to.be.gte(reservePercentage); - expect(daiReserveRatio).to.be.gte(reservePercentage); - expect(postLPBalance).to.be.eq(ZERO); - expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - }); - - it("should update to the correct position units", async () => { - await subject(); - - const wethBalance = await setup.weth.balanceOf(setToken.address); - const daiBalance = await setup.dai.balanceOf(setToken.address); - const lpBalance = await uniswapSetup.wethDaiStakingRewards.balanceOf(setToken.address); - const totalSupply = await setToken.totalSupply(); - - const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - const lpPositionUnit = await setToken.getExternalPositionRealUnit( - uniswapSetup.wethDaiPool.address, - yieldStrategy.address - ); - - expect(wethPositionUnit).to.eq(preciseDiv(wethBalance, totalSupply)); - expect(daiPositionUnit).to.eq(preciseDiv(daiBalance, totalSupply)); - expect(lpPositionUnit).to.eq(preciseDiv(lpBalance, totalSupply)); - }); - - describe("when tokens in Set don't exceed reserve amounts", async () => { - beforeEach(async () => { - await subject(); - - await godModule.transferTokens(setToken.address, setup.dai.address, owner.address, ether(2500)); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("SetToken assets must be > desired"); - }); - }); - }); - - // describe("#disengage", async () => { - // let subjectCaller: Account; - - // beforeEach(async () => { - // await yieldStrategy.connect(manager.wallet).initialize( - // setToken.address, - // reservePercentage, - // slippageTolerance, - // rewardFee, - // withdrawalFee - // ); - - // await yieldStrategy.connect(feeRecipient.wallet).engage(); - // await godModule.transferTokens(setToken.address, setup.dai.address, owner.address, ether(2500)); - - // subjectCaller = manager; - // }); - - // async function subject(): Promise { - // return await yieldStrategy.connect(subjectCaller.wallet).disengage(); - // } - - // it("should have the correct amount of weth and dai in the Set", async () => { - // await subject(); - - // const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - // setToken, - // setup.weth, - // setup.dai, - // uniswapSetup.wethDaiPool, - // uniswapSetup.wethDaiStakingRewards - // ); - - // const postWethBalance = await setup.weth.balanceOf(setToken.address); - // const postDaiBalance = await setup.dai.balanceOf(setToken.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - // const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - // const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - // expect(wethReserveRatio).to.be.gte(reservePercentage); - // expect(daiReserveRatio).to.be.gte(reservePercentage); - // expect(postLPBalance).to.be.eq(ZERO); - // expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - // }); - - // describe("when tokens in Set exceed reserve amounts", async () => { - // beforeEach(async () => { - // await setup.dai.connect(owner.wallet).transfer(setToken.address, ether(2500)); - // }); - - // it("should revert", async () => { - // await expect(subject()).to.be.revertedWith("SetToken assets must be < desired"); - // }); - // }); - // }); - - describe("#reap", async () => { - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - await yieldStrategy.connect(feeRecipient.wallet).engage(); - - await increaseTimeAsync(ONE_HOUR_IN_SECONDS.div(24)); - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).reap(); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - await subject(); - - const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - setToken, - setup.weth, - setup.dai, - uniswapSetup.wethDaiPool, - uniswapSetup.wethDaiStakingRewards - ); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - expect(wethReserveRatio).to.be.gte(reservePercentage); - expect(daiReserveRatio).to.be.gte(reservePercentage); - expect(postLPBalance).to.be.eq(ZERO); - expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - }); - }); - - describe("#rebalance", async () => { - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).rebalance(); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - const [wethRebalanceAmount, expectedReceiveQuantity] = await calculateRebalanceFlows( - setToken, - uniswapSetup.router, - ZERO, - setup.weth, - setup.dai, - await setup.ETH_USD_Oracle.read(), - ); - - const preWethBalance = await setup.weth.balanceOf(setToken.address); - const preDaiBalance = await setup.dai.balanceOf(setToken.address); - - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - - expect(postWethBalance).to.eq(preWethBalance.sub(wethRebalanceAmount)); - expect(postDaiBalance).to.eq(preDaiBalance.add(expectedReceiveQuantity)); - }); - - it("should update to the correct position units", async () => { - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const totalSupply = await setToken.totalSupply(); - - const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - - expect(wethPositionUnit).to.eq(preciseDiv(postWethBalance, totalSupply)); - expect(daiPositionUnit).to.eq(preciseDiv(postDaiBalance, totalSupply)); - }); - }); - - describe("#rebalanceSome", async () => { - let wethRebalanceAmount: BigNumber; - - let subjectUSDDifference: BigNumber; - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - [ wethRebalanceAmount, , ] = await calculateRebalanceQuantity( - ZERO, - setToken, - setup.weth, - setup.dai, - await setup.ETH_USD_Oracle.read(), - ); - - subjectUSDDifference = wethRebalanceAmount.div(2); - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).rebalanceSome(subjectUSDDifference); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - const [, expectedReceiveQuantity] = await uniswapSetup.router.getAmountsOut( - subjectUSDDifference, - [setup.weth.address, setup.dai.address] - ); - const preWethBalance = await setup.weth.balanceOf(setToken.address); - const preDaiBalance = await setup.dai.balanceOf(setToken.address); - - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - - expect(postWethBalance).to.eq(preWethBalance.sub(subjectUSDDifference)); - expect(postDaiBalance).to.eq(preDaiBalance.add(expectedReceiveQuantity)); - }); - - it("should update to the correct position units", async () => { - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const totalSupply = await setToken.totalSupply(); - - const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - - expect(wethPositionUnit).to.eq(preciseDiv(postWethBalance, totalSupply)); - expect(daiPositionUnit).to.eq(preciseDiv(postDaiBalance, totalSupply)); - }); - - describe("when rebalancing more than allowed", async () => { - beforeEach(async () => { - subjectUSDDifference = wethRebalanceAmount.add(1); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Delta must be less than max"); - }); - }); - }); - - // describe("#unstakeAndRedeem", async () => { - // let subjectSetTokenQuantity: BigNumber; - // let subjectCaller: Account; - - // beforeEach(async () => { - // await yieldStrategy.connect(manager.wallet).initialize( - // setToken.address, - // reservePercentage, - // slippageTolerance, - // rewardFee, - // withdrawalFee - // ); - - // await yieldStrategy.connect(feeRecipient.wallet).engage(); - - // subjectSetTokenQuantity = ether(100); - // subjectCaller = owner; - // }); - - // async function subject(): Promise { - // return await yieldStrategy.connect(subjectCaller.wallet).unstakeAndRedeem(subjectSetTokenQuantity); - // } - - // it("should have the correct amount of weth and dai in the Set", async () => { - // await subject(); - - // const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - // setToken, - // setup.weth, - // setup.dai, - // uniswapSetup.wethDaiPool, - // uniswapSetup.wethDaiStakingRewards - // ); - - // const postWethBalance = await setup.weth.balanceOf(setToken.address); - // const postDaiBalance = await setup.dai.balanceOf(setToken.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - // const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - // const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - // expect(wethReserveRatio).to.be.gte(reservePercentage); - // expect(daiReserveRatio).to.be.gte(reservePercentage); - // expect(postLPBalance).to.be.eq(ZERO); - // expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - // }); - - // it("should send the correct amounts to the redeemer", async () => { - // const preWethBalance = await setup.weth.balanceOf(owner.address); - // const preDaiBalance = await setup.dai.balanceOf(owner.address); - // const preLPBalance = await uniswapSetup.wethDaiPool.balanceOf(owner.address); - - // await subject(); - - // const postWethBalance = await setup.weth.balanceOf(owner.address); - // const postDaiBalance = await setup.dai.balanceOf(owner.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(owner.address); - - // const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - // const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - // const lpPositionUnit = await setToken.getExternalPositionRealUnit(yieldStrategy.address, uniswapSetup.wethDaiPool.address); - - // expect(postWethBalance).to.be.gte( - // preWethBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(wethPositionUnit, PRECISE_UNIT.sub(withdrawalFee)))) - // ); - // expect(postDaiBalance).to.be.gte( - // preDaiBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(daiPositionUnit, PRECISE_UNIT.sub(withdrawalFee)))) - // ); - // expect(postLPBalance).to.be.gte( - // preLPBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(lpPositionUnit, PRECISE_UNIT.sub(withdrawalFee)))) - // ); - // }); - - // it("should send the correct amounts to the feeRecipient", async () => { - // const preWethBalance = await setup.weth.balanceOf(feeRecipient.address); - // const preDaiBalance = await setup.dai.balanceOf(feeRecipient.address); - // const preLPBalance = await uniswapSetup.wethDaiPool.balanceOf(feeRecipient.address); - - // await subject(); - - // const postWethBalance = await setup.weth.balanceOf(feeRecipient.address); - // const postDaiBalance = await setup.dai.balanceOf(feeRecipient.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(feeRecipient.address); - - // const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - // const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - // const lpPositionUnit = await setToken.getExternalPositionRealUnit(yieldStrategy.address, uniswapSetup.wethDaiPool.address); - - // expect(postWethBalance).to.be.gte( - // preWethBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(wethPositionUnit, withdrawalFee))) - // ); - // expect(postDaiBalance).to.be.gte( - // preDaiBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(daiPositionUnit, withdrawalFee))) - // ); - // expect(postLPBalance).to.be.gte( - // preLPBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(lpPositionUnit, withdrawalFee))) - // ); - // }); - - // describe("when user tries to redeem more than their share", async () => { - // beforeEach(async () => { - // subjectSetTokenQuantity = ether(2000); - // }); - - // it("should revert", async () => { - // await expect(subject()).to.be.revertedWith("User must have sufficient SetToken"); - // }); - // }); - // }); - - describe("#initialize", async () => { - let subjectSetToken: Address; - let subjectReservePercentage: BigNumber; - let subjectSlippageTolerance: BigNumber; - let subjectRewardFee: BigNumber; - let subjectWithdrawalFee: BigNumber; - let subjectCaller: Account; - - beforeEach(async () => { - subjectSetToken = setToken.address; - subjectReservePercentage = reservePercentage; - subjectSlippageTolerance = slippageTolerance; - subjectRewardFee = rewardFee; - subjectWithdrawalFee = withdrawalFee; - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).initialize( - subjectSetToken, - subjectReservePercentage, - subjectSlippageTolerance, - subjectRewardFee, - subjectWithdrawalFee - ); - } - - it("should set the correct parameters", async () => { - await subject(); - - const receivedSetToken = await yieldStrategy.setToken(); - const reservePercentage = await yieldStrategy.reservePercentage(); - const slippageTolerance = await yieldStrategy.slippageTolerance(); - const rewardFee = await yieldStrategy.rewardFee(); - const withdrawalFee = await yieldStrategy.withdrawalFee(); - - expect(receivedSetToken).to.eq(subjectSetToken); - expect(reservePercentage).to.eq(subjectReservePercentage); - expect(slippageTolerance).to.eq(subjectSlippageTolerance); - expect(rewardFee).to.eq(subjectRewardFee); - expect(withdrawalFee).to.eq(subjectWithdrawalFee); - }); - - it("should enable the Module on the SetToken", async () => { - await subject(); - - const isModuleEnabled = await setToken.isInitializedModule(yieldStrategy.address); - expect(isModuleEnabled).to.be.true; - }); - - describe("when initialize has already been called", async () => { - beforeEach(async () => { - await subject(); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("May only be called once"); - }); - }); - - describe("when caller is not Set manager", async () => { - beforeEach(async () => { - subjectCaller = owner; - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Must be the SetToken manager"); - }); - }); - }); - - // describe("#removeModule", async () => { - // let subjectModule: Address; - // let subjectCaller: Account; - - // beforeEach(async () => { - // await yieldStrategy.connect(manager.wallet).initialize( - // setToken.address, - // reservePercentage, - // slippageTolerance, - // rewardFee, - // withdrawalFee - // ); - - // await yieldStrategy.connect(feeRecipient.wallet).engage(); - - // subjectModule = yieldStrategy.address; - // subjectCaller = manager; - // }); - - // async function subject(): Promise { - // return await setToken.connect(subjectCaller.wallet).removeModule(subjectModule); - // } - - // async function subjectAttacker(): Promise { - // return await yieldStrategy.connect(subjectCaller.wallet).removeModule(); - // } - - // it("should only have ETH and DAI positions open", async () => { - // await subject(); - - // const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - // setToken, - // setup.weth, - // setup.dai, - // uniswapSetup.wethDaiPool, - // uniswapSetup.wethDaiStakingRewards - // ); - - // const postWethBalance = await setup.weth.balanceOf(setToken.address); - // const postDaiBalance = await setup.dai.balanceOf(setToken.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - // const postLPStakingBalance = await uniswapSetup.wethDaiStakingRewards.balanceOf(setToken.address); - - // const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - // const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - // expect(wethReserveRatio).to.eq(PRECISE_UNIT); - // expect(daiReserveRatio).to.eq(PRECISE_UNIT); - // expect(postLPBalance).to.be.eq(ZERO); - // expect(postLPStakingBalance).to.be.eq(ZERO); - // }); - - // describe("when caller is not SetToken", async () => { - // beforeEach(async () => { - // subjectCaller = manager; - // }); - - // it("should revert", async () => { - // await expect(subjectAttacker()).to.be.revertedWith("Caller must be SetToken"); - // }); - // }); - // }); - - async function baseTestSetup(): Promise { - await setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, ether(1000)); - await setup.dai.connect(owner.wallet).approve(uniswapSetup.router.address, ether(350000)); - await uniswapSetup.router.addLiquidity( - setup.weth.address, - setup.dai.address, - ether(1000), - ether(230000), - ether(999), - ether(225000), - owner.address, - MAX_UINT_256 - ); - - await setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, ether(2000)); - await uniswapSetup.uni.connect(owner.wallet).approve(uniswapSetup.router.address, ether(400000)); - await uniswapSetup.router.connect(owner.wallet).addLiquidity( - setup.weth.address, - uniswapSetup.uni.address, - ether(2000), - ether(400000), - ether(1485), - ether(173000), - owner.address, - MAX_UINT_256 - ); - - await setup.weth.connect(owner.wallet).approve(setup.issuanceModule.address, ether(1000)); - await setup.dai.connect(owner.wallet).approve(setup.issuanceModule.address, ether(350000)); - await setup.issuanceModule.connect(owner.wallet).issue(setToken.address, ether(1000), owner.address); - } -}); \ No newline at end of file diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index 78832a5db..d6faf8ad3 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -83,7 +83,6 @@ export { UniswapV2Factory } from "../../typechain/UniswapV2Factory"; export { UniswapV2Pair } from "../../typechain/UniswapV2Pair"; export { UniswapV2Router02 } from "../../typechain/UniswapV2Router02"; export { UniswapYieldHook } from "../../typechain/UniswapYieldHook"; -export { UniswapYieldStrategy } from "../../typechain/UniswapYieldStrategy"; export { WETH9 } from "../../typechain/WETH9"; export { WrapAdapterMock } from "../../typechain/WrapAdapterMock"; export { WrapModule } from "../../typechain/WrapModule"; diff --git a/utils/deploys/deployModules.ts b/utils/deploys/deployModules.ts index 46a50b841..1b488159d 100644 --- a/utils/deploys/deployModules.ts +++ b/utils/deploys/deployModules.ts @@ -18,9 +18,8 @@ import { StakingModule, StreamingFeeModule, TradeModule, - UniswapYieldStrategy, UniswapV2ExchangeAdapterV2, - WrapModule + WrapModule } from "../contracts"; import { Address } from "../types"; @@ -40,7 +39,6 @@ import { SingleIndexModule__factory } from "../../typechain/factories/SingleInde import { StakingModule__factory } from "../../typechain/factories/StakingModule__factory"; import { StreamingFeeModule__factory } from "../../typechain/factories/StreamingFeeModule__factory"; import { TradeModule__factory } from "../../typechain/factories/TradeModule__factory"; -import { UniswapYieldStrategy__factory } from "../../typechain/factories/UniswapYieldStrategy__factory"; import { UniswapV2ExchangeAdapterV2__factory } from "../../typechain/factories/UniswapV2ExchangeAdapterV2__factory"; import { WrapModule__factory } from "../../typechain/factories/WrapModule__factory"; @@ -107,32 +105,6 @@ export default class DeployModules { return await new CustomOracleNavIssuanceModule__factory(this._deployerSigner).deploy(controller, weth); } - public async deployUniswapYieldStrategy( - _controller: Address, - _uniswapRouter: Address, - _lpToken: Address, - _assetOne: Address, - _assetTwo: Address, - _uni: Address, - _rewarder: Address, - _feeRecipient: Address - ): Promise { - return await new UniswapYieldStrategy__factory(this._deployerSigner).deploy( - _controller, - _uniswapRouter, - _lpToken, - _assetOne, - _assetTwo, - _uni, - _rewarder, - _feeRecipient - ); - } - - public async getUniswapYieldStrategy(uniswapYieldStrategy: Address): Promise { - return await new UniswapYieldStrategy__factory(this._deployerSigner).attach(uniswapYieldStrategy); - } - public async getNavIssuanceModule(navIssuanceModule: Address): Promise { return await new NavIssuanceModule__factory(this._deployerSigner).attach(navIssuanceModule); } @@ -172,7 +144,7 @@ export default class DeployModules { public async deployBalancerV1ExchangeAdapter(balancerProxy: Address): Promise { return await new BalancerV1ExchangeAdapter__factory(this._deployerSigner).deploy( balancerProxy - ) + ); } public async deployGovernanceModule(controller: Address): Promise { From 43a889b3df5746a48b70b6713b13bbabd8073dae Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 17:50:23 -0700 Subject: [PATCH 11/19] yearn vaults --- contracts/interfaces/external/IYearnVault.sol | 24 + .../mocks/external/YearnStrategyMock.sol | 124 ++ .../protocol/integration/YearnWrapAdapter.sol | 132 ++ .../integration/oracles/YearnVaultOracle.sol | 94 + external/abi/yearn/Registry.json | 589 ++++++ external/abi/yearn/Vault.json | 1666 ++++++++++++++++ external/contracts/yearn/BaseStrategy.sol | 861 +++++++++ external/contracts/yearn/Registry.vy | 330 ++++ external/contracts/yearn/Vault.vy | 1667 +++++++++++++++++ test/fixtures/yearn.spec.ts | 69 + .../oracles/yearnVaultOracle.spec.ts | 134 ++ test/integration/yearnWrapModule.spec.ts | 195 ++ utils/contracts/index.ts | 3 + utils/contracts/yearn.ts | 3 + utils/deploys/deployAdapters.ts | 6 + utils/deploys/deployExternal.ts | 22 + utils/deploys/deployMocks.ts | 6 + utils/deploys/deployOracles.ts | 14 +- utils/fixtures/index.ts | 1 + utils/fixtures/yearnFixture.ts | 65 + utils/test/index.ts | 3 +- 21 files changed, 6004 insertions(+), 4 deletions(-) create mode 100644 contracts/interfaces/external/IYearnVault.sol create mode 100644 contracts/mocks/external/YearnStrategyMock.sol create mode 100644 contracts/protocol/integration/YearnWrapAdapter.sol create mode 100644 contracts/protocol/integration/oracles/YearnVaultOracle.sol create mode 100644 external/abi/yearn/Registry.json create mode 100644 external/abi/yearn/Vault.json create mode 100644 external/contracts/yearn/BaseStrategy.sol create mode 100644 external/contracts/yearn/Registry.vy create mode 100644 external/contracts/yearn/Vault.vy create mode 100644 test/fixtures/yearn.spec.ts create mode 100644 test/integration/oracles/yearnVaultOracle.spec.ts create mode 100644 test/integration/yearnWrapModule.spec.ts create mode 100644 utils/contracts/yearn.ts create mode 100644 utils/fixtures/yearnFixture.ts diff --git a/contracts/interfaces/external/IYearnVault.sol b/contracts/interfaces/external/IYearnVault.sol new file mode 100644 index 000000000..0592fb811 --- /dev/null +++ b/contracts/interfaces/external/IYearnVault.sol @@ -0,0 +1,24 @@ +/* + Copyright 2021 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; + +interface IYearnVault { + function token() external view returns(address); + function pricePerShare() external view returns(uint256); +} diff --git a/contracts/mocks/external/YearnStrategyMock.sol b/contracts/mocks/external/YearnStrategyMock.sol new file mode 100644 index 000000000..27758a0f9 --- /dev/null +++ b/contracts/mocks/external/YearnStrategyMock.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.6.10; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {BaseStrategyInitializable, StrategyParams, VaultAPI} from "../../../external/contracts/yearn/BaseStrategy.sol"; + +/* + * This Strategy serves as both a mock Strategy for testing, and an example + * for integrators on how to use BaseStrategy + */ + +contract YearnStrategyMock is BaseStrategyInitializable { + bool public doReentrancy; + bool public delegateEverything; + + // Some token that needs to be protected for some reason + // Initialize this to some fake address, because we're just using it + // to test `BaseStrategy.protectedTokens()` + address public constant protectedToken = address(0xbad); + + constructor(address _vault) public BaseStrategyInitializable(_vault) {} + + function name() external override view returns (string memory) { + return string(abi.encodePacked("YearnStrategyMock ", apiVersion())); + } + + // NOTE: This is a test-only function to simulate delegation + function _toggleDelegation() public { + delegateEverything = !delegateEverything; + } + + function delegatedAssets() external override view returns (uint256) { + if (delegateEverything) { + return vault.strategies(address(this)).totalDebt; + } else { + return 0; + } + } + + // NOTE: This is a test-only function to simulate losses + function _takeFunds(uint256 amount) public { + want.safeTransfer(msg.sender, amount); + } + + // NOTE: This is a test-only function to enable reentrancy on withdraw + function _toggleReentrancyExploit() public { + doReentrancy = !doReentrancy; + } + + // NOTE: This is a test-only function to simulate a wrong want token + function _setWant(IERC20 _want) public { + want = _want; + } + + function estimatedTotalAssets() public override view returns (uint256) { + // For mock, this is just everything we have + return want.balanceOf(address(this)); + } + + function prepareReturn(uint256 _debtOutstanding) + internal + override + returns ( + uint256 _profit, + uint256 _loss, + uint256 _debtPayment + ) + { + // During testing, send this contract some tokens to simulate "Rewards" + uint256 totalAssets = want.balanceOf(address(this)); + uint256 totalDebt = vault.strategies(address(this)).totalDebt; + if (totalAssets > _debtOutstanding) { + _debtPayment = _debtOutstanding; + totalAssets = totalAssets.sub(_debtOutstanding); + } else { + _debtPayment = totalAssets; + totalAssets = 0; + } + totalDebt = totalDebt.sub(_debtPayment); + + if (totalAssets > totalDebt) { + _profit = totalAssets.sub(totalDebt); + } else { + _loss = totalDebt.sub(totalAssets); + } + } + + function adjustPosition(uint256 _debtOutstanding) internal override { + // Whatever we have "free", consider it "invested" now + } + + function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _liquidatedAmount, uint256 _loss) { + if (doReentrancy) { + // simulate a malicious protocol or reentrancy situation triggered by strategy withdraw interactions + uint256 stratBalance = VaultAPI(address(vault)).balanceOf(address(this)); + VaultAPI(address(vault)).withdraw(stratBalance, address(this)); + } + + uint256 totalDebt = vault.strategies(address(this)).totalDebt; + uint256 totalAssets = want.balanceOf(address(this)); + if (_amountNeeded > totalAssets) { + _liquidatedAmount = totalAssets; + _loss = _amountNeeded.sub(totalAssets); + } else { + // NOTE: Just in case something was stolen from this contract + if (totalDebt > totalAssets) { + _loss = totalDebt.sub(totalAssets); + if (_loss > _amountNeeded) _loss = _amountNeeded; + } + _liquidatedAmount = _amountNeeded; + } + } + + function prepareMigration(address _newStrategy) internal override { + // Nothing needed here because no additional tokens/tokenized positions for mock + } + + function protectedTokens() internal override view returns (address[] memory) { + address[] memory protected = new address[](1); + protected[0] = protectedToken; + return protected; + } +} diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol new file mode 100644 index 000000000..227fc0ba2 --- /dev/null +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -0,0 +1,132 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; +pragma experimental "ABIEncoderV2"; + +import { IYearnVault } from "../../interfaces/external/IYearnVault.sol"; + +/** + * @title YearnWrapAdapter + * @author Set Protocol + * + * Wrap adapter for Yearn that returns data for wraps/unwraps of tokens + */ +contract YearnWrapAdapter { + + /* ============ Modifiers ============ */ + + /** + * Throws if the underlying/wrapped token pair is not valid + */ + modifier onlyValidTokenPair(address _underlyingToken, address _wrappedToken) { + require(validTokenPair(_underlyingToken, _wrappedToken), "Must be a valid token pair"); + _; + } + + /* ============ Constants ============ */ + + // Mock address to indicate ETH. + address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /* ============ Constructor ============ */ + + constructor() public { } + + /* ============ External Getter Functions ============ */ + + /** + * Generates the calldata to wrap an underlying asset into a wrappedToken. + * + * @param _underlyingToken Address of the component to be wrapped + * @param _wrappedToken Address of the desired wrapped token + * @param _underlyingUnits Total quantity of underlying units to wrap + * + * @return address Target contract address + * @return uint256 Total quantity of underlying units (if underlying is ETH) + * @return bytes Wrap calldata + */ + function getWrapCallData( + address _underlyingToken, + address _wrappedToken, + uint256 _underlyingUnits + ) + external + view + onlyValidTokenPair(_underlyingToken, _wrappedToken) + returns (address, uint256, bytes memory) + { + uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0; + + // deposit(address _reserve, uint256 _amount, uint16 _referralCode) + bytes memory callData = abi.encodeWithSignature("deposit(uint256)", _underlyingUnits); + + return (address(_wrappedToken), value, callData); + } + + /** + * Generates the calldata to unwrap a wrapped asset into its underlying. + * + * @param _underlyingToken Address of the underlying asset + * @param _wrappedToken Address of the component to be unwrapped + * @param _wrappedTokenUnits Total quantity of wrapped token units to unwrap + * + * @return address Target contract address + * @return uint256 Total quantity of wrapped token units to unwrap. This will always be 0 for unwrapping + * @return bytes Unwrap calldata + */ + function getUnwrapCallData( + address _underlyingToken, + address _wrappedToken, + uint256 _wrappedTokenUnits + ) + external + view + onlyValidTokenPair(_underlyingToken, _wrappedToken) + returns (address, uint256, bytes memory) + { + // redeem(uint256 _amount) + bytes memory callData = abi.encodeWithSignature("withdraw(uint256)", _wrappedTokenUnits); + + return (address(_wrappedToken), 0, callData); + } + + /** + * Returns the address to approve source tokens for wrapping. + * + * @return address Address of the contract to approve tokens to + */ + function getSpenderAddress(address /* _underlyingToken */, address _wrappedToken) external view returns(address) { + return address(_wrappedToken); + } + + /* ============ Internal Functions ============ */ + + /** + * Validates the underlying and wrapped token pair + * + * @param _underlyingToken Address of the underlying asset + * @param _wrappedToken Address of the wrapped asset + * + * @return bool Whether or not the wrapped token accepts the underlying token as collateral + */ + function validTokenPair(address _underlyingToken, address _wrappedToken) internal view returns(bool) { + address unwrappedToken = IYearnVault(_wrappedToken).token(); + return unwrappedToken == _underlyingToken; + } +} diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol new file mode 100644 index 000000000..ed09d791d --- /dev/null +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -0,0 +1,94 @@ +/* + Copyright 2021 Set Labs Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.6.10; + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol"; +import { IYearnVault } from "../../../interfaces/external/IYearnVault.sol"; +import { IOracle } from "../../../interfaces/IOracle.sol"; + + +/** + * @title YearnVaultOracle + * @author Set Protocol, Ember Fund + * + * Oracle built to retrieve the Yearn vault price + */ +contract YearnVaultOracle is IOracle +{ + using SafeMath for uint256; + using PreciseUnitMath for uint256; + + + /* ============ State Variables ============ */ + IYearnVault public vault; + IOracle public underlyingOracle; // Underlying token oracle + string public dataDescription; + + // Price per share values are scaled by 1e18 + uint256 internal constant scalingFactor = 10 ** 18; + + // CToken Full Unit + uint256 public cTokenFullUnit; + + // Underlying Asset Full Unit + uint256 public underlyingFullUnit; + + /* ============ Constructor ============ */ + + /* + * @param _vault The address of Yearn Vault Token + * @param _underlyingOracle The address of the underlying oracle + * @param _underlyingFullUnit The full unit of the underlying asset + * @param _dataDescription Human readable description of oracle + */ + constructor( + IYearnVault _vault, + IOracle _underlyingOracle, + uint256 _underlyingFullUnit, + string memory _dataDescription + ) + public + { + vault = _vault; + underlyingFullUnit = _underlyingFullUnit; + underlyingOracle = _underlyingOracle; + dataDescription = _dataDescription; + } + + /** + * Returns the price value of a full vault token denominated in underlyingOracle value + & + * The underlying oracle is assumed to return a price of 18 decimal + * for a single full token of the underlying asset. The derived price + * of the vault token is then the price of a unit of underlying multiplied + * by the exchangeRate, adjusted for decimal differences, and descaled. + */ + function read() + external + override + view + returns (uint256) + { + // Retrieve the price of the underlying + uint256 underlyingPrice = underlyingOracle.read(); + + // Retrieve price per share + uint256 pricePerShare = vault.pricePerShare(); + uint256 normalizedPricePerShare = pricePerShare.preciseDiv(underlyingFullUnit); + + return normalizedPricePerShare.preciseMul(underlyingPrice); + } +} diff --git a/external/abi/yearn/Registry.json b/external/abi/yearn/Registry.json new file mode 100644 index 000000000..44603c974 --- /dev/null +++ b/external/abi/yearn/Registry.json @@ -0,0 +1,589 @@ +{ + "_format": "hh-vyper-artifact-1", + "contractName": "Registry", + "abi": [ + { + "name": "NewRelease", + "inputs": [ + { + "name": "release_id", + "type": "uint256", + "indexed": true + }, + { + "name": "template", + "type": "address", + "indexed": false + }, + { + "name": "api_version", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewVault", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true + }, + { + "name": "vault_id", + "type": "uint256", + "indexed": true + }, + { + "name": "vault", + "type": "address", + "indexed": false + }, + { + "name": "api_version", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewExperimentalVault", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true + }, + { + "name": "deployer", + "type": "address", + "indexed": true + }, + { + "name": "vault", + "type": "address", + "indexed": false + }, + { + "name": "api_version", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "NewGovernance", + "inputs": [ + { + "name": "governance", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "VaultTagged", + "inputs": [ + { + "name": "vault", + "type": "address", + "indexed": false + }, + { + "name": "tag", + "type": "string", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "constructor", + "inputs": [], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGovernance", + "inputs": [ + { + "name": "governance", + "type": "address" + } + ], + "outputs": [], + "gas": 36245 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "acceptGovernance", + "inputs": [], + "outputs": [], + "gas": 37517 + }, + { + "stateMutability": "view", + "type": "function", + "name": "latestRelease", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 6831 + }, + { + "stateMutability": "view", + "type": "function", + "name": "latestVault", + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2587 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newRelease", + "inputs": [ + { + "name": "vault", + "type": "address" + } + ], + "outputs": [], + "gas": 82588 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "releaseDelta", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newExperimentalVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "newExperimentalVault", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "guardian", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "releaseDelta", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "endorseVault", + "inputs": [ + { + "name": "vault", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "endorseVault", + "inputs": [ + { + "name": "vault", + "type": "address" + }, + { + "name": "releaseDelta", + "type": "uint256" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setBanksy", + "inputs": [ + { + "name": "tagger", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setBanksy", + "inputs": [ + { + "name": "tagger", + "type": "address" + }, + { + "name": "allowed", + "type": "bool" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "tagVault", + "inputs": [ + { + "name": "vault", + "type": "address" + }, + { + "name": "tag", + "type": "string" + } + ], + "outputs": [], + "gas": 186064 + }, + { + "stateMutability": "view", + "type": "function", + "name": "numReleases", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1388 + }, + { + "stateMutability": "view", + "type": "function", + "name": "releases", + "inputs": [ + { + "name": "arg0", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1533 + }, + { + "stateMutability": "view", + "type": "function", + "name": "numVaults", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1663 + }, + { + "stateMutability": "view", + "type": "function", + "name": "vaults", + "inputs": [ + { + "name": "arg0", + "type": "address" + }, + { + "name": "arg1", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1808 + }, + { + "stateMutability": "view", + "type": "function", + "name": "tokens", + "inputs": [ + { + "name": "arg0", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1623 + }, + { + "stateMutability": "view", + "type": "function", + "name": "numTokens", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1538 + }, + { + "stateMutability": "view", + "type": "function", + "name": "isRegistered", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 1783 + }, + { + "stateMutability": "view", + "type": "function", + "name": "governance", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1598 + }, + { + "stateMutability": "view", + "type": "function", + "name": "pendingGovernance", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 1628 + }, + { + "stateMutability": "view", + "type": "function", + "name": "tags", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 10229 + }, + { + "stateMutability": "view", + "type": "function", + "name": "banksy", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 1903 + } + ], + "bytecode": "0x336007556115eb56600436101561000d576110f9565b600035601c52600051341561002157600080fd5b63ab033ea98114156100535760043560a01c1561003d57600080fd5b600754331461004b57600080fd5b600435600855005b63238efcbc81141561009f57600854331461006d57600080fd5b3360075533610140527f4f386975ea1c2f7cf1845b08bee00626fbb624ecad16254d63d9bb9ba86526de6020610140a1005b637be0ca5e8114156101735760606101a0600463258294106101405261015c60016000546001808210156100d257600080fd5b8082039050905060e05260c052604060c020545afa6100f057600080fd5b603f3d116100fd57600080fd5b601d6101a06101a05101511061011257600080fd5b6000506101c08051602001806102408284600060045af161013257600080fd5b5050610240518061026001818260206001820306601f82010390500336823750506020610220526040610240510160206001820306601f8201039050610220f35b63e177dc708114156101db5760043560a01c1561018f57600080fd5b600360043560e05260c052604060c020600260043560e05260c052604060c020546001808210156101bf57600080fd5b8082039050905060e05260c052604060c0205460005260206000f35b6333990d4b8114156104315760043560a01c156101f757600080fd5b600754331461020557600080fd5b6000546101405260006101405111156102e1576060610400600463258294106103a0526103bc6004355afa61023957600080fd5b603f3d1161024657600080fd5b601d6104006104005101511061025b57600080fd5b600050610420602001516060610340600463258294106102e0526102fc60016101405160018082101561028d57600080fd5b8082039050905060e05260c052604060c020545afa6102ab57600080fd5b603f3d116102b857600080fd5b601d610340610340510151106102cd57600080fd5b60005061036060200151186102e157600080fd5b60043560016101405160e05260c052604060c0205561014051600181818301101561030b57600080fd5b8082019050905060005560606101c0600463258294106101605261017c6004355afa61033657600080fd5b603f3d1161034357600080fd5b601d6101c06101c05101511061035857600080fd5b6000506101e08051602001806102208284600060045af161037857600080fd5b50506004356102c052604061028052610280516102e052610220805160200180610280516102c0018284600060045af16103b157600080fd5b5050610280516102c00151806020610280516102c0010101818260206001820306601f82010390500336823750506020610280516102c0015160206001820306601f820103905061028051010161028052610140517fa6fbd216b6734f34092f1be6b7247e1551a6d4f2d5000c53721cfdc119a5b7cf610280516102c0a2005b63108ca11e81141561044857600061022052610469565b63b0b40fce81141561046457602060a461022037600050610469565b6106f4565b60043560a01c1561047957600080fd5b60243560a01c1561048957600080fd5b60443560a01c1561049957600080fd5b60606064356004016101403760406064356004013511156104b957600080fd5b60406084356004016101c03760206084356004013511156104d957600080fd5b60075433146104e757600080fd5b6000546001808210156104f957600080fd5b80820390509050610220518082101561051157600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e060043561028052336102a0526044356102c0526024356102e05280610300526101408080516020018084610280018284600060045af161058557600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af16105c457600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156106065761061b565b610440515160206104405103610440526105f4565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a05261018052610160526101405261046051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160043561028052610260516102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506102605160005260206000f35b635b73aa0d81141561070b5760006102205261072c565b635bd4b0f281141561072757602060c46102203760005061072c565b610a64565b60043560a01c1561073c57600080fd5b60243560a01c1561074c57600080fd5b60443560a01c1561075c57600080fd5b60643560a01c1561076c57600080fd5b606060843560040161014037604060843560040135111561078c57600080fd5b604060a4356004016101c037602060a4356004013511156107ac57600080fd5b6000546001808210156107be57600080fd5b8082039050905061022051808210156107d657600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e0600435610280526024356102a0526064356102c0526044356102e05280610300526101408080516020018084610280018284600060045af161084c57600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af161088b57600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156108cd576108e2565b610440515160206104405103610440526108bb565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052610460516102605260606102e0600463258294106102805261029c610260515afa61095d57600080fd5b603f3d1161096a57600080fd5b601d6102e06102e05101511061097f57600080fd5b6000506103008051602001806103408284600060045af161099f57600080fd5b5050610260516103e05260406103a0526103a051610400526103408051602001806103a0516103e0018284600060045af16109d957600080fd5b50506103a0516103e001518060206103a0516103e0010101818260206001820306601f820103905003368237505060206103a0516103e0015160206001820306601f82010390506103a05101016103a052336004357f57a9cdc2a05e05f66e76769bdbe88e21ec45d9ee0f97d4cb60395d4c75dcbcda6103a0516103e0a36102605160005260206000f35b6329b2e0c6811415610a7b57600061014052610a9c565b63b366a35c811415610a97576020602461014037600050610a9c565b610cac565b60043560a01c15610aac57600080fd5b6007543314610aba57600080fd5b3360206101c06004635aa6e6756101605261017c6004355afa610adc57600080fd5b601f3d11610ae957600080fd5b6000506101c05114610afa57600080fd5b600054600180821015610b0c57600080fd5b808203905090506101405180821015610b2457600080fd5b80820390509050610160526060610240600463258294106101e0526101fc60016101605160e05260c052604060c020545afa610b5f57600080fd5b603f3d11610b6c57600080fd5b601d61024061024051015110610b8157600080fd5b6000506102608051602001806101808284600060045af1610ba157600080fd5b50506101a0516060610300600463258294106102a0526102bc6004355afa610bc857600080fd5b603f3d11610bd557600080fd5b601d61030061030051015110610bea57600080fd5b6000506103206020015114610bfe57600080fd5b6020610240600463fc0c546a6101e0526101fc6004355afa610c1f57600080fd5b601f3d11610c2c57600080fd5b60005061024051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605161026051610280526004356102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052600050005b632cad8f9f811415610cc357600161014052610cf4565b635e05f6af811415610cef5760243560011c15610cdf57600080fd5b6020602461014037600050610cf4565b610d29565b60043560a01c15610d0457600080fd5b6007543314610d1257600080fd5b61014051600a60043560e05260c052604060c02055005b6360bd68f8811415610ea25760043560a01c15610d4557600080fd5b6098602435600401610140376078602435600401351115610d6557600080fd5b600754331815610d8757600a3360e05260c052604060c02054610d8757600080fd5b61014080600960043560e05260c052604060c02060c052602060c020602082510161012060006005818352015b82610120516020021115610dc757610de9565b61012051602002850151610120518501555b8151600101808352811415610db4575b50505050505060043561024052604061020052610200516102605261014080516020018061020051610240018284600060045af1610e2657600080fd5b505061020051610240015180602061020051610240010101818260206001820306601f8201039050033682375050602061020051610240015160206001820306601f8201039050610200510101610200527f07bd58794c2ca0ae152f7719eb5f02c654476de972cadec0e8191ae8be42096d61020051610240a1005b6356e0a94b811415610eba5760005460005260206000f35b63b6a9f40f811415610ee057600160043560e05260c052604060c0205460005260206000f35b63f9c7bba5811415610f165760043560a01c15610efc57600080fd5b600260043560e05260c052604060c0205460005260206000f35b637bbfc69e811415610f5a5760043560a01c15610f3257600080fd5b600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b634f64b2be811415610f8057600460043560e05260c052604060c0205460005260206000f35b638e499bcf811415610f985760055460005260206000f35b63c3c5a547811415610fce5760043560a01c15610fb457600080fd5b600660043560e05260c052604060c0205460005260206000f35b635aa6e675811415610fe65760075460005260206000f35b63f39c38a0811415610ffe5760085460005260206000f35b6358b8f8428114156110c15760043560a01c1561101a57600080fd5b600960043560e05260c052604060c0208060c052602060c020610180602082540161012060006005818352015b8261012051602002111561105a5761107c565b61012051850154610120516020028501525b8151600101808352811415611047575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63ee711ed58114156110f75760043560a01c156110dd57600080fd5b600a60043560e05260c052604060c0205460005260206000f35b505b60006000fd5b610220526101405261016052610180526101a0526101c0526101e05261020052610240526000610320525b6102405160206001820306601f82010390506103205110151561114c57611165565b610320516102600152610320516020016103205261112a565b6102c0526000610320525b6102c05160206001820306601f820103905061032051101515611192576111ab565b610320516102e001526103205160200161032052611170565b60005060016102005160e05260c052604060c0205461034052600061034051186111d457600080fd5b7f602d3d8160093d39f3363d3d373d3d3d363d7300000000000000000000000000610380526103405160601b610393527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006103a75260366103806000f061036052610360513b61124357600080fd5b6000600061016460c063a5b81fdf61038052610140516103a052610160516103c052610180516103e052806104005261024080805160200180846103a0018284600060045af161129257600080fd5b50508051820160206001820306601f820103905060200191505080610420526102c080805160200180846103a0018284600060045af16112d157600080fd5b5050506101a0516104405261039c90506000610360515af16112f257600080fd5b6103605160005260005161022051565b61018052610140526101605260026101405160e05260c052604060c020546101a05260006101a0511115611409576060610460600463258294106104005261041c610160515afa61135257600080fd5b603f3d1161135f57600080fd5b601d6104606104605101511061137457600080fd5b6000506104806020015160606103a0600463258294106103405261035c60036101405160e05260c052604060c0206101a0516001808210156113b557600080fd5b8082039050905060e05260c052604060c020545afa6113d357600080fd5b603f3d116113e057600080fd5b601d6103a06103a0510151106113f557600080fd5b6000506103c0602001511861140957600080fd5b6101605160036101405160e05260c052604060c0206101a05160e05260c052604060c020556101a051600181818301101561144357600080fd5b8082019050905060026101405160e05260c052604060c0205560066101405160e05260c052604060c0205415156114bd57600160066101405160e05260c052604060c0205561014051600460055460e05260c052604060c020556005805460018181830110156114b257600080fd5b808201905090508155505b6060610220600463258294106101c0526101dc610160515afa6114df57600080fd5b603f3d116114ec57600080fd5b601d6102206102205101511061150157600080fd5b6000506102408051602001806102808284600060045af161152157600080fd5b5050610160516103205260406102e0526102e051610340526102808051602001806102e051610320018284600060045af161155b57600080fd5b50506102e05161032001518060206102e051610320010101818260206001820306601f820103905003368237505060206102e051610320015160206001820306601f82010390506102e05101016102e0526101a051610140517fce089905ba4a4d622553bcb5646fd23e895c256f0376eee04e99e61cec1dc7e86102e051610320a361018051565b6100086115eb036100086000396100086115eb036000f3", + "deployedBytecode": "0x600436101561000d576110f9565b600035601c52600051341561002157600080fd5b63ab033ea98114156100535760043560a01c1561003d57600080fd5b600754331461004b57600080fd5b600435600855005b63238efcbc81141561009f57600854331461006d57600080fd5b3360075533610140527f4f386975ea1c2f7cf1845b08bee00626fbb624ecad16254d63d9bb9ba86526de6020610140a1005b637be0ca5e8114156101735760606101a0600463258294106101405261015c60016000546001808210156100d257600080fd5b8082039050905060e05260c052604060c020545afa6100f057600080fd5b603f3d116100fd57600080fd5b601d6101a06101a05101511061011257600080fd5b6000506101c08051602001806102408284600060045af161013257600080fd5b5050610240518061026001818260206001820306601f82010390500336823750506020610220526040610240510160206001820306601f8201039050610220f35b63e177dc708114156101db5760043560a01c1561018f57600080fd5b600360043560e05260c052604060c020600260043560e05260c052604060c020546001808210156101bf57600080fd5b8082039050905060e05260c052604060c0205460005260206000f35b6333990d4b8114156104315760043560a01c156101f757600080fd5b600754331461020557600080fd5b6000546101405260006101405111156102e1576060610400600463258294106103a0526103bc6004355afa61023957600080fd5b603f3d1161024657600080fd5b601d6104006104005101511061025b57600080fd5b600050610420602001516060610340600463258294106102e0526102fc60016101405160018082101561028d57600080fd5b8082039050905060e05260c052604060c020545afa6102ab57600080fd5b603f3d116102b857600080fd5b601d610340610340510151106102cd57600080fd5b60005061036060200151186102e157600080fd5b60043560016101405160e05260c052604060c0205561014051600181818301101561030b57600080fd5b8082019050905060005560606101c0600463258294106101605261017c6004355afa61033657600080fd5b603f3d1161034357600080fd5b601d6101c06101c05101511061035857600080fd5b6000506101e08051602001806102208284600060045af161037857600080fd5b50506004356102c052604061028052610280516102e052610220805160200180610280516102c0018284600060045af16103b157600080fd5b5050610280516102c00151806020610280516102c0010101818260206001820306601f82010390500336823750506020610280516102c0015160206001820306601f820103905061028051010161028052610140517fa6fbd216b6734f34092f1be6b7247e1551a6d4f2d5000c53721cfdc119a5b7cf610280516102c0a2005b63108ca11e81141561044857600061022052610469565b63b0b40fce81141561046457602060a461022037600050610469565b6106f4565b60043560a01c1561047957600080fd5b60243560a01c1561048957600080fd5b60443560a01c1561049957600080fd5b60606064356004016101403760406064356004013511156104b957600080fd5b60406084356004016101c03760206084356004013511156104d957600080fd5b60075433146104e757600080fd5b6000546001808210156104f957600080fd5b80820390509050610220518082101561051157600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e060043561028052336102a0526044356102c0526024356102e05280610300526101408080516020018084610280018284600060045af161058557600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af16105c457600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156106065761061b565b610440515160206104405103610440526105f4565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a05261018052610160526101405261046051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160043561028052610260516102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506102605160005260206000f35b635b73aa0d81141561070b5760006102205261072c565b635bd4b0f281141561072757602060c46102203760005061072c565b610a64565b60043560a01c1561073c57600080fd5b60243560a01c1561074c57600080fd5b60443560a01c1561075c57600080fd5b60643560a01c1561076c57600080fd5b606060843560040161014037604060843560040135111561078c57600080fd5b604060a4356004016101c037602060a4356004013511156107ac57600080fd5b6000546001808210156107be57600080fd5b8082039050905061022051808210156107d657600080fd5b80820390509050610240526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605160e0600435610280526024356102a0526064356102c0526044356102e05280610300526101408080516020018084610280018284600060045af161084c57600080fd5b50508051820160206001820306601f820103905060200191505080610320526101c08080516020018084610280018284600060045af161088b57600080fd5b50505061024051610340525061032051806102800180518060206001820306601f82010390508201610440525050505b6103606104405110156108cd576108e2565b610440515160206104405103610440526108bb565b6103405161032051610300516102e0516102c0516102a05161028051600658016110ff565b61046052610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052610460516102605260606102e0600463258294106102805261029c610260515afa61095d57600080fd5b603f3d1161096a57600080fd5b601d6102e06102e05101511061097f57600080fd5b6000506103008051602001806103408284600060045af161099f57600080fd5b5050610260516103e05260406103a0526103a051610400526103408051602001806103a0516103e0018284600060045af16109d957600080fd5b50506103a0516103e001518060206103a0516103e0010101818260206001820306601f820103905003368237505060206103a0516103e0015160206001820306601f82010390506103a05101016103a052336004357f57a9cdc2a05e05f66e76769bdbe88e21ec45d9ee0f97d4cb60395d4c75dcbcda6103a0516103e0a36102605160005260206000f35b6329b2e0c6811415610a7b57600061014052610a9c565b63b366a35c811415610a97576020602461014037600050610a9c565b610cac565b60043560a01c15610aac57600080fd5b6007543314610aba57600080fd5b3360206101c06004635aa6e6756101605261017c6004355afa610adc57600080fd5b601f3d11610ae957600080fd5b6000506101c05114610afa57600080fd5b600054600180821015610b0c57600080fd5b808203905090506101405180821015610b2457600080fd5b80820390509050610160526060610240600463258294106101e0526101fc60016101605160e05260c052604060c020545afa610b5f57600080fd5b603f3d11610b6c57600080fd5b601d61024061024051015110610b8157600080fd5b6000506102608051602001806101808284600060045af1610ba157600080fd5b50506101a0516060610300600463258294106102a0526102bc6004355afa610bc857600080fd5b603f3d11610bd557600080fd5b601d61030061030051015110610bea57600080fd5b6000506103206020015114610bfe57600080fd5b6020610240600463fc0c546a6101e0526101fc6004355afa610c1f57600080fd5b601f3d11610c2c57600080fd5b60005061024051610260526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605161026051610280526004356102a0526102a0516102805160065801611302565b610260526102405261022052610200526101e0526101c0526101a052610180526101605261014052600050005b632cad8f9f811415610cc357600161014052610cf4565b635e05f6af811415610cef5760243560011c15610cdf57600080fd5b6020602461014037600050610cf4565b610d29565b60043560a01c15610d0457600080fd5b6007543314610d1257600080fd5b61014051600a60043560e05260c052604060c02055005b6360bd68f8811415610ea25760043560a01c15610d4557600080fd5b6098602435600401610140376078602435600401351115610d6557600080fd5b600754331815610d8757600a3360e05260c052604060c02054610d8757600080fd5b61014080600960043560e05260c052604060c02060c052602060c020602082510161012060006005818352015b82610120516020021115610dc757610de9565b61012051602002850151610120518501555b8151600101808352811415610db4575b50505050505060043561024052604061020052610200516102605261014080516020018061020051610240018284600060045af1610e2657600080fd5b505061020051610240015180602061020051610240010101818260206001820306601f8201039050033682375050602061020051610240015160206001820306601f8201039050610200510101610200527f07bd58794c2ca0ae152f7719eb5f02c654476de972cadec0e8191ae8be42096d61020051610240a1005b6356e0a94b811415610eba5760005460005260206000f35b63b6a9f40f811415610ee057600160043560e05260c052604060c0205460005260206000f35b63f9c7bba5811415610f165760043560a01c15610efc57600080fd5b600260043560e05260c052604060c0205460005260206000f35b637bbfc69e811415610f5a5760043560a01c15610f3257600080fd5b600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b634f64b2be811415610f8057600460043560e05260c052604060c0205460005260206000f35b638e499bcf811415610f985760055460005260206000f35b63c3c5a547811415610fce5760043560a01c15610fb457600080fd5b600660043560e05260c052604060c0205460005260206000f35b635aa6e675811415610fe65760075460005260206000f35b63f39c38a0811415610ffe5760085460005260206000f35b6358b8f8428114156110c15760043560a01c1561101a57600080fd5b600960043560e05260c052604060c0208060c052602060c020610180602082540161012060006005818352015b8261012051602002111561105a5761107c565b61012051850154610120516020028501525b8151600101808352811415611047575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63ee711ed58114156110f75760043560a01c156110dd57600080fd5b600a60043560e05260c052604060c0205460005260206000f35b505b60006000fd5b610220526101405261016052610180526101a0526101c0526101e05261020052610240526000610320525b6102405160206001820306601f82010390506103205110151561114c57611165565b610320516102600152610320516020016103205261112a565b6102c0526000610320525b6102c05160206001820306601f820103905061032051101515611192576111ab565b610320516102e001526103205160200161032052611170565b60005060016102005160e05260c052604060c0205461034052600061034051186111d457600080fd5b7f602d3d8160093d39f3363d3d373d3d3d363d7300000000000000000000000000610380526103405160601b610393527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006103a75260366103806000f061036052610360513b61124357600080fd5b6000600061016460c063a5b81fdf61038052610140516103a052610160516103c052610180516103e052806104005261024080805160200180846103a0018284600060045af161129257600080fd5b50508051820160206001820306601f820103905060200191505080610420526102c080805160200180846103a0018284600060045af16112d157600080fd5b5050506101a0516104405261039c90506000610360515af16112f257600080fd5b6103605160005260005161022051565b61018052610140526101605260026101405160e05260c052604060c020546101a05260006101a0511115611409576060610460600463258294106104005261041c610160515afa61135257600080fd5b603f3d1161135f57600080fd5b601d6104606104605101511061137457600080fd5b6000506104806020015160606103a0600463258294106103405261035c60036101405160e05260c052604060c0206101a0516001808210156113b557600080fd5b8082039050905060e05260c052604060c020545afa6113d357600080fd5b603f3d116113e057600080fd5b601d6103a06103a0510151106113f557600080fd5b6000506103c0602001511861140957600080fd5b6101605160036101405160e05260c052604060c0206101a05160e05260c052604060c020556101a051600181818301101561144357600080fd5b8082019050905060026101405160e05260c052604060c0205560066101405160e05260c052604060c0205415156114bd57600160066101405160e05260c052604060c0205561014051600460055460e05260c052604060c020556005805460018181830110156114b257600080fd5b808201905090508155505b6060610220600463258294106101c0526101dc610160515afa6114df57600080fd5b603f3d116114ec57600080fd5b601d6102206102205101511061150157600080fd5b6000506102408051602001806102808284600060045af161152157600080fd5b5050610160516103205260406102e0526102e051610340526102808051602001806102e051610320018284600060045af161155b57600080fd5b50506102e05161032001518060206102e051610320010101818260206001820306601f820103905003368237505060206102e051610320015160206001820306601f82010390506102e05101016102e0526101a051610140517fce089905ba4a4d622553bcb5646fd23e895c256f0376eee04e99e61cec1dc7e86102e051610320a36101805156", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/external/abi/yearn/Vault.json b/external/abi/yearn/Vault.json new file mode 100644 index 000000000..cd9256e8e --- /dev/null +++ b/external/abi/yearn/Vault.json @@ -0,0 +1,1666 @@ +{ + "_format": "hh-vyper-artifact-1", + "contractName": "Vault", + "abi": [ + { + "name": "Transfer", + "inputs": [ + { + "name": "sender", + "type": "address", + "indexed": true + }, + { + "name": "receiver", + "type": "address", + "indexed": true + }, + { + "name": "value", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true + }, + { + "name": "spender", + "type": "address", + "indexed": true + }, + { + "name": "value", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyAdded", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "debtRatio", + "type": "uint256", + "indexed": false + }, + { + "name": "minDebtPerHarvest", + "type": "uint256", + "indexed": false + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256", + "indexed": false + }, + { + "name": "performanceFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyReported", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "gain", + "type": "uint256", + "indexed": false + }, + { + "name": "loss", + "type": "uint256", + "indexed": false + }, + { + "name": "debtPaid", + "type": "uint256", + "indexed": false + }, + { + "name": "totalGain", + "type": "uint256", + "indexed": false + }, + { + "name": "totalLoss", + "type": "uint256", + "indexed": false + }, + { + "name": "totalDebt", + "type": "uint256", + "indexed": false + }, + { + "name": "debtAdded", + "type": "uint256", + "indexed": false + }, + { + "name": "debtRatio", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateGovernance", + "inputs": [ + { + "name": "governance", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateManagement", + "inputs": [ + { + "name": "management", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateGuestList", + "inputs": [ + { + "name": "guestList", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateRewards", + "inputs": [ + { + "name": "rewards", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateDepositLimit", + "inputs": [ + { + "name": "depositLimit", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdatePerformanceFee", + "inputs": [ + { + "name": "performanceFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateManagementFee", + "inputs": [ + { + "name": "managementFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateGuardian", + "inputs": [ + { + "name": "guardian", + "type": "address", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "EmergencyShutdown", + "inputs": [ + { + "name": "active", + "type": "bool", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "UpdateWithdrawalQueue", + "inputs": [ + { + "name": "queue", + "type": "address[20]", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdateDebtRatio", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "debtRatio", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdateMinDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "minDebtPerHarvest", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdateMaxDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyUpdatePerformanceFee", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + }, + { + "name": "performanceFee", + "type": "uint256", + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyMigrated", + "inputs": [ + { + "name": "oldVersion", + "type": "address", + "indexed": true + }, + { + "name": "newVersion", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyRevoked", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyRemovedFromQueue", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "StrategyAddedToQueue", + "inputs": [ + { + "name": "strategy", + "type": "address", + "indexed": true + } + ], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "nameOverride", + "type": "string" + }, + { + "name": "symbolOverride", + "type": "string" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "governance", + "type": "address" + }, + { + "name": "rewards", + "type": "address" + }, + { + "name": "nameOverride", + "type": "string" + }, + { + "name": "symbolOverride", + "type": "string" + }, + { + "name": "guardian", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "pure", + "type": "function", + "name": "apiVersion", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 4546 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setName", + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "outputs": [], + "gas": 107044 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setSymbol", + "inputs": [ + { + "name": "symbol", + "type": "string" + } + ], + "outputs": [], + "gas": 71894 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGovernance", + "inputs": [ + { + "name": "governance", + "type": "address" + } + ], + "outputs": [], + "gas": 36365 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "acceptGovernance", + "inputs": [], + "outputs": [], + "gas": 37637 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setManagement", + "inputs": [ + { + "name": "management", + "type": "address" + } + ], + "outputs": [], + "gas": 37775 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGuestList", + "inputs": [ + { + "name": "guestList", + "type": "address" + } + ], + "outputs": [], + "gas": 37805 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setRewards", + "inputs": [ + { + "name": "rewards", + "type": "address" + } + ], + "outputs": [], + "gas": 37835 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setLockedProfitDegration", + "inputs": [ + { + "name": "degration", + "type": "uint256" + } + ], + "outputs": [], + "gas": 36519 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setDepositLimit", + "inputs": [ + { + "name": "limit", + "type": "uint256" + } + ], + "outputs": [], + "gas": 37795 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setPerformanceFee", + "inputs": [ + { + "name": "fee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 37929 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setManagementFee", + "inputs": [ + { + "name": "fee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 37959 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setGuardian", + "inputs": [ + { + "name": "guardian", + "type": "address" + } + ], + "outputs": [], + "gas": 39203 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setEmergencyShutdown", + "inputs": [ + { + "name": "active", + "type": "bool" + } + ], + "outputs": [], + "gas": 39274 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "setWithdrawalQueue", + "inputs": [ + { + "name": "queue", + "type": "address[20]" + } + ], + "outputs": [], + "gas": 763950 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 76768 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 116531 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 38271 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "increaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 40312 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "decreaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 40336 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "permit", + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + }, + { + "name": "expiry", + "type": "uint256" + }, + { + "name": "signature", + "type": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 81264 + }, + { + "stateMutability": "view", + "type": "function", + "name": "totalAssets", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 4098 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "deposit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_amount", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_amount", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "maxAvailableShares", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 366010 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "maxShares", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "maxShares", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "maxShares", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + }, + { + "name": "maxLoss", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "pricePerShare", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 17041 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "addStrategy", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "debtRatio", + "type": "uint256" + }, + { + "name": "minDebtPerHarvest", + "type": "uint256" + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256" + }, + { + "name": "performanceFee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 1485796 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyDebtRatio", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "debtRatio", + "type": "uint256" + } + ], + "outputs": [], + "gas": 115193 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyMinDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "minDebtPerHarvest", + "type": "uint256" + } + ], + "outputs": [], + "gas": 42441 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyMaxDebtPerHarvest", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256" + } + ], + "outputs": [], + "gas": 42471 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "updateStrategyPerformanceFee", + "inputs": [ + { + "name": "strategy", + "type": "address" + }, + { + "name": "performanceFee", + "type": "uint256" + } + ], + "outputs": [], + "gas": 41251 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "migrateStrategy", + "inputs": [ + { + "name": "oldVersion", + "type": "address" + }, + { + "name": "newVersion", + "type": "address" + } + ], + "outputs": [], + "gas": 1141468 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "revokeStrategy", + "inputs": [], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "revokeStrategy", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "addStrategyToQueue", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [], + "gas": 1199804 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "removeStrategyFromQueue", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [], + "gas": 23088703 + }, + { + "stateMutability": "view", + "type": "function", + "name": "debtOutstanding", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "debtOutstanding", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "creditAvailable", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "creditAvailable", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "availableDepositLimit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 9551 + }, + { + "stateMutability": "view", + "type": "function", + "name": "expectedReturn", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "expectedReturn", + "inputs": [ + { + "name": "strategy", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "report", + "inputs": [ + { + "name": "gain", + "type": "uint256" + }, + { + "name": "loss", + "type": "uint256" + }, + { + "name": "_debtPayment", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 1117355 + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 8750 + }, + { + "stateMutability": "view", + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "gas": 7803 + }, + { + "stateMutability": "view", + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2408 + }, + { + "stateMutability": "view", + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2653 + }, + { + "stateMutability": "view", + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "arg0", + "type": "address" + }, + { + "name": "arg1", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2898 + }, + { + "stateMutability": "view", + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2498 + }, + { + "stateMutability": "view", + "type": "function", + "name": "token", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2528 + }, + { + "stateMutability": "view", + "type": "function", + "name": "governance", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2558 + }, + { + "stateMutability": "view", + "type": "function", + "name": "management", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2588 + }, + { + "stateMutability": "view", + "type": "function", + "name": "guardian", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2618 + }, + { + "stateMutability": "view", + "type": "function", + "name": "guestList", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2648 + }, + { + "stateMutability": "view", + "type": "function", + "name": "strategies", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "performanceFee", + "type": "uint256" + }, + { + "name": "activation", + "type": "uint256" + }, + { + "name": "debtRatio", + "type": "uint256" + }, + { + "name": "minDebtPerHarvest", + "type": "uint256" + }, + { + "name": "maxDebtPerHarvest", + "type": "uint256" + }, + { + "name": "lastReport", + "type": "uint256" + }, + { + "name": "totalDebt", + "type": "uint256" + }, + { + "name": "totalGain", + "type": "uint256" + }, + { + "name": "totalLoss", + "type": "uint256" + } + ], + "gas": 11001 + }, + { + "stateMutability": "view", + "type": "function", + "name": "withdrawalQueue", + "inputs": [ + { + "name": "arg0", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 2817 + }, + { + "stateMutability": "view", + "type": "function", + "name": "emergencyShutdown", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "gas": 2738 + }, + { + "stateMutability": "view", + "type": "function", + "name": "depositLimit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2768 + }, + { + "stateMutability": "view", + "type": "function", + "name": "debtRatio", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2798 + }, + { + "stateMutability": "view", + "type": "function", + "name": "totalDebt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2828 + }, + { + "stateMutability": "view", + "type": "function", + "name": "delegatedAssets", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2858 + }, + { + "stateMutability": "view", + "type": "function", + "name": "lastReport", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2888 + }, + { + "stateMutability": "view", + "type": "function", + "name": "activation", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2918 + }, + { + "stateMutability": "view", + "type": "function", + "name": "lockedProfit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2948 + }, + { + "stateMutability": "view", + "type": "function", + "name": "lockedProfitDegration", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 2978 + }, + { + "stateMutability": "view", + "type": "function", + "name": "rewards", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "gas": 3008 + }, + { + "stateMutability": "view", + "type": "function", + "name": "managementFee", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 3038 + }, + { + "stateMutability": "view", + "type": "function", + "name": "performanceFee", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 3068 + }, + { + "stateMutability": "view", + "type": "function", + "name": "nonces", + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "gas": 3313 + }, + { + "stateMutability": "view", + "type": "function", + "name": "DOMAIN_SEPARATOR", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "gas": 3128 + } + ], + "bytecode": "0x61511956600436101561000d57613ced565b600035601c52600051341561002157600080fd5b6383b43589811415610037573361022052610068565b63a5b81fdf8114156100635760a43560a01c1561005357600080fd5b602060a461022037600050610068565b6106ab565b60043560a01c1561007857600080fd5b60243560a01c1561008857600080fd5b60443560a01c1561009857600080fd5b60606064356004016101403760406064356004013511156100b857600080fd5b60406084356004016101c03760206084356004013511156100d857600080fd5b601554156100e557600080fd5b600435600655600061028052610280805160208201209050610140805160208201209050141561022c576000606061032060046395d89b416102c0526102dc6004355afa61013257600080fd5b603f3d1161013f57600080fd5b60156103206103205101511061015457600080fd5b6000506103406014806020846103e001018260208501600060045af15050805182019150506007610380527f20795661756c74000000000000000000000000000000000000000000000000006103a0526103806007806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600060c052602060c020602082510161012060006002818352015b826101205160200211156101ff57610221565b61012051602002850151610120518501555b81516001018083528114156101ec575b505050505050610287565b61014080600060c052602060c020602082510161012060006003818352015b8261012051602002111561025e57610280565b61012051602002850151610120518501555b815160010180835281141561024b575b5050505050505b6000610280526102808051602082012090506101c080516020820120905014156103c857600060026102c0527f79760000000000000000000000000000000000000000000000000000000000006102e0526102c06002806020846103e001018260208501600060045af1505080518201915050606061038060046395d89b416103205261033c6004355afa61031b57600080fd5b603f3d1161032857600080fd5b60156103806103805101511061033d57600080fd5b6000506103a06014806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600160c052602060c020602082510161012060006002818352015b8261012051602002111561039b576103bd565b61012051602002850151610120518501555b8151600101808352811415610388575b505050505050610423565b6101c080600160c052602060c020602082510161012060006002818352015b826101205160200211156103fa5761041c565b61012051602002850151610120518501555b81516001018083528114156103e7575b5050505050505b60206102a0600463313ce5676102405261025c6004355afa61044457600080fd5b601f3d1161045157600080fd5b6000506102a051600255602435600755602435610240527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610240a1602435600855602435610240527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610240a1604435601855604435610240527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610240a16102205160095561022051610240527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610240a16103e8601a556103e86102405261024051610260527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610260a160c860195560c86102405261024051610260527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610260a142601455426015556529d635a8e00060175560007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f602082610620010152602081019050600b610500527f596561726e205661756c740000000000000000000000000000000000000000006105205261050080516020820120905060208261062001015260208101905060056105c0527f302e332e340000000000000000000000000000000000000000000000000000006105e0526105c0805160208201209050602082610620010152602081019050466020826106200101526020810190503060208261062001015260208101905080610620526106209050805160208201209050601c55005b6325829410811415610740576005610140527f302e332e34000000000000000000000000000000000000000000000000000000610160526101408051602001806101e08284600060045af16106ff57600080fd5b50506101e0518061020001818260206001820306601f820103905003368237505060206101c05260406101e0510160206001820306601f82010390506101c0f35b63c47f00278114156107d657604a60043560040161014037602a60043560040135111561076c57600080fd5b600754331461077a57600080fd5b61014080600060c052602060c020602082510161012060006003818352015b826101205160200211156107ac576107ce565b61012051602002850151610120518501555b8151600101808352811415610799575b505050505050005b63b84c824681141561086c57603460043560040161014037601460043560040135111561080257600080fd5b600754331461081057600080fd5b61014080600160c052602060c020602082510161012060006002818352015b8261012051602002111561084257610864565b61012051602002850151610120518501555b815160010180835281141561082f575b505050505050005b63ab033ea981141561089e5760043560a01c1561088857600080fd5b600754331461089657600080fd5b600435600a55005b63238efcbc8114156108ea57600a5433146108b857600080fd5b3360075533610140527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610140a1005b63d4a22bde81141561094a5760043560a01c1561090657600080fd5b600754331461091457600080fd5b600435600855600435610140527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610140a1005b630b5b78eb8114156109aa5760043560a01c1561096657600080fd5b600754331461097457600080fd5b600435600b55600435610140527f6d674c311329fb38bbc96dc33d2aad03b9bf9fcfdd8f5e5054fda291a5b3c1f86020610140a1005b63ec38a862811415610a0a5760043560a01c156109c657600080fd5b60075433146109d457600080fd5b600435601855600435610140527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610140a1005b638402a84f811415610a43576007543314610a2457600080fd5b670de0b6b3a76400006004351115610a3b57600080fd5b600435601755005b63bdc8144b811415610a93576007543314610a5d57600080fd5b600435600f55600435610140527fae565aab888bca5e19e25a13db7b0c9144305bf55cb0f3f4d724f730e5acdd626020610140a1005b6370897b23811415610af4576007543314610aad57600080fd5b6127106004351115610abe57600080fd5b600435601a55600435610140527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610140a1005b63fe56e232811415610b55576007543314610b0e57600080fd5b6127106004351115610b1f57600080fd5b600435601955600435610140527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610140a1005b638a0dac4a811415610c065760043560a01c15610b7157600080fd5b600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610bb05760018352610bc0565b8151600101808352811415610b94575b50505061014051610bd057600080fd5b600435600955600435610140527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610140a1005b6314c64402811415610cd25760043560011c15610c2257600080fd5b60043515610c8e57600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610c695760018352610c79565b8151600101808352811415610c4d575b50505061014051610c8957600080fd5b610c9c565b6007543314610c9c57600080fd5b600435600e55600435610140527fba40372a3a724dca3c57156128ef1e896724b65b37a17f190b1ad5de68f3a4f36020610140a1005b6394148415811415610f17576000610120525b610120516004013560a01c15610cfa57600080fd5b6020610120510161012052610280610120511015610d1757610ce5565b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610d565760018352610d66565b8151600101808352811415610d3a575b50505061014051610d7657600080fd5b61014060006014818352015b60046101405160148110610d9557600080fd5b60200201351515610dc3576101405160148110610db157600080fd5b600d60c052602060c020015415610dc6565b60005b15610dd057610e53565b60006001600c60046101405160148110610de957600080fd5b602002013560e05260c052604060c02060c052602060c020015411610e0d57600080fd5b60046101405160148110610e2057600080fd5b60200201356101405160148110610e3657600080fd5b600d60c052602060c02001555b8151600101808352811415610d82575b50506004356101405260243561016052604435610180526064356101a0526084356101c05260a4356101e05260c4356102005260e43561022052610104356102405261012435610260526101443561028052610164356102a052610184356102c0526101a4356102e0526101c435610300526101e43561032052610204356103405261022435610360526102443561038052610264356103a0527f695ac3ac73f08f2002284ffe563cefe798ee2878a5e04219522e2e99eb89d168610280610140a1005b63a9059cbb811415610f695760043560a01c15610f3357600080fd5b336101405260043561016052602435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b6323b872dd81141561109e5760043560a01c15610f8557600080fd5b60243560a01c15610f9557600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460043560e05260c052604060c0203360e05260c052604060c02054101561106657600460043560e05260c052604060c0203360e05260c052604060c020546044358082101561100657600080fd5b808203905090506101405261014051600460043560e05260c052604060c0203360e05260c052604060c020556101405161016052336004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610160a35b6004356101405260243561016052604435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b63095ea7b38114156111175760043560a01c156110ba57600080fd5b60243560043360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63395093518114156111c45760043560a01c1561113357600080fd5b60043360e05260c052604060c02060043560e05260c052604060c020805460243581818301101561116357600080fd5b8082019050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63a457c2d781141561126f5760043560a01c156111e057600080fd5b60043360e05260c052604060c02060043560e05260c052604060c02080546024358082101561120e57600080fd5b8082039050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b639fd5a6cf81141561166c5760043560a01c1561128b57600080fd5b60243560a01c1561129b57600080fd5b60616084356004016101403760416084356004013511156112bb57600080fd5b6000600435186112ca57600080fd5b60643515156112da5760016112e1565b4260643510155b6112ea57600080fd5b601b60043560e05260c052604060c020546101e05260006002610520527f19010000000000000000000000000000000000000000000000000000000000006105405261052060028060208461078001018260208501600060045af1505080518201915050601c5460208261078001015260208101905060007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c96020826106800101526020810190506004356020826106800101526020810190506024356020826106800101526020810190506044356020826106800101526020810190506101e05160208261068001015260208101905060643560208261068001015260208101905080610680526106809050805160208201209050602082610780010152602081019050806107805261078090508051602082012090506102005260006020602082066103000161014051828401111561144457600080fd5b6041806103208260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561148257600080fd5b809190121561149057600080fd5b806020036101000a8204905090509050610220526020602060208206610320016101405182840111156114c257600080fd5b6041806103408260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561150057600080fd5b809190121561150e57600080fd5b806020036101000a82049050905090506102405260406001602082066103400161014051828401111561154057600080fd5b6041806103608260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561157e57600080fd5b809190121561158c57600080fd5b806020036101000a8204905090509050610260526004356102005161028052610260516102a052610220516102c052610240516102e052602060c0608061028060015afa5060c051146115de57600080fd5b604435600460043560e05260c052604060c02060243560e05260c052604060c020556101e051600181818301101561161557600080fd5b80820190509050601b60043560e05260c052604060c02055604435610280526024356004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610280a3600160005260206000f35b6301e1d1148114156116925760065801614149565b610140526101405160005260206000f35b63d0e30db08114156116cd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052611727565b63b6b55f258114156116ee5733610160526020600461014037600050611727565b636e553f6581141561172257602060046101403760243560a01c1561171257600080fd5b6020602461016037600050611727565b61197b565b601d541561173457600080fd5b6001601d55600e541561174657600080fd5b61014051610180527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61018051141561181057600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051808210156117b257600080fd5b80820390509050602061024060246370a082316101c052336101e0526101dc6006545afa6117df57600080fd5b601f3d116117ec57600080fd5b60005061024051808211156118015780611803565b815b9050905061018052611861565b600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a0516101805181818301101561184f57600080fd5b80820190509050111561186157600080fd5b6000610180511161187157600080fd5b6000600b5418156118c75760206102406044635ed7660e6101a052336101c052610180516101e0526101bc600b545afa6118aa57600080fd5b601f3d116118b757600080fd5b600050610240516118c757600080fd5b6101405161016051610180516101a051610160516101c052610180516101e0526101e0516101c051600658016141ac565b610240526101a052610180526101605261014052610240516101a0526101405161016051610180516101a0516006546101c052336101e0523061020052610180516102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000506101a0516000526000601d5560206000f35b6375de2902811415611ad95760206101e060246370a0823161016052306101805261017c6006545afa6119ad57600080fd5b601f3d116119ba57600080fd5b6000506101e051610200526101405161016051610180516101a0516101c0516101e051610200516102005161022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515611a4d57611aca565b61014080516101405161016051610180516006600c6101605160e05260c052604060c02060c052602060c02001546101a0526101a0516006580161445a565b6102005261018052610160526101405261020051818183011015611aaf57600080fd5b808201905090508152505b8151600101808352811415611a2a575b50506101405160005260206000f35b633ccfd60b811415611b1a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052600161018052611bbb565b632e1a7d4d811415611b415733610160526001610180526020600461014037600050611bbb565b62f714ce811415611b7a57600161018052602060046101403760243560a01c15611b6a57600080fd5b6020602461016037600050611bbb565b63e63697c8811415611bb657602060046101403760243560a01c15611b9e57600080fd5b60206024610160376020604461018037600050611bbb565b612214565b601d5415611bc857600080fd5b6001601d55610140516101a052612710610180511115611be757600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101a0511415611c265760033360e05260c052604060c020546101a0525b60033360e05260c052604060c020546101a0511115611c4457600080fd5b60006101a05111611c5457600080fd5b6101405161016051610180516101a0516101c0516101a0516101e0526101e051600658016142e6565b610240526101c0526101a052610180526101605261014052610240516101c05260006101e052602061028060246370a0823161020052306102205261021c6006545afa611cc957600080fd5b601f3d11611cd657600080fd5b600050610280516101c0511115612001576102c060006014818352015b6102c051600d60c052602060c02001546102a0526102a0511515611d1657611ffe565b602061038060246370a0823161030052306103205261031c6006545afa611d3c57600080fd5b601f3d11611d4957600080fd5b600050610380516102e0526102e0516101c051111515611d6857611ffe565b6101c0516102e05180821015611d7d57600080fd5b8082039050905061030052610300516006600c6102a05160e05260c052604060c02060c052602060c020015480821115611db75780611db9565b815b9050905061030052610300511515611dd057611fee565b60206103c06024632e1a7d4d61034052610300516103605261035c60006102a0515af1611dfc57600080fd5b601f3d11611e0957600080fd5b6000506103c0516103205260206103e060246370a0823161036052306103805261037c6006545afa611e3a57600080fd5b601f3d11611e4757600080fd5b6000506103e0516102e05180821015611e5f57600080fd5b80820390509050610340526000610320511115611ef4576101c080516103205180821015611e8c57600080fd5b808203905090508152506101e0805161032051818183011015611eae57600080fd5b808201905090508152506008600c6102a05160e05260c052604060c02060c052602060c02001805461032051818183011015611ee957600080fd5b808201905090508155505b6006600c6102a05160e05260c052604060c02060c052602060c0200180546103405161032051818183011015611f2957600080fd5b8082019050905080821015611f3d57600080fd5b80820390509050815550601180546103405161032051818183011015611f6257600080fd5b8082019050905080821015611f7657600080fd5b80820390509050815550610140610360525b61036051516020610360510161036052610360610360511015611faa57611f88565b6102a05161038052610380516006580161452f565b610340610360525b6103605152602061036051036103605261014061036051101515611fea57611fc7565b6000505b8151600101808352811415611cf3575b50505b60206102a060246370a0823161022052306102405261023c6006545afa61202757600080fd5b601f3d1161203457600080fd5b6000506102a05161020052610200516101c05111156120c957610200516101c0526101405161016051610180516101a0516101c0516101e051610200516101c0516101e05181818301101561208857600080fd5b8082019050905061022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101a0525b610180516101c0516101e0518181830110156120e457600080fd5b8082019050905080820282158284830414176120ff57600080fd5b80905090509050612710808204905090506101e051111561211f57600080fd5b600580546101a0518082101561213457600080fd5b8082039050905081555060033360e05260c052604060c02080546101a0518082101561215f57600080fd5b808203905090508155506101a051610220526000337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610220a36101405161016051610180516101a0516101c0516101e051610200516006546102205261016051610240526101c0516102605261026051610240516102205160065801613cf3565b610200526101e0526101c0526101a0526101805261016052610140526000506101c0516000526000601d5560206000f35b6399530b0681141561225757604e6002541061222f57600080fd5b600254600a0a6101405261014051600658016142e6565b6101a0526101a05160005260206000f35b6314b4e26e81141561249c5760043560a01c1561227357600080fd5b6013600d60c052602060c02001541561228b57600080fd5b600e541561229857600080fd5b60075433146122a657600080fd5b6000600435186122b557600080fd5b6001600c60043560e05260c052604060c02060c052602060c0200154156122db57600080fd5b60206101a0600463fbfa77cf6101405261015c6004355afa6122fc57600080fd5b601f3d1161230957600080fd5b6000506101a051301461231b57600080fd5b60206101a06004631f1fcd516101405261015c6004355afa61233c57600080fd5b601f3d1161234957600080fd5b6000506101a0516006541461235d57600080fd5b61271060105460243581818301101561237557600080fd5b80820190509050111561238757600080fd5b606435604435111561239857600080fd5b612710601a54808210156123ab57600080fd5b8082039050905060843511156123c057600080fd5b600c60043560e05260c052604060c02060c052602060c0206084358155426001820155602435600282015560443560038201556064356004820155426005820155600060068201556000600782015560006008820155506024356101405260443561016052606435610180526084356101a0526004357f5a6abd2af9fe6c0554fa08649e2d86e4393ff19dc304d072d38d295c9291d4dc6080610140a26010805460243581818301101561247357600080fd5b808201905090508155506004356013600d60c052602060c02001556006580161460c565b600050005b637c6a4f248114156125f95760043560a01c156124b857600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156124f75760018352612507565b81516001018083528114156124db575b5050506101405161251757600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161253f57600080fd5b601080546002600c60043560e05260c052604060c02060c052602060c02001548082101561256c57600080fd5b808203905090508155506024356002600c60043560e05260c052604060c02060c052602060c0200155601080546024358181830110156125ab57600080fd5b8082019050905081555061271060105411156125c657600080fd5b602435610140526004357fbda9398315c83ccef012bcaa318a2ff7b680f36429d36597bd4bc25ac11ead596020610140a2005b63e722befe8114156127185760043560a01c1561261557600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156126545760018352612664565b8151600101808352811415612638575b5050506101405161267457600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161269c57600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c020015410156126c657600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f0b728ad785976532c4aaadde09b1cba5f262a7090e83c62d2377bc405678b29c6020610140a2005b634757a1568114156128375760043560a01c1561273457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156127735760018352612783565b8151600101808352811415612757575b5050506101405161279357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116127bb57600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c020015411156127e557600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f1796a8e0760e2de5b72e7bf64fccb7666c48ceab94cb6cae7cb7eff4b6f641ab6020610140a2005b63d0194ed68114156129005760043560a01c1561285357600080fd5b600754331461286157600080fd5b612710601a548082101561287457600080fd5b80820390509050602435111561288957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116128b157600080fd5b602435600c60043560e05260c052604060c02060c052602060c02055602435610140526004357fe57488a65fa53066d4c25bac90db47dda4e5de3025ac12bf76ff07211cf7f39e6020610140a2005b636cb56d19811415612c185760043560a01c1561291c57600080fd5b60243560a01c1561292c57600080fd5b600754331461293a57600080fd5b60006024351861294957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161297157600080fd5b6001600c60243560e05260c052604060c02060c052602060c02001541561299757600080fd5b610140600c60043560e05260c052604060c0208060c052602060c02054825260018160c052602060c0200154826020015260028160c052602060c0200154826040015260038160c052602060c0200154826060015260048160c052602060c0200154826080015260058160c052602060c02001548260a0015260068160c052602060c02001548260c0015260078160c052602060c02001548260e0015260088160c052602060c020015482610100015250506101405161016051610180516101a0516101c0516101e0516102005161022051610240516004356102605261026051600658016146ef565b6102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506010805461018051818183011015612abf57600080fd5b8082019050905081555060006006600c60043560e05260c052604060c02060c052602060c0200155600c60243560e05260c052604060c02060c052602060c0206101405181556101e05160018201556101805160028201556101a05160038201556101c05160048201556101e05160058201556102005160068201556000600782015560006008820155506004353b612b5757600080fd5b60006000602463ce5494bb610260526024356102805261027c60006004355af1612b8057600080fd5b6024356004357f100b69bb6b504e1252e36b375233158edee64d071b399e2f81473a695fd1b02160006000a361026060006014818352015b6004356102605160148110612bcc57600080fd5b600d60c052602060c02001541415612c04576024356102605160148110612bf257600080fd5b600d60c052602060c020015560006000f35b8151600101808352811415612bb8575b5050005b63a0e4af9a811415612c2e573361014052612c5f565b63bb994d48811415612c5a5760043560a01c15612c4a57600080fd5b6020600461014037600050612c5f565b612ce8565b61014051610180526007546101a0526009546101c05260006101605261016061012060006003818352015b610120516020026101800151331415612ca65760018352612cb6565b8151600101808352811415612c8a575b50505061016051612cc657600080fd5b61014051610140516101605261016051600658016146ef565b61014052600050005b63f76e4caa811415612e645760043560a01c15612d0457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612d435760018352612d53565b8151600101808352811415612d27575b50505061014051612d6357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c020015411612d8b57600080fd5b60006101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515612dc057612e02565b6004356101605118612dd157600080fd5b61014080516001818183011015612de757600080fd5b808201905090508152505b8151600101808352811415612d9d575b505060146101405110612e1457600080fd5b6004356013600d60c052602060c0200155610140516006580161460c565b610140526000506004357fa8727d412c6fa1e2497d6d6f275e2d9fe4d9318d5b793632e60ad9d38ee8f1fa60006000a2005b63b22439f5811415612f8b5760043560a01c15612e8057600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612ebf5760018352612ecf565b8151600101808352811415612ea3575b50505061014051612edf57600080fd5b61014060006014818352015b6004356101405160148110612eff57600080fd5b600d60c052602060c02001541415612f735760006101405160148110612f2457600080fd5b600d60c052602060c0200155610140516006580161460c565b610140526000506004357f8e1ec3c16d6a67ea8effe2ac7adef9c2de0bc0dc47c49cdf18f6a8b0048085be60006000a260006000f35b8151600101808352811415612eeb575b505060006000fd5b63bf3759b5811415612fa1573361014052612fd2565b63bdcf36bb811415612fcd5760043560a01c15612fbd57600080fd5b6020600461014037600050612fd2565b613000565b610140516101405161016052610160516006580161477e565b6101c052610140526101c05160005260206000f35b63112c1f9b811415613016573361014052613047565b63d76480138114156130425760043560a01c1561303257600080fd5b6020600461014037600050613047565b613075565b610140516101405161016052610160516006580161487d565b6101c052610140526101c05160005260206000f35b63153c27c48114156130e55760065801614149565b6101405261014051600f5411156130d857600f546101405160065801614149565b610160526101405261016051808210156130c457600080fd5b8082039050905060005260206000f36130e3565b600060005260206000f35b005b63d3406abd8114156130fb57336101405261312c565b6333586b678114156131275760043560a01c1561311757600080fd5b602060046101403760005061312c565b61315a565b6101405161014051610160526101605160065801614ad8565b6101c052610140526101c05160005260206000f35b63a1d9bafc81141561367f5760006001600c3360e05260c052604060c02060c052602060c02001541161318c57600080fd5b6004356044358181830110156131a157600080fd5b8082019050905060206101c060246370a0823161014052336101605261015c6006545afa6131ce57600080fd5b601f3d116131db57600080fd5b6000506101c05110156131ed57600080fd5b6000602435111561321957336101405260243561016052610160516101405160065801614c2b565b6000505b336101405260043561016052610160516101405160065801614e0d565b6000506007600c3360e05260c052604060c02060c052602060c02001805460043581818301101561326657600080fd5b80820190509050815550610140513361016052610160516006580161477e565b6101c052610140526101c0516101405260443561014051808211156132ab57806132ad565b815b90509050610160526000610160511115613337576006600c3360e05260c052604060c02060c052602060c02001805461016051808210156132ed57600080fd5b8082039050905081555060118054610160518082101561330c57600080fd5b808203905090508155506101408051610160518082101561332c57600080fd5b808203905090508152505b610140516101605161018051336101a0526101a0516006580161487d565b61020052610180526101605261014052610200516101805260006101805111156133d3576006600c3360e05260c052604060c02060c052602060c020018054610180518181830110156133a757600080fd5b8082019050905081555060118054610180518181830110156133c857600080fd5b808201905090508155505b600435610160518181830110156133e957600080fd5b808201905090506101a052610180516101a051101561346b576101405161016051610180516101a0516006546101c052336101e052610180516101a0518082101561343357600080fd5b8082039050905061020052610200516101e0516101c05160065801613cf3565b6101a0526101805261016052610140526000506134e7565b610180516101a05111156134e7576101405161016051610180516101a0516006546101c052336101e05230610200526101a05161018051808210156134af57600080fd5b808203905090506102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000505b6101405161016051610180516101a051336101c0526101c0516006580161452f565b6101a052610180526101605261014052600050426005600c3360e05260c052604060c02060c052602060c0200155426014556004356016556004356101c0526024356101e05261016051610200526007600c3360e05260c052604060c02060c052602060c0200154610220526008600c3360e05260c052604060c02060c052602060c0200154610240526006600c3360e05260c052604060c02060c052602060c02001546102605261018051610280526002600c3360e05260c052604060c02060c052602060c02001546102a052337f67f96d2854a335a4cadb49f84fd3ca6f990744ddb3feceeb4b349d2d53d32ad36101006101c0a26002600c3360e05260c052604060c02060c052602060c0200154151561362757600161362b565b600e545b15613670576020610220600463efbb5cb06101c0526101dc335afa61364f57600080fd5b601f3d1161365c57600080fd5b6000506102205160005260206000f361367d565b6101405160005260206000f35b005b6301681a628114156136b5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140526136d6565b636ea056a98114156136d15760206024610140376000506136d6565b6137b6565b60043560a01c156136e657600080fd5b60075433146136f457600080fd5b6006546004351861370457600080fd5b61014051610160527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61016051141561377657602061020060246370a0823161018052306101a05261019c6004355afa61375d57600080fd5b601f3d1161376a57600080fd5b60005061020051610160525b6101405161016051600435610180526007546101a052610160516101c0526101c0516101a0516101805160065801613cf3565b6101605261014052600050005b6306fdde0381141561385b5760008060c052602060c020610180602082540161012060006003818352015b826101205160200211156137f457613816565b61012051850154610120516020028501525b81516001018083528114156137e1575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b6395d89b418114156139005760018060c052602060c020610180602082540161012060006002818352015b82610120516020021115613899576138bb565b61012051850154610120516020028501525b8151600101808352811415613886575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63313ce5678114156139185760025460005260206000f35b6370a0823181141561394e5760043560a01c1561393457600080fd5b600360043560e05260c052604060c0205460005260206000f35b63dd62ed3e8114156139a25760043560a01c1561396a57600080fd5b60243560a01c1561397a57600080fd5b600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b6318160ddd8114156139ba5760055460005260206000f35b63fc0c546a8114156139d25760065460005260206000f35b635aa6e6758114156139ea5760075460005260206000f35b6388a8d602811415613a025760085460005260206000f35b63452a9320811415613a1a5760095460005260206000f35b6346d55875811415613a3257600b5460005260206000f35b6339ebf823811415613b4c5760043560a01c15613a4e57600080fd5b600c60043560e05260c052604060c0206101408080808460c052602060c0205481525050602081019050808060018560c052602060c020015481525050602081019050808060028560c052602060c020015481525050602081019050808060038560c052602060c020015481525050602081019050808060048560c052602060c020015481525050602081019050808060058560c052602060c020015481525050602081019050808060068560c052602060c020015481525050602081019050808060078560c052602060c020015481525050602081019050808060088560c052602060c0200154815250506101209050905060c05260c051610140f35b63c822adda811415613b7d5760043560148110613b6857600080fd5b600d60c052602060c020015460005260206000f35b633403c2fc811415613b9557600e5460005260206000f35b63ecf70858811415613bad57600f5460005260206000f35b63cea55f57811415613bc55760105460005260206000f35b63fc7b9c18811415613bdd5760115460005260206000f35b638e6350e2811415613bf55760125460005260206000f35b63c3535b52811415613c0d5760145460005260206000f35b633629c8de811415613c255760155460005260206000f35b6344b81396811415613c3d5760165460005260206000f35b632140254d811415613c555760175460005260206000f35b639ec5a894811415613c6d5760185460005260206000f35b63a6f7f5d6811415613c855760195460005260206000f35b6387788782811415613c9d57601a5460005260206000f35b637ecebe00811415613cd35760043560a01c15613cb957600080fd5b601b60043560e05260c052604060c0205460005260206000f35b633644e515811415613ceb57601c5460005260206000f35b505b60006000fd5b6101a05261014052610160526101805260006004610220527fa9059cbb000000000000000000000000000000000000000000000000000000006102405261022060048060208461028001018260208501600060045af15050805182019150506101605160208261028001015260208101905061018051602082610280010152602081019050806102805261028090508051602001806103208284600060045af1613d9c57600080fd5b505060206103e0610320516103406000610140515af1613dbb57600080fd5b60203d80821115613dcc5780613dce565b815b905090506103c0526103c08051602001806101c08284600060045af1613df357600080fd5b505060006101c0511115613e87576101c0806020015160008251806020901315613e1c57600080fd5b8091901215613e2a57600080fd5b806020036101000a820490509050905015151515613e87576308c379a0610220526020610240526010610260527f5472616e73666572206661696c656421000000000000000000000000000000006102805261026050606461023cfd5b6101a051565b6101c0526101405261016052610180526101a05260006004610240527f23b872dd00000000000000000000000000000000000000000000000000000000610260526102406004806020846102a001018260208501600060045af1505080518201915050610160516020826102a0010152602081019050610180516020826102a00101526020810190506101a0516020826102a0010152602081019050806102a0526102a090508051602001806103608284600060045af1613f4d57600080fd5b50506020610440610360516103806000610140515af1613f6c57600080fd5b60203d80821115613f7d5780613f7f565b815b90509050610420526104208051602001806101e08284600060045af1613fa457600080fd5b505060006101e0511115614038576101e0806020015160008251806020901315613fcd57600080fd5b8091901215613fdb57600080fd5b806020036101000a820490509050905015151515614038576308c379a0610240526020610260526010610280527f5472616e73666572206661696c656421000000000000000000000000000000006102a05261028050606461025cfd5b6101c051565b6101a052610140526101605261018052306101e05260006102005260006101c0526101c061012060006002818352015b610120516020026101e0015161016051141561408d576001835261409d565b815160010180835281141561406e575b5050506101c051156140ae57600080fd5b60036101405160e05260c052604060c020805461018051808210156140d257600080fd5b8082039050905081555060036101605160e05260c052604060c02080546101805181818301101561410257600080fd5b80820190509050815550610180516101c05261016051610140517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101c0a36101a051565b6101405260206101e060246370a0823161016052306101805261017c6006545afa61417357600080fd5b601f3d1161418057600080fd5b6000506101e05160115481818301101561419957600080fd5b8082019050905060005260005161014051565b61018052610140526101605260006101a0526005546101c05260006101c051111561424757610160516101c05180820282158284830414176141ed57600080fd5b809050905090506101405161016051610180516101a0516101c05160065801614149565b6101e0526101c0526101a0526101805261016052610140526101e051808061423857600080fd5b8204905090506101a052614250565b610160516101a0525b6101c0516101a05181818301101561426757600080fd5b8082019050905060055560036101405160e05260c052604060c02080546101a05181818301101561429757600080fd5b808201905090508155506101a0516101e0526101405160007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101e0a36101a05160005260005161018051565b61016052610140526005541515614307576101405160005260005161016051565b426014548082101561431857600080fd5b80820390509050601754808202821582848304141761433657600080fd5b80905090509050610180526101405161016051610180516101a05160065801614149565b6101c0526101a0526101805261016052610140526101c0516101a052670de0b6b3a76400006101805110156143ef576101a080516016546101805160165480820282158284830414176143ac57600080fd5b80905090509050670de0b6b3a764000080820490509050808210156143d057600080fd5b80820390509050808210156143e457600080fd5b808203905090508152505b6103e8610140516101a051808202821582848304141761440e57600080fd5b80905090509050808202821582848304141761442957600080fd5b80905090509050600554808061443e57600080fd5b8204905090506103e88082049050905060005260005161016051565b61016052610140526000610140516101605160065801614149565b61018052610160526101405261018051111561451f576103e86101405160055480820282158284830414176144a957600080fd5b8090509050905080820282158284830414176144c457600080fd5b8090509050905061014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180806144ff57600080fd5b8204905090506103e880820490509050600052600051610160515661452d565b600060005260005161016051565b005b61016052610140526012805460136101405160e05260c052604060c020548082101561455a57600080fd5b808203905090508155506006600c6101405160e05260c052604060c02060c052602060c020015460206102006004638e6350e26101a0526101bc610140515afa6145a357600080fd5b601f3d116145b057600080fd5b60005061020051808211156145c557806145c7565b815b905090506101805260128054610180518181830110156145e657600080fd5b808201905090508155506101805160136101405160e05260c052604060c0205561016051565b6101405260006101605261018060006014818352015b610180516014811061463357600080fd5b600d60c052602060c02001546101a0526101a0511515614672576101608051600181818301101561466357600080fd5b808201905090508152506146d7565b60006101605111156146d7576101a05161018051610160518082101561469757600080fd5b80820390509050601481106146ab57600080fd5b600d60c052602060c0200155600061018051601481106146ca57600080fd5b600d60c052602060c02001555b8151600101808352811415614622575b505061014051565b6101605261014052601080546002600c6101405160e05260c052604060c02060c052602060c02001548082101561472557600080fd5b8082039050905081555060006002600c6101405160e05260c052604060c02060c052602060c0200155610140517f4201c688d84c01154d321afa0c72f1bffe9eef53005c9de9d035074e71e9b32a60006000a261016051565b61016052610140526002600c6101405160e05260c052604060c02060c052602060c020015461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180820282158284830414176147e057600080fd5b8090509050905061271080820490509050610180526006600c6101405160e05260c052604060c02060c052602060c02001546101a052600e5415614832576101a051600052600051610160515661487b565b610180516101a051111515614853576000600052600051610160515661487b565b6101a051610180518082101561486857600080fd5b8082039050905060005260005161016051565b005b6101605261014052600e541561489b57600060005260005161016051565b61014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051610180526010546101805180820282158284830414176148e357600080fd5b80905090509050612710808204905090506101a0526011546101c0526002600c6101405160e05260c052604060c02060c052602060c020015461018051808202821582848304141761493457600080fd5b80905090509050612710808204905090506101e0526006600c6101405160e05260c052604060c02060c052602060c0200154610200526003600c6101405160e05260c052604060c02060c052602060c0200154610220526004600c6101405160e05260c052604060c02060c052602060c020015461024052610200516101e0511115156149c25760016149cd565b6101c0516101a05111155b156149e057600060005260005161016051565b6101e05161020051808210156149f557600080fd5b8082039050905061026052610260516101a0516101c05180821015614a1957600080fd5b8082039050905080821115614a2e5780614a30565b815b905090506102605261026051602061030060246370a0823161028052306102a05261029c6006545afa614a6257600080fd5b601f3d11614a6f57600080fd5b6000506103005180821115614a845780614a86565b815b905090506102605261022051610260511015614aae5760006000526000516101605156614ad6565b610260516102405180821115614ac45780614ac6565b815b9050905060005260005161016051565b005b61016052610140526005600c6101405160e05260c052604060c02060c052602060c020015461018052426101805180821015614b1357600080fd5b808203905090506101a052610180516001600c6101405160e05260c052604060c02060c052602060c020015480821015614b4c57600080fd5b808203905090506101c05260006101a0511115614bb25760006101c0511115614baa5760206102c060046322f3e2d46102605261027c610140515afa614b9157600080fd5b601f3d11614b9e57600080fd5b6000506102c051614bad565b60005b614bb5565b60005b15614c1b576007600c6101405160e05260c052604060c02060c052602060c02001546101a0518082028215828483041417614bef57600080fd5b809050905090506101c0518080614c0557600080fd5b8204905090506000526000516101605156614c29565b600060005260005161016051565b005b6101805261014052610160526006600c6101405160e05260c052604060c02060c052602060c02001546101a052610160516101a0511015614c6b57600080fd5b6008600c6101405160e05260c052604060c02060c052602060c02001805461016051818183011015614c9c57600080fd5b808201905090508155506101a0516101605180821015614cbb57600080fd5b808203905090506006600c6101405160e05260c052604060c02060c052602060c0200155601180546101605180821015614cf457600080fd5b808203905090508155506002600c6101405160e05260c052604060c02060c052602060c02001546101c052610160516127108082028215828483041417614d3a57600080fd5b809050905090506101405161016051610180516101a0516101c0516101e05160065801614149565b610200526101e0526101c0526101a052610180526101605261014052610200518080614d8d57600080fd5b8204905090506101c05180821115614da55780614da7565b815b905090506101e0526002600c6101405160e05260c052604060c02060c052602060c0200180546101e05180821015614dde57600080fd5b80820390509050815550601080546101e05180821015614dfd57600080fd5b8082039050905081555061018051565b61018052610140526101605260115460125480821015614e2c57600080fd5b808203905090504260145480821015614e4457600080fd5b808203905090508082028215828483041417614e5f57600080fd5b809050905090506019548082028215828483041417614e7d57600080fd5b80905090509050612710808204905090506301e18558808204905090506101a05260006101c0526000610160511115614f425761016051600c6101405160e05260c052604060c02060c052602060c020548082028215828483041417614ee257600080fd5b80905090509050612710808204905090506101c0526101a0805161016051601a548082028215828483041417614f1757600080fd5b8090509050905061271080820490509050818183011015614f3757600080fd5b808201905090508152505b6101a0516101c051818183011015614f5957600080fd5b808201905090506101e05260006101e051111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526101e051610240526102405161022051600658016141ac565b6102a052610200526101e0526101c0526101a0526101805261016052610140526102a0516102005260006101c0511115615088576101c051610200518082028215828483041417614ffa57600080fd5b809050905090506101e051808061501057600080fd5b820490509050610220526101405161016051610180516101a0516101c0516101e05161020051610220513061024052610140516102605261022051610280526102805161026051610240516006580161403e565b61022052610200526101e0526101c0526101a0526101805261016052610140526000505b600060033060e05260c052604060c02054111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526018546102405260033060e05260c052604060c02054610260526102605161024051610220516006580161403e565b610200526101e0526101c0526101a0526101805261016052610140526000505b61018051565b61000461511903610004600039610004615119036000f3", + "deployedBytecode": "0x600436101561000d57613ced565b600035601c52600051341561002157600080fd5b6383b43589811415610037573361022052610068565b63a5b81fdf8114156100635760a43560a01c1561005357600080fd5b602060a461022037600050610068565b6106ab565b60043560a01c1561007857600080fd5b60243560a01c1561008857600080fd5b60443560a01c1561009857600080fd5b60606064356004016101403760406064356004013511156100b857600080fd5b60406084356004016101c03760206084356004013511156100d857600080fd5b601554156100e557600080fd5b600435600655600061028052610280805160208201209050610140805160208201209050141561022c576000606061032060046395d89b416102c0526102dc6004355afa61013257600080fd5b603f3d1161013f57600080fd5b60156103206103205101511061015457600080fd5b6000506103406014806020846103e001018260208501600060045af15050805182019150506007610380527f20795661756c74000000000000000000000000000000000000000000000000006103a0526103806007806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600060c052602060c020602082510161012060006002818352015b826101205160200211156101ff57610221565b61012051602002850151610120518501555b81516001018083528114156101ec575b505050505050610287565b61014080600060c052602060c020602082510161012060006003818352015b8261012051602002111561025e57610280565b61012051602002850151610120518501555b815160010180835281141561024b575b5050505050505b6000610280526102808051602082012090506101c080516020820120905014156103c857600060026102c0527f79760000000000000000000000000000000000000000000000000000000000006102e0526102c06002806020846103e001018260208501600060045af1505080518201915050606061038060046395d89b416103205261033c6004355afa61031b57600080fd5b603f3d1161032857600080fd5b60156103806103805101511061033d57600080fd5b6000506103a06014806020846103e001018260208501600060045af1505080518201915050806103e0526103e0905080600160c052602060c020602082510161012060006002818352015b8261012051602002111561039b576103bd565b61012051602002850151610120518501555b8151600101808352811415610388575b505050505050610423565b6101c080600160c052602060c020602082510161012060006002818352015b826101205160200211156103fa5761041c565b61012051602002850151610120518501555b81516001018083528114156103e7575b5050505050505b60206102a0600463313ce5676102405261025c6004355afa61044457600080fd5b601f3d1161045157600080fd5b6000506102a051600255602435600755602435610240527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610240a1602435600855602435610240527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610240a1604435601855604435610240527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610240a16102205160095561022051610240527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610240a16103e8601a556103e86102405261024051610260527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610260a160c860195560c86102405261024051610260527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610260a142601455426015556529d635a8e00060175560007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f602082610620010152602081019050600b610500527f596561726e205661756c740000000000000000000000000000000000000000006105205261050080516020820120905060208261062001015260208101905060056105c0527f302e332e340000000000000000000000000000000000000000000000000000006105e0526105c0805160208201209050602082610620010152602081019050466020826106200101526020810190503060208261062001015260208101905080610620526106209050805160208201209050601c55005b6325829410811415610740576005610140527f302e332e34000000000000000000000000000000000000000000000000000000610160526101408051602001806101e08284600060045af16106ff57600080fd5b50506101e0518061020001818260206001820306601f820103905003368237505060206101c05260406101e0510160206001820306601f82010390506101c0f35b63c47f00278114156107d657604a60043560040161014037602a60043560040135111561076c57600080fd5b600754331461077a57600080fd5b61014080600060c052602060c020602082510161012060006003818352015b826101205160200211156107ac576107ce565b61012051602002850151610120518501555b8151600101808352811415610799575b505050505050005b63b84c824681141561086c57603460043560040161014037601460043560040135111561080257600080fd5b600754331461081057600080fd5b61014080600160c052602060c020602082510161012060006002818352015b8261012051602002111561084257610864565b61012051602002850151610120518501555b815160010180835281141561082f575b505050505050005b63ab033ea981141561089e5760043560a01c1561088857600080fd5b600754331461089657600080fd5b600435600a55005b63238efcbc8114156108ea57600a5433146108b857600080fd5b3360075533610140527f8d55d160c0009eb3d739442df0a3ca089ed64378bfac017e7ddad463f9815b876020610140a1005b63d4a22bde81141561094a5760043560a01c1561090657600080fd5b600754331461091457600080fd5b600435600855600435610140527fff54978127edd34aec0f9061fb3b155fbe0ededdfa881ee3e0d541d3a1eef4386020610140a1005b630b5b78eb8114156109aa5760043560a01c1561096657600080fd5b600754331461097457600080fd5b600435600b55600435610140527f6d674c311329fb38bbc96dc33d2aad03b9bf9fcfdd8f5e5054fda291a5b3c1f86020610140a1005b63ec38a862811415610a0a5760043560a01c156109c657600080fd5b60075433146109d457600080fd5b600435601855600435610140527fdf3c41a916aecbf42361a147f8348c242662c3ce20ecef30e826b80642477a3d6020610140a1005b638402a84f811415610a43576007543314610a2457600080fd5b670de0b6b3a76400006004351115610a3b57600080fd5b600435601755005b63bdc8144b811415610a93576007543314610a5d57600080fd5b600435600f55600435610140527fae565aab888bca5e19e25a13db7b0c9144305bf55cb0f3f4d724f730e5acdd626020610140a1005b6370897b23811415610af4576007543314610aad57600080fd5b6127106004351115610abe57600080fd5b600435601a55600435610140527f0810a1c261ca2c0cd86a0152c51c43ba9dc329639d2349f98140891b2ea798eb6020610140a1005b63fe56e232811415610b55576007543314610b0e57600080fd5b6127106004351115610b1f57600080fd5b600435601955600435610140527f7a7883b0074f96e2c7fab65eb25abf624c488761a5db889e3bb84855dcc6daaf6020610140a1005b638a0dac4a811415610c065760043560a01c15610b7157600080fd5b600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610bb05760018352610bc0565b8151600101808352811415610b94575b50505061014051610bd057600080fd5b600435600955600435610140527f837b9ad138a0a1839a9637afce5306a5c13e23eb63365686843a5319a243609c6020610140a1005b6314c64402811415610cd25760043560011c15610c2257600080fd5b60043515610c8e57600954610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610c695760018352610c79565b8151600101808352811415610c4d575b50505061014051610c8957600080fd5b610c9c565b6007543314610c9c57600080fd5b600435600e55600435610140527fba40372a3a724dca3c57156128ef1e896724b65b37a17f190b1ad5de68f3a4f36020610140a1005b6394148415811415610f17576000610120525b610120516004013560a01c15610cfa57600080fd5b6020610120510161012052610280610120511015610d1757610ce5565b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415610d565760018352610d66565b8151600101808352811415610d3a575b50505061014051610d7657600080fd5b61014060006014818352015b60046101405160148110610d9557600080fd5b60200201351515610dc3576101405160148110610db157600080fd5b600d60c052602060c020015415610dc6565b60005b15610dd057610e53565b60006001600c60046101405160148110610de957600080fd5b602002013560e05260c052604060c02060c052602060c020015411610e0d57600080fd5b60046101405160148110610e2057600080fd5b60200201356101405160148110610e3657600080fd5b600d60c052602060c02001555b8151600101808352811415610d82575b50506004356101405260243561016052604435610180526064356101a0526084356101c05260a4356101e05260c4356102005260e43561022052610104356102405261012435610260526101443561028052610164356102a052610184356102c0526101a4356102e0526101c435610300526101e43561032052610204356103405261022435610360526102443561038052610264356103a0527f695ac3ac73f08f2002284ffe563cefe798ee2878a5e04219522e2e99eb89d168610280610140a1005b63a9059cbb811415610f695760043560a01c15610f3357600080fd5b336101405260043561016052602435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b6323b872dd81141561109e5760043560a01c15610f8557600080fd5b60243560a01c15610f9557600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460043560e05260c052604060c0203360e05260c052604060c02054101561106657600460043560e05260c052604060c0203360e05260c052604060c020546044358082101561100657600080fd5b808203905090506101405261014051600460043560e05260c052604060c0203360e05260c052604060c020556101405161016052336004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610160a35b6004356101405260243561016052604435610180526101805161016051610140516006580161403e565b600050600160005260206000f35b63095ea7b38114156111175760043560a01c156110ba57600080fd5b60243560043360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63395093518114156111c45760043560a01c1561113357600080fd5b60043360e05260c052604060c02060043560e05260c052604060c020805460243581818301101561116357600080fd5b8082019050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b63a457c2d781141561126f5760043560a01c156111e057600080fd5b60043360e05260c052604060c02060043560e05260c052604060c02080546024358082101561120e57600080fd5b8082039050905081555060043360e05260c052604060c02060043560e05260c052604060c0205461014052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610140a3600160005260206000f35b639fd5a6cf81141561166c5760043560a01c1561128b57600080fd5b60243560a01c1561129b57600080fd5b60616084356004016101403760416084356004013511156112bb57600080fd5b6000600435186112ca57600080fd5b60643515156112da5760016112e1565b4260643510155b6112ea57600080fd5b601b60043560e05260c052604060c020546101e05260006002610520527f19010000000000000000000000000000000000000000000000000000000000006105405261052060028060208461078001018260208501600060045af1505080518201915050601c5460208261078001015260208101905060007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c96020826106800101526020810190506004356020826106800101526020810190506024356020826106800101526020810190506044356020826106800101526020810190506101e05160208261068001015260208101905060643560208261068001015260208101905080610680526106809050805160208201209050602082610780010152602081019050806107805261078090508051602082012090506102005260006020602082066103000161014051828401111561144457600080fd5b6041806103208260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561148257600080fd5b809190121561149057600080fd5b806020036101000a8204905090509050610220526020602060208206610320016101405182840111156114c257600080fd5b6041806103408260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561150057600080fd5b809190121561150e57600080fd5b806020036101000a82049050905090506102405260406001602082066103400161014051828401111561154057600080fd5b6041806103608260206020880688030161014001600060045af150508181528090509050905080602001516000825180602090131561157e57600080fd5b809190121561158c57600080fd5b806020036101000a8204905090509050610260526004356102005161028052610260516102a052610220516102c052610240516102e052602060c0608061028060015afa5060c051146115de57600080fd5b604435600460043560e05260c052604060c02060243560e05260c052604060c020556101e051600181818301101561161557600080fd5b80820190509050601b60043560e05260c052604060c02055604435610280526024356004357f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610280a3600160005260206000f35b6301e1d1148114156116925760065801614149565b610140526101405160005260206000f35b63d0e30db08114156116cd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052611727565b63b6b55f258114156116ee5733610160526020600461014037600050611727565b636e553f6581141561172257602060046101403760243560a01c1561171257600080fd5b6020602461016037600050611727565b61197b565b601d541561173457600080fd5b6001601d55600e541561174657600080fd5b61014051610180527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61018051141561181057600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051808210156117b257600080fd5b80820390509050602061024060246370a082316101c052336101e0526101dc6006545afa6117df57600080fd5b601f3d116117ec57600080fd5b60005061024051808211156118015780611803565b815b9050905061018052611861565b600f5461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a0516101805181818301101561184f57600080fd5b80820190509050111561186157600080fd5b6000610180511161187157600080fd5b6000600b5418156118c75760206102406044635ed7660e6101a052336101c052610180516101e0526101bc600b545afa6118aa57600080fd5b601f3d116118b757600080fd5b600050610240516118c757600080fd5b6101405161016051610180516101a051610160516101c052610180516101e0526101e0516101c051600658016141ac565b610240526101a052610180526101605261014052610240516101a0526101405161016051610180516101a0516006546101c052336101e0523061020052610180516102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000506101a0516000526000601d5560206000f35b6375de2902811415611ad95760206101e060246370a0823161016052306101805261017c6006545afa6119ad57600080fd5b601f3d116119ba57600080fd5b6000506101e051610200526101405161016051610180516101a0516101c0516101e051610200516102005161022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515611a4d57611aca565b61014080516101405161016051610180516006600c6101605160e05260c052604060c02060c052602060c02001546101a0526101a0516006580161445a565b6102005261018052610160526101405261020051818183011015611aaf57600080fd5b808201905090508152505b8151600101808352811415611a2a575b50506101405160005260206000f35b633ccfd60b811415611b1a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140523361016052600161018052611bbb565b632e1a7d4d811415611b415733610160526001610180526020600461014037600050611bbb565b62f714ce811415611b7a57600161018052602060046101403760243560a01c15611b6a57600080fd5b6020602461016037600050611bbb565b63e63697c8811415611bb657602060046101403760243560a01c15611b9e57600080fd5b60206024610160376020604461018037600050611bbb565b612214565b601d5415611bc857600080fd5b6001601d55610140516101a052612710610180511115611be757600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101a0511415611c265760033360e05260c052604060c020546101a0525b60033360e05260c052604060c020546101a0511115611c4457600080fd5b60006101a05111611c5457600080fd5b6101405161016051610180516101a0516101c0516101a0516101e0526101e051600658016142e6565b610240526101c0526101a052610180526101605261014052610240516101c05260006101e052602061028060246370a0823161020052306102205261021c6006545afa611cc957600080fd5b601f3d11611cd657600080fd5b600050610280516101c0511115612001576102c060006014818352015b6102c051600d60c052602060c02001546102a0526102a0511515611d1657611ffe565b602061038060246370a0823161030052306103205261031c6006545afa611d3c57600080fd5b601f3d11611d4957600080fd5b600050610380516102e0526102e0516101c051111515611d6857611ffe565b6101c0516102e05180821015611d7d57600080fd5b8082039050905061030052610300516006600c6102a05160e05260c052604060c02060c052602060c020015480821115611db75780611db9565b815b9050905061030052610300511515611dd057611fee565b60206103c06024632e1a7d4d61034052610300516103605261035c60006102a0515af1611dfc57600080fd5b601f3d11611e0957600080fd5b6000506103c0516103205260206103e060246370a0823161036052306103805261037c6006545afa611e3a57600080fd5b601f3d11611e4757600080fd5b6000506103e0516102e05180821015611e5f57600080fd5b80820390509050610340526000610320511115611ef4576101c080516103205180821015611e8c57600080fd5b808203905090508152506101e0805161032051818183011015611eae57600080fd5b808201905090508152506008600c6102a05160e05260c052604060c02060c052602060c02001805461032051818183011015611ee957600080fd5b808201905090508155505b6006600c6102a05160e05260c052604060c02060c052602060c0200180546103405161032051818183011015611f2957600080fd5b8082019050905080821015611f3d57600080fd5b80820390509050815550601180546103405161032051818183011015611f6257600080fd5b8082019050905080821015611f7657600080fd5b80820390509050815550610140610360525b61036051516020610360510161036052610360610360511015611faa57611f88565b6102a05161038052610380516006580161452f565b610340610360525b6103605152602061036051036103605261014061036051101515611fea57611fc7565b6000505b8151600101808352811415611cf3575b50505b60206102a060246370a0823161022052306102405261023c6006545afa61202757600080fd5b601f3d1161203457600080fd5b6000506102a05161020052610200516101c05111156120c957610200516101c0526101405161016051610180516101a0516101c0516101e051610200516101c0516101e05181818301101561208857600080fd5b8082019050905061022052610220516006580161445a565b61028052610200526101e0526101c0526101a052610180526101605261014052610280516101a0525b610180516101c0516101e0518181830110156120e457600080fd5b8082019050905080820282158284830414176120ff57600080fd5b80905090509050612710808204905090506101e051111561211f57600080fd5b600580546101a0518082101561213457600080fd5b8082039050905081555060033360e05260c052604060c02080546101a0518082101561215f57600080fd5b808203905090508155506101a051610220526000337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610220a36101405161016051610180516101a0516101c0516101e051610200516006546102205261016051610240526101c0516102605261026051610240516102205160065801613cf3565b610200526101e0526101c0526101a0526101805261016052610140526000506101c0516000526000601d5560206000f35b6399530b0681141561225757604e6002541061222f57600080fd5b600254600a0a6101405261014051600658016142e6565b6101a0526101a05160005260206000f35b6314b4e26e81141561249c5760043560a01c1561227357600080fd5b6013600d60c052602060c02001541561228b57600080fd5b600e541561229857600080fd5b60075433146122a657600080fd5b6000600435186122b557600080fd5b6001600c60043560e05260c052604060c02060c052602060c0200154156122db57600080fd5b60206101a0600463fbfa77cf6101405261015c6004355afa6122fc57600080fd5b601f3d1161230957600080fd5b6000506101a051301461231b57600080fd5b60206101a06004631f1fcd516101405261015c6004355afa61233c57600080fd5b601f3d1161234957600080fd5b6000506101a0516006541461235d57600080fd5b61271060105460243581818301101561237557600080fd5b80820190509050111561238757600080fd5b606435604435111561239857600080fd5b612710601a54808210156123ab57600080fd5b8082039050905060843511156123c057600080fd5b600c60043560e05260c052604060c02060c052602060c0206084358155426001820155602435600282015560443560038201556064356004820155426005820155600060068201556000600782015560006008820155506024356101405260443561016052606435610180526084356101a0526004357f5a6abd2af9fe6c0554fa08649e2d86e4393ff19dc304d072d38d295c9291d4dc6080610140a26010805460243581818301101561247357600080fd5b808201905090508155506004356013600d60c052602060c02001556006580161460c565b600050005b637c6a4f248114156125f95760043560a01c156124b857600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156124f75760018352612507565b81516001018083528114156124db575b5050506101405161251757600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161253f57600080fd5b601080546002600c60043560e05260c052604060c02060c052602060c02001548082101561256c57600080fd5b808203905090508155506024356002600c60043560e05260c052604060c02060c052602060c0200155601080546024358181830110156125ab57600080fd5b8082019050905081555061271060105411156125c657600080fd5b602435610140526004357fbda9398315c83ccef012bcaa318a2ff7b680f36429d36597bd4bc25ac11ead596020610140a2005b63e722befe8114156127185760043560a01c1561261557600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156126545760018352612664565b8151600101808352811415612638575b5050506101405161267457600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161269c57600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c020015410156126c657600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f0b728ad785976532c4aaadde09b1cba5f262a7090e83c62d2377bc405678b29c6020610140a2005b634757a1568114156128375760043560a01c1561273457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b6101205160200261016001513314156127735760018352612783565b8151600101808352811415612757575b5050506101405161279357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116127bb57600080fd5b6024356003600c60043560e05260c052604060c02060c052602060c020015411156127e557600080fd5b6024356004600c60043560e05260c052604060c02060c052602060c0200155602435610140526004357f1796a8e0760e2de5b72e7bf64fccb7666c48ceab94cb6cae7cb7eff4b6f641ab6020610140a2005b63d0194ed68114156129005760043560a01c1561285357600080fd5b600754331461286157600080fd5b612710601a548082101561287457600080fd5b80820390509050602435111561288957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c0200154116128b157600080fd5b602435600c60043560e05260c052604060c02060c052602060c02055602435610140526004357fe57488a65fa53066d4c25bac90db47dda4e5de3025ac12bf76ff07211cf7f39e6020610140a2005b636cb56d19811415612c185760043560a01c1561291c57600080fd5b60243560a01c1561292c57600080fd5b600754331461293a57600080fd5b60006024351861294957600080fd5b60006001600c60043560e05260c052604060c02060c052602060c02001541161297157600080fd5b6001600c60243560e05260c052604060c02060c052602060c02001541561299757600080fd5b610140600c60043560e05260c052604060c0208060c052602060c02054825260018160c052602060c0200154826020015260028160c052602060c0200154826040015260038160c052602060c0200154826060015260048160c052602060c0200154826080015260058160c052602060c02001548260a0015260068160c052602060c02001548260c0015260078160c052602060c02001548260e0015260088160c052602060c020015482610100015250506101405161016051610180516101a0516101c0516101e0516102005161022051610240516004356102605261026051600658016146ef565b6102405261022052610200526101e0526101c0526101a0526101805261016052610140526000506010805461018051818183011015612abf57600080fd5b8082019050905081555060006006600c60043560e05260c052604060c02060c052602060c0200155600c60243560e05260c052604060c02060c052602060c0206101405181556101e05160018201556101805160028201556101a05160038201556101c05160048201556101e05160058201556102005160068201556000600782015560006008820155506004353b612b5757600080fd5b60006000602463ce5494bb610260526024356102805261027c60006004355af1612b8057600080fd5b6024356004357f100b69bb6b504e1252e36b375233158edee64d071b399e2f81473a695fd1b02160006000a361026060006014818352015b6004356102605160148110612bcc57600080fd5b600d60c052602060c02001541415612c04576024356102605160148110612bf257600080fd5b600d60c052602060c020015560006000f35b8151600101808352811415612bb8575b5050005b63a0e4af9a811415612c2e573361014052612c5f565b63bb994d48811415612c5a5760043560a01c15612c4a57600080fd5b6020600461014037600050612c5f565b612ce8565b61014051610180526007546101a0526009546101c05260006101605261016061012060006003818352015b610120516020026101800151331415612ca65760018352612cb6565b8151600101808352811415612c8a575b50505061016051612cc657600080fd5b61014051610140516101605261016051600658016146ef565b61014052600050005b63f76e4caa811415612e645760043560a01c15612d0457600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612d435760018352612d53565b8151600101808352811415612d27575b50505061014051612d6357600080fd5b60006001600c60043560e05260c052604060c02060c052602060c020015411612d8b57600080fd5b60006101405261018060006014818352015b61018051600d60c052602060c020015461016052610160511515612dc057612e02565b6004356101605118612dd157600080fd5b61014080516001818183011015612de757600080fd5b808201905090508152505b8151600101808352811415612d9d575b505060146101405110612e1457600080fd5b6004356013600d60c052602060c0200155610140516006580161460c565b610140526000506004357fa8727d412c6fa1e2497d6d6f275e2d9fe4d9318d5b793632e60ad9d38ee8f1fa60006000a2005b63b22439f5811415612f8b5760043560a01c15612e8057600080fd5b600854610160526007546101805260006101405261014061012060006002818352015b610120516020026101600151331415612ebf5760018352612ecf565b8151600101808352811415612ea3575b50505061014051612edf57600080fd5b61014060006014818352015b6004356101405160148110612eff57600080fd5b600d60c052602060c02001541415612f735760006101405160148110612f2457600080fd5b600d60c052602060c0200155610140516006580161460c565b610140526000506004357f8e1ec3c16d6a67ea8effe2ac7adef9c2de0bc0dc47c49cdf18f6a8b0048085be60006000a260006000f35b8151600101808352811415612eeb575b505060006000fd5b63bf3759b5811415612fa1573361014052612fd2565b63bdcf36bb811415612fcd5760043560a01c15612fbd57600080fd5b6020600461014037600050612fd2565b613000565b610140516101405161016052610160516006580161477e565b6101c052610140526101c05160005260206000f35b63112c1f9b811415613016573361014052613047565b63d76480138114156130425760043560a01c1561303257600080fd5b6020600461014037600050613047565b613075565b610140516101405161016052610160516006580161487d565b6101c052610140526101c05160005260206000f35b63153c27c48114156130e55760065801614149565b6101405261014051600f5411156130d857600f546101405160065801614149565b610160526101405261016051808210156130c457600080fd5b8082039050905060005260206000f36130e3565b600060005260206000f35b005b63d3406abd8114156130fb57336101405261312c565b6333586b678114156131275760043560a01c1561311757600080fd5b602060046101403760005061312c565b61315a565b6101405161014051610160526101605160065801614ad8565b6101c052610140526101c05160005260206000f35b63a1d9bafc81141561367f5760006001600c3360e05260c052604060c02060c052602060c02001541161318c57600080fd5b6004356044358181830110156131a157600080fd5b8082019050905060206101c060246370a0823161014052336101605261015c6006545afa6131ce57600080fd5b601f3d116131db57600080fd5b6000506101c05110156131ed57600080fd5b6000602435111561321957336101405260243561016052610160516101405160065801614c2b565b6000505b336101405260043561016052610160516101405160065801614e0d565b6000506007600c3360e05260c052604060c02060c052602060c02001805460043581818301101561326657600080fd5b80820190509050815550610140513361016052610160516006580161477e565b6101c052610140526101c0516101405260443561014051808211156132ab57806132ad565b815b90509050610160526000610160511115613337576006600c3360e05260c052604060c02060c052602060c02001805461016051808210156132ed57600080fd5b8082039050905081555060118054610160518082101561330c57600080fd5b808203905090508155506101408051610160518082101561332c57600080fd5b808203905090508152505b610140516101605161018051336101a0526101a0516006580161487d565b61020052610180526101605261014052610200516101805260006101805111156133d3576006600c3360e05260c052604060c02060c052602060c020018054610180518181830110156133a757600080fd5b8082019050905081555060118054610180518181830110156133c857600080fd5b808201905090508155505b600435610160518181830110156133e957600080fd5b808201905090506101a052610180516101a051101561346b576101405161016051610180516101a0516006546101c052336101e052610180516101a0518082101561343357600080fd5b8082039050905061020052610200516101e0516101c05160065801613cf3565b6101a0526101805261016052610140526000506134e7565b610180516101a05111156134e7576101405161016051610180516101a0516006546101c052336101e05230610200526101a05161018051808210156134af57600080fd5b808203905090506102205261022051610200516101e0516101c05160065801613e8d565b6101a0526101805261016052610140526000505b6101405161016051610180516101a051336101c0526101c0516006580161452f565b6101a052610180526101605261014052600050426005600c3360e05260c052604060c02060c052602060c0200155426014556004356016556004356101c0526024356101e05261016051610200526007600c3360e05260c052604060c02060c052602060c0200154610220526008600c3360e05260c052604060c02060c052602060c0200154610240526006600c3360e05260c052604060c02060c052602060c02001546102605261018051610280526002600c3360e05260c052604060c02060c052602060c02001546102a052337f67f96d2854a335a4cadb49f84fd3ca6f990744ddb3feceeb4b349d2d53d32ad36101006101c0a26002600c3360e05260c052604060c02060c052602060c0200154151561362757600161362b565b600e545b15613670576020610220600463efbb5cb06101c0526101dc335afa61364f57600080fd5b601f3d1161365c57600080fd5b6000506102205160005260206000f361367d565b6101405160005260206000f35b005b6301681a628114156136b5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610140526136d6565b636ea056a98114156136d15760206024610140376000506136d6565b6137b6565b60043560a01c156136e657600080fd5b60075433146136f457600080fd5b6006546004351861370457600080fd5b61014051610160527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61016051141561377657602061020060246370a0823161018052306101a05261019c6004355afa61375d57600080fd5b601f3d1161376a57600080fd5b60005061020051610160525b6101405161016051600435610180526007546101a052610160516101c0526101c0516101a0516101805160065801613cf3565b6101605261014052600050005b6306fdde0381141561385b5760008060c052602060c020610180602082540161012060006003818352015b826101205160200211156137f457613816565b61012051850154610120516020028501525b81516001018083528114156137e1575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b6395d89b418114156139005760018060c052602060c020610180602082540161012060006002818352015b82610120516020021115613899576138bb565b61012051850154610120516020028501525b8151600101808352811415613886575b50505050505061018051806101a001818260206001820306601f82010390500336823750506020610160526040610180510160206001820306601f8201039050610160f35b63313ce5678114156139185760025460005260206000f35b6370a0823181141561394e5760043560a01c1561393457600080fd5b600360043560e05260c052604060c0205460005260206000f35b63dd62ed3e8114156139a25760043560a01c1561396a57600080fd5b60243560a01c1561397a57600080fd5b600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f35b6318160ddd8114156139ba5760055460005260206000f35b63fc0c546a8114156139d25760065460005260206000f35b635aa6e6758114156139ea5760075460005260206000f35b6388a8d602811415613a025760085460005260206000f35b63452a9320811415613a1a5760095460005260206000f35b6346d55875811415613a3257600b5460005260206000f35b6339ebf823811415613b4c5760043560a01c15613a4e57600080fd5b600c60043560e05260c052604060c0206101408080808460c052602060c0205481525050602081019050808060018560c052602060c020015481525050602081019050808060028560c052602060c020015481525050602081019050808060038560c052602060c020015481525050602081019050808060048560c052602060c020015481525050602081019050808060058560c052602060c020015481525050602081019050808060068560c052602060c020015481525050602081019050808060078560c052602060c020015481525050602081019050808060088560c052602060c0200154815250506101209050905060c05260c051610140f35b63c822adda811415613b7d5760043560148110613b6857600080fd5b600d60c052602060c020015460005260206000f35b633403c2fc811415613b9557600e5460005260206000f35b63ecf70858811415613bad57600f5460005260206000f35b63cea55f57811415613bc55760105460005260206000f35b63fc7b9c18811415613bdd5760115460005260206000f35b638e6350e2811415613bf55760125460005260206000f35b63c3535b52811415613c0d5760145460005260206000f35b633629c8de811415613c255760155460005260206000f35b6344b81396811415613c3d5760165460005260206000f35b632140254d811415613c555760175460005260206000f35b639ec5a894811415613c6d5760185460005260206000f35b63a6f7f5d6811415613c855760195460005260206000f35b6387788782811415613c9d57601a5460005260206000f35b637ecebe00811415613cd35760043560a01c15613cb957600080fd5b601b60043560e05260c052604060c0205460005260206000f35b633644e515811415613ceb57601c5460005260206000f35b505b60006000fd5b6101a05261014052610160526101805260006004610220527fa9059cbb000000000000000000000000000000000000000000000000000000006102405261022060048060208461028001018260208501600060045af15050805182019150506101605160208261028001015260208101905061018051602082610280010152602081019050806102805261028090508051602001806103208284600060045af1613d9c57600080fd5b505060206103e0610320516103406000610140515af1613dbb57600080fd5b60203d80821115613dcc5780613dce565b815b905090506103c0526103c08051602001806101c08284600060045af1613df357600080fd5b505060006101c0511115613e87576101c0806020015160008251806020901315613e1c57600080fd5b8091901215613e2a57600080fd5b806020036101000a820490509050905015151515613e87576308c379a0610220526020610240526010610260527f5472616e73666572206661696c656421000000000000000000000000000000006102805261026050606461023cfd5b6101a051565b6101c0526101405261016052610180526101a05260006004610240527f23b872dd00000000000000000000000000000000000000000000000000000000610260526102406004806020846102a001018260208501600060045af1505080518201915050610160516020826102a0010152602081019050610180516020826102a00101526020810190506101a0516020826102a0010152602081019050806102a0526102a090508051602001806103608284600060045af1613f4d57600080fd5b50506020610440610360516103806000610140515af1613f6c57600080fd5b60203d80821115613f7d5780613f7f565b815b90509050610420526104208051602001806101e08284600060045af1613fa457600080fd5b505060006101e0511115614038576101e0806020015160008251806020901315613fcd57600080fd5b8091901215613fdb57600080fd5b806020036101000a820490509050905015151515614038576308c379a0610240526020610260526010610280527f5472616e73666572206661696c656421000000000000000000000000000000006102a05261028050606461025cfd5b6101c051565b6101a052610140526101605261018052306101e05260006102005260006101c0526101c061012060006002818352015b610120516020026101e0015161016051141561408d576001835261409d565b815160010180835281141561406e575b5050506101c051156140ae57600080fd5b60036101405160e05260c052604060c020805461018051808210156140d257600080fd5b8082039050905081555060036101605160e05260c052604060c02080546101805181818301101561410257600080fd5b80820190509050815550610180516101c05261016051610140517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101c0a36101a051565b6101405260206101e060246370a0823161016052306101805261017c6006545afa61417357600080fd5b601f3d1161418057600080fd5b6000506101e05160115481818301101561419957600080fd5b8082019050905060005260005161014051565b61018052610140526101605260006101a0526005546101c05260006101c051111561424757610160516101c05180820282158284830414176141ed57600080fd5b809050905090506101405161016051610180516101a0516101c05160065801614149565b6101e0526101c0526101a0526101805261016052610140526101e051808061423857600080fd5b8204905090506101a052614250565b610160516101a0525b6101c0516101a05181818301101561426757600080fd5b8082019050905060055560036101405160e05260c052604060c02080546101a05181818301101561429757600080fd5b808201905090508155506101a0516101e0526101405160007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101e0a36101a05160005260005161018051565b61016052610140526005541515614307576101405160005260005161016051565b426014548082101561431857600080fd5b80820390509050601754808202821582848304141761433657600080fd5b80905090509050610180526101405161016051610180516101a05160065801614149565b6101c0526101a0526101805261016052610140526101c0516101a052670de0b6b3a76400006101805110156143ef576101a080516016546101805160165480820282158284830414176143ac57600080fd5b80905090509050670de0b6b3a764000080820490509050808210156143d057600080fd5b80820390509050808210156143e457600080fd5b808203905090508152505b6103e8610140516101a051808202821582848304141761440e57600080fd5b80905090509050808202821582848304141761442957600080fd5b80905090509050600554808061443e57600080fd5b8204905090506103e88082049050905060005260005161016051565b61016052610140526000610140516101605160065801614149565b61018052610160526101405261018051111561451f576103e86101405160055480820282158284830414176144a957600080fd5b8090509050905080820282158284830414176144c457600080fd5b8090509050905061014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180806144ff57600080fd5b8204905090506103e880820490509050600052600051610160515661452d565b600060005260005161016051565b005b61016052610140526012805460136101405160e05260c052604060c020548082101561455a57600080fd5b808203905090508155506006600c6101405160e05260c052604060c02060c052602060c020015460206102006004638e6350e26101a0526101bc610140515afa6145a357600080fd5b601f3d116145b057600080fd5b60005061020051808211156145c557806145c7565b815b905090506101805260128054610180518181830110156145e657600080fd5b808201905090508155506101805160136101405160e05260c052604060c0205561016051565b6101405260006101605261018060006014818352015b610180516014811061463357600080fd5b600d60c052602060c02001546101a0526101a0511515614672576101608051600181818301101561466357600080fd5b808201905090508152506146d7565b60006101605111156146d7576101a05161018051610160518082101561469757600080fd5b80820390509050601481106146ab57600080fd5b600d60c052602060c0200155600061018051601481106146ca57600080fd5b600d60c052602060c02001555b8151600101808352811415614622575b505061014051565b6101605261014052601080546002600c6101405160e05260c052604060c02060c052602060c02001548082101561472557600080fd5b8082039050905081555060006002600c6101405160e05260c052604060c02060c052602060c0200155610140517f4201c688d84c01154d321afa0c72f1bffe9eef53005c9de9d035074e71e9b32a60006000a261016051565b61016052610140526002600c6101405160e05260c052604060c02060c052602060c020015461014051610160516101805160065801614149565b6101a0526101805261016052610140526101a05180820282158284830414176147e057600080fd5b8090509050905061271080820490509050610180526006600c6101405160e05260c052604060c02060c052602060c02001546101a052600e5415614832576101a051600052600051610160515661487b565b610180516101a051111515614853576000600052600051610160515661487b565b6101a051610180518082101561486857600080fd5b8082039050905060005260005161016051565b005b6101605261014052600e541561489b57600060005260005161016051565b61014051610160516101805160065801614149565b6101a0526101805261016052610140526101a051610180526010546101805180820282158284830414176148e357600080fd5b80905090509050612710808204905090506101a0526011546101c0526002600c6101405160e05260c052604060c02060c052602060c020015461018051808202821582848304141761493457600080fd5b80905090509050612710808204905090506101e0526006600c6101405160e05260c052604060c02060c052602060c0200154610200526003600c6101405160e05260c052604060c02060c052602060c0200154610220526004600c6101405160e05260c052604060c02060c052602060c020015461024052610200516101e0511115156149c25760016149cd565b6101c0516101a05111155b156149e057600060005260005161016051565b6101e05161020051808210156149f557600080fd5b8082039050905061026052610260516101a0516101c05180821015614a1957600080fd5b8082039050905080821115614a2e5780614a30565b815b905090506102605261026051602061030060246370a0823161028052306102a05261029c6006545afa614a6257600080fd5b601f3d11614a6f57600080fd5b6000506103005180821115614a845780614a86565b815b905090506102605261022051610260511015614aae5760006000526000516101605156614ad6565b610260516102405180821115614ac45780614ac6565b815b9050905060005260005161016051565b005b61016052610140526005600c6101405160e05260c052604060c02060c052602060c020015461018052426101805180821015614b1357600080fd5b808203905090506101a052610180516001600c6101405160e05260c052604060c02060c052602060c020015480821015614b4c57600080fd5b808203905090506101c05260006101a0511115614bb25760006101c0511115614baa5760206102c060046322f3e2d46102605261027c610140515afa614b9157600080fd5b601f3d11614b9e57600080fd5b6000506102c051614bad565b60005b614bb5565b60005b15614c1b576007600c6101405160e05260c052604060c02060c052602060c02001546101a0518082028215828483041417614bef57600080fd5b809050905090506101c0518080614c0557600080fd5b8204905090506000526000516101605156614c29565b600060005260005161016051565b005b6101805261014052610160526006600c6101405160e05260c052604060c02060c052602060c02001546101a052610160516101a0511015614c6b57600080fd5b6008600c6101405160e05260c052604060c02060c052602060c02001805461016051818183011015614c9c57600080fd5b808201905090508155506101a0516101605180821015614cbb57600080fd5b808203905090506006600c6101405160e05260c052604060c02060c052602060c0200155601180546101605180821015614cf457600080fd5b808203905090508155506002600c6101405160e05260c052604060c02060c052602060c02001546101c052610160516127108082028215828483041417614d3a57600080fd5b809050905090506101405161016051610180516101a0516101c0516101e05160065801614149565b610200526101e0526101c0526101a052610180526101605261014052610200518080614d8d57600080fd5b8204905090506101c05180821115614da55780614da7565b815b905090506101e0526002600c6101405160e05260c052604060c02060c052602060c0200180546101e05180821015614dde57600080fd5b80820390509050815550601080546101e05180821015614dfd57600080fd5b8082039050905081555061018051565b61018052610140526101605260115460125480821015614e2c57600080fd5b808203905090504260145480821015614e4457600080fd5b808203905090508082028215828483041417614e5f57600080fd5b809050905090506019548082028215828483041417614e7d57600080fd5b80905090509050612710808204905090506301e18558808204905090506101a05260006101c0526000610160511115614f425761016051600c6101405160e05260c052604060c02060c052602060c020548082028215828483041417614ee257600080fd5b80905090509050612710808204905090506101c0526101a0805161016051601a548082028215828483041417614f1757600080fd5b8090509050905061271080820490509050818183011015614f3757600080fd5b808201905090508152505b6101a0516101c051818183011015614f5957600080fd5b808201905090506101e05260006101e051111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526101e051610240526102405161022051600658016141ac565b6102a052610200526101e0526101c0526101a0526101805261016052610140526102a0516102005260006101c0511115615088576101c051610200518082028215828483041417614ffa57600080fd5b809050905090506101e051808061501057600080fd5b820490509050610220526101405161016051610180516101a0516101c0516101e05161020051610220513061024052610140516102605261022051610280526102805161026051610240516006580161403e565b61022052610200526101e0526101c0526101a0526101805261016052610140526000505b600060033060e05260c052604060c02054111561510f576101405161016051610180516101a0516101c0516101e0516102005130610220526018546102405260033060e05260c052604060c02054610260526102605161024051610220516006580161403e565b610200526101e0526101c0526101a0526101805261016052610140526000505b6101805156", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/external/contracts/yearn/BaseStrategy.sol b/external/contracts/yearn/BaseStrategy.sol new file mode 100644 index 000000000..fa6261007 --- /dev/null +++ b/external/contracts/yearn/BaseStrategy.sol @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.6.0 <0.7.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; + +struct StrategyParams { + uint256 performanceFee; + uint256 activation; + uint256 debtRatio; + uint256 minDebtPerHarvest; + uint256 maxDebtPerHarvest; + uint256 lastReport; + uint256 totalDebt; + uint256 totalGain; + uint256 totalLoss; +} + +interface VaultAPI is IERC20 { + function name() external view returns (string calldata); + + function symbol() external view returns (string calldata); + + function decimals() external view returns (uint256); + + function apiVersion() external pure returns (string memory); + + function permit( + address owner, + address spender, + uint256 amount, + uint256 expiry, + bytes calldata signature + ) external returns (bool); + + // NOTE: Vyper produces multiple signatures for a given function with "default" args + function deposit() external returns (uint256); + + function deposit(uint256 amount) external returns (uint256); + + function deposit(uint256 amount, address recipient) external returns (uint256); + + // NOTE: Vyper produces multiple signatures for a given function with "default" args + function withdraw() external returns (uint256); + + function withdraw(uint256 maxShares) external returns (uint256); + + function withdraw(uint256 maxShares, address recipient) external returns (uint256); + + function token() external view returns (address); + + function strategies(address _strategy) external view returns (StrategyParams memory); + + function pricePerShare() external view returns (uint256); + + function totalAssets() external view returns (uint256); + + function depositLimit() external view returns (uint256); + + function maxAvailableShares() external view returns (uint256); + + /** + * View how much the Vault would increase this Strategy's borrow limit, + * based on its present performance (since its last report). Can be used to + * determine expectedReturn in your Strategy. + */ + function creditAvailable() external view returns (uint256); + + /** + * View how much the Vault would like to pull back from the Strategy, + * based on its present performance (since its last report). Can be used to + * determine expectedReturn in your Strategy. + */ + function debtOutstanding() external view returns (uint256); + + /** + * View how much the Vault expect this Strategy to return at the current + * block, based on its present performance (since its last report). Can be + * used to determine expectedReturn in your Strategy. + */ + function expectedReturn() external view returns (uint256); + + /** + * This is the main contact point where the Strategy interacts with the + * Vault. It is critical that this call is handled as intended by the + * Strategy. Therefore, this function will be called by BaseStrategy to + * make sure the integration is correct. + */ + function report( + uint256 _gain, + uint256 _loss, + uint256 _debtPayment + ) external returns (uint256); + + /** + * This function should only be used in the scenario where the Strategy is + * being retired but no migration of the positions are possible, or in the + * extreme scenario that the Strategy needs to be put into "Emergency Exit" + * mode in order for it to exit as quickly as possible. The latter scenario + * could be for any reason that is considered "critical" that the Strategy + * exits its position as fast as possible, such as a sudden change in + * market conditions leading to losses, or an imminent failure in an + * external dependency. + */ + function revokeStrategy() external; + + /** + * View the governance address of the Vault to assert privileged functions + * can only be called by governance. The Strategy serves the Vault, so it + * is subject to governance defined by the Vault. + */ + function governance() external view returns (address); + + /** + * View the management address of the Vault to assert privileged functions + * can only be called by management. The Strategy serves the Vault, so it + * is subject to management defined by the Vault. + */ + function management() external view returns (address); + + /** + * View the guardian address of the Vault to assert privileged functions + * can only be called by guardian. The Strategy serves the Vault, so it + * is subject to guardian defined by the Vault. + */ + function guardian() external view returns (address); +} + +/** + * This interface is here for the keeper bot to use. + */ +interface StrategyAPI { + function name() external view returns (string memory); + + function vault() external view returns (address); + + function want() external view returns (address); + + function apiVersion() external pure returns (string memory); + + function keeper() external view returns (address); + + function isActive() external view returns (bool); + + function delegatedAssets() external view returns (uint256); + + function estimatedTotalAssets() external view returns (uint256); + + function tendTrigger(uint256 callCost) external view returns (bool); + + function tend() external; + + function harvestTrigger(uint256 callCost) external view returns (bool); + + function harvest() external; + + event Harvested(uint256 profit, uint256 loss, uint256 debtPayment, uint256 debtOutstanding); +} + +/** + * @title Yearn Base Strategy + * @author yearn.finance + * @notice + * BaseStrategy implements all of the required functionality to interoperate + * closely with the Vault contract. This contract should be inherited and the + * abstract methods implemented to adapt the Strategy to the particular needs + * it has to create a return. + * + * Of special interest is the relationship between `harvest()` and + * `vault.report()'. `harvest()` may be called simply because enough time has + * elapsed since the last report, and not because any funds need to be moved + * or positions adjusted. This is critical so that the Vault may maintain an + * accurate picture of the Strategy's performance. See `vault.report()`, + * `harvest()`, and `harvestTrigger()` for further details. + */ +abstract contract BaseStrategy { + using SafeMath for uint256; + using SafeERC20 for IERC20; + string public metadataURI; + + /** + * @notice + * Used to track which version of `StrategyAPI` this Strategy + * implements. + * @dev The Strategy's version must match the Vault's `API_VERSION`. + * @return A string which holds the current API version of this contract. + */ + function apiVersion() public pure returns (string memory) { + return "0.3.4"; + } + + /** + * @notice This Strategy's name. + * @dev + * You can use this field to manage the "version" of this Strategy, e.g. + * `StrategySomethingOrOtherV1`. However, "API Version" is managed by + * `apiVersion()` function above. + * @return This Strategy's name. + */ + function name() external virtual view returns (string memory); + + /** + * @notice + * The amount (priced in want) of the total assets managed by this strategy should not count + * towards Yearn's TVL calculations. + * @dev + * You can override this field to set it to a non-zero value if some of the assets of this + * Strategy is somehow delegated inside another part of of Yearn's ecosystem e.g. another Vault. + * Note that this value must be strictly less than or equal to the amount provided by + * `estimatedTotalAssets()` below, as the TVL calc will be total assets minus delegated assets. + * Also note that this value is used to determine the total assets under management by this + * strategy, for the purposes of computing the management fee in `Vault` + * @return + * The amount of assets this strategy manages that should not be included in Yearn's Total Value + * Locked (TVL) calculation across it's ecosystem. + */ + function delegatedAssets() external virtual view returns (uint256) { + return 0; + } + + VaultAPI public vault; + address public strategist; + address public rewards; + address public keeper; + + IERC20 public want; + + // So indexers can keep track of this + event Harvested(uint256 profit, uint256 loss, uint256 debtPayment, uint256 debtOutstanding); + + event UpdatedStrategist(address newStrategist); + + event UpdatedKeeper(address newKeeper); + + event UpdatedRewards(address rewards); + + event UpdatedMinReportDelay(uint256 delay); + + event UpdatedMaxReportDelay(uint256 delay); + + event UpdatedProfitFactor(uint256 profitFactor); + + event UpdatedDebtThreshold(uint256 debtThreshold); + + event EmergencyExitEnabled(); + + event UpdatedMetadataURI(string metadataURI); + + // The minimum number of seconds between harvest calls. See + // `setMinReportDelay()` for more details. + uint256 public minReportDelay; + + // The maximum number of seconds between harvest calls. See + // `setMaxReportDelay()` for more details. + uint256 public maxReportDelay; + + // The minimum multiple that `callCost` must be above the credit/profit to + // be "justifiable". See `setProfitFactor()` for more details. + uint256 public profitFactor; + + // Use this to adjust the threshold at which running a debt causes a + // harvest trigger. See `setDebtThreshold()` for more details. + uint256 public debtThreshold; + + // See note on `setEmergencyExit()`. + bool public emergencyExit; + + // modifiers + modifier onlyAuthorized() { + require(msg.sender == strategist || msg.sender == governance(), "!authorized"); + _; + } + + modifier onlyStrategist() { + require(msg.sender == strategist, "!strategist"); + _; + } + + modifier onlyGovernance() { + require(msg.sender == governance(), "!authorized"); + _; + } + + modifier onlyKeepers() { + require( + msg.sender == keeper || + msg.sender == strategist || + msg.sender == governance() || + msg.sender == vault.guardian() || + msg.sender == vault.management(), + "!authorized" + ); + _; + } + + constructor(address _vault) public { + _initialize(_vault, msg.sender, msg.sender, msg.sender); + } + + /** + * @notice + * Initializes the Strategy, this is called only once, when the + * contract is deployed. + * @dev `_vault` should implement `VaultAPI`. + * @param _vault The address of the Vault responsible for this Strategy. + */ + function _initialize( + address _vault, + address _strategist, + address _rewards, + address _keeper + ) internal { + require(address(want) == address(0), "Strategy already initialized"); + + vault = VaultAPI(_vault); + want = IERC20(vault.token()); + want.safeApprove(_vault, uint256(-1)); // Give Vault unlimited access (might save gas) + strategist = _strategist; + rewards = _rewards; + keeper = _keeper; + + // initialize variables + minReportDelay = 0; + maxReportDelay = 86400; + profitFactor = 100; + debtThreshold = 0; + + vault.approve(rewards, uint256(-1)); // Allow rewards to be pulled + } + + /** + * @notice + * Used to change `strategist`. + * + * This may only be called by governance or the existing strategist. + * @param _strategist The new address to assign as `strategist`. + */ + function setStrategist(address _strategist) external onlyAuthorized { + require(_strategist != address(0)); + strategist = _strategist; + emit UpdatedStrategist(_strategist); + } + + /** + * @notice + * Used to change `keeper`. + * + * `keeper` is the only address that may call `tend()` or `harvest()`, + * other than `governance()` or `strategist`. However, unlike + * `governance()` or `strategist`, `keeper` may *only* call `tend()` + * and `harvest()`, and no other authorized functions, following the + * principle of least privilege. + * + * This may only be called by governance or the strategist. + * @param _keeper The new address to assign as `keeper`. + */ + function setKeeper(address _keeper) external onlyAuthorized { + require(_keeper != address(0)); + keeper = _keeper; + emit UpdatedKeeper(_keeper); + } + + /** + * @notice + * Used to change `rewards`. EOA or smart contract which has the permission + * to pull rewards from the vault. + * + * This may only be called by the strategist. + * @param _rewards The address to use for pulling rewards. + */ + function setRewards(address _rewards) external onlyStrategist { + require(_rewards != address(0)); + vault.approve(rewards, 0); + rewards = _rewards; + vault.approve(rewards, uint256(-1)); + emit UpdatedRewards(_rewards); + } + + /** + * @notice + * Used to change `minReportDelay`. `minReportDelay` is the minimum number + * of blocks that should pass for `harvest()` to be called. + * + * For external keepers (such as the Keep3r network), this is the minimum + * time between jobs to wait. (see `harvestTrigger()` + * for more details.) + * + * This may only be called by governance or the strategist. + * @param _delay The minimum number of seconds to wait between harvests. + */ + function setMinReportDelay(uint256 _delay) external onlyAuthorized { + minReportDelay = _delay; + emit UpdatedMinReportDelay(_delay); + } + + /** + * @notice + * Used to change `maxReportDelay`. `maxReportDelay` is the maximum number + * of blocks that should pass for `harvest()` to be called. + * + * For external keepers (such as the Keep3r network), this is the maximum + * time between jobs to wait. (see `harvestTrigger()` + * for more details.) + * + * This may only be called by governance or the strategist. + * @param _delay The maximum number of seconds to wait between harvests. + */ + function setMaxReportDelay(uint256 _delay) external onlyAuthorized { + maxReportDelay = _delay; + emit UpdatedMaxReportDelay(_delay); + } + + /** + * @notice + * Used to change `profitFactor`. `profitFactor` is used to determine + * if it's worthwhile to harvest, given gas costs. (See `harvestTrigger()` + * for more details.) + * + * This may only be called by governance or the strategist. + * @param _profitFactor A ratio to multiply anticipated + * `harvest()` gas cost against. + */ + function setProfitFactor(uint256 _profitFactor) external onlyAuthorized { + profitFactor = _profitFactor; + emit UpdatedProfitFactor(_profitFactor); + } + + /** + * @notice + * Sets how far the Strategy can go into loss without a harvest and report + * being required. + * + * By default this is 0, meaning any losses would cause a harvest which + * will subsequently report the loss to the Vault for tracking. (See + * `harvestTrigger()` for more details.) + * + * This may only be called by governance or the strategist. + * @param _debtThreshold How big of a loss this Strategy may carry without + * being required to report to the Vault. + */ + function setDebtThreshold(uint256 _debtThreshold) external onlyAuthorized { + debtThreshold = _debtThreshold; + emit UpdatedDebtThreshold(_debtThreshold); + } + + /** + * @notice + * Used to change `metadataURI`. `metadataURI` is used to store the URI + * of the file describing the strategy. + * + * This may only be called by governance or the strategist. + * @param _metadataURI The URI that describe the strategy. + */ + function setMetadataURI(string calldata _metadataURI) external onlyAuthorized { + metadataURI = _metadataURI; + emit UpdatedMetadataURI(_metadataURI); + } + + /** + * Resolve governance address from Vault contract, used to make assertions + * on protected functions in the Strategy. + */ + function governance() internal view returns (address) { + return vault.governance(); + } + + /** + * @notice + * Provide an accurate estimate for the total amount of assets + * (principle + return) that this Strategy is currently managing, + * denominated in terms of `want` tokens. + * + * This total should be "realizable" e.g. the total value that could + * *actually* be obtained from this Strategy if it were to divest its + * entire position based on current on-chain conditions. + * @dev + * Care must be taken in using this function, since it relies on external + * systems, which could be manipulated by the attacker to give an inflated + * (or reduced) value produced by this function, based on current on-chain + * conditions (e.g. this function is possible to influence through + * flashloan attacks, oracle manipulations, or other DeFi attack + * mechanisms). + * + * It is up to governance to use this function to correctly order this + * Strategy relative to its peers in the withdrawal queue to minimize + * losses for the Vault based on sudden withdrawals. This value should be + * higher than the total debt of the Strategy and higher than its expected + * value to be "safe". + * @return The estimated total assets in this Strategy. + */ + function estimatedTotalAssets() public virtual view returns (uint256); + + /* + * @notice + * Provide an indication of whether this strategy is currently "active" + * in that it is managing an active position, or will manage a position in + * the future. This should correlate to `harvest()` activity, so that Harvest + * events can be tracked externally by indexing agents. + * @return True if the strategy is actively managing a position. + */ + function isActive() public view returns (bool) { + return vault.strategies(address(this)).debtRatio > 0 || estimatedTotalAssets() > 0; + } + + /** + * Perform any Strategy unwinding or other calls necessary to capture the + * "free return" this Strategy has generated since the last time its core + * position(s) were adjusted. Examples include unwrapping extra rewards. + * This call is only used during "normal operation" of a Strategy, and + * should be optimized to minimize losses as much as possible. + * + * This method returns any realized profits and/or realized losses + * incurred, and should return the total amounts of profits/losses/debt + * payments (in `want` tokens) for the Vault's accounting (e.g. + * `want.balanceOf(this) >= _debtPayment + _profit - _loss`). + * + * `_debtOutstanding` will be 0 if the Strategy is not past the configured + * debt limit, otherwise its value will be how far past the debt limit + * the Strategy is. The Strategy's debt limit is configured in the Vault. + * + * NOTE: `_debtPayment` should be less than or equal to `_debtOutstanding`. + * It is okay for it to be less than `_debtOutstanding`, as that + * should only used as a guide for how much is left to pay back. + * Payments should be made to minimize loss from slippage, debt, + * withdrawal fees, etc. + * + * See `vault.debtOutstanding()`. + */ + function prepareReturn(uint256 _debtOutstanding) + internal + virtual + returns ( + uint256 _profit, + uint256 _loss, + uint256 _debtPayment + ); + + /** + * Perform any adjustments to the core position(s) of this Strategy given + * what change the Vault made in the "investable capital" available to the + * Strategy. Note that all "free capital" in the Strategy after the report + * was made is available for reinvestment. Also note that this number + * could be 0, and you should handle that scenario accordingly. + * + * See comments regarding `_debtOutstanding` on `prepareReturn()`. + */ + function adjustPosition(uint256 _debtOutstanding) internal virtual; + + /** + * Liquidate up to `_amountNeeded` of `want` of this strategy's positions, + * irregardless of slippage. Any excess will be re-invested with `adjustPosition()`. + * This function should return the amount of `want` tokens made available by the + * liquidation. If there is a difference between them, `_loss` indicates whether the + * difference is due to a realized loss, or if there is some other sitution at play + * (e.g. locked funds) where the amount made available is less than what is needed. + * This function is used during emergency exit instead of `prepareReturn()` to + * liquidate all of the Strategy's positions back to the Vault. + * + * NOTE: The invariant `_liquidatedAmount + _loss <= _amountNeeded` should always be maintained + */ + function liquidatePosition(uint256 _amountNeeded) internal virtual returns (uint256 _liquidatedAmount, uint256 _loss); + + /** + * @notice + * Provide a signal to the keeper that `tend()` should be called. The + * keeper will provide the estimated gas cost that they would pay to call + * `tend()`, and this function should use that estimate to make a + * determination if calling it is "worth it" for the keeper. This is not + * the only consideration into issuing this trigger, for example if the + * position would be negatively affected if `tend()` is not called + * shortly, then this can return `true` even if the keeper might be + * "at a loss" (keepers are always reimbursed by Yearn). + * @dev + * `callCost` must be priced in terms of `want`. + * + * This call and `harvestTrigger()` should never return `true` at the same + * time. + * @param callCost The keeper's estimated cast cost to call `tend()`. + * @return `true` if `tend()` should be called, `false` otherwise. + */ + function tendTrigger(uint256 callCost) public virtual view returns (bool) { + // We usually don't need tend, but if there are positions that need + // active maintainence, overriding this function is how you would + // signal for that. + return false; + } + + /** + * @notice + * Adjust the Strategy's position. The purpose of tending isn't to + * realize gains, but to maximize yield by reinvesting any returns. + * + * See comments on `adjustPosition()`. + * + * This may only be called by governance, the strategist, or the keeper. + */ + function tend() external onlyKeepers { + // Don't take profits with this call, but adjust for better gains + adjustPosition(vault.debtOutstanding()); + } + + /** + * @notice + * Provide a signal to the keeper that `harvest()` should be called. The + * keeper will provide the estimated gas cost that they would pay to call + * `harvest()`, and this function should use that estimate to make a + * determination if calling it is "worth it" for the keeper. This is not + * the only consideration into issuing this trigger, for example if the + * position would be negatively affected if `harvest()` is not called + * shortly, then this can return `true` even if the keeper might be "at a + * loss" (keepers are always reimbursed by Yearn). + * @dev + * `callCost` must be priced in terms of `want`. + * + * This call and `tendTrigger` should never return `true` at the + * same time. + * + * See `min/maxReportDelay`, `profitFactor`, `debtThreshold` to adjust the + * strategist-controlled parameters that will influence whether this call + * returns `true` or not. These parameters will be used in conjunction + * with the parameters reported to the Vault (see `params`) to determine + * if calling `harvest()` is merited. + * + * It is expected that an external system will check `harvestTrigger()`. + * This could be a script run off a desktop or cloud bot (e.g. + * https://github.com/iearn-finance/yearn-vaults/blob/master/scripts/keep.py), + * or via an integration with the Keep3r network (e.g. + * https://github.com/Macarse/GenericKeep3rV2/blob/master/contracts/keep3r/GenericKeep3rV2.sol). + * @param callCost The keeper's estimated cast cost to call `harvest()`. + * @return `true` if `harvest()` should be called, `false` otherwise. + */ + function harvestTrigger(uint256 callCost) public virtual view returns (bool) { + StrategyParams memory params = vault.strategies(address(this)); + + // Should not trigger if Strategy is not activated + if (params.activation == 0) return false; + + // Should not trigger if we haven't waited long enough since previous harvest + if (block.timestamp.sub(params.lastReport) < minReportDelay) return false; + + // Should trigger if hasn't been called in a while + if (block.timestamp.sub(params.lastReport) >= maxReportDelay) return true; + + // If some amount is owed, pay it back + // NOTE: Since debt is based on deposits, it makes sense to guard against large + // changes to the value from triggering a harvest directly through user + // behavior. This should ensure reasonable resistance to manipulation + // from user-initiated withdrawals as the outstanding debt fluctuates. + uint256 outstanding = vault.debtOutstanding(); + if (outstanding > debtThreshold) return true; + + // Check for profits and losses + uint256 total = estimatedTotalAssets(); + // Trigger if we have a loss to report + if (total.add(debtThreshold) < params.totalDebt) return true; + + uint256 profit = 0; + if (total > params.totalDebt) profit = total.sub(params.totalDebt); // We've earned a profit! + + // Otherwise, only trigger if it "makes sense" economically (gas cost + // is debtOutstanding ? totalAssets : debtOutstanding); + // NOTE: take up any remainder here as profit + if (debtPayment > debtOutstanding) { + profit = debtPayment.sub(debtOutstanding); + debtPayment = debtOutstanding; + } + } else { + // Free up returns for Vault to pull + (profit, loss, debtPayment) = prepareReturn(debtOutstanding); + } + + // Allow Vault to take up to the "harvested" balance of this contract, + // which is the amount it has earned since the last time it reported to + // the Vault. + debtOutstanding = vault.report(profit, loss, debtPayment); + + // Check if free returns are left, and re-invest them + adjustPosition(debtOutstanding); + + emit Harvested(profit, loss, debtPayment, debtOutstanding); + } + + /** + * @notice + * Withdraws `_amountNeeded` to `vault`. + * + * This may only be called by the Vault. + * @param _amountNeeded How much `want` to withdraw. + * @return _loss Any realized losses + */ + function withdraw(uint256 _amountNeeded) external returns (uint256 _loss) { + require(msg.sender == address(vault), "!vault"); + // Liquidate as much as possible to `want`, up to `_amountNeeded` + uint256 amountFreed; + (amountFreed, _loss) = liquidatePosition(_amountNeeded); + // Send it directly back (NOTE: Using `msg.sender` saves some gas here) + want.safeTransfer(msg.sender, amountFreed); + // NOTE: Reinvest anything leftover on next `tend`/`harvest` + } + + /** + * Do anything necessary to prepare this Strategy for migration, such as + * transferring any reserve or LP tokens, CDPs, or other tokens or stores of + * value. + */ + function prepareMigration(address _newStrategy) internal virtual; + + /** + * @notice + * Transfers all `want` from this Strategy to `_newStrategy`. + * + * This may only be called by governance or the Vault. + * @dev + * The new Strategy's Vault must be the same as this Strategy's Vault. + * @param _newStrategy The Strategy to migrate to. + */ + function migrate(address _newStrategy) external { + require(msg.sender == address(vault) || msg.sender == governance()); + require(BaseStrategy(_newStrategy).vault() == vault); + prepareMigration(_newStrategy); + want.safeTransfer(_newStrategy, want.balanceOf(address(this))); + } + + /** + * @notice + * Activates emergency exit. Once activated, the Strategy will exit its + * position upon the next harvest, depositing all funds into the Vault as + * quickly as is reasonable given on-chain conditions. + * + * This may only be called by governance or the strategist. + * @dev + * See `vault.setEmergencyShutdown()` and `harvest()` for further details. + */ + function setEmergencyExit() external onlyAuthorized { + emergencyExit = true; + vault.revokeStrategy(); + + emit EmergencyExitEnabled(); + } + + /** + * Override this to add all tokens/tokenized positions this contract + * manages on a *persistent* basis (e.g. not just for swapping back to + * want ephemerally). + * + * NOTE: Do *not* include `want`, already included in `sweep` below. + * + * Example: + * + * function protectedTokens() internal override view returns (address[] memory) { + * address[] memory protected = new address[](3); + * protected[0] = tokenA; + * protected[1] = tokenB; + * protected[2] = tokenC; + * return protected; + * } + */ + function protectedTokens() internal virtual view returns (address[] memory); + + /** + * @notice + * Removes tokens from this Strategy that are not the type of tokens + * managed by this Strategy. This may be used in case of accidentally + * sending the wrong kind of token to this Strategy. + * + * Tokens will be sent to `governance()`. + * + * This will fail if an attempt is made to sweep `want`, or any tokens + * that are protected by this Strategy. + * + * This may only be called by governance. + * @dev + * Implement `protectedTokens()` to specify any additional tokens that + * should be protected from sweeping in addition to `want`. + * @param _token The token to transfer out of this vault. + */ + function sweep(address _token) external onlyGovernance { + require(_token != address(want), "!want"); + require(_token != address(vault), "!shares"); + + address[] memory _protectedTokens = protectedTokens(); + for (uint256 i; i < _protectedTokens.length; i++) require(_token != _protectedTokens[i], "!protected"); + + IERC20(_token).safeTransfer(governance(), IERC20(_token).balanceOf(address(this))); + } +} + +abstract contract BaseStrategyInitializable is BaseStrategy { + event Cloned(address indexed clone); + + constructor(address _vault) public BaseStrategy(_vault) {} + + function initialize( + address _vault, + address _strategist, + address _rewards, + address _keeper + ) external virtual { + _initialize(_vault, _strategist, _rewards, _keeper); + } + + function clone(address _vault) external returns (address) { + return this.clone(_vault, msg.sender, msg.sender, msg.sender); + } + + function clone( + address _vault, + address _strategist, + address _rewards, + address _keeper + ) external returns (address newStrategy) { + // Copied from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol + bytes20 addressBytes = bytes20(address(this)); + + assembly { + // EIP-1167 bytecode + let clone_code := mload(0x40) + mstore(clone_code, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(clone_code, 0x14), addressBytes) + mstore(add(clone_code, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + newStrategy := create(0, clone_code, 0x37) + } + + BaseStrategyInitializable(newStrategy).initialize(_vault, _strategist, _rewards, _keeper); + + emit Cloned(newStrategy); + } +} diff --git a/external/contracts/yearn/Registry.vy b/external/contracts/yearn/Registry.vy new file mode 100644 index 000000000..de2cfb222 --- /dev/null +++ b/external/contracts/yearn/Registry.vy @@ -0,0 +1,330 @@ +# @version 0.2.11 + + +interface Vault: + def token() -> address: view + def apiVersion() -> String[28]: view + def governance() -> address: view + def initialize( + token: address, + governance: address, + rewards: address, + name: String[64], + symbol: String[32], + guardian: address, + ): nonpayable + + +# len(releases) +numReleases: public(uint256) +releases: public(HashMap[uint256, address]) + +# Token => len(vaults) +numVaults: public(HashMap[address, uint256]) +vaults: public(HashMap[address, HashMap[uint256, address]]) + +# Index of token added => token address +tokens: public(HashMap[uint256, address]) +# len(tokens) +numTokens: public(uint256) +# Inclusion check for token +isRegistered: public(HashMap[address, bool]) + +# 2-phase commit +governance: public(address) +pendingGovernance: public(address) + +tags: public(HashMap[address, String[120]]) +banksy: public(HashMap[address, bool]) # could be anyone + +event NewRelease: + release_id: indexed(uint256) + template: address + api_version: String[28] + +event NewVault: + token: indexed(address) + vault_id: indexed(uint256) + vault: address + api_version: String[28] + +event NewExperimentalVault: + token: indexed(address) + deployer: indexed(address) + vault: address + api_version: String[28] + +event NewGovernance: + governance: address + +event VaultTagged: + vault: address + tag: String[120] + +@external +def __init__(): + self.governance = msg.sender + + +@external +def setGovernance(governance: address): + """ + @notice Starts the 1st phase of the governance transfer. + @dev Throws if the caller is not current governance. + @param governance The next governance address + """ + assert msg.sender == self.governance # dev: unauthorized + self.pendingGovernance = governance + + +@external +def acceptGovernance(): + """ + @notice Completes the 2nd phase of the governance transfer. + @dev + Throws if the caller is not the pending caller. + Emits a `NewGovernance` event. + """ + assert msg.sender == self.pendingGovernance # dev: unauthorized + self.governance = msg.sender + log NewGovernance(msg.sender) + + +@view +@external +def latestRelease() -> String[28]: + """ + @notice Returns the api version of the latest release. + @dev Throws if no releases are registered yet. + @return The api version of the latest release. + """ + # NOTE: Throws if there has not been a release yet + return Vault(self.releases[self.numReleases - 1]).apiVersion() # dev: no release + + +@view +@external +def latestVault(token: address) -> address: + """ + @notice Returns the latest deployed vault for the given token. + @dev Throws if no vaults are endorsed yet for the given token. + @param token The token address to find the latest vault for. + @return The address of the latest vault for the given token. + """ + # NOTE: Throws if there has not been a deployed vault yet for this token + return self.vaults[token][self.numVaults[token] - 1] # dev: no vault for token + + +@external +def newRelease(vault: address): + """ + @notice + Add a previously deployed Vault as the template contract for the latest release, + to be used by further "forwarder-style" delegatecall proxy contracts that can be + deployed from the registry throw other methods (to save gas). + @dev + Throws if caller isn't `self.governance`. + Throws if `vault`'s governance isn't `self.governance`. + Throws if the api version is the same as the previous release. + Emits a `NewVault` event. + @param vault The vault that will be used as the template contract for the next release. + """ + assert msg.sender == self.governance # dev: unauthorized + + # Check if the release is different from the current one + # NOTE: This doesn't check for strict semver-style linearly increasing release versions + release_id: uint256 = self.numReleases # Next id in series + if release_id > 0: + assert ( + Vault(self.releases[release_id - 1]).apiVersion() + != Vault(vault).apiVersion() + ) # dev: same api version + # else: we are adding the first release to the Registry! + + # Update latest release + self.releases[release_id] = vault + self.numReleases = release_id + 1 + + # Log the release for external listeners (e.g. Graph) + log NewRelease(release_id, vault, Vault(vault).apiVersion()) + + +@internal +def _newProxyVault( + token: address, + governance: address, + rewards: address, + guardian: address, + name: String[64], + symbol: String[32], + releaseTarget: uint256, +) -> address: + release: address = self.releases[releaseTarget] + assert release != ZERO_ADDRESS # dev: unknown release + vault: address = create_forwarder_to(release) + + # NOTE: Must initialize the Vault atomically with deploying it + Vault(vault).initialize(token, governance, rewards, name, symbol, guardian) + + return vault + + +@internal +def _registerVault(token: address, vault: address): + # Check if there is an existing deployment for this token at the particular api version + # NOTE: This doesn't check for strict semver-style linearly increasing release versions + vault_id: uint256 = self.numVaults[token] # Next id in series + if vault_id > 0: + assert ( + Vault(self.vaults[token][vault_id - 1]).apiVersion() + != Vault(vault).apiVersion() + ) # dev: same api version + # else: we are adding a new token to the Registry + + # Update the latest deployment + self.vaults[token][vault_id] = vault + self.numVaults[token] = vault_id + 1 + + # Register tokens for endorsed vaults + if not self.isRegistered[token]: + self.isRegistered[token] = True + self.tokens[self.numTokens] = token + self.numTokens += 1 + + # Log the deployment for external listeners (e.g. Graph) + log NewVault(token, vault_id, vault, Vault(vault).apiVersion()) + + +@external +def newVault( + token: address, + guardian: address, + rewards: address, + name: String[64], + symbol: String[32], + releaseDelta: uint256 = 0, # NOTE: Uses latest by default +) -> address: + """ + @notice + Create a new vault for the given token using the latest release in the registry, + as a simple "forwarder-style" delegatecall proxy to the latest release. Also adds + the new vault to the list of "endorsed" vaults for that token. + @dev + `governance` is set in the new vault as `self.governance`, with no ability to override. + Throws if caller isn't `self.governance`. + Throws if no releases are registered yet. + Throws if there already is a registered vault for the given token with the latest api version. + Emits a `NewVault` event. + @param token The token that may be deposited into the new Vault. + @param guardian The address authorized for guardian interactions in the new Vault. + @param rewards The address to use for collecting rewards in the new Vault + @param name Specify a custom Vault name. Set to empty string for default choice. + @param symbol Specify a custom Vault symbol name. Set to empty string for default choice. + @param releaseDelta Specify the number of releases prior to the latest to use as a target. Default is latest. + @return The address of the newly-deployed vault + """ + assert msg.sender == self.governance # dev: unauthorized + + # NOTE: Underflow if no releases created yet, or targeting prior to release history + releaseTarget: uint256 = self.numReleases - 1 - releaseDelta # dev: no releases + vault: address = self._newProxyVault(token, msg.sender, rewards, guardian, name, symbol, releaseTarget) + + self._registerVault(token, vault) + + return vault + + +@external +def newExperimentalVault( + token: address, + governance: address, + guardian: address, + rewards: address, + name: String[64], + symbol: String[32], + releaseDelta: uint256 = 0, # NOTE: Uses latest by default +) -> address: + """ + @notice + Create a new vault for the given token using the latest release in the registry, + as a simple "forwarder-style" delegatecall proxy to the latest release. Does not add + the new vault to the list of "endorsed" vaults for that token. + @dev + Throws if no releases are registered yet. + Emits a `NewExperimentalVault` event. + @param token The token that may be deposited into the new Vault. + @param governance The address authorized for governance interactions in the new Vault. + @param guardian The address authorized for guardian interactions in the new Vault. + @param rewards The address to use for collecting rewards in the new Vault + @param name Specify a custom Vault name. Set to empty string for default choice. + @param symbol Specify a custom Vault symbol name. Set to empty string for default choice. + @param releaseDelta Specify the number of releases prior to the latest to use as a target. Default is latest. + @return The address of the newly-deployed vault + """ + # NOTE: Underflow if no releases created yet, or targeting prior to release history + releaseTarget: uint256 = self.numReleases - 1 - releaseDelta # dev: no releases + # NOTE: Anyone can call this method, as a convenience to Strategist' experiments + vault: address = self._newProxyVault(token, governance, rewards, guardian, name, symbol, releaseTarget) + + # NOTE: Not registered, so emit an "experiment" event here instead + log NewExperimentalVault(token, msg.sender, vault, Vault(vault).apiVersion()) + + return vault + + +@external +def endorseVault(vault: address, releaseDelta: uint256 = 0): + """ + @notice + Adds an existing vault to the list of "endorsed" vaults for that token. + @dev + `governance` is set in the new vault as `self.governance`, with no ability to override. + Throws if caller isn't `self.governance`. + Throws if `vault`'s governance isn't `self.governance`. + Throws if no releases are registered yet. + Throws if `vault`'s api version does not match latest release. + Throws if there already is a deployment for the vault's token with the latest api version. + Emits a `NewVault` event. + @param vault The vault that will be endorsed by the Registry. + @param releaseDelta Specify the number of releases prior to the latest to use as a target. Default is latest. + """ + assert msg.sender == self.governance # dev: unauthorized + assert Vault(vault).governance() == msg.sender # dev: not governed + + # NOTE: Underflow if no releases created yet, or targeting prior to release history + releaseTarget: uint256 = self.numReleases - 1 - releaseDelta # dev: no releases + api_version: String[28] = Vault(self.releases[releaseTarget]).apiVersion() + assert Vault(vault).apiVersion() == api_version # dev: not target release + + # Add to the end of the list of vaults for token + self._registerVault(Vault(vault).token(), vault) + + +@external +def setBanksy(tagger: address, allowed: bool = True): + """ + @notice Set the ability of a particular tagger to tag current vaults. + @dev Throws if caller is not `self.governance`. + @param tagger The address to approve or deny access to tagging. + @param allowed Whether to approve or deny `tagger`. Defaults to approve. + """ + assert msg.sender == self.governance # dev: unauthorized + self.banksy[tagger] = allowed + + +@external +def tagVault(vault: address, tag: String[120]): + """ + @notice Tag a Vault with a message. + @dev + Throws if caller is not `self.governance` or an approved tagger. + Emits a `VaultTagged` event. + @param vault The address to tag with the given `tag` message. + @param tag The message to tag `vault` with. + """ + if msg.sender != self.governance: + assert self.banksy[msg.sender] # dev: not banksy + # else: we are governance, we can do anything banksy can do + + self.tags[vault] = tag + log VaultTagged(vault, tag) diff --git a/external/contracts/yearn/Vault.vy b/external/contracts/yearn/Vault.vy new file mode 100644 index 000000000..fc88f3c0c --- /dev/null +++ b/external/contracts/yearn/Vault.vy @@ -0,0 +1,1667 @@ +# @version 0.2.11 +""" +@title Yearn Token Vault +@license GNU AGPLv3 +@author yearn.finance +@notice + Yearn Token Vault. Holds an underlying token, and allows users to interact + with the Yearn ecosystem through Strategies connected to the Vault. + Vaults are not limited to a single Strategy, they can have as many Strategies + as can be designed (however the withdrawal queue is capped at 20.) + Deposited funds are moved into the most impactful strategy that has not + already reached its limit for assets under management, regardless of which + Strategy a user's funds end up in, they receive their portion of yields + generated across all Strategies. + When a user withdraws, if there are no funds sitting undeployed in the + Vault, the Vault withdraws funds from Strategies in the order of least + impact. (Funds are taken from the Strategy that will disturb everyone's + gains the least, then the next least, etc.) In order to achieve this, the + withdrawal queue's order must be properly set and managed by the community + (through governance). + Vault Strategies are parameterized to pursue the highest risk-adjusted yield. + There is an "Emergency Shutdown" mode. When the Vault is put into emergency + shutdown, assets will be recalled from the Strategies as quickly as is + practical (given on-chain conditions), minimizing loss. Deposits are + halted, new Strategies may not be added, and each Strategy exits with the + minimum possible damage to position, while opening up deposits to be + withdrawn by users. There are no restrictions on withdrawals above what is + expected under Normal Operation. + For further details, please refer to the specification: + https://github.com/iearn-finance/yearn-vaults/blob/master/SPECIFICATION.md +""" + +API_VERSION: constant(String[28]) = "0.3.4" + +from vyper.interfaces import ERC20 + +implements: ERC20 + + +interface DetailedERC20: + def name() -> String[42]: view + def symbol() -> String[20]: view + def decimals() -> uint256: view + + +interface Strategy: + def want() -> address: view + def vault() -> address: view + def isActive() -> bool: view + def delegatedAssets() -> uint256: view + def estimatedTotalAssets() -> uint256: view + def withdraw(_amount: uint256) -> uint256: nonpayable + def migrate(_newStrategy: address): nonpayable + + +interface GuestList: + def authorized(guest: address, amount: uint256) -> bool: view + + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + value: uint256 + + +event Approval: + owner: indexed(address) + spender: indexed(address) + value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +totalSupply: public(uint256) + +token: public(ERC20) +governance: public(address) +management: public(address) +guardian: public(address) +pendingGovernance: address +guestList: public(GuestList) + +struct StrategyParams: + performanceFee: uint256 # Strategist's fee (basis points) + activation: uint256 # Activation block.timestamp + debtRatio: uint256 # Maximum borrow amount (in BPS of total assets) + minDebtPerHarvest: uint256 # Lower limit on the increase of debt since last harvest + maxDebtPerHarvest: uint256 # Upper limit on the increase of debt since last harvest + lastReport: uint256 # block.timestamp of the last time a report occured + totalDebt: uint256 # Total outstanding debt that Strategy has + totalGain: uint256 # Total returns that Strategy has realized for Vault + totalLoss: uint256 # Total losses that Strategy has realized for Vault + + +event StrategyAdded: + strategy: indexed(address) + debtRatio: uint256 # Maximum borrow amount (in BPS of total assets) + minDebtPerHarvest: uint256 # Lower limit on the increase of debt since last harvest + maxDebtPerHarvest: uint256 # Upper limit on the increase of debt since last harvest + performanceFee: uint256 # Strategist's fee (basis points) + + +event StrategyReported: + strategy: indexed(address) + gain: uint256 + loss: uint256 + debtPaid: uint256 + totalGain: uint256 + totalLoss: uint256 + totalDebt: uint256 + debtAdded: uint256 + debtRatio: uint256 + + +event UpdateGovernance: + governance: address # New active governance + + +event UpdateManagement: + management: address # New active manager + + +event UpdateGuestList: + guestList: address # Vault guest list address + + +event UpdateRewards: + rewards: address # New active rewards recipient + + +event UpdateDepositLimit: + depositLimit: uint256 # New active deposit limit + + +event UpdatePerformanceFee: + performanceFee: uint256 # New active performance fee + + +event UpdateManagementFee: + managementFee: uint256 # New active management fee + + +event UpdateGuardian: + guardian: address # Address of the active guardian + + +event EmergencyShutdown: + active: bool # New emergency shutdown state (if false, normal operation enabled) + + +event UpdateWithdrawalQueue: + queue: address[MAXIMUM_STRATEGIES] # New active withdrawal queue + + +event StrategyUpdateDebtRatio: + strategy: indexed(address) # Address of the strategy for the debt ratio adjustment + debtRatio: uint256 # The new debt limit for the strategy (in BPS of total assets) + + +event StrategyUpdateMinDebtPerHarvest: + strategy: indexed(address) # Address of the strategy for the rate limit adjustment + minDebtPerHarvest: uint256 # Lower limit on the increase of debt since last harvest + + +event StrategyUpdateMaxDebtPerHarvest: + strategy: indexed(address) # Address of the strategy for the rate limit adjustment + maxDebtPerHarvest: uint256 # Upper limit on the increase of debt since last harvest + + +event StrategyUpdatePerformanceFee: + strategy: indexed(address) # Address of the strategy for the performance fee adjustment + performanceFee: uint256 # The new performance fee for the strategy + + +event StrategyMigrated: + oldVersion: indexed(address) # Old version of the strategy to be migrated + newVersion: indexed(address) # New version of the strategy + + +event StrategyRevoked: + strategy: indexed(address) # Address of the strategy that is revoked + + +event StrategyRemovedFromQueue: + strategy: indexed(address) # Address of the strategy that is removed from the withdrawal queue + + +event StrategyAddedToQueue: + strategy: indexed(address) # Address of the strategy that is added to the withdrawal queue + + + +# NOTE: Track the total for overhead targeting purposes +strategies: public(HashMap[address, StrategyParams]) +MAXIMUM_STRATEGIES: constant(uint256) = 20 +DEGREDATION_COEFFICIENT: constant(uint256) = 10 ** 18 + +# Ordering that `withdraw` uses to determine which strategies to pull funds from +# NOTE: Does *NOT* have to match the ordering of all the current strategies that +# exist, but it is recommended that it does or else withdrawal depth is +# limited to only those inside the queue. +# NOTE: Ordering is determined by governance, and should be balanced according +# to risk, slippage, and/or volatility. Can also be ordered to increase the +# withdrawal speed of a particular Strategy. +# NOTE: The first time a ZERO_ADDRESS is encountered, it stops withdrawing +withdrawalQueue: public(address[MAXIMUM_STRATEGIES]) + +emergencyShutdown: public(bool) + +depositLimit: public(uint256) # Limit for totalAssets the Vault can hold +debtRatio: public(uint256) # Debt ratio for the Vault across all strategies (in BPS, <= 10k) +totalDebt: public(uint256) # Amount of tokens that all strategies have borrowed +delegatedAssets: public(uint256) # Amount of tokens that all strategies delegate to other Vaults +# NOTE: Cached value used solely for proper bookkeeping +_strategy_delegatedAssets: HashMap[address, uint256] +lastReport: public(uint256) # block.timestamp of last report +activation: public(uint256) # block.timestamp of contract deployment +lockedProfit: public(uint256) # how much profit is locked and cant be withdrawn + +lockedProfitDegration: public(uint256) # rate per block of degration. DEGREDATION_COEFFICIENT is 100% per block +rewards: public(address) # Rewards contract where Governance fees are sent to +# Governance Fee for management of Vault (given to `rewards`) +managementFee: public(uint256) +# Governance Fee for performance of Vault (given to `rewards`) +performanceFee: public(uint256) +MAX_BPS: constant(uint256) = 10_000 # 100%, or 10k basis points +# NOTE: A four-century period will be missing 3 of its 100 Julian leap years, leaving 97. +# So the average year has 365 + 97/400 = 365.2425 days +# ERROR(Julian): -0.0078 +# ERROR(Gregorian): -0.0003 +SECS_PER_YEAR: constant(uint256) = 31_556_952 # 365.2425 days +# `nonces` track `permit` approvals with signature. +nonces: public(HashMap[address, uint256]) +DOMAIN_SEPARATOR: public(bytes32) +DOMAIN_TYPE_HASH: constant(bytes32) = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)') +PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + + +@external +def initialize( + token: address, + governance: address, + rewards: address, + nameOverride: String[64], + symbolOverride: String[32], + guardian: address = msg.sender, +): + """ + @notice + Initializes the Vault, this is called only once, when the contract is + deployed. + The performance fee is set to 10% of yield, per Strategy. + The management fee is set to 2%, per year. + The initial deposit limit is set to 0 (deposits disabled); it must be + updated after initialization. + @dev + If `nameOverride` is not specified, the name will be 'yearn' + combined with the name of `token`. + If `symbolOverride` is not specified, the symbol will be 'y' + combined with the symbol of `token`. + @param token The token that may be deposited into this Vault. + @param governance The address authorized for governance interactions. + @param rewards The address to distribute rewards to. + @param nameOverride Specify a custom Vault name. Leave empty for default choice. + @param symbolOverride Specify a custom Vault symbol name. Leave empty for default choice. + @param guardian The address authorized for guardian interactions. Defaults to caller. + """ + assert self.activation == 0 # dev: no devops199 + self.token = ERC20(token) + if nameOverride == "": + self.name = concat(DetailedERC20(token).symbol(), " yVault") + else: + self.name = nameOverride + if symbolOverride == "": + self.symbol = concat("yv", DetailedERC20(token).symbol()) + else: + self.symbol = symbolOverride + self.decimals = DetailedERC20(token).decimals() + self.governance = governance + log UpdateGovernance(governance) + self.management = governance + log UpdateManagement(governance) + self.rewards = rewards + log UpdateRewards(rewards) + self.guardian = guardian + log UpdateGuardian(guardian) + self.performanceFee = 1000 # 10% of yield (per Strategy) + log UpdatePerformanceFee(convert(1000, uint256)) + self.managementFee = 200 # 2% per year + log UpdateManagementFee(convert(200, uint256)) + self.lastReport = block.timestamp + self.activation = block.timestamp + self.lockedProfitDegration = convert(DEGREDATION_COEFFICIENT * 46 /10 ** 6 , uint256) # 6 hours in blocks + # EIP-712 + self.DOMAIN_SEPARATOR = keccak256( + concat( + DOMAIN_TYPE_HASH, + keccak256(convert("Yearn Vault", Bytes[11])), + keccak256(convert(API_VERSION, Bytes[28])), + convert(chain.id, bytes32), + convert(self, bytes32) + ) + ) + + +@pure +@external +def apiVersion() -> String[28]: + """ + @notice + Used to track the deployed version of this contract. In practice you + can use this version number to compare with Yearn's GitHub and + determine which version of the source matches this deployed contract. + @dev + All strategies must have an `apiVersion()` that matches the Vault's + `API_VERSION`. + @return API_VERSION which holds the current version of this contract. + """ + return API_VERSION + + +@external +def setName(name: String[42]): + """ + @notice + Used to change the value of `name`. + This may only be called by governance. + @param name The new name to use. + """ + assert msg.sender == self.governance + self.name = name + + +@external +def setSymbol(symbol: String[20]): + """ + @notice + Used to change the value of `symbol`. + This may only be called by governance. + @param symbol The new symbol to use. + """ + assert msg.sender == self.governance + self.symbol = symbol + + +# 2-phase commit for a change in governance +@external +def setGovernance(governance: address): + """ + @notice + Nominate a new address to use as governance. + The change does not go into effect immediately. This function sets a + pending change, and the governance address is not updated until + the proposed governance address has accepted the responsibility. + This may only be called by the current governance address. + @param governance The address requested to take over Vault governance. + """ + assert msg.sender == self.governance + self.pendingGovernance = governance + + +@external +def acceptGovernance(): + """ + @notice + Once a new governance address has been proposed using setGovernance(), + this function may be called by the proposed address to accept the + responsibility of taking over governance for this contract. + This may only be called by the proposed governance address. + @dev + setGovernance() should be called by the existing governance address, + prior to calling this function. + """ + assert msg.sender == self.pendingGovernance + self.governance = msg.sender + log UpdateGovernance(msg.sender) + + +@external +def setManagement(management: address): + """ + @notice + Changes the management address. + Management is able to make some investment decisions adjusting parameters. + This may only be called by governance. + @param management The address to use for managing. + """ + assert msg.sender == self.governance + self.management = management + log UpdateManagement(management) + + +@external +def setGuestList(guestList: address): + """ + @notice + Used to set or change `guestList`. A guest list is another contract + that dictates who is allowed to participate in a Vault (and transfer + shares). + This may only be called by governance. + @param guestList The address of the `GuestList` contract to use. + """ + assert msg.sender == self.governance + self.guestList = GuestList(guestList) + log UpdateGuestList(guestList) + + +@external +def setRewards(rewards: address): + """ + @notice + Changes the rewards address. Any distributed rewards + will cease flowing to the old address and begin flowing + to this address once the change is in effect. + This will not change any Strategy reports in progress, only + new reports made after this change goes into effect. + This may only be called by governance. + @param rewards The address to use for collecting rewards. + """ + assert msg.sender == self.governance + self.rewards = rewards + log UpdateRewards(rewards) + +@external +def setLockedProfitDegration(degration: uint256): + """ + @notice + Changes the locked profit degration. + @param degration The rate of degration in percent per second scaled to 1e18. + """ + assert msg.sender == self.governance + # Since "degration" is of type uint256 it can never be less than zero + assert degration <= DEGREDATION_COEFFICIENT + self.lockedProfitDegration = degration + +@external +def setDepositLimit(limit: uint256): + """ + @notice + Changes the maximum amount of tokens that can be deposited in this Vault. + Note, this is not how much may be deposited by a single depositor, + but the maximum amount that may be deposited across all depositors. + This may only be called by governance. + @param limit The new deposit limit to use. + """ + assert msg.sender == self.governance + self.depositLimit = limit + log UpdateDepositLimit(limit) + + +@external +def setPerformanceFee(fee: uint256): + """ + @notice + Used to change the value of `performanceFee`. + Should set this value below the maximum strategist performance fee. + This may only be called by governance. + @param fee The new performance fee to use. + """ + assert msg.sender == self.governance + assert fee <= MAX_BPS + self.performanceFee = fee + log UpdatePerformanceFee(fee) + + +@external +def setManagementFee(fee: uint256): + """ + @notice + Used to change the value of `managementFee`. + This may only be called by governance. + @param fee The new management fee to use. + """ + assert msg.sender == self.governance + assert fee <= MAX_BPS + self.managementFee = fee + log UpdateManagementFee(fee) + + +@external +def setGuardian(guardian: address): + """ + @notice + Used to change the address of `guardian`. + This may only be called by governance or the existing guardian. + @param guardian The new guardian address to use. + """ + assert msg.sender in [self.guardian, self.governance] + self.guardian = guardian + log UpdateGuardian(guardian) + + +@external +def setEmergencyShutdown(active: bool): + """ + @notice + Activates or deactivates Vault mode where all Strategies go into full + withdrawal. + During Emergency Shutdown: + 1. No Users may deposit into the Vault (but may withdraw as usual.) + 2. Governance may not add new Strategies. + 3. Each Strategy must pay back their debt as quickly as reasonable to + minimally affect their position. + 4. Only Governance may undo Emergency Shutdown. + See contract level note for further details. + This may only be called by governance or the guardian. + @param active + If true, the Vault goes into Emergency Shutdown. If false, the Vault + goes back into Normal Operation. + """ + if active: + assert msg.sender in [self.guardian, self.governance] + else: + assert msg.sender == self.governance + self.emergencyShutdown = active + log EmergencyShutdown(active) + + +@external +def setWithdrawalQueue(queue: address[MAXIMUM_STRATEGIES]): + """ + @notice + Updates the withdrawalQueue to match the addresses and order specified + by `queue`. + There can be fewer strategies than the maximum, as well as fewer than + the total number of strategies active in the vault. `withdrawalQueue` + will be updated in a gas-efficient manner, assuming the input is well- + ordered with 0x0 only at the end. + This may only be called by governance or management. + @dev + This is order sensitive, specify the addresses in the order in which + funds should be withdrawn (so `queue`[0] is the first Strategy withdrawn + from, `queue`[1] is the second, etc.) + This means that the least impactful Strategy (the Strategy that will have + its core positions impacted the least by having funds removed) should be + at `queue`[0], then the next least impactful at `queue`[1], and so on. + @param queue + The array of addresses to use as the new withdrawal queue. This is + order sensitive. + """ + assert msg.sender in [self.management, self.governance] + # HACK: Temporary until Vyper adds support for Dynamic arrays + for i in range(MAXIMUM_STRATEGIES): + if queue[i] == ZERO_ADDRESS and self.withdrawalQueue[i] == ZERO_ADDRESS: + break + assert self.strategies[queue[i]].activation > 0 + self.withdrawalQueue[i] = queue[i] + log UpdateWithdrawalQueue(queue) + + +@internal +def erc20_safe_transfer(token: address, receiver: address, amount: uint256): + # Used only to send tokens that are not the type managed by this Vault. + # HACK: Used to handle non-compliant tokens like USDT + response: Bytes[32] = raw_call( + token, + concat( + method_id("transfer(address,uint256)"), + convert(receiver, bytes32), + convert(amount, bytes32), + ), + max_outsize=32, + ) + if len(response) > 0: + assert convert(response, bool), "Transfer failed!" + + +@internal +def erc20_safe_transferFrom(token: address, sender: address, receiver: address, amount: uint256): + # Used only to send tokens that are not the type managed by this Vault. + # HACK: Used to handle non-compliant tokens like USDT + response: Bytes[32] = raw_call( + token, + concat( + method_id("transferFrom(address,address,uint256)"), + convert(sender, bytes32), + convert(receiver, bytes32), + convert(amount, bytes32), + ), + max_outsize=32, + ) + if len(response) > 0: + assert convert(response, bool), "Transfer failed!" + + +@internal +def _transfer(sender: address, receiver: address, amount: uint256): + # See note on `transfer()`. + + # Protect people from accidentally sending their shares to bad places + assert not (receiver in [self, ZERO_ADDRESS]) + self.balanceOf[sender] -= amount + self.balanceOf[receiver] += amount + log Transfer(sender, receiver, amount) + + +@external +def transfer(receiver: address, amount: uint256) -> bool: + """ + @notice + Transfers shares from the caller's address to `receiver`. This function + will always return true, unless the user is attempting to transfer + shares to this contract's address, or to 0x0. + @param receiver + The address shares are being transferred to. Must not be this contract's + address, must not be 0x0. + @param amount The quantity of shares to transfer. + @return + True if transfer is sent to an address other than this contract's or + 0x0, otherwise the transaction will fail. + """ + self._transfer(msg.sender, receiver, amount) + return True + + +@external +def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: + """ + @notice + Transfers `amount` shares from `sender` to `receiver`. This operation will + always return true, unless the user is attempting to transfer shares + to this contract's address, or to 0x0. + Unless the caller has given this contract unlimited approval, + transfering shares will decrement the caller's `allowance` by `amount`. + @param sender The address shares are being transferred from. + @param receiver + The address shares are being transferred to. Must not be this contract's + address, must not be 0x0. + @param amount The quantity of shares to transfer. + @return + True if transfer is sent to an address other than this contract's or + 0x0, otherwise the transaction will fail. + """ + # Unlimited approval (saves an SSTORE) + if (self.allowance[sender][msg.sender] < MAX_UINT256): + allowance: uint256 = self.allowance[sender][msg.sender] - amount + self.allowance[sender][msg.sender] = allowance + # NOTE: Allows log filters to have a full accounting of allowance changes + log Approval(sender, msg.sender, allowance) + self._transfer(sender, receiver, amount) + return True + + +@external +def approve(spender: address, amount: uint256) -> bool: + """ + @dev Approve the passed address to spend the specified amount of tokens on behalf of + `msg.sender`. Beware that changing an allowance with this method brings the risk + that someone may use both the old and the new allowance by unfortunate transaction + ordering. See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param spender The address which will spend the funds. + @param amount The amount of tokens to be spent. + """ + self.allowance[msg.sender][spender] = amount + log Approval(msg.sender, spender, amount) + return True + + +@external +def increaseAllowance(spender: address, amount: uint256) -> bool: + """ + @dev Increase the allowance of the passed address to spend the total amount of tokens + on behalf of msg.sender. This method mitigates the risk that someone may use both + the old and the new allowance by unfortunate transaction ordering. + See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param spender The address which will spend the funds. + @param amount The amount of tokens to increase the allowance by. + """ + self.allowance[msg.sender][spender] += amount + log Approval(msg.sender, spender, self.allowance[msg.sender][spender]) + return True + + +@external +def decreaseAllowance(spender: address, amount: uint256) -> bool: + """ + @dev Decrease the allowance of the passed address to spend the total amount of tokens + on behalf of msg.sender. This method mitigates the risk that someone may use both + the old and the new allowance by unfortunate transaction ordering. + See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param spender The address which will spend the funds. + @param amount The amount of tokens to decrease the allowance by. + """ + self.allowance[msg.sender][spender] -= amount + log Approval(msg.sender, spender, self.allowance[msg.sender][spender]) + return True + + +@external +def permit(owner: address, spender: address, amount: uint256, expiry: uint256, signature: Bytes[65]) -> bool: + """ + @notice + Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @param owner The address which is a source of funds and has signed the Permit. + @param spender The address which is allowed to spend the funds. + @param amount The amount of tokens to be spent. + @param expiry The timestamp after which the Permit is no longer valid. + @param signature A valid secp256k1 signature of Permit by owner encoded as r, s, v. + @return True, if transaction completes successfully + """ + assert owner != ZERO_ADDRESS # dev: invalid owner + assert expiry == 0 or expiry >= block.timestamp # dev: permit expired + nonce: uint256 = self.nonces[owner] + digest: bytes32 = keccak256( + concat( + b'\x19\x01', + self.DOMAIN_SEPARATOR, + keccak256( + concat( + PERMIT_TYPE_HASH, + convert(owner, bytes32), + convert(spender, bytes32), + convert(amount, bytes32), + convert(nonce, bytes32), + convert(expiry, bytes32), + ) + ) + ) + ) + # NOTE: signature is packed as r, s, v + r: uint256 = convert(slice(signature, 0, 32), uint256) + s: uint256 = convert(slice(signature, 32, 32), uint256) + v: uint256 = convert(slice(signature, 64, 1), uint256) + assert ecrecover(digest, v, r, s) == owner # dev: invalid signature + self.allowance[owner][spender] = amount + self.nonces[owner] = nonce + 1 + log Approval(owner, spender, amount) + return True + + +@view +@internal +def _totalAssets() -> uint256: + # See note on `totalAssets()`. + return self.token.balanceOf(self) + self.totalDebt + + +@view +@external +def totalAssets() -> uint256: + """ + @notice + Returns the total quantity of all assets under control of this + Vault, whether they're loaned out to a Strategy, or currently held in + the Vault. + @return The total assets under control of this Vault. + """ + return self._totalAssets() + + +@internal +def _issueSharesForAmount(to: address, amount: uint256) -> uint256: + # Issues `amount` Vault shares to `to`. + # Shares must be issued prior to taking on new collateral, or + # calculation will be wrong. This means that only *trusted* tokens + # (with no capability for exploitative behavior) can be used. + shares: uint256 = 0 + # HACK: Saves 2 SLOADs (~4000 gas) + totalSupply: uint256 = self.totalSupply + if totalSupply > 0: + # Mint amount of shares based on what the Vault is managing overall + # NOTE: if sqrt(token.totalSupply()) > 1e39, this could potentially revert + shares = amount * totalSupply / self._totalAssets() + else: + # No existing shares, so mint 1:1 + shares = amount + + # Mint new shares + self.totalSupply = totalSupply + shares + self.balanceOf[to] += shares + log Transfer(ZERO_ADDRESS, to, shares) + + return shares + + +@external +@nonreentrant("withdraw") +def deposit(_amount: uint256 = MAX_UINT256, recipient: address = msg.sender) -> uint256: + """ + @notice + Deposits `_amount` `token`, issuing shares to `recipient`. If the + Vault is in Emergency Shutdown, deposits will not be accepted and this + call will fail. + @dev + Measuring quantity of shares to issues is based on the total + outstanding debt that this contract has ("expected value") instead + of the total balance sheet it has ("estimated value") has important + security considerations, and is done intentionally. If this value were + measured against external systems, it could be purposely manipulated by + an attacker to withdraw more assets than they otherwise should be able + to claim by redeeming their shares. + On deposit, this means that shares are issued against the total amount + that the deposited capital can be given in service of the debt that + Strategies assume. If that number were to be lower than the "expected + value" at some future point, depositing shares via this method could + entitle the depositor to *less* than the deposited value once the + "realized value" is updated from further reports by the Strategies + to the Vaults. + Care should be taken by integrators to account for this discrepancy, + by using the view-only methods of this contract (both off-chain and + on-chain) to determine if depositing into the Vault is a "good idea". + @param _amount The quantity of tokens to deposit, defaults to all. + @param recipient + The address to issue the shares in this Vault to. Defaults to the + caller's address. + @return The issued Vault shares. + """ + assert not self.emergencyShutdown # Deposits are locked out + + amount: uint256 = _amount + + # If _amount not specified, transfer the full token balance, + # up to deposit limit + if amount == MAX_UINT256: + amount = min( + self.depositLimit - self._totalAssets(), + self.token.balanceOf(msg.sender), + ) + else: + # Ensure deposit limit is respected + assert self._totalAssets() + amount <= self.depositLimit + + # Ensure we are depositing something + assert amount > 0 + + # Ensure deposit is permitted by guest list + if self.guestList.address != ZERO_ADDRESS: + assert self.guestList.authorized(msg.sender, amount) + + # Issue new shares (needs to be done before taking deposit to be accurate) + # Shares are issued to recipient (may be different from msg.sender) + # See @dev note, above. + shares: uint256 = self._issueSharesForAmount(recipient, amount) + + # Tokens are transferred from msg.sender (may be different from _recipient) + self.erc20_safe_transferFrom(self.token.address, msg.sender, self, amount) + + return shares # Just in case someone wants them + + +@view +@internal +def _shareValue(shares: uint256) -> uint256: + # Returns price = 1:1 if vault is empty + if self.totalSupply == 0: + return shares + + # Determines the current value of `shares`. + # NOTE: if sqrt(Vault.totalAssets()) >>> 1e39, this could potentially revert + lockedFundsRatio: uint256 = (block.timestamp - self.lastReport) * self.lockedProfitDegration + freeFunds: uint256 = self._totalAssets() + + if(lockedFundsRatio < DEGREDATION_COEFFICIENT): + freeFunds -= (self.lockedProfit - (lockedFundsRatio * self.lockedProfit / DEGREDATION_COEFFICIENT)) + # NOTE: using 1e3 for extra precision here, when decimals is low + return ((10 ** 3 * (shares * freeFunds)) / self.totalSupply) / 10 ** 3 + + +@view +@internal +def _sharesForAmount(amount: uint256) -> uint256: + # Determines how many shares `amount` of token would receive. + # See dev note on `deposit`. + if self._totalAssets() > 0: + # NOTE: if sqrt(token.totalSupply()) > 1e37, this could potentially revert + return ((10 ** 3 * (amount * self.totalSupply)) / self._totalAssets()) / 10 ** 3 + else: + return 0 + + +@view +@external +def maxAvailableShares() -> uint256: + """ + @notice + Determines the maximum quantity of shares this Vault can facilitate a + withdrawal for, factoring in assets currently residing in the Vault, + as well as those deployed to strategies on the Vault's balance sheet. + @dev + Regarding how shares are calculated, see dev note on `deposit`. + If you want to calculated the maximum a user could withdraw up to, + you want to use this function. + Note that the amount provided by this function is the theoretical + maximum possible from withdrawing, the real amount depends on the + realized losses incurred during withdrawal. + @return The total quantity of shares this Vault can provide. + """ + shares: uint256 = self._sharesForAmount(self.token.balanceOf(self)) + + for strategy in self.withdrawalQueue: + if strategy == ZERO_ADDRESS: + break + shares += self._sharesForAmount(self.strategies[strategy].totalDebt) + + return shares + + +@internal +def _updateDelegatedAssets(strategy: address): + self.delegatedAssets -= self._strategy_delegatedAssets[strategy] + # NOTE: Use `min(totalDebt, delegatedAssets)` as a guard against improper computation + delegatedAssets: uint256 = min( + self.strategies[strategy].totalDebt, + Strategy(strategy).delegatedAssets(), + ) + self.delegatedAssets += delegatedAssets + self._strategy_delegatedAssets[strategy] = delegatedAssets + + +@external +@nonreentrant("withdraw") +def withdraw( + maxShares: uint256 = MAX_UINT256, + recipient: address = msg.sender, + maxLoss: uint256 = 1, # 0.01% [BPS] +) -> uint256: + """ + @notice + Withdraws the calling account's tokens from this Vault, redeeming + amount `_shares` for an appropriate amount of tokens. + See note on `setWithdrawalQueue` for further details of withdrawal + ordering and behavior. + @dev + Measuring the value of shares is based on the total outstanding debt + that this contract has ("expected value") instead of the total balance + sheet it has ("estimated value") has important security considerations, + and is done intentionally. If this value were measured against external + systems, it could be purposely manipulated by an attacker to withdraw + more assets than they otherwise should be able to claim by redeeming + their shares. + On withdrawal, this means that shares are redeemed against the total + amount that the deposited capital had "realized" since the point it + was deposited, up until the point it was withdrawn. If that number + were to be higher than the "expected value" at some future point, + withdrawing shares via this method could entitle the depositor to + *more* than the expected value once the "realized value" is updated + from further reports by the Strategies to the Vaults. + Under exceptional scenarios, this could cause earlier withdrawals to + earn "more" of the underlying assets than Users might otherwise be + entitled to, if the Vault's estimated value were otherwise measured + through external means, accounting for whatever exceptional scenarios + exist for the Vault (that aren't covered by the Vault's own design.) + @param maxShares + How many shares to try and redeem for tokens, defaults to all. + @param recipient + The address to issue the shares in this Vault to. Defaults to the + caller's address. + @param maxLoss + The maximum acceptable loss to sustain on withdrawal. Defaults to 0.01%. + @return The quantity of tokens redeemed for `_shares`. + """ + shares: uint256 = maxShares # May reduce this number below + + # Max Loss is <=100%, revert otherwise + assert maxLoss <= MAX_BPS + + # If _shares not specified, transfer full share balance + if shares == MAX_UINT256: + shares = self.balanceOf[msg.sender] + + # Limit to only the shares they own + assert shares <= self.balanceOf[msg.sender] + + # Ensure we are withdrawing something + assert shares > 0 + + # See @dev note, above. + value: uint256 = self._shareValue(shares) + + totalLoss: uint256 = 0 + if value > self.token.balanceOf(self): + # We need to go get some from our strategies in the withdrawal queue + # NOTE: This performs forced withdrawals from each Strategy. During + # forced withdrawal, a Strategy may realize a loss. That loss + # is reported back to the Vault, and the will affect the amount + # of tokens that the withdrawer receives for their shares. They + # can optionally specify the maximum acceptable loss (in BPS) + # to prevent excessive losses on their withdrawals (which may + # happen in certain edge cases where Strategies realize a loss) + for strategy in self.withdrawalQueue: + if strategy == ZERO_ADDRESS: + break # We've exhausted the queue + + vault_balance: uint256 = self.token.balanceOf(self) + if value <= vault_balance: + break # We're done withdrawing + + amountNeeded: uint256 = value - vault_balance + + # NOTE: Don't withdraw more than the debt so that Strategy can still + # continue to work based on the profits it has + # NOTE: This means that user will lose out on any profits that each + # Strategy in the queue would return on next harvest, benefiting others + amountNeeded = min(amountNeeded, self.strategies[strategy].totalDebt) + if amountNeeded == 0: + continue # Nothing to withdraw from this Strategy, try the next one + + # Force withdraw amount from each Strategy in the order set by governance + loss: uint256 = Strategy(strategy).withdraw(amountNeeded) + withdrawn: uint256 = self.token.balanceOf(self) - vault_balance + + # NOTE: Withdrawer incurs any losses from liquidation + if loss > 0: + value -= loss + totalLoss += loss + self.strategies[strategy].totalLoss += loss + + # Reduce the Strategy's debt by the amount withdrawn ("realized returns") + # NOTE: This doesn't add to returns as it's not earned by "normal means" + self.strategies[strategy].totalDebt -= withdrawn + loss + self.totalDebt -= withdrawn + loss + + # Ensure that delegated asset cached value is kept up to date + self._updateDelegatedAssets(strategy) + + # NOTE: We have withdrawn everything possible out of the withdrawal queue + # but we still don't have enough to fully pay them back, so adjust + # to the total amount we've freed up through forced withdrawals + vault_balance: uint256 = self.token.balanceOf(self) + if value > vault_balance: + value = vault_balance + # NOTE: Burn # of shares that corresponds to what Vault has on-hand, + # including the losses that were incurred above during withdrawals + shares = self._sharesForAmount(value + totalLoss) + + # NOTE: This loss protection is put in place to revert if losses from + # withdrawing are more than what is considered acceptable. + assert totalLoss <= maxLoss * (value + totalLoss) / MAX_BPS + + # Burn shares (full value of what is being withdrawn) + self.totalSupply -= shares + self.balanceOf[msg.sender] -= shares + log Transfer(msg.sender, ZERO_ADDRESS, shares) + + # Withdraw remaining balance to _recipient (may be different to msg.sender) (minus fee) + self.erc20_safe_transfer(self.token.address, recipient, value) + + return value + + +@view +@external +def pricePerShare() -> uint256: + """ + @notice Gives the price for a single Vault share. + @dev See dev note on `withdraw`. + @return The value of a single share. + """ + return self._shareValue(10 ** self.decimals) + + +@internal +def _organizeWithdrawalQueue(): + # Reorganize `withdrawalQueue` based on premise that if there is an + # empty value between two actual values, then the empty value should be + # replaced by the later value. + # NOTE: Relative ordering of non-zero values is maintained. + offset: uint256 = 0 + for idx in range(MAXIMUM_STRATEGIES): + strategy: address = self.withdrawalQueue[idx] + if strategy == ZERO_ADDRESS: + offset += 1 # how many values we need to shift, always `<= idx` + elif offset > 0: + self.withdrawalQueue[idx - offset] = strategy + self.withdrawalQueue[idx] = ZERO_ADDRESS + + +@external +def addStrategy( + strategy: address, + debtRatio: uint256, + minDebtPerHarvest: uint256, + maxDebtPerHarvest: uint256, + performanceFee: uint256, +): + """ + @notice + Add a Strategy to the Vault. + This may only be called by governance. + @dev + The Strategy will be appended to `withdrawalQueue`, call + `setWithdrawalQueue` to change the order. + @param strategy The address of the Strategy to add. + @param debtRatio + The share of the total assets in the `vault that the `strategy` has access to. + @param minDebtPerHarvest + Lower limit on the increase of debt since last harvest + @param maxDebtPerHarvest + Upper limit on the increase of debt since last harvest + @param performanceFee + The fee the strategist will receive based on this Vault's performance. + """ + # Check if queue is full + assert self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] == ZERO_ADDRESS + + # Check calling conditions + assert not self.emergencyShutdown + assert msg.sender == self.governance + + # Check strategy configuration + assert strategy != ZERO_ADDRESS + assert self.strategies[strategy].activation == 0 + assert self == Strategy(strategy).vault() + assert self.token.address == Strategy(strategy).want() + + # Check strategy parameters + assert self.debtRatio + debtRatio <= MAX_BPS + assert minDebtPerHarvest <= maxDebtPerHarvest + assert performanceFee <= MAX_BPS - self.performanceFee + + # Add strategy to approved strategies + self.strategies[strategy] = StrategyParams({ + performanceFee: performanceFee, + activation: block.timestamp, + debtRatio: debtRatio, + minDebtPerHarvest: minDebtPerHarvest, + maxDebtPerHarvest: maxDebtPerHarvest, + lastReport: block.timestamp, + totalDebt: 0, + totalGain: 0, + totalLoss: 0, + }) + log StrategyAdded(strategy, debtRatio, minDebtPerHarvest, maxDebtPerHarvest, performanceFee) + + # Update Vault parameters + self.debtRatio += debtRatio + + # Add strategy to the end of the withdrawal queue + self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy + self._organizeWithdrawalQueue() + + +@external +def updateStrategyDebtRatio( + strategy: address, + debtRatio: uint256, +): + """ + @notice + Change the quantity of assets `strategy` may manage. + This may be called by governance or management. + @param strategy The Strategy to update. + @param debtRatio The quantity of assets `strategy` may now manage. + """ + assert msg.sender in [self.management, self.governance] + assert self.strategies[strategy].activation > 0 + self.debtRatio -= self.strategies[strategy].debtRatio + self.strategies[strategy].debtRatio = debtRatio + self.debtRatio += debtRatio + assert self.debtRatio <= MAX_BPS + log StrategyUpdateDebtRatio(strategy, debtRatio) + + +@external +def updateStrategyMinDebtPerHarvest( + strategy: address, + minDebtPerHarvest: uint256, +): + """ + @notice + Change the quantity assets per block this Vault may deposit to or + withdraw from `strategy`. + This may only be called by governance or management. + @param strategy The Strategy to update. + @param minDebtPerHarvest + Lower limit on the increase of debt since last harvest + """ + assert msg.sender in [self.management, self.governance] + assert self.strategies[strategy].activation > 0 + assert self.strategies[strategy].maxDebtPerHarvest >= minDebtPerHarvest + self.strategies[strategy].minDebtPerHarvest = minDebtPerHarvest + log StrategyUpdateMinDebtPerHarvest(strategy, minDebtPerHarvest) + + +@external +def updateStrategyMaxDebtPerHarvest( + strategy: address, + maxDebtPerHarvest: uint256, +): + """ + @notice + Change the quantity assets per block this Vault may deposit to or + withdraw from `strategy`. + This may only be called by governance or management. + @param strategy The Strategy to update. + @param maxDebtPerHarvest + Upper limit on the increase of debt since last harvest + """ + assert msg.sender in [self.management, self.governance] + assert self.strategies[strategy].activation > 0 + assert self.strategies[strategy].minDebtPerHarvest <= maxDebtPerHarvest + self.strategies[strategy].maxDebtPerHarvest = maxDebtPerHarvest + log StrategyUpdateMaxDebtPerHarvest(strategy, maxDebtPerHarvest) + + +@external +def updateStrategyPerformanceFee( + strategy: address, + performanceFee: uint256, +): + """ + @notice + Change the fee the strategist will receive based on this Vault's + performance. + This may only be called by governance. + @param strategy The Strategy to update. + @param performanceFee The new fee the strategist will receive. + """ + assert msg.sender == self.governance + assert performanceFee <= MAX_BPS - self.performanceFee + assert self.strategies[strategy].activation > 0 + self.strategies[strategy].performanceFee = performanceFee + log StrategyUpdatePerformanceFee(strategy, performanceFee) + + +@internal +def _revokeStrategy(strategy: address): + self.debtRatio -= self.strategies[strategy].debtRatio + self.strategies[strategy].debtRatio = 0 + log StrategyRevoked(strategy) + + +@external +def migrateStrategy(oldVersion: address, newVersion: address): + """ + @notice + Migrates a Strategy, including all assets from `oldVersion` to + `newVersion`. + This may only be called by governance. + @dev + Strategy must successfully migrate all capital and positions to new + Strategy, or else this will upset the balance of the Vault. + The new Strategy should be "empty" e.g. have no prior commitments to + this Vault, otherwise it could have issues. + @param oldVersion The existing Strategy to migrate from. + @param newVersion The new Strategy to migrate to. + """ + assert msg.sender == self.governance + assert newVersion != ZERO_ADDRESS + assert self.strategies[oldVersion].activation > 0 + assert self.strategies[newVersion].activation == 0 + + strategy: StrategyParams = self.strategies[oldVersion] + + self._revokeStrategy(oldVersion) + # _revokeStrategy will lower the debtRatio + self.debtRatio += strategy.debtRatio + # Debt is migrated to new strategy + self.strategies[oldVersion].totalDebt = 0 + + self.strategies[newVersion] = StrategyParams({ + performanceFee: strategy.performanceFee, + # NOTE: use last report for activation time, so E[R] calc works + activation: strategy.lastReport, + debtRatio: strategy.debtRatio, + minDebtPerHarvest: strategy.minDebtPerHarvest, + maxDebtPerHarvest: strategy.maxDebtPerHarvest, + lastReport: strategy.lastReport, + totalDebt: strategy.totalDebt, + totalGain: 0, + totalLoss: 0, + }) + + Strategy(oldVersion).migrate(newVersion) + log StrategyMigrated(oldVersion, newVersion) + + for idx in range(MAXIMUM_STRATEGIES): + if self.withdrawalQueue[idx] == oldVersion: + self.withdrawalQueue[idx] = newVersion + return # Don't need to reorder anything because we swapped + + +@external +def revokeStrategy(strategy: address = msg.sender): + """ + @notice + Revoke a Strategy, setting its debt limit to 0 and preventing any + future deposits. + This function should only be used in the scenario where the Strategy is + being retired but no migration of the positions are possible, or in the + extreme scenario that the Strategy needs to be put into "Emergency Exit" + mode in order for it to exit as quickly as possible. The latter scenario + could be for any reason that is considered "critical" that the Strategy + exits its position as fast as possible, such as a sudden change in market + conditions leading to losses, or an imminent failure in an external + dependency. + This may only be called by governance, the guardian, or the Strategy + itself. Note that a Strategy will only revoke itself during emergency + shutdown. + @param strategy The Strategy to revoke. + """ + assert msg.sender in [strategy, self.governance, self.guardian] + self._revokeStrategy(strategy) + + +@external +def addStrategyToQueue(strategy: address): + """ + @notice + Adds `strategy` to `withdrawalQueue`. + This may only be called by governance or management. + @dev + The Strategy will be appended to `withdrawalQueue`, call + `setWithdrawalQueue` to change the order. + @param strategy The Strategy to add. + """ + assert msg.sender in [self.management, self.governance] + # Must be a current Strategy + assert self.strategies[strategy].activation > 0 + # Can't already be in the queue + last_idx: uint256 = 0 + for s in self.withdrawalQueue: + if s == ZERO_ADDRESS: + break + assert s != strategy + last_idx += 1 + # Check if queue is full + assert last_idx < MAXIMUM_STRATEGIES + + self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy + self._organizeWithdrawalQueue() + log StrategyAddedToQueue(strategy) + + +@external +def removeStrategyFromQueue(strategy: address): + """ + @notice + Remove `strategy` from `withdrawalQueue`. + This may only be called by governance or management. + @dev + We don't do this with revokeStrategy because it should still + be possible to withdraw from the Strategy if it's unwinding. + @param strategy The Strategy to remove. + """ + assert msg.sender in [self.management, self.governance] + for idx in range(MAXIMUM_STRATEGIES): + if self.withdrawalQueue[idx] == strategy: + self.withdrawalQueue[idx] = ZERO_ADDRESS + self._organizeWithdrawalQueue() + log StrategyRemovedFromQueue(strategy) + return # We found the right location and cleared it + raise # We didn't find the Strategy in the queue + + +@view +@internal +def _debtOutstanding(strategy: address) -> uint256: + # See note on `debtOutstanding()`. + strategy_debtLimit: uint256 = self.strategies[strategy].debtRatio * self._totalAssets() / MAX_BPS + strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt + + if self.emergencyShutdown: + return strategy_totalDebt + elif strategy_totalDebt <= strategy_debtLimit: + return 0 + else: + return strategy_totalDebt - strategy_debtLimit + + +@view +@external +def debtOutstanding(strategy: address = msg.sender) -> uint256: + """ + @notice + Determines if `strategy` is past its debt limit and if any tokens + should be withdrawn to the Vault. + @param strategy The Strategy to check. Defaults to the caller. + @return The quantity of tokens to withdraw. + """ + return self._debtOutstanding(strategy) + + +@view +@internal +def _creditAvailable(strategy: address) -> uint256: + # See note on `creditAvailable()`. + if self.emergencyShutdown: + return 0 + + vault_totalAssets: uint256 = self._totalAssets() + vault_debtLimit: uint256 = self.debtRatio * vault_totalAssets / MAX_BPS + vault_totalDebt: uint256 = self.totalDebt + strategy_debtLimit: uint256 = self.strategies[strategy].debtRatio * vault_totalAssets / MAX_BPS + strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt + strategy_minDebtPerHarvest: uint256 = self.strategies[strategy].minDebtPerHarvest + strategy_maxDebtPerHarvest: uint256 = self.strategies[strategy].maxDebtPerHarvest + + # Exhausted credit line + if strategy_debtLimit <= strategy_totalDebt or vault_debtLimit <= vault_totalDebt: + return 0 + + # Start with debt limit left for the Strategy + available: uint256 = strategy_debtLimit - strategy_totalDebt + + # Adjust by the global debt limit left + available = min(available, vault_debtLimit - vault_totalDebt) + + # Can only borrow up to what the contract has in reserve + # NOTE: Running near 100% is discouraged + available = min(available, self.token.balanceOf(self)) + + # Adjust by min and max borrow limits (per harvest) + # NOTE: min increase can be used to ensure that if a strategy has a minimum + # amount of capital needed to purchase a position, it's not given capital + # it can't make use of yet. + # NOTE: max increase is used to make sure each harvest isn't bigger than what + # is authorized. This combined with adjusting min and max periods in + # `BaseStrategy` can be used to effect a "rate limit" on capital increase. + if available < strategy_minDebtPerHarvest: + return 0 + else: + return min(available, strategy_maxDebtPerHarvest) + +@view +@external +def creditAvailable(strategy: address = msg.sender) -> uint256: + """ + @notice + Amount of tokens in Vault a Strategy has access to as a credit line. + This will check the Strategy's debt limit, as well as the tokens + available in the Vault, and determine the maximum amount of tokens + (if any) the Strategy may draw on. + In the rare case the Vault is in emergency shutdown this will return 0. + @param strategy The Strategy to check. Defaults to caller. + @return The quantity of tokens available for the Strategy to draw on. + """ + return self._creditAvailable(strategy) + + +@view +@internal +def _expectedReturn(strategy: address) -> uint256: + # See note on `expectedReturn()`. + strategy_lastReport: uint256 = self.strategies[strategy].lastReport + timeSinceLastHarvest: uint256 = block.timestamp - strategy_lastReport + totalHarvestTime: uint256 = strategy_lastReport - self.strategies[strategy].activation + + # NOTE: If either `timeSinceLastHarvest` or `totalHarvestTime` is 0, we can short-circuit to `0` + if timeSinceLastHarvest > 0 and totalHarvestTime > 0 and Strategy(strategy).isActive(): + # NOTE: Unlikely to throw unless strategy accumalates >1e68 returns + # NOTE: Calculate average over period of time where harvests have occured in the past + return (self.strategies[strategy].totalGain * timeSinceLastHarvest) / totalHarvestTime + else: + return 0 # Covers the scenario when block.timestamp == activation + + +@view +@external +def availableDepositLimit() -> uint256: + if self.depositLimit > self._totalAssets(): + return self.depositLimit - self._totalAssets() + else: + return 0 + + +@view +@external +def expectedReturn(strategy: address = msg.sender) -> uint256: + """ + @notice + Provide an accurate expected value for the return this `strategy` + would provide to the Vault the next time `report()` is called + (since the last time it was called). + @param strategy The Strategy to determine the expected return for. Defaults to caller. + @return + The anticipated amount `strategy` should make on its investment + since its last report. + """ + return self._expectedReturn(strategy) + + +@internal +def _reportLoss(strategy: address, loss: uint256): + # Loss can only be up the amount of debt issued to strategy + totalDebt: uint256 = self.strategies[strategy].totalDebt + assert totalDebt >= loss + self.strategies[strategy].totalLoss += loss + self.strategies[strategy].totalDebt = totalDebt - loss + self.totalDebt -= loss + + # Also, make sure we reduce our trust with the strategy by the same amount + debtRatio: uint256 = self.strategies[strategy].debtRatio + ratio_change: uint256 = min(loss * MAX_BPS / self._totalAssets(), debtRatio) + self.strategies[strategy].debtRatio -= ratio_change + self.debtRatio -= ratio_change + +@internal +def _assessFees(strategy: address, gain: uint256): + # Issue new shares to cover fees + # NOTE: In effect, this reduces overall share price by the combined fee + # NOTE: may throw if Vault.totalAssets() > 1e64, or not called for more than a year + governance_fee: uint256 = ( + ( + (self.totalDebt - self.delegatedAssets) + * (block.timestamp - self.lastReport) + * self.managementFee + ) + / MAX_BPS + / SECS_PER_YEAR + ) + strategist_fee: uint256 = 0 # Only applies in certain conditions + + # NOTE: Applies if Strategy is not shutting down, or it is but all debt paid off + # NOTE: No fee is taken when a Strategy is unwinding it's position, until all debt is paid + if gain > 0: + # NOTE: Unlikely to throw unless strategy reports >1e72 harvest profit + strategist_fee = ( + gain * self.strategies[strategy].performanceFee + ) / MAX_BPS + # NOTE: Unlikely to throw unless strategy reports >1e72 harvest profit + governance_fee += gain * self.performanceFee / MAX_BPS + + # NOTE: This must be called prior to taking new collateral, + # or the calculation will be wrong! + # NOTE: This must be done at the same time, to ensure the relative + # ratio of governance_fee : strategist_fee is kept intact + total_fee: uint256 = governance_fee + strategist_fee + if total_fee > 0: # NOTE: If mgmt fee is 0% and no gains were realized, skip + reward: uint256 = self._issueSharesForAmount(self, total_fee) + + # Send the rewards out as new shares in this Vault + if strategist_fee > 0: # NOTE: Guard against DIV/0 fault + # NOTE: Unlikely to throw unless sqrt(reward) >>> 1e39 + strategist_reward: uint256 = (strategist_fee * reward) / total_fee + self._transfer(self, strategy, strategist_reward) + # NOTE: Strategy distributes rewards at the end of harvest() + # NOTE: Governance earns any dust leftover from flooring math above + if self.balanceOf[self] > 0: + self._transfer(self, self.rewards, self.balanceOf[self]) + + +@external +def report(gain: uint256, loss: uint256, _debtPayment: uint256) -> uint256: + """ + @notice + Reports the amount of assets the calling Strategy has free (usually in + terms of ROI). + The performance fee is determined here, off of the strategy's profits + (if any), and sent to governance. + The strategist's fee is also determined here (off of profits), to be + handled according to the strategist on the next harvest. + This may only be called by a Strategy managed by this Vault. + @dev + For approved strategies, this is the most efficient behavior. + The Strategy reports back what it has free, then Vault "decides" + whether to take some back or give it more. Note that the most it can + take is `gain + _debtPayment`, and the most it can give is all of the + remaining reserves. Anything outside of those bounds is abnormal behavior. + All approved strategies must have increased diligence around + calling this function, as abnormal behavior could become catastrophic. + @param gain + Amount Strategy has realized as a gain on it's investment since its + last report, and is free to be given back to Vault as earnings + @param loss + Amount Strategy has realized as a loss on it's investment since its + last report, and should be accounted for on the Vault's balance sheet + @param _debtPayment + Amount Strategy has made available to cover outstanding debt + @return Amount of debt outstanding (if totalDebt > debtLimit or emergency shutdown). + """ + + # Only approved strategies can call this function + assert self.strategies[msg.sender].activation > 0 + # No lying about total available to withdraw! + assert self.token.balanceOf(msg.sender) >= gain + _debtPayment + + # We have a loss to report, do it before the rest of the calculations + if loss > 0: + self._reportLoss(msg.sender, loss) + + # Assess both management fee and performance fee, and issue both as shares of the vault + self._assessFees(msg.sender, gain) + + # Returns are always "realized gains" + self.strategies[msg.sender].totalGain += gain + + # Outstanding debt the Strategy wants to take back from the Vault (if any) + # NOTE: debtOutstanding <= StrategyParams.totalDebt + debt: uint256 = self._debtOutstanding(msg.sender) + debtPayment: uint256 = min(_debtPayment, debt) + + if debtPayment > 0: + self.strategies[msg.sender].totalDebt -= debtPayment + self.totalDebt -= debtPayment + debt -= debtPayment + # NOTE: `debt` is being tracked for later + + # Compute the line of credit the Vault is able to offer the Strategy (if any) + credit: uint256 = self._creditAvailable(msg.sender) + + # Update the actual debt based on the full credit we are extending to the Strategy + # or the returns if we are taking funds back + # NOTE: credit + self.strategies[msg.sender].totalDebt is always < self.debtLimit + # NOTE: At least one of `credit` or `debt` is always 0 (both can be 0) + if credit > 0: + self.strategies[msg.sender].totalDebt += credit + self.totalDebt += credit + + # Give/take balance to Strategy, based on the difference between the reported gains + # (if any), the debt payment (if any), the credit increase we are offering (if any), + # and the debt needed to be paid off (if any) + # NOTE: This is just used to adjust the balance of tokens between the Strategy and + # the Vault based on the Strategy's debt limit (as well as the Vault's). + totalAvail: uint256 = gain + debtPayment + if totalAvail < credit: # credit surplus, give to Strategy + self.erc20_safe_transfer(self.token.address, msg.sender, credit - totalAvail) + elif totalAvail > credit: # credit deficit, take from Strategy + self.erc20_safe_transferFrom(self.token.address, msg.sender, self, totalAvail - credit) + # else, don't do anything because it is balanced + + # Update cached value of delegated assets + # (used to properly account for mgmt fee in `_assessFees`) + self._updateDelegatedAssets(msg.sender) + + # Update reporting time + self.strategies[msg.sender].lastReport = block.timestamp + self.lastReport = block.timestamp + self.lockedProfit = gain # profit is locked and gradually released per block + + log StrategyReported( + msg.sender, + gain, + loss, + debtPayment, + self.strategies[msg.sender].totalGain, + self.strategies[msg.sender].totalLoss, + self.strategies[msg.sender].totalDebt, + credit, + self.strategies[msg.sender].debtRatio, + ) + + if self.strategies[msg.sender].debtRatio == 0 or self.emergencyShutdown: + # Take every last penny the Strategy has (Emergency Exit/revokeStrategy) + # NOTE: This is different than `debt` in order to extract *all* of the returns + return Strategy(msg.sender).estimatedTotalAssets() + else: + # Otherwise, just return what we have as debt outstanding + return debt + + +@external +def sweep(token: address, amount: uint256 = MAX_UINT256): + """ + @notice + Removes tokens from this Vault that are not the type of token managed + by this Vault. This may be used in case of accidentally sending the + wrong kind of token to this Vault. + Tokens will be sent to `governance`. + This will fail if an attempt is made to sweep the tokens that this + Vault manages. + This may only be called by governance. + @param token The token to transfer out of this vault. + @param amount The quantity or tokenId to transfer out. + """ + assert msg.sender == self.governance + # Can't be used to steal what this Vault is protecting + assert token != self.token.address + value: uint256 = amount + if value == MAX_UINT256: + value = ERC20(token).balanceOf(self) + self.erc20_safe_transfer(token, self.governance, value) diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts new file mode 100644 index 000000000..13af643c1 --- /dev/null +++ b/test/fixtures/yearn.spec.ts @@ -0,0 +1,69 @@ +import "module-alias/register"; + +import { Account } from "@utils/test/types"; +import DeployHelper from "@utils/deploys"; +import { + ether, +} from "@utils/index"; +import { + addSnapshotBeforeRestoreAfterEach, + getAccounts, + getYearnFixture, + getWaffleExpect +} from "@utils/test/index"; +import { StandardTokenMock } from "../../typechain/StandardTokenMock"; +import { YearnFixture } from "@utils/fixtures"; +import { Vault } from "../../typechain/Vault"; + + +const expect = getWaffleExpect(); + +describe("YearnFixture", () => { + let owner: Account; + + // let setup: SystemFixture; + let yearnSetup: YearnFixture; + let dai: StandardTokenMock; + + before(async () => { + [ + owner, + ] = await getAccounts(); + + // setup = getSystemFixture(owner.address); + const deployer = new DeployHelper(owner.wallet); + dai = await deployer.mocks.deployTokenMock(owner.address, ether(10000), 18); + + yearnSetup = getYearnFixture(owner.address); + + // await setup.initialize(); + await yearnSetup.initialize(); + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#createAndEnableVaultWithStrategyMock", async () => { + async function subject(): Promise { + return await yearnSetup.createAndEnableVaultWithStrategyMock( + dai.address, + owner.address, + owner.address, + owner.address, + "MockStrategy", + "M", + ether(100) + ); + } + + it("should create and enable a vault", async () => { + const vault = await subject(); + + const governance = await vault.pricePerShare(); + expect(governance).to.eq(ether(1)); + + // const strategies = await vault.strategies(); + // console.log("strategies", strategies); + }); + }); + +}); diff --git a/test/integration/oracles/yearnVaultOracle.spec.ts b/test/integration/oracles/yearnVaultOracle.spec.ts new file mode 100644 index 000000000..822edc262 --- /dev/null +++ b/test/integration/oracles/yearnVaultOracle.spec.ts @@ -0,0 +1,134 @@ +import "module-alias/register"; +import { BigNumber } from "@ethersproject/bignumber"; + +import { Address } from "@utils/types"; +import { Account } from "@utils/test/types"; +import { OracleMock, YearnVaultOracle } from "@utils/contracts"; +import { Vault } from "../../../typechain/Vault"; + +import DeployHelper from "@utils/deploys"; + +import { + ether +} from "@utils/index"; +import { + getAccounts, + getWaffleExpect, + getSystemFixture, + getYearnFixture, + addSnapshotBeforeRestoreAfterEach, +} from "@utils/test/index"; +import { YearnFixture, SystemFixture } from "@utils/fixtures"; + +const expect = getWaffleExpect(); + +describe("CTokenOracle", () => { + let owner: Account; + let deployer: DeployHelper; + let setup: SystemFixture; + + let yearnSetup: YearnFixture; + let daiVault: Vault; + + let daiUsdcOracle: OracleMock; + let daiUsdcPrice: BigNumber; + let yearnVaultDaiOracle: YearnVaultOracle; + let daiFullUnit: BigNumber; + + before(async () => { + [ + owner, + ] = await getAccounts(); + + // System setup + deployer = new DeployHelper(owner.wallet); + setup = getSystemFixture(owner.address); + await setup.initialize(); + + // Yearn setup + yearnSetup = getYearnFixture(owner.address); + await yearnSetup.initialize(); + + daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( + setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) + ); + + daiUsdcPrice = ether(1); + daiUsdcOracle = await deployer.mocks.deployOracleMock(daiUsdcPrice); + daiFullUnit = BigNumber.from("1000000000000000000"); + yearnVaultDaiOracle = await deployer.oracles.deployYearnVaultOracle( + daiVault.address, + daiUsdcOracle.address, + daiFullUnit, + "yvDAIUSDC Oracle" + ); + + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#constructor", async () => { + let subjectVaultAddress: Address; + let subjectUnderlyingOracle: Address; + let subjectUnderlyingFullUnit: BigNumber; + let subjectDataDescription: string; + + before(async () => { + subjectVaultAddress = daiVault.address; + subjectUnderlyingFullUnit = BigNumber.from("1000000000000000000"); + subjectUnderlyingOracle = daiUsdcOracle.address; + subjectDataDescription = "yvDAI Oracle"; + }); + + async function subject(): Promise { + return deployer.oracles.deployYearnVaultOracle( + subjectVaultAddress, + subjectUnderlyingOracle, + subjectUnderlyingFullUnit, + subjectDataDescription + ); + } + + it("sets the correct vault address", async () => { + const yearVaultOracle = await subject(); + const vaultAddress = await yearVaultOracle.vault(); + expect(vaultAddress).to.equal(subjectVaultAddress); + }); + + it("sets the correct underlying full unit", async () => { + const yearVaultOracle = await subject(); + const underlyingFullUnit = await yearVaultOracle.underlyingFullUnit(); + expect(underlyingFullUnit).to.eq(subjectUnderlyingFullUnit); + }); + + it("sets the correct underlying oracle address", async () => { + const yearVaultOracle = await subject(); + const underlyingOracleAddress = await yearVaultOracle.underlyingOracle(); + expect(underlyingOracleAddress).to.eq(subjectUnderlyingOracle); + }); + + it("sets the correct data description", async () => { + const yearVaultOracle = await subject(); + const actualDataDescription = await yearVaultOracle.dataDescription(); + expect(actualDataDescription).to.eq(subjectDataDescription); + }); + + }); + + + describe("#read", async () => { + + async function subject(): Promise { + return yearnVaultDaiOracle.read(); + } + + it("returns the correct vault value", async () => { + const result = await subject(); + const expectedResult = ether(1) + .div(daiFullUnit) + .mul(daiUsdcPrice); + + expect(result).to.eq(expectedResult); + }); + }); +}); diff --git a/test/integration/yearnWrapModule.spec.ts b/test/integration/yearnWrapModule.spec.ts new file mode 100644 index 000000000..a3b26d4fd --- /dev/null +++ b/test/integration/yearnWrapModule.spec.ts @@ -0,0 +1,195 @@ +import "module-alias/register"; +import { BigNumber } from "@ethersproject/bignumber"; + +import { Address } from "@utils/types"; +import { Account } from "@utils/test/types"; +import { ADDRESS_ZERO } from "@utils/constants"; +import { YearnWrapAdapter, SetToken, WrapModule } from "@utils/contracts"; +import DeployHelper from "@utils/deploys"; +import { + ether, + preciseMul, +} from "@utils/index"; +import { + getAccounts, + getWaffleExpect, + getSystemFixture, + getYearnFixture, + addSnapshotBeforeRestoreAfterEach, +} from "@utils/test/index"; +import { YearnFixture, SystemFixture } from "@utils/fixtures"; +import { Vault } from "@utils/contracts/yearn"; + + +const expect = getWaffleExpect(); + +describe("yearnWrapModule", () => { + let owner: Account; + let deployer: DeployHelper; + let setup: SystemFixture; + + let yearnSetup: YearnFixture; + let daiVault: Vault; + + let wrapModule: WrapModule; + let yearnWrapAdapter: YearnWrapAdapter; + + const yearnWrapAdapterIntegrationName: string = "YEARN_WRAPPER"; + + before(async () => { + [ + owner, + ] = await getAccounts(); + + // System setup + deployer = new DeployHelper(owner.wallet); + setup = getSystemFixture(owner.address); + await setup.initialize(); + + // Yearn setup + yearnSetup = getYearnFixture(owner.address); + await yearnSetup.initialize(); + + daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( + setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) + ); + + // WrapModule setup + wrapModule = await deployer.modules.deployWrapModule(setup.controller.address, setup.weth.address); + await setup.controller.addModule(wrapModule.address); + + // CompoundWrapAdapter setup + yearnWrapAdapter = await deployer.adapters.deployYearnWrapAdapter(); + await setup.integrationRegistry.addIntegration(wrapModule.address, yearnWrapAdapterIntegrationName, yearnWrapAdapter.address); + }); + + addSnapshotBeforeRestoreAfterEach(); + + context("when a SetToken has been deployed and issued", async () => { + let setToken: SetToken; + let setTokensIssued: BigNumber; + + before(async () => { + setToken = await setup.createSetToken( + [setup.dai.address], + [ether(1)], + [setup.issuanceModule.address, wrapModule.address] + ); + + // Initialize modules + await setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO); + await wrapModule.initialize(setToken.address); + + // Issue some Sets + setTokensIssued = ether(10); + const underlyingRequired = setTokensIssued; + await setup.dai.approve(setup.issuanceModule.address, underlyingRequired); + await setup.issuanceModule.issue(setToken.address, setTokensIssued, owner.address); + }); + + describe("#wrap", async () => { + let subjectSetToken: Address; + let subjectUnderlyingToken: Address; + let subjectWrappedToken: Address; + let subjectUnderlyingUnits: BigNumber; + let subjectIntegrationName: string; + let subjectCaller: Account; + + beforeEach(async () => { + subjectSetToken = setToken.address; + subjectUnderlyingToken = setup.dai.address; + subjectWrappedToken = daiVault.address; + subjectUnderlyingUnits = ether(1); + subjectIntegrationName = yearnWrapAdapterIntegrationName; + subjectCaller = owner; + }); + + async function subject(): Promise { + return wrapModule.connect(subjectCaller.wallet).wrap( + subjectSetToken, + subjectUnderlyingToken, + subjectWrappedToken, + subjectUnderlyingUnits, + subjectIntegrationName, + ); + } + + it("should reduce the underlying quantity and mint the wrapped asset to the SetToken", async () => { + const previousUnderlyingBalance = await setup.dai.balanceOf(setToken.address); + const previousWrappedBalance = await daiVault.balanceOf(setToken.address); + + await subject(); + + const underlyingBalance = await setup.dai.balanceOf(setToken.address); + const wrappedBalance = await daiVault.balanceOf(setToken.address); + + const expectedUnderlyingBalance = previousUnderlyingBalance.sub(setTokensIssued); + expect(underlyingBalance).to.eq(expectedUnderlyingBalance); + + const expectedWrappedBalance = previousWrappedBalance.add(setTokensIssued); + expect(wrappedBalance).to.eq(expectedWrappedBalance); + }); + }); + + describe("#unwrap", () => { + let subjectSetToken: Address; + let subjectUnderlyingToken: Address; + let subjectWrappedToken: Address; + let subjectWrappedTokenUnits: BigNumber; + let subjectIntegrationName: string; + let subjectCaller: Account; + + let wrappedQuantity: BigNumber; + + beforeEach(async () => { + subjectSetToken = setToken.address; + subjectUnderlyingToken = setup.dai.address; + subjectWrappedToken = daiVault.address; + subjectWrappedTokenUnits = ether(0.5); + subjectIntegrationName = yearnWrapAdapterIntegrationName; + subjectCaller = owner; + + wrappedQuantity = ether(1); + + await wrapModule.wrap( + subjectSetToken, + subjectUnderlyingToken, + subjectWrappedToken, + wrappedQuantity, + subjectIntegrationName, + ); + }); + + async function subject(): Promise { + return wrapModule.connect(subjectCaller.wallet).unwrap( + subjectSetToken, + subjectUnderlyingToken, + subjectWrappedToken, + subjectWrappedTokenUnits, + subjectIntegrationName, + { + gasLimit: 5000000, + } + ); + } + + it("should burn the wrapped asset to the SetToken and increase the underlying quantity", async () => { + const previousUnderlyingBalance = await setup.dai.balanceOf(setToken.address); + const previousWrappedBalance = await daiVault.balanceOf(setToken.address); + + await subject(); + + const underlyingBalance = await setup.dai.balanceOf(setToken.address); + const wrappedBalance = await daiVault.balanceOf(setToken.address); + + const delta = preciseMul(setTokensIssued, wrappedQuantity.sub(subjectWrappedTokenUnits)); + + const expectedUnderlyingBalance = previousUnderlyingBalance.add(delta); + expect(underlyingBalance).to.eq(expectedUnderlyingBalance); + + const expectedWrappedBalance = previousWrappedBalance.sub(delta); + expect(wrappedBalance).to.eq(expectedWrappedBalance); + }); + }); + }); +}); diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index d6faf8ad3..c61e60381 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -5,6 +5,8 @@ export { AaveLendingPoolMock } from "../../typechain/AaveLendingPoolMock"; export { AaveMigrationWrapAdapter } from "../../typechain/AaveMigrationWrapAdapter"; export { AaveWrapAdapter } from "../../typechain/AaveWrapAdapter"; export { CompoundWrapAdapter } from "../../typechain/CompoundWrapAdapter"; +export { YearnWrapAdapter } from "../../typechain/YearnWrapAdapter"; +export { YearnStrategyMock } from "../../typechain/YearnStrategyMock"; export { AddressArrayUtilsMock } from "../../typechain/AddressArrayUtilsMock"; export { AirdropModule } from "../../typechain/AirdropModule"; export { AmmAdapterMock } from "../../typechain/AmmAdapterMock"; @@ -53,6 +55,7 @@ export { OneInchExchangeMock } from "../../typechain/OneInchExchangeMock"; export { OracleAdapterMock } from "../../typechain/OracleAdapterMock"; export { OracleMock } from "../../typechain/OracleMock"; export { CTokenOracle } from "../../typechain/CTokenOracle"; +export { YearnVaultOracle } from "../../typechain/YearnVaultOracle"; export { PositionMock } from "../../typechain/PositionMock"; export { PreciseUnitMathMock } from "../../typechain/PreciseUnitMathMock"; export { PriceOracle } from "../../typechain/PriceOracle"; diff --git a/utils/contracts/yearn.ts b/utils/contracts/yearn.ts new file mode 100644 index 000000000..02ea94ff1 --- /dev/null +++ b/utils/contracts/yearn.ts @@ -0,0 +1,3 @@ +// External Yearn Contracts +export { Vault } from "../../typechain/Vault"; +export { Registry } from "../../typechain/Registry"; diff --git a/utils/deploys/deployAdapters.ts b/utils/deploys/deployAdapters.ts index 52302c28a..85f2be534 100644 --- a/utils/deploys/deployAdapters.ts +++ b/utils/deploys/deployAdapters.ts @@ -10,6 +10,7 @@ import { AaveMigrationWrapAdapter, AaveWrapAdapter, CompoundWrapAdapter, + YearnWrapAdapter, UniswapPairPriceAdapter, UniswapV2ExchangeAdapter, UniswapV2ExchangeAdapterV2, @@ -32,6 +33,7 @@ import { ZeroExApiAdapter__factory } from "../../typechain/factories/ZeroExApiAd import { AaveMigrationWrapAdapter__factory } from "../../typechain/factories/AaveMigrationWrapAdapter__factory"; import { AaveWrapAdapter__factory } from "../../typechain/factories/AaveWrapAdapter__factory"; import { CompoundWrapAdapter__factory } from "../../typechain/factories/CompoundWrapAdapter__factory"; +import { YearnWrapAdapter__factory } from "../../typechain/factories/YearnWrapAdapter__factory"; import { UniswapPairPriceAdapter__factory } from "../../typechain/factories/UniswapPairPriceAdapter__factory"; import { UniswapV2ExchangeAdapter__factory } from "../../typechain/factories/UniswapV2ExchangeAdapter__factory"; import { UniswapV2ExchangeAdapterV2__factory } from "../../typechain/factories/UniswapV2ExchangeAdapterV2__factory"; @@ -101,6 +103,10 @@ export default class DeployAdapters { ).deploy(); } + public async deployYearnWrapAdapter(): Promise { + return await new YearnWrapAdapter__factory(this._deployerSigner).deploy(); + } + public async deployCompoundLikeGovernanceAdapter(governanceAlpha: Address, governanceToken: Address): Promise { return await new CompoundLikeGovernanceAdapter__factory(this._deployerSigner).deploy(governanceAlpha, governanceToken); } diff --git a/utils/deploys/deployExternal.ts b/utils/deploys/deployExternal.ts index 29e54577d..015344cc4 100644 --- a/utils/deploys/deployExternal.ts +++ b/utils/deploys/deployExternal.ts @@ -124,6 +124,14 @@ import { ExchangeProxy__factory } from "../../typechain/factories/ExchangeProxy_ import { DelegateRegistry__factory } from "../../typechain/factories/DelegateRegistry__factory"; +import { + Vault, + Registry +} from "../contracts/yearn"; +import { Registry__factory } from "../../typechain/factories/Registry__factory"; +import { Vault__factory } from "../../typechain/factories/Vault__factory"; + + export default class DeployExternalContracts { private _deployerSigner: Signer; @@ -518,4 +526,18 @@ export default class DeployExternalContracts { public async deployDelegateRegistry(): Promise { return await new DelegateRegistry__factory(this._deployerSigner).deploy(); } + + // YEARN + public async deployVault(): Promise { + return await new Vault__factory(this._deployerSigner).deploy(); + } + + public async getVault(vaultAddress: Address): Promise { + return await new Vault__factory(this._deployerSigner).attach(vaultAddress); + } + + public async deployRegistry(): Promise { + return await new Registry__factory(this._deployerSigner).deploy(); + } + } diff --git a/utils/deploys/deployMocks.ts b/utils/deploys/deployMocks.ts index 16516caad..9b76dfabd 100644 --- a/utils/deploys/deployMocks.ts +++ b/utils/deploys/deployMocks.ts @@ -40,6 +40,7 @@ import { Uint256ArrayUtilsMock, WrapAdapterMock, ZeroExMock, + YearnStrategyMock } from "../contracts"; import { convertLibraryNameToLinkId, ether } from "../common"; @@ -81,6 +82,7 @@ import { WrapAdapterMock__factory } from "../../typechain/factories/WrapAdapterM import { ZeroExMock__factory } from "../../typechain/factories/ZeroExMock__factory"; import { SynthMock__factory } from "../../typechain/factories/SynthMock__factory"; import { SynthetixExchangerMock__factory } from "../../typechain/factories/SynthetixExchangerMock__factory"; +import { YearnStrategyMock__factory } from "../../typechain/factories/YearnStrategyMock__factory"; export default class DeployMocks { private _deployerSigner: Signer; @@ -315,6 +317,10 @@ export default class DeployMocks { return await new CustomSetValuerMock__factory(this._deployerSigner).deploy(); } + public async deployYearnStrategyMock(vault: Address): Promise { + return await new YearnStrategyMock__factory(this._deployerSigner).deploy(vault); + } + /************************************* * Instance getters ************************************/ diff --git a/utils/deploys/deployOracles.ts b/utils/deploys/deployOracles.ts index e049d3652..65523fdc7 100644 --- a/utils/deploys/deployOracles.ts +++ b/utils/deploys/deployOracles.ts @@ -2,11 +2,10 @@ import { Signer } from "ethers"; import { Address } from "../types"; import { BigNumber } from "@ethersproject/bignumber"; -import { - CTokenOracle, -} from "../contracts"; +import { CTokenOracle, YearnVaultOracle } from "../contracts"; import { CTokenOracle__factory } from "../../typechain/factories/CTokenOracle__factory"; +import { YearnVaultOracle__factory } from "../../typechain/factories/YearnVaultOracle__factory"; export default class DeployOracles { private _deployerSigner: Signer; @@ -24,4 +23,13 @@ export default class DeployOracles { return await new CTokenOracle__factory(this._deployerSigner) .deploy(cToken, underlyingOracle, cTokenFullUnit, underlyingFullUnit, dataDescription); } + + public async deployYearnVaultOracle( + vault: Address, + underlyingOracle: Address, + underlyingFullUnit: BigNumber, + dataDescription: string): Promise { + return await new YearnVaultOracle__factory(this._deployerSigner).deploy(vault, underlyingOracle, underlyingFullUnit, dataDescription); + } + } diff --git a/utils/fixtures/index.ts b/utils/fixtures/index.ts index 90762cf86..dd4a01107 100644 --- a/utils/fixtures/index.ts +++ b/utils/fixtures/index.ts @@ -4,3 +4,4 @@ export { CompoundFixture } from "./compoundFixture"; export { CurveFixture } from "./curveFixture"; export { SystemFixture } from "./systemFixture"; export { UniswapFixture } from "./uniswapFixture"; +export { YearnFixture } from "./yearnFixture"; diff --git a/utils/fixtures/yearnFixture.ts b/utils/fixtures/yearnFixture.ts new file mode 100644 index 000000000..26d2cc910 --- /dev/null +++ b/utils/fixtures/yearnFixture.ts @@ -0,0 +1,65 @@ +import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers"; +import { BigNumber } from "@ethersproject/bignumber"; +import { Signer } from "ethers"; + +import { + Vault, + Registry +} from "../contracts/yearn"; +import DeployHelper from "../deploys"; +import { Address } from "../types"; + +export class YearnFixture { + private _deployer: DeployHelper; + private _ownerAddress: Address; + private _ownerSigner: Signer; + + public registry: Registry; + + constructor(provider: Web3Provider | JsonRpcProvider, ownerAddress: Address) { + this._ownerAddress = ownerAddress; + this._ownerSigner = provider.getSigner(ownerAddress); + this._deployer = new DeployHelper(this._ownerSigner); + } + + public async initialize(): Promise { + this.registry = await this._deployer.external.deployRegistry(); // self.governance = msg.sender == ownerAddress + } + + public async createAndEnableVaultWithStrategyMock( + underlying: Address, + governance: Address, + guardian: Address, + rewards: Address, + name: string, + symbol: string, + depositLimit: BigNumber + ): Promise { + // https://github.com/yearn/yearn-vaults/blob/master/docs/OPERATIONS.md + const emptyVault = await this._deployer.external.deployVault(); + await emptyVault["initialize(address,address,address,string,string,address)"] + (underlying, governance, rewards, name, symbol, guardian); + + await emptyVault.setGovernance(this._ownerAddress); + await emptyVault.acceptGovernance(); + + await this.registry.newRelease(emptyVault.address); + await this.registry["newVault(address,address,address,string,string)"](underlying, guardian, rewards, name, symbol); + + const vaultAddress = await this.registry.latestVault(underlying); + + const vault = await this._deployer.external.getVault(vaultAddress); + await vault.setDepositLimit(depositLimit); + await vault.setManagementFee(0); + + const strategy = await this._deployer.mocks.deployYearnStrategyMock(vault.address); + + await vault.addStrategy(strategy.address, 9800, 0, 1000, 0); + + await strategy.setKeeper(this._ownerAddress); + await strategy.setRewards(rewards); + + + return vault; + } +} diff --git a/utils/test/index.ts b/utils/test/index.ts index 693646527..b818e3884 100644 --- a/utils/test/index.ts +++ b/utils/test/index.ts @@ -2,7 +2,7 @@ import { ethers } from "hardhat"; import { Address } from "../types"; -import { AaveFixture, BalancerFixture, CompoundFixture, CurveFixture, SystemFixture, UniswapFixture } from "../fixtures"; +import { AaveFixture, BalancerFixture, CompoundFixture, CurveFixture, SystemFixture, UniswapFixture, YearnFixture } from "../fixtures"; import { Blockchain, ProtocolUtils } from "../common"; // Hardhat-Provider Aware Exports @@ -15,6 +15,7 @@ export const getBalancerFixture = (ownerAddress: Address) => new BalancerFixture export const getCurveFixture = (ownerAddress: Address) => new CurveFixture(provider, ownerAddress); export const getCompoundFixture = (ownerAddress: Address) => new CompoundFixture(provider, ownerAddress); export const getUniswapFixture = (ownerAddress: Address) => new UniswapFixture(provider, ownerAddress); +export const getYearnFixture = (ownerAddress: Address) => new YearnFixture(provider, ownerAddress); export { getAccounts, From 5bf7561cb3dca736c07685086bcb3753eee02296 Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 18:05:42 -0700 Subject: [PATCH 12/19] update comment --- test/integration/yearnWrapModule.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/yearnWrapModule.spec.ts b/test/integration/yearnWrapModule.spec.ts index a3b26d4fd..7a777ea24 100644 --- a/test/integration/yearnWrapModule.spec.ts +++ b/test/integration/yearnWrapModule.spec.ts @@ -58,7 +58,7 @@ describe("yearnWrapModule", () => { wrapModule = await deployer.modules.deployWrapModule(setup.controller.address, setup.weth.address); await setup.controller.addModule(wrapModule.address); - // CompoundWrapAdapter setup + // YearnWrapAdapter setup yearnWrapAdapter = await deployer.adapters.deployYearnWrapAdapter(); await setup.integrationRegistry.addIntegration(wrapModule.address, yearnWrapAdapterIntegrationName, yearnWrapAdapter.address); }); From 9d14027f681db18fa9f1a2d3c0c4579e252da437 Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 19:43:59 -0700 Subject: [PATCH 13/19] improve oracle test readability --- contracts/protocol/integration/YearnWrapAdapter.sol | 1 - test/fixtures/yearn.spec.ts | 6 ------ test/integration/oracles/yearnVaultOracle.spec.ts | 9 +++++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol index 227fc0ba2..fc931ea51 100644 --- a/contracts/protocol/integration/YearnWrapAdapter.sol +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -73,7 +73,6 @@ contract YearnWrapAdapter { { uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0; - // deposit(address _reserve, uint256 _amount, uint16 _referralCode) bytes memory callData = abi.encodeWithSignature("deposit(uint256)", _underlyingUnits); return (address(_wrappedToken), value, callData); diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts index 13af643c1..b33aa5f21 100644 --- a/test/fixtures/yearn.spec.ts +++ b/test/fixtures/yearn.spec.ts @@ -20,8 +20,6 @@ const expect = getWaffleExpect(); describe("YearnFixture", () => { let owner: Account; - - // let setup: SystemFixture; let yearnSetup: YearnFixture; let dai: StandardTokenMock; @@ -30,13 +28,11 @@ describe("YearnFixture", () => { owner, ] = await getAccounts(); - // setup = getSystemFixture(owner.address); const deployer = new DeployHelper(owner.wallet); dai = await deployer.mocks.deployTokenMock(owner.address, ether(10000), 18); yearnSetup = getYearnFixture(owner.address); - // await setup.initialize(); await yearnSetup.initialize(); }); @@ -61,8 +57,6 @@ describe("YearnFixture", () => { const governance = await vault.pricePerShare(); expect(governance).to.eq(ether(1)); - // const strategies = await vault.strategies(); - // console.log("strategies", strategies); }); }); diff --git a/test/integration/oracles/yearnVaultOracle.spec.ts b/test/integration/oracles/yearnVaultOracle.spec.ts index 822edc262..3968ae2cf 100644 --- a/test/integration/oracles/yearnVaultOracle.spec.ts +++ b/test/integration/oracles/yearnVaultOracle.spec.ts @@ -53,7 +53,7 @@ describe("CTokenOracle", () => { setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) ); - daiUsdcPrice = ether(1); + daiUsdcPrice = BigNumber.from("1000000000000000000"); daiUsdcOracle = await deployer.mocks.deployOracleMock(daiUsdcPrice); daiFullUnit = BigNumber.from("1000000000000000000"); yearnVaultDaiOracle = await deployer.oracles.deployYearnVaultOracle( @@ -117,6 +117,11 @@ describe("CTokenOracle", () => { describe("#read", async () => { + let subjectUnderlyingPricePerShare: BigNumber; + + before(async () => { + subjectUnderlyingPricePerShare = BigNumber.from("1000000000000000000"); + }); async function subject(): Promise { return yearnVaultDaiOracle.read(); @@ -124,7 +129,7 @@ describe("CTokenOracle", () => { it("returns the correct vault value", async () => { const result = await subject(); - const expectedResult = ether(1) + const expectedResult = subjectUnderlyingPricePerShare .div(daiFullUnit) .mul(daiUsdcPrice); From 7a4824a411339d1882e850d132b6d32aacf6972d Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 19:52:00 -0700 Subject: [PATCH 14/19] remove eth support --- contracts/protocol/integration/YearnWrapAdapter.sol | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol index fc931ea51..523968056 100644 --- a/contracts/protocol/integration/YearnWrapAdapter.sol +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -39,11 +39,6 @@ contract YearnWrapAdapter { _; } - /* ============ Constants ============ */ - - // Mock address to indicate ETH. - address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - /* ============ Constructor ============ */ constructor() public { } @@ -71,11 +66,8 @@ contract YearnWrapAdapter { onlyValidTokenPair(_underlyingToken, _wrappedToken) returns (address, uint256, bytes memory) { - uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0; - bytes memory callData = abi.encodeWithSignature("deposit(uint256)", _underlyingUnits); - - return (address(_wrappedToken), value, callData); + return (address(_wrappedToken), 0, callData); } /** @@ -99,9 +91,7 @@ contract YearnWrapAdapter { onlyValidTokenPair(_underlyingToken, _wrappedToken) returns (address, uint256, bytes memory) { - // redeem(uint256 _amount) bytes memory callData = abi.encodeWithSignature("withdraw(uint256)", _wrappedTokenUnits); - return (address(_wrappedToken), 0, callData); } From 989b4a0ee901c825924e474a0a0add885220b7d6 Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 20:00:08 -0700 Subject: [PATCH 15/19] better commet --- test/fixtures/yearn.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts index b33aa5f21..7dec11895 100644 --- a/test/fixtures/yearn.spec.ts +++ b/test/fixtures/yearn.spec.ts @@ -54,8 +54,8 @@ describe("YearnFixture", () => { it("should create and enable a vault", async () => { const vault = await subject(); - const governance = await vault.pricePerShare(); - expect(governance).to.eq(ether(1)); + const pricePerShare = await vault.pricePerShare(); // No deposiit into the vault. 1 share = 1 underlying + expect(pricePerShare).to.eq(ether(1)); }); }); From 653040298b972f8dd5a706997fe398b8220a598c Mon Sep 17 00:00:00 2001 From: mario Date: Sun, 4 Apr 2021 20:11:12 -0700 Subject: [PATCH 16/19] remove unused var --- contracts/protocol/integration/oracles/YearnVaultOracle.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol index ed09d791d..17b4a68b7 100644 --- a/contracts/protocol/integration/oracles/YearnVaultOracle.sol +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -40,9 +40,6 @@ contract YearnVaultOracle is IOracle // Price per share values are scaled by 1e18 uint256 internal constant scalingFactor = 10 ** 18; - // CToken Full Unit - uint256 public cTokenFullUnit; - // Underlying Asset Full Unit uint256 public underlyingFullUnit; From a8fb44a91df68d19f84c2f9ee60340ccb2c2a59e Mon Sep 17 00:00:00 2001 From: mario Date: Tue, 6 Apr 2021 12:31:38 -0700 Subject: [PATCH 17/19] address PR comments --- contracts/mocks/external/YearnVaultMock.sol | 28 ++++++++++++++++ .../protocol/integration/YearnWrapAdapter.sol | 2 +- .../integration/oracles/YearnVaultOracle.sol | 25 ++++++--------- test/fixtures/yearn.spec.ts | 2 +- .../oracles/yearnVaultOracle.spec.ts | 32 +++++++------------ utils/contracts/index.ts | 1 + utils/deploys/deployMocks.ts | 7 ++++ 7 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 contracts/mocks/external/YearnVaultMock.sol diff --git a/contracts/mocks/external/YearnVaultMock.sol b/contracts/mocks/external/YearnVaultMock.sol new file mode 100644 index 000000000..c7874d0ff --- /dev/null +++ b/contracts/mocks/external/YearnVaultMock.sol @@ -0,0 +1,28 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; + +contract YearnVaultMock { + uint256 public pricePerShare; + + constructor(uint256 _pricePerShare) public { + pricePerShare = _pricePerShare; + } + +} diff --git a/contracts/protocol/integration/YearnWrapAdapter.sol b/contracts/protocol/integration/YearnWrapAdapter.sol index 523968056..141147667 100644 --- a/contracts/protocol/integration/YearnWrapAdapter.sol +++ b/contracts/protocol/integration/YearnWrapAdapter.sol @@ -23,7 +23,7 @@ import { IYearnVault } from "../../interfaces/external/IYearnVault.sol"; /** * @title YearnWrapAdapter - * @author Set Protocol + * @author Set Protocol, Ember Fund * * Wrap adapter for Yearn that returns data for wraps/unwraps of tokens */ diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol index 17b4a68b7..601d79b34 100644 --- a/contracts/protocol/integration/oracles/YearnVaultOracle.sol +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -37,19 +37,16 @@ contract YearnVaultOracle is IOracle IOracle public underlyingOracle; // Underlying token oracle string public dataDescription; - // Price per share values are scaled by 1e18 - uint256 internal constant scalingFactor = 10 ** 18; - // Underlying Asset Full Unit uint256 public underlyingFullUnit; /* ============ Constructor ============ */ /* - * @param _vault The address of Yearn Vault Token - * @param _underlyingOracle The address of the underlying oracle - * @param _underlyingFullUnit The full unit of the underlying asset - * @param _dataDescription Human readable description of oracle + * @param _vault The address of Yearn Vault Token + * @param _underlyingOracle The address of the underlying oracle + * @param _underlyingFullUnit The full unit of the underlying asset + * @param _dataDescription Human readable description of oracle */ constructor( IYearnVault _vault, @@ -66,12 +63,9 @@ contract YearnVaultOracle is IOracle } /** - * Returns the price value of a full vault token denominated in underlyingOracle value - & - * The underlying oracle is assumed to return a price of 18 decimal - * for a single full token of the underlying asset. The derived price - * of the vault token is then the price of a unit of underlying multiplied - * by the exchangeRate, adjusted for decimal differences, and descaled. + * Returns the price value of a full vault token denominated in underlyingOracle value. + * The derived price of the vault token is the price of a share multiplied divided by + * underlying full unit and multiplied by the underlying price. */ function read() external @@ -82,10 +76,9 @@ contract YearnVaultOracle is IOracle // Retrieve the price of the underlying uint256 underlyingPrice = underlyingOracle.read(); - // Retrieve price per share + // Price per share is the amount of the underlying asset per 1 full vaultToken uint256 pricePerShare = vault.pricePerShare(); - uint256 normalizedPricePerShare = pricePerShare.preciseDiv(underlyingFullUnit); - return normalizedPricePerShare.preciseMul(underlyingPrice); + return pricePerShare.mul(underlyingPrice).div(underlyingFullUnit); } } diff --git a/test/fixtures/yearn.spec.ts b/test/fixtures/yearn.spec.ts index 7dec11895..453ec2dc4 100644 --- a/test/fixtures/yearn.spec.ts +++ b/test/fixtures/yearn.spec.ts @@ -54,7 +54,7 @@ describe("YearnFixture", () => { it("should create and enable a vault", async () => { const vault = await subject(); - const pricePerShare = await vault.pricePerShare(); // No deposiit into the vault. 1 share = 1 underlying + const pricePerShare = await vault.pricePerShare(); // No deposit into the vault. 1 share = 1 underlying expect(pricePerShare).to.eq(ether(1)); }); diff --git a/test/integration/oracles/yearnVaultOracle.spec.ts b/test/integration/oracles/yearnVaultOracle.spec.ts index 3968ae2cf..a10f3b30a 100644 --- a/test/integration/oracles/yearnVaultOracle.spec.ts +++ b/test/integration/oracles/yearnVaultOracle.spec.ts @@ -3,8 +3,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import { Address } from "@utils/types"; import { Account } from "@utils/test/types"; -import { OracleMock, YearnVaultOracle } from "@utils/contracts"; -import { Vault } from "../../../typechain/Vault"; +import { OracleMock, YearnVaultOracle, YearnVaultMock } from "@utils/contracts"; import DeployHelper from "@utils/deploys"; @@ -22,18 +21,19 @@ import { YearnFixture, SystemFixture } from "@utils/fixtures"; const expect = getWaffleExpect(); -describe("CTokenOracle", () => { +describe("YearnVaultOracle", () => { let owner: Account; let deployer: DeployHelper; let setup: SystemFixture; let yearnSetup: YearnFixture; - let daiVault: Vault; + let daiVault: YearnVaultMock; let daiUsdcOracle: OracleMock; let daiUsdcPrice: BigNumber; let yearnVaultDaiOracle: YearnVaultOracle; let daiFullUnit: BigNumber; + let pricePerShare: BigNumber; before(async () => { [ @@ -49,13 +49,11 @@ describe("CTokenOracle", () => { yearnSetup = getYearnFixture(owner.address); await yearnSetup.initialize(); - daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( - setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) - ); - - daiUsdcPrice = BigNumber.from("1000000000000000000"); + pricePerShare = ether(1.5); + daiVault = await deployer.mocks.deployYearnVaultMock(pricePerShare); + daiUsdcPrice = ether(1); daiUsdcOracle = await deployer.mocks.deployOracleMock(daiUsdcPrice); - daiFullUnit = BigNumber.from("1000000000000000000"); + daiFullUnit = ether(1); yearnVaultDaiOracle = await deployer.oracles.deployYearnVaultOracle( daiVault.address, daiUsdcOracle.address, @@ -75,7 +73,7 @@ describe("CTokenOracle", () => { before(async () => { subjectVaultAddress = daiVault.address; - subjectUnderlyingFullUnit = BigNumber.from("1000000000000000000"); + subjectUnderlyingFullUnit = ether(1); subjectUnderlyingOracle = daiUsdcOracle.address; subjectDataDescription = "yvDAI Oracle"; }); @@ -117,11 +115,6 @@ describe("CTokenOracle", () => { describe("#read", async () => { - let subjectUnderlyingPricePerShare: BigNumber; - - before(async () => { - subjectUnderlyingPricePerShare = BigNumber.from("1000000000000000000"); - }); async function subject(): Promise { return yearnVaultDaiOracle.read(); @@ -129,10 +122,9 @@ describe("CTokenOracle", () => { it("returns the correct vault value", async () => { const result = await subject(); - const expectedResult = subjectUnderlyingPricePerShare - .div(daiFullUnit) - .mul(daiUsdcPrice); - + const expectedResult = pricePerShare + .mul(daiUsdcPrice) + .div(daiFullUnit); expect(result).to.eq(expectedResult); }); }); diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index c61e60381..50069a357 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -55,6 +55,7 @@ export { OneInchExchangeMock } from "../../typechain/OneInchExchangeMock"; export { OracleAdapterMock } from "../../typechain/OracleAdapterMock"; export { OracleMock } from "../../typechain/OracleMock"; export { CTokenOracle } from "../../typechain/CTokenOracle"; +export { YearnVaultMock } from "../../typechain/YearnVaultMock"; export { YearnVaultOracle } from "../../typechain/YearnVaultOracle"; export { PositionMock } from "../../typechain/PositionMock"; export { PreciseUnitMathMock } from "../../typechain/PreciseUnitMathMock"; diff --git a/utils/deploys/deployMocks.ts b/utils/deploys/deployMocks.ts index 9b76dfabd..5aad45b66 100644 --- a/utils/deploys/deployMocks.ts +++ b/utils/deploys/deployMocks.ts @@ -28,6 +28,7 @@ import { OneInchExchangeMock, OracleAdapterMock, OracleMock, + YearnVaultMock, PositionMock, PreciseUnitMathMock, ResourceIdentifierMock, @@ -70,6 +71,7 @@ import { NAVIssuanceHookMock__factory } from "../../typechain/factories/NAVIssua import { OneInchExchangeMock__factory } from "../../typechain/factories/OneInchExchangeMock__factory"; import { OracleAdapterMock__factory } from "../../typechain/factories/OracleAdapterMock__factory"; import { OracleMock__factory } from "../../typechain/factories/OracleMock__factory"; +import { YearnVaultMock__factory } from "../../typechain/factories/YearnVaultMock__factory"; import { PositionMock__factory } from "../../typechain/factories/PositionMock__factory"; import { PreciseUnitMathMock__factory } from "../../typechain/factories/PreciseUnitMathMock__factory"; import { ResourceIdentifierMock__factory } from "../../typechain/factories/ResourceIdentifierMock__factory"; @@ -175,6 +177,11 @@ export default class DeployMocks { return await new OracleMock__factory(this._deployerSigner).deploy(initialValue); } + public async deployYearnVaultMock(pricePerShare: BigNumberish): Promise { + return await new YearnVaultMock__factory(this._deployerSigner).deploy(pricePerShare); + } + + public async deployOracleAdapterMock( asset: Address, dummyPrice: BigNumber From 3e402bb5e1da359b61aa5fad1616584ae67e49bc Mon Sep 17 00:00:00 2001 From: mario Date: Fri, 9 Apr 2021 16:02:08 -0700 Subject: [PATCH 18/19] state vars should be immutable --- contracts/protocol/integration/oracles/YearnVaultOracle.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/integration/oracles/YearnVaultOracle.sol b/contracts/protocol/integration/oracles/YearnVaultOracle.sol index 601d79b34..da541a8b3 100644 --- a/contracts/protocol/integration/oracles/YearnVaultOracle.sol +++ b/contracts/protocol/integration/oracles/YearnVaultOracle.sol @@ -33,12 +33,12 @@ contract YearnVaultOracle is IOracle /* ============ State Variables ============ */ - IYearnVault public vault; - IOracle public underlyingOracle; // Underlying token oracle + IYearnVault public immutable vault; + IOracle public immutable underlyingOracle; // Underlying token oracle string public dataDescription; // Underlying Asset Full Unit - uint256 public underlyingFullUnit; + uint256 public immutable underlyingFullUnit; /* ============ Constructor ============ */ From f4e3917818b83ad78c8a09cb0f64cade94e75b24 Mon Sep 17 00:00:00 2001 From: mario Date: Mon, 12 Apr 2021 15:29:33 -0700 Subject: [PATCH 19/19] add test --- test/integration/yearnWrapModule.spec.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/integration/yearnWrapModule.spec.ts b/test/integration/yearnWrapModule.spec.ts index 7a777ea24..1d4c4e5de 100644 --- a/test/integration/yearnWrapModule.spec.ts +++ b/test/integration/yearnWrapModule.spec.ts @@ -51,7 +51,7 @@ describe("yearnWrapModule", () => { await yearnSetup.initialize(); daiVault = await yearnSetup.createAndEnableVaultWithStrategyMock( - setup.dai.address, owner.address, owner.address, owner.address, "MockStrategy", "M", ether(100) + setup.dai.address, owner.address, owner.address, owner.address, "daiMockStrategy", "yvDAI", ether(100) ); // WrapModule setup @@ -129,6 +129,7 @@ describe("yearnWrapModule", () => { const expectedWrappedBalance = previousWrappedBalance.add(setTokensIssued); expect(wrappedBalance).to.eq(expectedWrappedBalance); }); + }); describe("#unwrap", () => { @@ -190,6 +191,17 @@ describe("yearnWrapModule", () => { const expectedWrappedBalance = previousWrappedBalance.sub(delta); expect(wrappedBalance).to.eq(expectedWrappedBalance); }); + + describe("when it is an invalid vault - underlying token", async () => { + beforeEach(async () => { + subjectUnderlyingToken = setup.usdc.address; + }); + + it("should revert as it the vault holds a different underlying token", async () => { + await expect(subject()).to.be.revertedWith("Must be a valid token pair"); + }); + }); + }); }); });