├── .circleci └── config.yml ├── .env.default ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── .spec.template.ts ├── README.md ├── contracts ├── ManagerCore.sol ├── extensions │ ├── BatchTradeExtension.sol │ ├── ClaimExtension.sol │ ├── DeltaNeutralBasisTradingStrategyExtension.sol │ ├── FeeSplitExtension.sol │ ├── IssuanceExtension.sol │ ├── PerpV2LeverageStrategyExtension.sol │ ├── StreamingFeeSplitExtension.sol │ ├── TradeExtension.sol │ └── WrapExtension.sol ├── factories │ └── DelegatedManagerFactory.sol ├── hooks │ └── SupplyCapIssuanceHook.sol ├── interfaces │ ├── IAdapter.sol │ ├── IBaseManager.sol │ ├── IChainlinkAggregatorV3.sol │ ├── IDelegatedManager.sol │ ├── IExtension.sol │ ├── IGlobalExtension.sol │ ├── IManagerCore.sol │ ├── IPriceFeed.sol │ └── IUniswapV3Quoter.sol ├── lib │ ├── BaseExtension.sol │ ├── BaseGlobalExtension.sol │ ├── MutualUpgrade.sol │ ├── MutualUpgradeV2.sol │ └── TimeLockUpgrade.sol ├── manager │ ├── BaseManager.sol │ └── DelegatedManager.sol └── mocks │ ├── BaseExtensionMock.sol │ ├── BaseGlobalExtensionMock.sol │ ├── ChainlinkAggregatorMock.sol │ ├── ManagerMock.sol │ ├── ModuleMock.sol │ ├── MutualUpgradeMock.sol │ ├── MutualUpgradeV2Mock.sol │ ├── PerpV2PriceFeedMock.sol │ └── TradeAdapterMock.sol ├── hardhat.config.ts ├── package.json ├── pull_request_template.md ├── subgraph └── test │ └── state-deploy-delegated-manager.ts ├── tasks ├── index.ts └── subtasks.ts ├── test ├── extensions │ ├── FeeSplitExtension.spec.ts │ ├── batchTradeExtension.spec.ts │ ├── claimExtension.spec.ts │ ├── deltaNeutralBasisTradingStrategyExtension.spec.ts │ ├── issuanceExtension.spec.ts │ ├── perpV2LeverageStrategyExtension.spec.ts │ ├── streamingFeeSplitExtension.spec.ts │ ├── tradeExtension.spec.ts │ └── wrapExtension.spec.ts ├── factories │ └── delegatedManagerFactory.spec.ts ├── hooks │ └── supplyCapIssuanceHook.spec.ts ├── integration │ └── batchTradeExtensionZeroEx.spec.ts ├── lib │ ├── baseExtension.spec.ts │ ├── baseGlobalExtension.spec.ts │ ├── mutualUpgrade.spec.ts │ └── mutualUpgradeV2.spec.ts ├── manager │ ├── baseManager.spec.ts │ └── delegatedManager.spec.ts └── managerCore.spec.ts ├── tsconfig.dist.json ├── tsconfig.json ├── utils ├── common │ ├── batchTradeUtils.ts │ ├── blockchainUtils.ts │ ├── conversionUtils.ts │ ├── feeModuleUtils.ts │ ├── index.ts │ ├── libraryUtils.ts │ ├── mathUtils.ts │ ├── protocolUtils.ts │ └── unitsUtils.ts ├── constants.ts ├── contracts │ └── index.ts ├── deploys │ ├── deployExtensions.ts │ ├── deployFactories.ts │ ├── deployGlobalExtensions.ts │ ├── deployHooks.ts │ ├── deployManager.ts │ ├── deployManagerCore.ts │ ├── deployMocks.ts │ ├── deploySetV2.ts │ └── index.ts ├── flexibleLeverageUtils │ ├── flexibleLeverage.ts │ └── index.ts ├── index.ts ├── test │ ├── accountUtils.ts │ ├── index.ts │ └── testingUtils.ts ├── types.js ├── types.ts └── wallets.ts └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | checkout_and_compile: 5 | docker: 6 | - image: circleci/node:12.16.0 7 | environment: 8 | NODE_OPTIONS: --max_old_space_size=8192 9 | resource_class: large 10 | working_directory: ~/set-v2-strategies 11 | steps: 12 | - checkout 13 | - restore_cache: 14 | key: module-cache-{{ checksum "yarn.lock" }} 15 | - run: 16 | name: Set Up Environment Variables 17 | command: cp .env.default .env 18 | - run: 19 | name: Fetch Dependencies 20 | command: yarn install 21 | - save_cache: 22 | key: module-cache-{{ checksum "yarn.lock" }} 23 | paths: 24 | - node_modules 25 | - run: 26 | name: Transpile Contracts 27 | command: yarn build 28 | - save_cache: 29 | key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} 30 | paths: 31 | - ~/set-v2-strategies 32 | test: 33 | docker: 34 | - image: circleci/node:12.16.0 35 | working_directory: ~/set-v2-strategies 36 | parallelism: 3 37 | steps: 38 | - restore_cache: 39 | key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} 40 | - run: 41 | name: Set Up Environment Variables 42 | command: cp .env.default .env 43 | - run: 44 | name: Test RPC 45 | command: yarn chain 46 | background: true 47 | - run: 48 | name: Hardhat Test 49 | command: | 50 | TEST_FILES="$(circleci tests glob "./test/**/*.spec.ts" | circleci tests split)" 51 | yarn test ${TEST_FILES} 52 | 53 | test_forked_network: 54 | docker: 55 | - image: circleci/node:12.16.0 56 | working_directory: ~/set-v2-strategies 57 | steps: 58 | - restore_cache: 59 | key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} 60 | - run: 61 | name: Set Up Environment Variables 62 | command: cp .env.default .env 63 | - run: 64 | name: Create shared coverage outputs folder 65 | command: mkdir -p /tmp/forked_coverage 66 | - run: 67 | name: Hardhat Test 68 | command: yarn test:fork:coverage 69 | - run: 70 | name: Save coverage 71 | command: | 72 | cp coverage.json /tmp/forked_coverage/forked_cov.json 73 | chmod -R 777 /tmp/forked_coverage/forked_cov.json 74 | - persist_to_workspace: 75 | root: /tmp/forked_coverage 76 | paths: 77 | - forked_cov.json 78 | 79 | coverage: 80 | docker: 81 | - image: circleci/node:12.16.0 82 | working_directory: ~/set-v2-strategies 83 | # When changing the parallelism value, you also 84 | # need to update the `persist_to_workspace` paths 85 | # in this job (below) as well as the list of files passed 86 | # to istanbul-combine in the `report_coverage` job 87 | parallelism: 5 88 | steps: 89 | - restore_cache: 90 | key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} 91 | - run: 92 | name: Set Up Environment Variables 93 | command: cp .env.default .env 94 | - run: 95 | name: Create shared coverage outputs folder 96 | command: mkdir -p /tmp/coverage 97 | - run: 98 | name: Coverage 99 | command: | 100 | TEST_FILES="{$(circleci tests glob "./test/**/*.spec.ts" | \ 101 | circleci tests split | xargs | sed -e 's/ /,/g')}" 102 | yarn coverage -- --testfiles "$TEST_FILES" 103 | - run: 104 | name: Save coverage 105 | command: | 106 | cp coverage.json /tmp/coverage/cov_$CIRCLE_NODE_INDEX.json 107 | chmod -R 777 /tmp/coverage/cov_$CIRCLE_NODE_INDEX.json 108 | - persist_to_workspace: 109 | root: /tmp/coverage 110 | paths: 111 | - cov_0.json 112 | - cov_1.json 113 | - cov_2.json 114 | - cov_3.json 115 | - cov_4.json 116 | 117 | report_coverage: 118 | docker: 119 | - image: circleci/node:12.16.0 120 | working_directory: ~/set-v2-strategies 121 | steps: 122 | - attach_workspace: 123 | at: /tmp/coverage 124 | - attach_workspace: 125 | at: /tmp/forked_coverage 126 | - restore_cache: 127 | key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} 128 | - run: 129 | name: Combine coverage reports 130 | command: | 131 | cp -R /tmp/coverage/* . 132 | cp -R /tmp/forked_coverage/* . 133 | npx istanbul-combine-updated -r lcov \ 134 | cov_0.json \ 135 | cov_1.json \ 136 | cov_2.json \ 137 | cov_3.json \ 138 | cov_4.json \ 139 | forked_cov.json 140 | - run: 141 | name: Upload coverage 142 | command: | 143 | cat coverage/lcov.info | node_modules/.bin/coveralls 144 | 145 | workflows: 146 | version: 2 147 | build-and-test: 148 | jobs: 149 | - checkout_and_compile 150 | - test: 151 | requires: 152 | - checkout_and_compile 153 | - test_forked_network: 154 | requires: 155 | - checkout_and_compile 156 | - coverage: 157 | requires: 158 | - checkout_and_compile 159 | - report_coverage: 160 | requires: 161 | - coverage 162 | - test_forked_network 163 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | # These are randomly generated hex so that CircleCI will work 2 | ALCHEMY_TOKEN=fake_alchemy_token 3 | INFURA_TOKEN=799e620c4b39064f7a8cfd8452976ed1 4 | KOVAN_DEPLOY_PRIVATE_KEY=0f3456f7f1ed59aaa29f35f4674a87e754e1055249a2888bdaec3dea49d2e456 5 | STAGING_MAINNET_DEPLOY_PRIVATE_KEY=0f3456f7f1ed59aaa29f35f4674a87e754e1055249a2888bdaec3dea49d2e456 6 | PRODUCTION_MAINNET_DEPLOY_PRIVATE_KEY=0f3456f7f1ed59aaa29f35f4674a87e754e1055249a2888bdaec3dea49d2e456 7 | 8 | NODE_OPTIONS=--max_old_space_size=8192 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": "tsconfig.json", 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "eslint-plugin-no-null", 13 | "eslint-plugin-jsdoc", 14 | "@typescript-eslint", 15 | "@typescript-eslint/tslint" 16 | ], 17 | "rules": { 18 | "@typescript-eslint/indent": [ 19 | "error", 20 | 2 21 | ], 22 | "@typescript-eslint/member-delimiter-style": [ 23 | "error", 24 | { 25 | "multiline": { 26 | "delimiter": "semi", 27 | "requireLast": true 28 | }, 29 | "singleline": { 30 | "delimiter": "semi", 31 | "requireLast": false 32 | } 33 | } 34 | ], 35 | "@typescript-eslint/prefer-namespace-keyword": "error", 36 | "@typescript-eslint/quotes": [ 37 | "error", 38 | "double", 39 | { 40 | "avoidEscape": true 41 | } 42 | ], 43 | "@typescript-eslint/semi": [ 44 | "error", 45 | "always" 46 | ], 47 | "@typescript-eslint/type-annotation-spacing": "error", 48 | "arrow-parens": [ 49 | "error", 50 | "as-needed" 51 | ], 52 | "brace-style": [ 53 | "error", 54 | "1tbs" 55 | ], 56 | "comma-dangle": [ 57 | "error", 58 | { 59 | "objects": "only-multiline", 60 | "arrays": "only-multiline", 61 | "imports": "only-multiline", 62 | "exports": "only-multiline", 63 | "functions": "only-multiline" 64 | } 65 | ], 66 | "jsdoc/check-alignment": "error", 67 | "jsdoc/check-indentation": "error", 68 | "jsdoc/newline-after-description": "error", 69 | "max-len": [ 70 | "error", 71 | { 72 | "code": 150 73 | } 74 | ], 75 | "no-null/no-null": "error", 76 | "no-trailing-spaces": "error", 77 | "no-unused-vars": "error", 78 | "no-var": "error", 79 | "prefer-const": "error", 80 | "quotes": "error", 81 | "semi": "error", 82 | "spaced-comment": [ 83 | "error", 84 | "always", 85 | { 86 | "markers": [ 87 | "/" 88 | ] 89 | } 90 | ], 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /artifacts 3 | /build 4 | /cache 5 | /dist 6 | /etherscan 7 | /flatten 8 | /deployments/kovan 9 | /deployments/localhost_31337 10 | /deployments/staging_mainnet_1 11 | /deployments/production_1 12 | /deployments/50-development.json 13 | /node_modules 14 | /transpiled 15 | /types/generated/* 16 | /typechain 17 | coverage.json 18 | coverage/ 19 | .env 20 | 21 | /.coverage_cache 22 | /.coverage_contracts 23 | 24 | ### Node ### 25 | 26 | # Logs 27 | *.log 28 | /logs 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # IDE Configs 34 | .vscode/* 35 | .idea 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SetProtocol/set-v2-strategies/49fe0ce05cde77561cb22e76d8fd812d9ac71daf/.npmignore -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | contracts/ 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "parser": "typescript", 4 | "tabWidth": 2, 5 | "trailingComma": "all", 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); // This module is already a solidity-coverage dep 2 | 3 | module.exports = { 4 | skipFiles: [ 5 | 'mocks', 6 | 'interfaces', 7 | 'protocol/modules/UniswapYieldStrategy.sol', 8 | 'product/AssetLimitHook.sol', 9 | 'protocol-viewers' 10 | ], 11 | providerOptions: { 12 | default_balance_ether: 100000000, 13 | gasLimit: 30000000, 14 | } 15 | } -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "reason-string": ["warn", { "maxLength": 50 }], 5 | "compiler-version": ["error", "0.6.10"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/mocks 3 | contracts/Migrations.sol 4 | contracts/external 5 | -------------------------------------------------------------------------------- /.spec.template.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | import { BigNumber } from "ethers/utils"; 3 | import { ethers } from "hardhat"; 4 | 5 | import { Address, Account } from "@utils/types"; 6 | import {} from "@utils/constants"; 7 | import {} from "@utils/contracts"; 8 | import DeployHelper from "@utils/deploys"; 9 | import { 10 | addSnapshotBeforeRestoreAfterEach, 11 | ether, 12 | getAccounts, 13 | getSystemFixture, 14 | getWaffleExpect, 15 | } from "@utils/index"; 16 | import { SystemFixture } from "@utils/fixtures"; 17 | import { ContractTransaction } from "ethers"; 18 | 19 | const expect = getWaffleExpect(); 20 | 21 | describe("SPEC TITLE", () => { 22 | let owner: Account; 23 | let deployer: DeployHelper; 24 | let setup: SystemFixture; 25 | 26 | before(async () => { 27 | [ 28 | owner, 29 | ] = await getAccounts(); 30 | 31 | deployer = new DeployHelper(owner.wallet); 32 | setup = getSystemFixture(owner.address); 33 | await setup.initialize(); 34 | }); 35 | 36 | addSnapshotBeforeRestoreAfterEach(); 37 | 38 | describe("#FUNCTION_NAME", async () => { 39 | let subjectArgument: Address; 40 | 41 | beforeEach(async () => { 42 | }); 43 | 44 | async function subject(): Promise { 45 | } 46 | 47 | it("should do something", async () => { 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | Coverage Status 6 |

7 | 8 | # Set V2 Strategies Contract Repository 9 | 10 | ## Contracts 11 | This repo contains management contracts for assets built with [Set Protocol](https://setprotocol.com/). It uses [Hardhat](https://hardhat.org/) as a development environment for compiling, testing, and deploying. 12 | 13 | ## Development 14 | 15 | To use console.log during Solidity development, follow the [guides](https://hardhat.org/guides/hardhat-console.html). 16 | 17 | ## Available Functionality 18 | 19 | ### Run Hardhat EVM 20 | 21 | `yarn chain` 22 | 23 | ### Build Contracts 24 | 25 | `yarn compile` 26 | 27 | ### Generate TypeChain Typings 28 | 29 | `yarn build` 30 | 31 | ### Run Contract Tests 32 | 33 | `yarn test` to run compiled contracts 34 | 35 | OR `yarn test:clean` if contracts have been typings need to be updated 36 | 37 | ### Run Coverage Report for Tests 38 | 39 | `yarn coverage` 40 | 41 | ## Installing from `npm` 42 | 43 | We publish our contracts as well as [hardhat][22] and [typechain][23] compilation artifacts to npm. 44 | 45 | ``` 46 | npm install @setprotocol/set-v2-strategies 47 | ``` 48 | 49 | [22]: https://www.npmjs.com/package/hardhat 50 | [23]: https://www.npmjs.com/package/typechain 51 | 52 | ## Contributing 53 | We highly encourage participation from the community to help shape the development of Set. If you are interested in developing on top of Set Protocol or have any questions, please ping us on [Discord](https://discord.gg/ZWY66aR). 54 | 55 | ## Security 56 | 57 | ### TODO: Independent Audits 58 | 59 | ### Code Coverage 60 | 61 | All smart contracts are tested and have 100% line and branch coverage. 62 | 63 | ### Vulnerability Disclosure Policy 64 | 65 | The disclosure of security vulnerabilities helps us ensure the security of our users. 66 | 67 | **How to report a security vulnerability?** 68 | 69 | If you believe you’ve found a security vulnerability in one of our contracts or platforms, 70 | send it to us by emailing [security@setprotocol.com](mailto:security@setprotocol.com). 71 | Please include the following details with your report: 72 | 73 | * A description of the location and potential impact of the vulnerability. 74 | 75 | * A detailed description of the steps required to reproduce the vulnerability. 76 | 77 | **Scope** 78 | 79 | Any vulnerability not previously disclosed by us or our independent auditors in their reports. 80 | 81 | **Guidelines** 82 | 83 | We require that all reporters: 84 | 85 | * Make every effort to avoid privacy violations, degradation of user experience, 86 | disruption to production systems, and destruction of data during security testing. 87 | 88 | * Use the identified communication channels to report vulnerability information to us. 89 | 90 | * Keep information about any vulnerabilities you’ve discovered confidential between yourself and 91 | Set until we’ve had 30 days to resolve the issue. 92 | 93 | If you follow these guidelines when reporting an issue to us, we commit to: 94 | 95 | * Not pursue or support any legal action related to your findings. 96 | 97 | * Work with you to understand and resolve the issue quickly 98 | (including an initial confirmation of your report within 72 hours of submission). 99 | 100 | * Grant a monetary reward based on the OWASP risk assessment methodology. 101 | -------------------------------------------------------------------------------- /contracts/ManagerCore.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 22 | 23 | import { AddressArrayUtils } from "@setprotocol/set-protocol-v2/contracts/lib/AddressArrayUtils.sol"; 24 | 25 | /** 26 | * @title ManagerCore 27 | * @author Set Protocol 28 | * 29 | * Registry for governance approved GlobalExtensions, DelegatedManagerFactories, and DelegatedManagers. 30 | */ 31 | contract ManagerCore is Ownable { 32 | using AddressArrayUtils for address[]; 33 | 34 | /* ============ Events ============ */ 35 | 36 | event ExtensionAdded(address indexed _extension); 37 | event ExtensionRemoved(address indexed _extension); 38 | event FactoryAdded(address indexed _factory); 39 | event FactoryRemoved(address indexed _factory); 40 | event ManagerAdded(address indexed _manager, address indexed _factory); 41 | event ManagerRemoved(address indexed _manager); 42 | 43 | /* ============ Modifiers ============ */ 44 | 45 | /** 46 | * Throws if function is called by any address other than a valid factory. 47 | */ 48 | modifier onlyFactory() { 49 | require(isFactory[msg.sender], "Only valid factories can call"); 50 | _; 51 | } 52 | 53 | modifier onlyInitialized() { 54 | require(isInitialized, "Contract must be initialized."); 55 | _; 56 | } 57 | 58 | /* ============ State Variables ============ */ 59 | 60 | // List of enabled extensions 61 | address[] public extensions; 62 | // List of enabled factories of managers 63 | address[] public factories; 64 | // List of enabled managers 65 | address[] public managers; 66 | 67 | // Mapping to check whether address is valid Extension, Factory, or Manager 68 | mapping(address => bool) public isExtension; 69 | mapping(address => bool) public isFactory; 70 | mapping(address => bool) public isManager; 71 | 72 | 73 | // Return true if the ManagerCore is initialized 74 | bool public isInitialized; 75 | 76 | /* ============ External Functions ============ */ 77 | 78 | /** 79 | * Initializes any predeployed factories. Note: This function can only be called by 80 | * the owner once to batch initialize the initial system contracts. 81 | * 82 | * @param _extensions List of extensions to add 83 | * @param _factories List of factories to add 84 | */ 85 | function initialize( 86 | address[] memory _extensions, 87 | address[] memory _factories 88 | ) 89 | external 90 | onlyOwner 91 | { 92 | require(!isInitialized, "ManagerCore is already initialized"); 93 | 94 | extensions = _extensions; 95 | factories = _factories; 96 | 97 | // Loop through and initialize isExtension and isFactory mapping 98 | for (uint256 i = 0; i < _extensions.length; i++) { 99 | _addExtension(_extensions[i]); 100 | } 101 | for (uint256 i = 0; i < _factories.length; i++) { 102 | _addFactory(_factories[i]); 103 | } 104 | 105 | // Set to true to only allow initialization once 106 | isInitialized = true; 107 | } 108 | 109 | /** 110 | * PRIVILEGED GOVERNANCE FUNCTION. Allows governance to add an extension 111 | * 112 | * @param _extension Address of the extension contract to add 113 | */ 114 | function addExtension(address _extension) external onlyInitialized onlyOwner { 115 | require(!isExtension[_extension], "Extension already exists"); 116 | 117 | _addExtension(_extension); 118 | 119 | extensions.push(_extension); 120 | } 121 | 122 | /** 123 | * PRIVILEGED GOVERNANCE FUNCTION. Allows governance to remove an extension 124 | * 125 | * @param _extension Address of the extension contract to remove 126 | */ 127 | function removeExtension(address _extension) external onlyInitialized onlyOwner { 128 | require(isExtension[_extension], "Extension does not exist"); 129 | 130 | extensions.removeStorage(_extension); 131 | 132 | isExtension[_extension] = false; 133 | 134 | emit ExtensionRemoved(_extension); 135 | } 136 | 137 | /** 138 | * PRIVILEGED GOVERNANCE FUNCTION. Allows governance to add a factory 139 | * 140 | * @param _factory Address of the factory contract to add 141 | */ 142 | function addFactory(address _factory) external onlyInitialized onlyOwner { 143 | require(!isFactory[_factory], "Factory already exists"); 144 | 145 | _addFactory(_factory); 146 | 147 | factories.push(_factory); 148 | } 149 | 150 | /** 151 | * PRIVILEGED GOVERNANCE FUNCTION. Allows governance to remove a factory 152 | * 153 | * @param _factory Address of the factory contract to remove 154 | */ 155 | function removeFactory(address _factory) external onlyInitialized onlyOwner { 156 | require(isFactory[_factory], "Factory does not exist"); 157 | 158 | factories.removeStorage(_factory); 159 | 160 | isFactory[_factory] = false; 161 | 162 | emit FactoryRemoved(_factory); 163 | } 164 | 165 | /** 166 | * PRIVILEGED FACTORY FUNCTION. Adds a newly deployed manager as an enabled manager. 167 | * 168 | * @param _manager Address of the manager contract to add 169 | */ 170 | function addManager(address _manager) external onlyInitialized onlyFactory { 171 | require(!isManager[_manager], "Manager already exists"); 172 | 173 | isManager[_manager] = true; 174 | 175 | managers.push(_manager); 176 | 177 | emit ManagerAdded(_manager, msg.sender); 178 | } 179 | 180 | /** 181 | * PRIVILEGED GOVERNANCE FUNCTION. Allows governance to remove a manager 182 | * 183 | * @param _manager Address of the manager contract to remove 184 | */ 185 | function removeManager(address _manager) external onlyInitialized onlyOwner { 186 | require(isManager[_manager], "Manager does not exist"); 187 | 188 | managers.removeStorage(_manager); 189 | 190 | isManager[_manager] = false; 191 | 192 | emit ManagerRemoved(_manager); 193 | } 194 | 195 | /* ============ External Getter Functions ============ */ 196 | 197 | function getExtensions() external view returns (address[] memory) { 198 | return extensions; 199 | } 200 | 201 | function getFactories() external view returns (address[] memory) { 202 | return factories; 203 | } 204 | 205 | function getManagers() external view returns (address[] memory) { 206 | return managers; 207 | } 208 | 209 | /* ============ Internal Functions ============ */ 210 | 211 | /** 212 | * Add an extension tracked on the ManagerCore 213 | * 214 | * @param _extension Address of the extension contract to add 215 | */ 216 | function _addExtension(address _extension) internal { 217 | require(_extension != address(0), "Zero address submitted."); 218 | 219 | isExtension[_extension] = true; 220 | 221 | emit ExtensionAdded(_extension); 222 | } 223 | 224 | /** 225 | * Add a factory tracked on the ManagerCore 226 | * 227 | * @param _factory Address of the factory contract to add 228 | */ 229 | function _addFactory(address _factory) internal { 230 | require(_factory != address(0), "Zero address submitted."); 231 | 232 | isFactory[_factory] = true; 233 | 234 | emit FactoryAdded(_factory); 235 | } 236 | } -------------------------------------------------------------------------------- /contracts/extensions/FeeSplitExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 IndexCooperative 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental ABIEncoderV2; 21 | 22 | import { Address } from "@openzeppelin/contracts/utils/Address.sol"; 23 | import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 24 | 25 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 26 | import { PreciseUnitMath } from "@setprotocol/set-protocol-v2/contracts/lib/PreciseUnitMath.sol"; 27 | import { IIssuanceModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/IIssuanceModule.sol"; 28 | import { IStreamingFeeModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/IStreamingFeeModule.sol"; 29 | 30 | import { BaseExtension } from "../lib/BaseExtension.sol"; 31 | import { IBaseManager } from "../interfaces/IBaseManager.sol"; 32 | import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol"; 33 | 34 | 35 | /** 36 | * @title FeeSplitExtension 37 | * @author Set Protocol 38 | * 39 | * Smart contract extension that allows for splitting and setting streaming and mint/redeem fees. 40 | */ 41 | contract FeeSplitExtension is BaseExtension, TimeLockUpgrade { 42 | using Address for address; 43 | using PreciseUnitMath for uint256; 44 | using SafeMath for uint256; 45 | 46 | /* ============ Events ============ */ 47 | 48 | event FeesDistributed( 49 | address indexed _operatorFeeRecipient, 50 | address indexed _methodologist, 51 | uint256 _operatorTake, 52 | uint256 _methodologistTake 53 | ); 54 | 55 | /* ============ State Variables ============ */ 56 | 57 | ISetToken public setToken; 58 | IStreamingFeeModule public streamingFeeModule; 59 | IIssuanceModule public issuanceModule; 60 | 61 | // Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist 62 | uint256 public operatorFeeSplit; 63 | 64 | // Address which receives operator's share of fees when they're distributed. (See IIP-72) 65 | address public operatorFeeRecipient; 66 | 67 | /* ============ Constructor ============ */ 68 | 69 | constructor( 70 | IBaseManager _manager, 71 | IStreamingFeeModule _streamingFeeModule, 72 | IIssuanceModule _issuanceModule, 73 | uint256 _operatorFeeSplit, 74 | address _operatorFeeRecipient 75 | ) 76 | public 77 | BaseExtension(_manager) 78 | { 79 | streamingFeeModule = _streamingFeeModule; 80 | issuanceModule = _issuanceModule; 81 | operatorFeeSplit = _operatorFeeSplit; 82 | operatorFeeRecipient = _operatorFeeRecipient; 83 | setToken = manager.setToken(); 84 | } 85 | 86 | /* ============ External Functions ============ */ 87 | 88 | /** 89 | * ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for 90 | * operator and methodologist, and sends to operator fee recipient and methodologist respectively. NOTE: mint/redeem fees 91 | * will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is 92 | * sufficient for accounting for all collected fees. 93 | */ 94 | function accrueFeesAndDistribute() public { 95 | // Emits a FeeActualized event 96 | streamingFeeModule.accrueFee(setToken); 97 | 98 | uint256 totalFees = setToken.balanceOf(address(this)); 99 | 100 | address methodologist = manager.methodologist(); 101 | 102 | uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit); 103 | uint256 methodologistTake = totalFees.sub(operatorTake); 104 | 105 | if (operatorTake > 0) { 106 | setToken.transfer(operatorFeeRecipient, operatorTake); 107 | } 108 | 109 | if (methodologistTake > 0) { 110 | setToken.transfer(methodologist, methodologistTake); 111 | } 112 | 113 | emit FeesDistributed(operatorFeeRecipient, methodologist, operatorTake, methodologistTake); 114 | } 115 | 116 | /** 117 | * ONLY OPERATOR: Updates streaming fee on StreamingFeeModule. 118 | * Because the method is timelocked, each party must call it twice: once to set the lock and once to execute. 119 | * 120 | * Method is timelocked to protect token owners from sudden changes in fee structure which 121 | * they would rather not bear. The delay gives them a chance to exit their positions without penalty. 122 | * 123 | * NOTE: This will accrue streaming fees though not send to operator fee recipient and methodologist. 124 | * 125 | * @param _newFee Percent of Set accruing to fee extension annually (1% = 1e16, 100% = 1e18) 126 | */ 127 | function updateStreamingFee(uint256 _newFee) 128 | external 129 | onlyOperator 130 | timeLockUpgrade 131 | { 132 | bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", manager.setToken(), _newFee); 133 | invokeManager(address(streamingFeeModule), callData); 134 | } 135 | 136 | /** 137 | * ONLY OPERATOR: Updates issue fee on IssuanceModule. Only is executed once time lock has passed. 138 | * Because the method is timelocked, each party must call it twice: once to set the lock and once to execute. 139 | * 140 | * Method is timelocked to protect token owners from sudden changes in fee structure which 141 | * they would rather not bear. The delay gives them a chance to exit their positions without penalty. 142 | * 143 | * @param _newFee New issue fee percentage in precise units (1% = 1e16, 100% = 1e18) 144 | */ 145 | function updateIssueFee(uint256 _newFee) 146 | external 147 | onlyOperator 148 | timeLockUpgrade 149 | { 150 | bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", manager.setToken(), _newFee); 151 | invokeManager(address(issuanceModule), callData); 152 | } 153 | 154 | /** 155 | * ONLY OPERATOR: Updates redeem fee on IssuanceModule. Only is executed once time lock has passed. 156 | * Because the method is timelocked, each party must call it twice: once to set the lock and once to execute. 157 | * 158 | * Method is timelocked to protect token owners from sudden changes in fee structure which 159 | * they would rather not bear. The delay gives them a chance to exit their positions without penalty. 160 | * 161 | * @param _newFee New redeem fee percentage in precise units (1% = 1e16, 100% = 1e18) 162 | */ 163 | function updateRedeemFee(uint256 _newFee) 164 | external 165 | onlyOperator 166 | timeLockUpgrade 167 | { 168 | bytes memory callData = abi.encodeWithSignature("updateRedeemFee(address,uint256)", manager.setToken(), _newFee); 169 | invokeManager(address(issuanceModule), callData); 170 | } 171 | 172 | /** 173 | * ONLY OPERATOR: Updates fee recipient on both streaming fee and issuance modules. 174 | * 175 | * @param _newFeeRecipient Address of new fee recipient. This should be the address of the fee extension itself. 176 | */ 177 | function updateFeeRecipient(address _newFeeRecipient) 178 | external 179 | onlyOperator 180 | { 181 | bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", manager.setToken(), _newFeeRecipient); 182 | invokeManager(address(streamingFeeModule), callData); 183 | invokeManager(address(issuanceModule), callData); 184 | } 185 | 186 | /** 187 | * ONLY OPERATOR: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16). 188 | * 189 | * @param _newFeeSplit Percent of fees in precise units (10^16 = 1%) sent to operator, (rest go to the methodologist). 190 | */ 191 | function updateFeeSplit(uint256 _newFeeSplit) 192 | external 193 | onlyOperator 194 | { 195 | require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%"); 196 | accrueFeesAndDistribute(); 197 | operatorFeeSplit = _newFeeSplit; 198 | } 199 | 200 | /** 201 | * ONLY OPERATOR: Updates the address that receives the operator's fees (see IIP-72) 202 | * 203 | * @param _newOperatorFeeRecipient Address to send operator's fees to. 204 | */ 205 | function updateOperatorFeeRecipient(address _newOperatorFeeRecipient) 206 | external 207 | onlyOperator 208 | { 209 | require(_newOperatorFeeRecipient != address(0), "Zero address not valid"); 210 | operatorFeeRecipient = _newOperatorFeeRecipient; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /contracts/extensions/StreamingFeeSplitExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { Address } from "@openzeppelin/contracts/utils/Address.sol"; 23 | import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 24 | 25 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 26 | import { IStreamingFeeModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/IStreamingFeeModule.sol"; 27 | import { PreciseUnitMath } from "@setprotocol/set-protocol-v2/contracts/lib/PreciseUnitMath.sol"; 28 | 29 | import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol"; 30 | import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; 31 | import { IManagerCore } from "../interfaces/IManagerCore.sol"; 32 | 33 | /** 34 | * @title StreamingFeeSplitExtension 35 | * @author Set Protocol 36 | * 37 | * Smart contract global extension which provides DelegatedManager owner and methodologist the ability to accrue and split 38 | * streaming fees. Owner may configure the fee split percentages. 39 | * 40 | * Notes 41 | * - the fee split is set on the Delegated Manager contract 42 | * - when fees distributed via this contract will be inclusive of all fee types 43 | */ 44 | contract StreamingFeeSplitExtension is BaseGlobalExtension { 45 | using Address for address; 46 | using PreciseUnitMath for uint256; 47 | using SafeMath for uint256; 48 | 49 | /* ============ Events ============ */ 50 | 51 | event StreamingFeeSplitExtensionInitialized( 52 | address indexed _setToken, 53 | address indexed _delegatedManager 54 | ); 55 | 56 | event FeesDistributed( 57 | address _setToken, 58 | address indexed _ownerFeeRecipient, 59 | address indexed _methodologist, 60 | uint256 _ownerTake, 61 | uint256 _methodologistTake 62 | ); 63 | 64 | /* ============ State Variables ============ */ 65 | 66 | // Instance of StreamingFeeModule 67 | IStreamingFeeModule public immutable streamingFeeModule; 68 | 69 | /* ============ Constructor ============ */ 70 | 71 | constructor( 72 | IManagerCore _managerCore, 73 | IStreamingFeeModule _streamingFeeModule 74 | ) 75 | public 76 | BaseGlobalExtension(_managerCore) 77 | { 78 | streamingFeeModule = _streamingFeeModule; 79 | } 80 | 81 | /* ============ External Functions ============ */ 82 | 83 | /** 84 | * ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for 85 | * owner and methodologist, and sends to owner fee recipient and methodologist respectively. 86 | */ 87 | function accrueFeesAndDistribute(ISetToken _setToken) public { 88 | // Emits a FeeActualized event 89 | streamingFeeModule.accrueFee(_setToken); 90 | 91 | IDelegatedManager delegatedManager = _manager(_setToken); 92 | 93 | uint256 totalFees = _setToken.balanceOf(address(delegatedManager)); 94 | 95 | address methodologist = delegatedManager.methodologist(); 96 | address ownerFeeRecipient = delegatedManager.ownerFeeRecipient(); 97 | 98 | uint256 ownerTake = totalFees.preciseMul(delegatedManager.ownerFeeSplit()); 99 | uint256 methodologistTake = totalFees.sub(ownerTake); 100 | 101 | if (ownerTake > 0) { 102 | delegatedManager.transferTokens(address(_setToken), ownerFeeRecipient, ownerTake); 103 | } 104 | 105 | if (methodologistTake > 0) { 106 | delegatedManager.transferTokens(address(_setToken), methodologist, methodologistTake); 107 | } 108 | 109 | emit FeesDistributed(address(_setToken), ownerFeeRecipient, methodologist, ownerTake, methodologistTake); 110 | } 111 | 112 | /** 113 | * ONLY OWNER: Initializes StreamingFeeModule on the SetToken associated with the DelegatedManager. 114 | * 115 | * @param _delegatedManager Instance of the DelegatedManager to initialize the StreamingFeeModule for 116 | * @param _settings FeeState struct defining fee parameters for StreamingFeeModule initialization 117 | */ 118 | function initializeModule( 119 | IDelegatedManager _delegatedManager, 120 | IStreamingFeeModule.FeeState memory _settings 121 | ) 122 | external 123 | onlyOwnerAndValidManager(_delegatedManager) 124 | { 125 | require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized"); 126 | 127 | _initializeModule(_delegatedManager.setToken(), _delegatedManager, _settings); 128 | } 129 | 130 | /** 131 | * ONLY OWNER: Initializes StreamingFeeSplitExtension to the DelegatedManager. 132 | * 133 | * @param _delegatedManager Instance of the DelegatedManager to initialize 134 | */ 135 | function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) { 136 | require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending"); 137 | 138 | ISetToken setToken = _delegatedManager.setToken(); 139 | 140 | _initializeExtension(setToken, _delegatedManager); 141 | 142 | emit StreamingFeeSplitExtensionInitialized(address(setToken), address(_delegatedManager)); 143 | } 144 | 145 | /** 146 | * ONLY OWNER: Initializes StreamingFeeSplitExtension to the DelegatedManager and StreamingFeeModule to the SetToken 147 | * 148 | * @param _delegatedManager Instance of the DelegatedManager to initialize 149 | * @param _settings FeeState struct defining fee parameters for StreamingFeeModule initialization 150 | */ 151 | function initializeModuleAndExtension( 152 | IDelegatedManager _delegatedManager, 153 | IStreamingFeeModule.FeeState memory _settings 154 | ) 155 | external 156 | onlyOwnerAndValidManager(_delegatedManager) 157 | { 158 | require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending"); 159 | 160 | ISetToken setToken = _delegatedManager.setToken(); 161 | 162 | _initializeExtension(setToken, _delegatedManager); 163 | _initializeModule(setToken, _delegatedManager, _settings); 164 | 165 | emit StreamingFeeSplitExtensionInitialized(address(setToken), address(_delegatedManager)); 166 | } 167 | 168 | /** 169 | * ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the StreamingFeeSplitExtension 170 | */ 171 | function removeExtension() external override { 172 | IDelegatedManager delegatedManager = IDelegatedManager(msg.sender); 173 | ISetToken setToken = delegatedManager.setToken(); 174 | 175 | _removeExtension(setToken, delegatedManager); 176 | } 177 | 178 | /** 179 | * ONLY OWNER: Updates streaming fee on StreamingFeeModule. 180 | * 181 | * NOTE: This will accrue streaming fees though not send to owner fee recipient and methodologist. 182 | * 183 | * @param _setToken Instance of the SetToken to update streaming fee for 184 | * @param _newFee Percent of Set accruing to fee extension annually (1% = 1e16, 100% = 1e18) 185 | */ 186 | function updateStreamingFee(ISetToken _setToken, uint256 _newFee) 187 | external 188 | onlyOwner(_setToken) 189 | { 190 | bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", _setToken, _newFee); 191 | _invokeManager(_manager(_setToken), address(streamingFeeModule), callData); 192 | } 193 | 194 | /** 195 | * ONLY OWNER: Updates fee recipient on StreamingFeeModule 196 | * 197 | * @param _setToken Instance of the SetToken to update fee recipient for 198 | * @param _newFeeRecipient Address of new fee recipient. This should be the address of the DelegatedManager 199 | */ 200 | function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient) 201 | external 202 | onlyOwner(_setToken) 203 | { 204 | bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", _setToken, _newFeeRecipient); 205 | _invokeManager(_manager(_setToken), address(streamingFeeModule), callData); 206 | } 207 | 208 | /* ============ Internal Functions ============ */ 209 | 210 | /** 211 | * Internal function to initialize StreamingFeeModule on the SetToken associated with the DelegatedManager. 212 | * 213 | * @param _setToken Instance of the SetToken corresponding to the DelegatedManager 214 | * @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for 215 | * @param _settings FeeState struct defining fee parameters for StreamingFeeModule initialization 216 | */ 217 | function _initializeModule( 218 | ISetToken _setToken, 219 | IDelegatedManager _delegatedManager, 220 | IStreamingFeeModule.FeeState memory _settings 221 | ) 222 | internal 223 | { 224 | bytes memory callData = abi.encodeWithSignature( 225 | "initialize(address,(address,uint256,uint256,uint256))", 226 | _setToken, 227 | _settings); 228 | _invokeManager(_delegatedManager, address(streamingFeeModule), callData); 229 | } 230 | } -------------------------------------------------------------------------------- /contracts/extensions/TradeExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 22 | import { ITradeModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/ITradeModule.sol"; 23 | 24 | import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol"; 25 | import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; 26 | import { IManagerCore } from "../interfaces/IManagerCore.sol"; 27 | 28 | /** 29 | * @title TradeExtension 30 | * @author Set Protocol 31 | * 32 | * Smart contract global extension which provides DelegatedManager privileged operator(s) the ability to trade on a DEX 33 | * and the owner the ability to restrict operator(s) permissions with an asset whitelist. 34 | */ 35 | contract TradeExtension is BaseGlobalExtension { 36 | 37 | /* ============ Events ============ */ 38 | 39 | event TradeExtensionInitialized( 40 | address indexed _setToken, 41 | address indexed _delegatedManager 42 | ); 43 | 44 | /* ============ State Variables ============ */ 45 | 46 | // Instance of TradeModule 47 | ITradeModule public immutable tradeModule; 48 | 49 | /* ============ Constructor ============ */ 50 | 51 | constructor( 52 | IManagerCore _managerCore, 53 | ITradeModule _tradeModule 54 | ) 55 | public 56 | BaseGlobalExtension(_managerCore) 57 | { 58 | tradeModule = _tradeModule; 59 | } 60 | 61 | /* ============ External Functions ============ */ 62 | 63 | /** 64 | * ONLY OWNER: Initializes TradeModule on the SetToken associated with the DelegatedManager. 65 | * 66 | * @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for 67 | */ 68 | function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) { 69 | require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized"); 70 | 71 | _initializeModule(_delegatedManager.setToken(), _delegatedManager); 72 | } 73 | 74 | /** 75 | * ONLY OWNER: Initializes TradeExtension to the DelegatedManager. 76 | * 77 | * @param _delegatedManager Instance of the DelegatedManager to initialize 78 | */ 79 | function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) { 80 | require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending"); 81 | 82 | ISetToken setToken = _delegatedManager.setToken(); 83 | 84 | _initializeExtension(setToken, _delegatedManager); 85 | 86 | emit TradeExtensionInitialized(address(setToken), address(_delegatedManager)); 87 | } 88 | 89 | /** 90 | * ONLY OWNER: Initializes TradeExtension to the DelegatedManager and TradeModule to the SetToken 91 | * 92 | * @param _delegatedManager Instance of the DelegatedManager to initialize 93 | */ 94 | function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){ 95 | require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending"); 96 | 97 | ISetToken setToken = _delegatedManager.setToken(); 98 | 99 | _initializeExtension(setToken, _delegatedManager); 100 | _initializeModule(setToken, _delegatedManager); 101 | 102 | emit TradeExtensionInitialized(address(setToken), address(_delegatedManager)); 103 | } 104 | 105 | /** 106 | * ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the TradeExtension 107 | */ 108 | function removeExtension() external override { 109 | IDelegatedManager delegatedManager = IDelegatedManager(msg.sender); 110 | ISetToken setToken = delegatedManager.setToken(); 111 | 112 | _removeExtension(setToken, delegatedManager); 113 | } 114 | 115 | /** 116 | * ONLY OPERATOR: Executes a trade on a supported DEX. 117 | * @dev Although the SetToken units are passed in for the send and receive quantities, the total quantity 118 | * sent and received is the quantity of SetToken units multiplied by the SetToken totalSupply. 119 | * 120 | * @param _setToken Instance of the SetToken to trade 121 | * @param _exchangeName Human readable name of the exchange in the integrations registry 122 | * @param _sendToken Address of the token to be sent to the exchange 123 | * @param _sendQuantity Units of token in SetToken sent to the exchange 124 | * @param _receiveToken Address of the token that will be received from the exchange 125 | * @param _minReceiveQuantity Min units of token in SetToken to be received from the exchange 126 | * @param _data Arbitrary bytes to be used to construct trade call data 127 | */ 128 | function trade( 129 | ISetToken _setToken, 130 | string memory _exchangeName, 131 | address _sendToken, 132 | uint256 _sendQuantity, 133 | address _receiveToken, 134 | uint256 _minReceiveQuantity, 135 | bytes memory _data 136 | ) 137 | external 138 | onlyOperator(_setToken) 139 | onlyAllowedAsset(_setToken, _receiveToken) 140 | { 141 | bytes memory callData = abi.encodeWithSignature( 142 | "trade(address,string,address,uint256,address,uint256,bytes)", 143 | _setToken, 144 | _exchangeName, 145 | _sendToken, 146 | _sendQuantity, 147 | _receiveToken, 148 | _minReceiveQuantity, 149 | _data 150 | ); 151 | _invokeManager(_manager(_setToken), address(tradeModule), callData); 152 | } 153 | 154 | /* ============ Internal Functions ============ */ 155 | 156 | /** 157 | * Internal function to initialize TradeModule on the SetToken associated with the DelegatedManager. 158 | * 159 | * @param _setToken Instance of the SetToken corresponding to the DelegatedManager 160 | * @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for 161 | */ 162 | function _initializeModule(ISetToken _setToken, IDelegatedManager _delegatedManager) internal { 163 | bytes memory callData = abi.encodeWithSignature("initialize(address)", _setToken); 164 | _invokeManager(_delegatedManager, address(tradeModule), callData); 165 | } 166 | } -------------------------------------------------------------------------------- /contracts/extensions/WrapExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 23 | import { IWETH } from "@setprotocol/set-protocol-v2/contracts/interfaces/external/IWETH.sol"; 24 | import { IWrapModuleV2 } from "@setprotocol/set-protocol-v2/contracts/interfaces/IWrapModuleV2.sol"; 25 | 26 | import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol"; 27 | import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; 28 | import { IManagerCore } from "../interfaces/IManagerCore.sol"; 29 | 30 | /** 31 | * @title WrapExtension 32 | * @author Set Protocol 33 | * 34 | * Smart contract global extension which provides DelegatedManager operator(s) the ability to wrap ERC20 and Ether positions 35 | * via third party protocols. 36 | * 37 | * Some examples of wrap actions include wrapping, DAI to cDAI (Compound) or Dai to aDai (AAVE). 38 | */ 39 | contract WrapExtension is BaseGlobalExtension { 40 | 41 | /* ============ Events ============ */ 42 | 43 | event WrapExtensionInitialized( 44 | address indexed _setToken, 45 | address indexed _delegatedManager 46 | ); 47 | 48 | /* ============ State Variables ============ */ 49 | 50 | // Instance of WrapModuleV2 51 | IWrapModuleV2 public immutable wrapModule; 52 | 53 | /* ============ Constructor ============ */ 54 | 55 | /** 56 | * Instantiate with ManagerCore address and WrapModuleV2 address. 57 | * 58 | * @param _managerCore Address of ManagerCore contract 59 | * @param _wrapModule Address of WrapModuleV2 contract 60 | */ 61 | constructor( 62 | IManagerCore _managerCore, 63 | IWrapModuleV2 _wrapModule 64 | ) 65 | public 66 | BaseGlobalExtension(_managerCore) 67 | { 68 | wrapModule = _wrapModule; 69 | } 70 | 71 | /* ============ External Functions ============ */ 72 | 73 | /** 74 | * ONLY OWNER: Initializes WrapModuleV2 on the SetToken associated with the DelegatedManager. 75 | * 76 | * @param _delegatedManager Instance of the DelegatedManager to initialize the WrapModuleV2 for 77 | */ 78 | function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) { 79 | _initializeModule(_delegatedManager.setToken(), _delegatedManager); 80 | } 81 | 82 | /** 83 | * ONLY OWNER: Initializes WrapExtension to the DelegatedManager. 84 | * 85 | * @param _delegatedManager Instance of the DelegatedManager to initialize 86 | */ 87 | function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) { 88 | ISetToken setToken = _delegatedManager.setToken(); 89 | 90 | _initializeExtension(setToken, _delegatedManager); 91 | 92 | emit WrapExtensionInitialized(address(setToken), address(_delegatedManager)); 93 | } 94 | 95 | /** 96 | * ONLY OWNER: Initializes WrapExtension to the DelegatedManager and TradeModule to the SetToken 97 | * 98 | * @param _delegatedManager Instance of the DelegatedManager to initialize 99 | */ 100 | function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){ 101 | ISetToken setToken = _delegatedManager.setToken(); 102 | 103 | _initializeExtension(setToken, _delegatedManager); 104 | _initializeModule(setToken, _delegatedManager); 105 | 106 | emit WrapExtensionInitialized(address(setToken), address(_delegatedManager)); 107 | } 108 | 109 | /** 110 | * ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the WrapExtension 111 | */ 112 | function removeExtension() external override { 113 | IDelegatedManager delegatedManager = IDelegatedManager(msg.sender); 114 | ISetToken setToken = delegatedManager.setToken(); 115 | 116 | _removeExtension(setToken, delegatedManager); 117 | } 118 | 119 | /** 120 | * ONLY OPERATOR: Instructs the SetToken to wrap an underlying asset into a wrappedToken via a specified adapter. 121 | * 122 | * @param _setToken Instance of the SetToken 123 | * @param _underlyingToken Address of the component to be wrapped 124 | * @param _wrappedToken Address of the desired wrapped token 125 | * @param _underlyingUnits Quantity of underlying units in Position units 126 | * @param _integrationName Name of wrap module integration (mapping on integration registry) 127 | * @param _wrapData Arbitrary bytes to pass into the WrapV2Adapter 128 | */ 129 | function wrap( 130 | ISetToken _setToken, 131 | address _underlyingToken, 132 | address _wrappedToken, 133 | uint256 _underlyingUnits, 134 | string calldata _integrationName, 135 | bytes memory _wrapData 136 | ) 137 | external 138 | onlyOperator(_setToken) 139 | onlyAllowedAsset(_setToken, _wrappedToken) 140 | { 141 | bytes memory callData = abi.encodeWithSelector( 142 | IWrapModuleV2.wrap.selector, 143 | _setToken, 144 | _underlyingToken, 145 | _wrappedToken, 146 | _underlyingUnits, 147 | _integrationName, 148 | _wrapData 149 | ); 150 | _invokeManager(_manager(_setToken), address(wrapModule), callData); 151 | } 152 | 153 | /** 154 | * ONLY OPERATOR: Instructs the SetToken to wrap Ether into a wrappedToken via a specified adapter. Since SetTokens 155 | * only hold WETH, in order to support protocols that collateralize with Ether the SetToken's WETH must be unwrapped 156 | * first before sending to the external protocol. 157 | * 158 | * @param _setToken Instance of the SetToken 159 | * @param _wrappedToken Address of the desired wrapped token 160 | * @param _underlyingUnits Quantity of underlying units in Position units 161 | * @param _integrationName Name of wrap module integration (mapping on integration registry) 162 | * @param _wrapData Arbitrary bytes to pass into the WrapV2Adapter 163 | */ 164 | function wrapWithEther( 165 | ISetToken _setToken, 166 | address _wrappedToken, 167 | uint256 _underlyingUnits, 168 | string calldata _integrationName, 169 | bytes memory _wrapData 170 | ) 171 | external 172 | onlyOperator(_setToken) 173 | onlyAllowedAsset(_setToken, _wrappedToken) 174 | { 175 | bytes memory callData = abi.encodeWithSelector( 176 | IWrapModuleV2.wrapWithEther.selector, 177 | _setToken, 178 | _wrappedToken, 179 | _underlyingUnits, 180 | _integrationName, 181 | _wrapData 182 | ); 183 | _invokeManager(_manager(_setToken), address(wrapModule), callData); 184 | } 185 | 186 | /** 187 | * ONLY OPERATOR: Instructs the SetToken to unwrap a wrapped asset into its underlying via a specified adapter. 188 | * 189 | * @param _setToken Instance of the SetToken 190 | * @param _underlyingToken Address of the underlying asset 191 | * @param _wrappedToken Address of the component to be unwrapped 192 | * @param _wrappedUnits Quantity of wrapped tokens in Position units 193 | * @param _integrationName ID of wrap module integration (mapping on integration registry) 194 | * @param _unwrapData Arbitrary bytes to pass into the WrapV2Adapter 195 | */ 196 | function unwrap( 197 | ISetToken _setToken, 198 | address _underlyingToken, 199 | address _wrappedToken, 200 | uint256 _wrappedUnits, 201 | string calldata _integrationName, 202 | bytes memory _unwrapData 203 | ) 204 | external 205 | onlyOperator(_setToken) 206 | onlyAllowedAsset(_setToken, _underlyingToken) 207 | { 208 | bytes memory callData = abi.encodeWithSelector( 209 | IWrapModuleV2.unwrap.selector, 210 | _setToken, 211 | _underlyingToken, 212 | _wrappedToken, 213 | _wrappedUnits, 214 | _integrationName, 215 | _unwrapData 216 | ); 217 | _invokeManager(_manager(_setToken), address(wrapModule), callData); 218 | } 219 | 220 | /** 221 | * ONLY OPERATOR: Instructs the SetToken to unwrap a wrapped asset collateralized by Ether into Wrapped Ether. Since 222 | * external protocol will send back Ether that Ether must be Wrapped into WETH in order to be accounted for by SetToken. 223 | * 224 | * @param _setToken Instance of the SetToken 225 | * @param _wrappedToken Address of the component to be unwrapped 226 | * @param _wrappedUnits Quantity of wrapped tokens in Position units 227 | * @param _integrationName ID of wrap module integration (mapping on integration registry) 228 | * @param _unwrapData Arbitrary bytes to pass into the WrapV2Adapter 229 | */ 230 | function unwrapWithEther( 231 | ISetToken _setToken, 232 | address _wrappedToken, 233 | uint256 _wrappedUnits, 234 | string calldata _integrationName, 235 | bytes memory _unwrapData 236 | ) 237 | external 238 | onlyOperator(_setToken) 239 | onlyAllowedAsset(_setToken, address(wrapModule.weth())) 240 | { 241 | bytes memory callData = abi.encodeWithSelector( 242 | IWrapModuleV2.unwrapWithEther.selector, 243 | _setToken, 244 | _wrappedToken, 245 | _wrappedUnits, 246 | _integrationName, 247 | _unwrapData 248 | ); 249 | _invokeManager(_manager(_setToken), address(wrapModule), callData); 250 | } 251 | 252 | /* ============ Internal Functions ============ */ 253 | 254 | /** 255 | * Internal function to initialize WrapModuleV2 on the SetToken associated with the DelegatedManager. 256 | * 257 | * @param _setToken Instance of the SetToken corresponding to the DelegatedManager 258 | * @param _delegatedManager Instance of the DelegatedManager to initialize the WrapModuleV2 for 259 | */ 260 | function _initializeModule(ISetToken _setToken, IDelegatedManager _delegatedManager) internal { 261 | bytes memory callData = abi.encodeWithSelector(IWrapModuleV2.initialize.selector, _setToken); 262 | _invokeManager(_delegatedManager, address(wrapModule), callData); 263 | } 264 | } -------------------------------------------------------------------------------- /contracts/hooks/SupplyCapIssuanceHook.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental ABIEncoderV2; 21 | 22 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 23 | import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 24 | 25 | import { IManagerIssuanceHook } from "@setprotocol/set-protocol-v2/contracts/interfaces/IManagerIssuanceHook.sol"; 26 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 27 | 28 | 29 | /** 30 | * @title SupplyCapIssuanceHook 31 | * @author Set Protocol 32 | * 33 | * Issuance hook that checks new issuances won't push SetToken totalSupply over supply cap. 34 | */ 35 | contract SupplyCapIssuanceHook is Ownable, IManagerIssuanceHook { 36 | using SafeMath for uint256; 37 | 38 | /* ============ Events ============ */ 39 | 40 | event SupplyCapUpdated(uint256 _newCap); 41 | 42 | /* ============ State Variables ============ */ 43 | 44 | // Cap on totalSupply of Sets 45 | uint256 public supplyCap; 46 | 47 | /* ============ Constructor ============ */ 48 | 49 | /** 50 | * Constructor, overwrites owner and original supply cap. 51 | * 52 | * @param _initialOwner Owner address, overwrites Ownable logic which sets to deployer as default 53 | * @param _supplyCap Supply cap for Set (in wei of Set) 54 | */ 55 | constructor( 56 | address _initialOwner, 57 | uint256 _supplyCap 58 | ) 59 | public 60 | { 61 | supplyCap = _supplyCap; 62 | 63 | // Overwrite _owner param of Ownable contract 64 | transferOwnership(_initialOwner); 65 | } 66 | 67 | /** 68 | * Adheres to IManagerIssuanceHook interface, and checks to make sure the current issue call won't push total supply over cap. 69 | */ 70 | function invokePreIssueHook( 71 | ISetToken _setToken, 72 | uint256 _issueQuantity, 73 | address /*_sender*/, 74 | address /*_to*/ 75 | ) 76 | external 77 | override 78 | { 79 | uint256 totalSupply = _setToken.totalSupply(); 80 | 81 | require(totalSupply.add(_issueQuantity) <= supplyCap, "Supply cap exceeded"); 82 | } 83 | 84 | /** 85 | * Adheres to IManagerIssuanceHook interface 86 | */ 87 | function invokePreRedeemHook( 88 | ISetToken _setToken, 89 | uint256 _redeemQuantity, 90 | address _sender, 91 | address _to 92 | ) 93 | external 94 | override 95 | {} 96 | 97 | /** 98 | * ONLY OWNER: Updates supply cap 99 | */ 100 | function updateSupplyCap(uint256 _newCap) external onlyOwner { 101 | supplyCap = _newCap; 102 | SupplyCapUpdated(_newCap); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contracts/interfaces/IAdapter.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { IBaseManager } from "./IBaseManager.sol"; 23 | 24 | interface IAdapter { 25 | function manager() external view returns (IBaseManager); 26 | } -------------------------------------------------------------------------------- /contracts/interfaces/IBaseManager.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 23 | 24 | interface IBaseManager { 25 | function setToken() external returns(ISetToken); 26 | 27 | function methodologist() external returns(address); 28 | 29 | function operator() external returns(address); 30 | 31 | function interactManager(address _module, bytes calldata _encoded) external; 32 | 33 | function transferTokens(address _token, address _destination, uint256 _amount) external; 34 | } -------------------------------------------------------------------------------- /contracts/interfaces/IChainlinkAggregatorV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License, Version 2.0 2 | pragma solidity 0.6.10; 3 | 4 | interface IChainlinkAggregatorV3 { 5 | function latestAnswer() external view returns (int256); 6 | } -------------------------------------------------------------------------------- /contracts/interfaces/IDelegatedManager.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 23 | 24 | interface IDelegatedManager { 25 | function interactManager(address _module, bytes calldata _encoded) external; 26 | 27 | function initializeExtension() external; 28 | 29 | function transferTokens(address _token, address _destination, uint256 _amount) external; 30 | 31 | function updateOwnerFeeSplit(uint256 _newFeeSplit) external; 32 | 33 | function updateOwnerFeeRecipient(address _newFeeRecipient) external; 34 | 35 | function setMethodologist(address _newMethodologist) external; 36 | 37 | function transferOwnership(address _owner) external; 38 | 39 | function setToken() external view returns(ISetToken); 40 | function owner() external view returns(address); 41 | function methodologist() external view returns(address); 42 | function operatorAllowlist(address _operator) external view returns(bool); 43 | function assetAllowlist(address _asset) external view returns(bool); 44 | function useAssetAllowlist() external view returns(bool); 45 | function isAllowedAsset(address _asset) external view returns(bool); 46 | function isPendingExtension(address _extension) external view returns(bool); 47 | function isInitializedExtension(address _extension) external view returns(bool); 48 | function getExtensions() external view returns(address[] memory); 49 | function getOperators() external view returns(address[] memory); 50 | function getAllowedAssets() external view returns(address[] memory); 51 | function ownerFeeRecipient() external view returns(address); 52 | function ownerFeeSplit() external view returns(uint256); 53 | } -------------------------------------------------------------------------------- /contracts/interfaces/IExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { IBaseManager } from "./IBaseManager.sol"; 23 | 24 | interface IExtension { 25 | function manager() external view returns (IBaseManager); 26 | } -------------------------------------------------------------------------------- /contracts/interfaces/IGlobalExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | pragma experimental "ABIEncoderV2"; 21 | 22 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 23 | 24 | interface IGlobalExtension { 25 | function removeExtension() external; 26 | } -------------------------------------------------------------------------------- /contracts/interfaces/IManagerCore.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | pragma solidity 0.6.10; 19 | 20 | interface IManagerCore { 21 | function addManager(address _manager) external; 22 | function isExtension(address _extension) external view returns(bool); 23 | function isFactory(address _factory) external view returns(bool); 24 | function isManager(address _manager) external view returns(bool); 25 | function owner() external view returns(address); 26 | } -------------------------------------------------------------------------------- /contracts/interfaces/IPriceFeed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT License 2 | pragma solidity 0.6.10; 3 | 4 | interface IPriceFeed { 5 | function decimals() external view returns (uint8); 6 | 7 | /// @dev Returns the index price of the token. 8 | /// @param interval The interval represents twap interval. 9 | function getPrice(uint256 interval) external view returns (uint256); 10 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV3Quoter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.6.10; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /// @title Quoter Interface 6 | /// @notice Supports quoting the calculated amounts from exact input or exact output swaps 7 | /// @dev These functions are not marked view because they rely on calling non-view functions and reverting 8 | /// to compute the result. They are also not gas efficient and should not be called on-chain. 9 | interface IUniswapV3Quoter { 10 | /// @notice Returns the amount out received for a given exact input swap without executing the swap 11 | /// @param path The path of the swap, i.e. each token pair and the pool fee 12 | /// @param amountIn The amount of the first token to swap 13 | /// @return amountOut The amount of the last token that would be received 14 | function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut); 15 | 16 | /// @notice Returns the amount out received for a given exact input but for a swap of a single pool 17 | /// @param tokenIn The token being swapped in 18 | /// @param tokenOut The token being swapped out 19 | /// @param fee The fee of the token pool to consider for the pair 20 | /// @param amountIn The desired input amount 21 | /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap 22 | /// @return amountOut The amount of `tokenOut` that would be received 23 | function quoteExactInputSingle( 24 | address tokenIn, 25 | address tokenOut, 26 | uint24 fee, 27 | uint256 amountIn, 28 | uint160 sqrtPriceLimitX96 29 | ) external returns (uint256 amountOut); 30 | 31 | /// @notice Returns the amount in required for a given exact output swap without executing the swap 32 | /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order 33 | /// @param amountOut The amount of the last token to receive 34 | /// @return amountIn The amount of first token required to be paid 35 | function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn); 36 | 37 | /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool 38 | /// @param tokenIn The token being swapped in 39 | /// @param tokenOut The token being swapped out 40 | /// @param fee The fee of the token pool to consider for the pair 41 | /// @param amountOut The desired output amount 42 | /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap 43 | /// @return amountIn The amount required as the input for the swap in order to receive `amountOut` 44 | function quoteExactOutputSingle( 45 | address tokenIn, 46 | address tokenOut, 47 | uint24 fee, 48 | uint256 amountOut, 49 | uint160 sqrtPriceLimitX96 50 | ) external returns (uint256 amountIn); 51 | } 52 | -------------------------------------------------------------------------------- /contracts/lib/BaseExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { AddressArrayUtils } from "@setprotocol/set-protocol-v2/contracts/lib/AddressArrayUtils.sol"; 22 | 23 | import { IBaseManager } from "../interfaces/IBaseManager.sol"; 24 | 25 | /** 26 | * @title BaseExtension 27 | * @author Set Protocol 28 | * 29 | * Abstract class that houses common extension-related state and functions. 30 | */ 31 | abstract contract BaseExtension { 32 | using AddressArrayUtils for address[]; 33 | 34 | /* ============ Events ============ */ 35 | 36 | event CallerStatusUpdated(address indexed _caller, bool _status); 37 | event AnyoneCallableUpdated(bool indexed _status); 38 | 39 | /* ============ Modifiers ============ */ 40 | 41 | /** 42 | * Throws if the sender is not the SetToken operator 43 | */ 44 | modifier onlyOperator() { 45 | require(msg.sender == manager.operator(), "Must be operator"); 46 | _; 47 | } 48 | 49 | /** 50 | * Throws if the sender is not the SetToken methodologist 51 | */ 52 | modifier onlyMethodologist() { 53 | require(msg.sender == manager.methodologist(), "Must be methodologist"); 54 | _; 55 | } 56 | 57 | /** 58 | * Throws if caller is a contract, can be used to stop flash loan and sandwich attacks 59 | */ 60 | modifier onlyEOA() { 61 | require(msg.sender == tx.origin, "Caller must be EOA Address"); 62 | _; 63 | } 64 | 65 | /** 66 | * Throws if not allowed caller 67 | */ 68 | modifier onlyAllowedCaller(address _caller) { 69 | require(isAllowedCaller(_caller), "Address not permitted to call"); 70 | _; 71 | } 72 | 73 | /* ============ State Variables ============ */ 74 | 75 | // Instance of manager contract 76 | IBaseManager public manager; 77 | 78 | // Boolean indicating if anyone can call function 79 | bool public anyoneCallable; 80 | 81 | // Mapping of addresses allowed to call function 82 | mapping(address => bool) public callAllowList; 83 | 84 | /* ============ Constructor ============ */ 85 | 86 | constructor(IBaseManager _manager) public { manager = _manager; } 87 | 88 | /* ============ External Functions ============ */ 89 | 90 | /** 91 | * OPERATOR ONLY: Toggle ability for passed addresses to call only allowed caller functions 92 | * 93 | * @param _callers Array of caller addresses to toggle status 94 | * @param _statuses Array of statuses for each caller 95 | */ 96 | function updateCallerStatus(address[] calldata _callers, bool[] calldata _statuses) external onlyOperator { 97 | require(_callers.length == _statuses.length, "Array length mismatch"); 98 | require(_callers.length > 0, "Array length must be > 0"); 99 | require(!_callers.hasDuplicate(), "Cannot duplicate callers"); 100 | 101 | for (uint256 i = 0; i < _callers.length; i++) { 102 | address caller = _callers[i]; 103 | bool status = _statuses[i]; 104 | callAllowList[caller] = status; 105 | emit CallerStatusUpdated(caller, status); 106 | } 107 | } 108 | 109 | /** 110 | * OPERATOR ONLY: Toggle whether anyone can call function, bypassing the callAllowlist 111 | * 112 | * @param _status Boolean indicating whether to allow anyone call 113 | */ 114 | function updateAnyoneCallable(bool _status) external onlyOperator { 115 | anyoneCallable = _status; 116 | emit AnyoneCallableUpdated(_status); 117 | } 118 | 119 | /* ============ Internal Functions ============ */ 120 | 121 | /** 122 | * Invoke call from manager 123 | * 124 | * @param _module Module to interact with 125 | * @param _encoded Encoded byte data 126 | */ 127 | function invokeManager(address _module, bytes memory _encoded) internal { 128 | manager.interactManager(_module, _encoded); 129 | } 130 | 131 | /** 132 | * Determine if passed address is allowed to call function. If anyoneCallable set to true anyone can call otherwise needs to be approved. 133 | * 134 | * return bool Boolean indicating if allowed caller 135 | */ 136 | function isAllowedCaller(address _caller) internal view virtual returns (bool) { 137 | return anyoneCallable || callAllowList[_caller]; 138 | } 139 | } -------------------------------------------------------------------------------- /contracts/lib/BaseGlobalExtension.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { AddressArrayUtils } from "@setprotocol/set-protocol-v2/contracts/lib/AddressArrayUtils.sol"; 22 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 23 | 24 | import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; 25 | import { IManagerCore } from "../interfaces/IManagerCore.sol"; 26 | 27 | /** 28 | * @title BaseGlobalExtension 29 | * @author Set Protocol 30 | * 31 | * Abstract class that houses common global extension-related functions. Global extensions must 32 | * also have their own initializeExtension function (not included here because interfaces will vary). 33 | */ 34 | abstract contract BaseGlobalExtension { 35 | using AddressArrayUtils for address[]; 36 | 37 | /* ============ Events ============ */ 38 | 39 | event ExtensionRemoved( 40 | address indexed _setToken, 41 | address indexed _delegatedManager 42 | ); 43 | 44 | /* ============ State Variables ============ */ 45 | 46 | // Address of the ManagerCore 47 | IManagerCore public immutable managerCore; 48 | 49 | // Mapping from Set Token to DelegatedManager 50 | mapping(ISetToken => IDelegatedManager) public setManagers; 51 | 52 | /* ============ Modifiers ============ */ 53 | 54 | /** 55 | * Throws if the sender is not the SetToken manager contract owner 56 | */ 57 | modifier onlyOwner(ISetToken _setToken) { 58 | require(msg.sender == _manager(_setToken).owner(), "Must be owner"); 59 | _; 60 | } 61 | 62 | /** 63 | * Throws if the sender is not the SetToken methodologist 64 | */ 65 | modifier onlyMethodologist(ISetToken _setToken) { 66 | require(msg.sender == _manager(_setToken).methodologist(), "Must be methodologist"); 67 | _; 68 | } 69 | 70 | /** 71 | * Throws if the sender is not a SetToken operator 72 | */ 73 | modifier onlyOperator(ISetToken _setToken) { 74 | require(_manager(_setToken).operatorAllowlist(msg.sender), "Must be approved operator"); 75 | _; 76 | } 77 | 78 | /** 79 | * Throws if the sender is not the SetToken manager contract owner or if the manager is not enabled on the ManagerCore 80 | */ 81 | modifier onlyOwnerAndValidManager(IDelegatedManager _delegatedManager) { 82 | require(msg.sender == _delegatedManager.owner(), "Must be owner"); 83 | require(managerCore.isManager(address(_delegatedManager)), "Must be ManagerCore-enabled manager"); 84 | _; 85 | } 86 | 87 | /** 88 | * Throws if asset is not allowed to be held by the Set 89 | */ 90 | modifier onlyAllowedAsset(ISetToken _setToken, address _asset) { 91 | require(_manager(_setToken).isAllowedAsset(_asset), "Must be allowed asset"); 92 | _; 93 | } 94 | 95 | /* ============ Constructor ============ */ 96 | 97 | /** 98 | * Set state variables 99 | * 100 | * @param _managerCore Address of managerCore contract 101 | */ 102 | constructor(IManagerCore _managerCore) public { 103 | managerCore = _managerCore; 104 | } 105 | 106 | /* ============ External Functions ============ */ 107 | 108 | /** 109 | * ONLY MANAGER: Deletes SetToken/Manager state from extension. Must only be callable by manager! 110 | */ 111 | function removeExtension() external virtual; 112 | 113 | /* ============ Internal Functions ============ */ 114 | 115 | /** 116 | * Invoke call from manager 117 | * 118 | * @param _delegatedManager Manager to interact with 119 | * @param _module Module to interact with 120 | * @param _encoded Encoded byte data 121 | */ 122 | function _invokeManager(IDelegatedManager _delegatedManager, address _module, bytes memory _encoded) internal { 123 | _delegatedManager.interactManager(_module, _encoded); 124 | } 125 | 126 | /** 127 | * Internal function to grab manager of passed SetToken from extensions data structure. 128 | * 129 | * @param _setToken SetToken who's manager is needed 130 | */ 131 | function _manager(ISetToken _setToken) internal view returns (IDelegatedManager) { 132 | return setManagers[_setToken]; 133 | } 134 | 135 | /** 136 | * Internal function to initialize extension to the DelegatedManager. 137 | * 138 | * @param _setToken Instance of the SetToken corresponding to the DelegatedManager 139 | * @param _delegatedManager Instance of the DelegatedManager to initialize 140 | */ 141 | function _initializeExtension(ISetToken _setToken, IDelegatedManager _delegatedManager) internal { 142 | setManagers[_setToken] = _delegatedManager; 143 | 144 | _delegatedManager.initializeExtension(); 145 | } 146 | 147 | /** 148 | * ONLY MANAGER: Internal function to delete SetToken/Manager state from extension 149 | */ 150 | function _removeExtension(ISetToken _setToken, IDelegatedManager _delegatedManager) internal { 151 | require(msg.sender == address(_manager(_setToken)), "Must be Manager"); 152 | 153 | delete setManagers[_setToken]; 154 | 155 | emit ExtensionRemoved(address(_setToken), address(_delegatedManager)); 156 | } 157 | } -------------------------------------------------------------------------------- /contracts/lib/MutualUpgrade.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | /** 22 | * @title MutualUpgrade 23 | * @author Set Protocol 24 | * 25 | * The MutualUpgrade contract contains a modifier for handling mutual upgrades between two parties 26 | */ 27 | contract MutualUpgrade { 28 | /* ============ State Variables ============ */ 29 | 30 | // Mapping of upgradable units and if upgrade has been initialized by other party 31 | mapping(bytes32 => bool) public mutualUpgrades; 32 | 33 | /* ============ Events ============ */ 34 | 35 | event MutualUpgradeRegistered( 36 | bytes32 _upgradeHash 37 | ); 38 | 39 | /* ============ Modifiers ============ */ 40 | 41 | modifier mutualUpgrade(address _signerOne, address _signerTwo) { 42 | require( 43 | msg.sender == _signerOne || msg.sender == _signerTwo, 44 | "Must be authorized address" 45 | ); 46 | 47 | address nonCaller = _getNonCaller(_signerOne, _signerTwo); 48 | 49 | // The upgrade hash is defined by the hash of the transaction call data and sender of msg, 50 | // which uniquely identifies the function, arguments, and sender. 51 | bytes32 expectedHash = keccak256(abi.encodePacked(msg.data, nonCaller)); 52 | 53 | if (!mutualUpgrades[expectedHash]) { 54 | bytes32 newHash = keccak256(abi.encodePacked(msg.data, msg.sender)); 55 | 56 | mutualUpgrades[newHash] = true; 57 | 58 | emit MutualUpgradeRegistered(newHash); 59 | 60 | return; 61 | } 62 | 63 | delete mutualUpgrades[expectedHash]; 64 | 65 | // Run the rest of the upgrades 66 | _; 67 | } 68 | 69 | /* ============ Internal Functions ============ */ 70 | 71 | function _getNonCaller(address _signerOne, address _signerTwo) internal view returns(address) { 72 | return msg.sender == _signerOne ? _signerTwo : _signerOne; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/lib/MutualUpgradeV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | /** 22 | * @title MutualUpgradeV2 23 | * @author Set Protocol 24 | * 25 | * The MutualUpgradeV2 contract contains a modifier for handling mutual upgrades between two parties 26 | * 27 | * CHANGELOG: 28 | * - Update mutualUpgrade to allow single transaction execution if the two signing addresses are the same 29 | */ 30 | contract MutualUpgradeV2 { 31 | /* ============ State Variables ============ */ 32 | 33 | // Mapping of upgradable units and if upgrade has been initialized by other party 34 | mapping(bytes32 => bool) public mutualUpgrades; 35 | 36 | /* ============ Events ============ */ 37 | 38 | event MutualUpgradeRegistered( 39 | bytes32 _upgradeHash 40 | ); 41 | 42 | /* ============ Modifiers ============ */ 43 | 44 | modifier mutualUpgrade(address _signerOne, address _signerTwo) { 45 | require( 46 | msg.sender == _signerOne || msg.sender == _signerTwo, 47 | "Must be authorized address" 48 | ); 49 | 50 | // If the two signing addresses are the same, skip upgrade hash step 51 | if (_signerOne == _signerTwo) { 52 | _; 53 | } 54 | 55 | address nonCaller = _getNonCaller(_signerOne, _signerTwo); 56 | 57 | // The upgrade hash is defined by the hash of the transaction call data and sender of msg, 58 | // which uniquely identifies the function, arguments, and sender. 59 | bytes32 expectedHash = keccak256(abi.encodePacked(msg.data, nonCaller)); 60 | 61 | if (!mutualUpgrades[expectedHash]) { 62 | bytes32 newHash = keccak256(abi.encodePacked(msg.data, msg.sender)); 63 | 64 | mutualUpgrades[newHash] = true; 65 | 66 | emit MutualUpgradeRegistered(newHash); 67 | 68 | return; 69 | } 70 | 71 | delete mutualUpgrades[expectedHash]; 72 | 73 | // Run the rest of the upgrades 74 | _; 75 | } 76 | 77 | /* ============ Internal Functions ============ */ 78 | 79 | function _getNonCaller(address _signerOne, address _signerTwo) internal view returns(address) { 80 | return msg.sender == _signerOne ? _signerTwo : _signerOne; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/lib/TimeLockUpgrade.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 22 | import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 23 | 24 | /** 25 | * @title TimeLockUpgrade 26 | * @author Set Protocol 27 | * 28 | * The TimeLockUpgrade contract contains a modifier for handling minimum time period updates 29 | */ 30 | contract TimeLockUpgrade is 31 | Ownable 32 | { 33 | using SafeMath for uint256; 34 | 35 | /* ============ State Variables ============ */ 36 | 37 | // Timelock Upgrade Period in seconds 38 | uint256 public timeLockPeriod; 39 | 40 | // Mapping of upgradable units and initialized timelock 41 | mapping(bytes32 => uint256) public timeLockedUpgrades; 42 | 43 | /* ============ Events ============ */ 44 | 45 | event UpgradeRegistered( 46 | bytes32 _upgradeHash, 47 | uint256 _timestamp 48 | ); 49 | 50 | /* ============ Modifiers ============ */ 51 | 52 | modifier timeLockUpgrade() { 53 | // If the time lock period is 0, then allow non-timebound upgrades. 54 | // This is useful for initialization of the protocol and for testing. 55 | if (timeLockPeriod == 0) { 56 | _; 57 | 58 | return; 59 | } 60 | 61 | // The upgrade hash is defined by the hash of the transaction call data, 62 | // which uniquely identifies the function as well as the passed in arguments. 63 | bytes32 upgradeHash = keccak256( 64 | abi.encodePacked( 65 | msg.data 66 | ) 67 | ); 68 | 69 | uint256 registrationTime = timeLockedUpgrades[upgradeHash]; 70 | 71 | // If the upgrade hasn't been registered, register with the current time. 72 | if (registrationTime == 0) { 73 | timeLockedUpgrades[upgradeHash] = block.timestamp; 74 | 75 | emit UpgradeRegistered( 76 | upgradeHash, 77 | block.timestamp 78 | ); 79 | 80 | return; 81 | } 82 | 83 | require( 84 | block.timestamp >= registrationTime.add(timeLockPeriod), 85 | "TimeLockUpgrade: Time lock period must have elapsed." 86 | ); 87 | 88 | // Reset the timestamp to 0 89 | timeLockedUpgrades[upgradeHash] = 0; 90 | 91 | // Run the rest of the upgrades 92 | _; 93 | } 94 | 95 | /* ============ Function ============ */ 96 | 97 | /** 98 | * Change timeLockPeriod period. Generally called after initially settings have been set up. 99 | * 100 | * @param _timeLockPeriod Time in seconds that upgrades need to be evaluated before execution 101 | */ 102 | function setTimeLockPeriod( 103 | uint256 _timeLockPeriod 104 | ) 105 | virtual 106 | external 107 | onlyOwner 108 | { 109 | // Only allow setting of the timeLockPeriod if the period is greater than the existing 110 | require( 111 | _timeLockPeriod > timeLockPeriod, 112 | "TimeLockUpgrade: New period must be greater than existing" 113 | ); 114 | 115 | timeLockPeriod = _timeLockPeriod; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /contracts/manager/BaseManager.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { Address } from "@openzeppelin/contracts/utils/Address.sol"; 22 | 23 | import { AddressArrayUtils } from "@setprotocol/set-protocol-v2/contracts/lib/AddressArrayUtils.sol"; 24 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 25 | 26 | import { IAdapter } from "../interfaces/IAdapter.sol"; 27 | 28 | 29 | /** 30 | * @title BaseManager 31 | * @author Set Protocol 32 | * 33 | * Smart contract manager that contains permissions and admin functionality 34 | */ 35 | contract BaseManager { 36 | using Address for address; 37 | using AddressArrayUtils for address[]; 38 | 39 | /* ============ Events ============ */ 40 | 41 | event AdapterAdded( 42 | address _adapter 43 | ); 44 | 45 | event AdapterRemoved( 46 | address _adapter 47 | ); 48 | 49 | event MethodologistChanged( 50 | address _oldMethodologist, 51 | address _newMethodologist 52 | ); 53 | 54 | event OperatorChanged( 55 | address _oldOperator, 56 | address _newOperator 57 | ); 58 | 59 | /* ============ Modifiers ============ */ 60 | 61 | /** 62 | * Throws if the sender is not the SetToken operator 63 | */ 64 | modifier onlyOperator() { 65 | require(msg.sender == operator, "Must be operator"); 66 | _; 67 | } 68 | 69 | /** 70 | * Throws if the sender is not the SetToken methodologist 71 | */ 72 | modifier onlyMethodologist() { 73 | require(msg.sender == methodologist, "Must be methodologist"); 74 | _; 75 | } 76 | 77 | /** 78 | * Throws if the sender is not a listed adapter 79 | */ 80 | modifier onlyAdapter() { 81 | require(isAdapter[msg.sender], "Must be adapter"); 82 | _; 83 | } 84 | 85 | /* ============ State Variables ============ */ 86 | 87 | // Instance of SetToken 88 | ISetToken public setToken; 89 | 90 | // Array of listed adapters 91 | address[] internal adapters; 92 | 93 | // Mapping to check if adapter is added 94 | mapping(address => bool) public isAdapter; 95 | 96 | // Address of operator which typically executes manager only functions on Set Protocol modules 97 | address public operator; 98 | 99 | // Address of methodologist which serves as providing methodology for the index 100 | address public methodologist; 101 | 102 | /* ============ Constructor ============ */ 103 | 104 | constructor( 105 | ISetToken _setToken, 106 | address _operator, 107 | address _methodologist 108 | ) 109 | public 110 | { 111 | setToken = _setToken; 112 | operator = _operator; 113 | methodologist = _methodologist; 114 | } 115 | 116 | /* ============ External Functions ============ */ 117 | 118 | /** 119 | * MUTUAL UPGRADE: Update the SetToken manager address. Operator and Methodologist must each call 120 | * this function to execute the update. 121 | * 122 | * @param _newManager New manager address 123 | */ 124 | function setManager(address _newManager) external onlyOperator { 125 | require(_newManager != address(0), "Zero address not valid"); 126 | setToken.setManager(_newManager); 127 | } 128 | 129 | /** 130 | * MUTUAL UPGRADE: Add a new adapter that the BaseManager can call. 131 | * 132 | * @param _adapter New adapter to add 133 | */ 134 | function addAdapter(address _adapter) external onlyOperator { 135 | require(!isAdapter[_adapter], "Adapter already exists"); 136 | require(address(IAdapter(_adapter).manager()) == address(this), "Adapter manager invalid"); 137 | 138 | adapters.push(_adapter); 139 | 140 | isAdapter[_adapter] = true; 141 | 142 | emit AdapterAdded(_adapter); 143 | } 144 | 145 | /** 146 | * MUTUAL UPGRADE: Remove an existing adapter tracked by the BaseManager. 147 | * 148 | * @param _adapter Old adapter to remove 149 | */ 150 | function removeAdapter(address _adapter) external onlyOperator { 151 | require(isAdapter[_adapter], "Adapter does not exist"); 152 | 153 | adapters.removeStorage(_adapter); 154 | 155 | isAdapter[_adapter] = false; 156 | 157 | emit AdapterRemoved(_adapter); 158 | } 159 | 160 | /** 161 | * ADAPTER ONLY: Interact with a module registered on the SetToken. 162 | * 163 | * @param _module Module to interact with 164 | * @param _data Byte data of function to call in module 165 | */ 166 | function interactManager(address _module, bytes calldata _data) external onlyAdapter { 167 | // Invoke call to module, assume value will always be 0 168 | _module.functionCallWithValue(_data, 0); 169 | } 170 | 171 | /** 172 | * OPERATOR ONLY: Add a new module to the SetToken. 173 | * 174 | * @param _module New module to add 175 | */ 176 | function addModule(address _module) external onlyOperator { 177 | setToken.addModule(_module); 178 | } 179 | 180 | /** 181 | * OPERATOR ONLY: Remove a new module from the SetToken. 182 | * 183 | * @param _module Module to remove 184 | */ 185 | function removeModule(address _module) external onlyOperator { 186 | setToken.removeModule(_module); 187 | } 188 | 189 | /** 190 | * METHODOLOGIST ONLY: Update the methodologist address 191 | * 192 | * @param _newMethodologist New methodologist address 193 | */ 194 | function setMethodologist(address _newMethodologist) external onlyMethodologist { 195 | emit MethodologistChanged(methodologist, _newMethodologist); 196 | 197 | methodologist = _newMethodologist; 198 | } 199 | 200 | /** 201 | * OPERATOR ONLY: Update the operator address 202 | * 203 | * @param _newOperator New operator address 204 | */ 205 | function setOperator(address _newOperator) external onlyOperator { 206 | emit OperatorChanged(operator, _newOperator); 207 | 208 | operator = _newOperator; 209 | } 210 | 211 | /* ============ External Getters ============ */ 212 | 213 | function getAdapters() external view returns(address[] memory) { 214 | return adapters; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /contracts/mocks/BaseExtensionMock.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { BaseExtension } from "../lib/BaseExtension.sol"; 22 | import { IBaseManager } from "../interfaces/IBaseManager.sol"; 23 | 24 | contract BaseExtensionMock is BaseExtension { 25 | 26 | constructor(IBaseManager _manager) public BaseExtension(_manager) {} 27 | 28 | /* ============ External Functions ============ */ 29 | 30 | function testInvokeManager(address _module, bytes calldata _encoded) external { 31 | invokeManager(_module, _encoded); 32 | } 33 | 34 | function testOnlyOperator() 35 | external 36 | onlyOperator 37 | {} 38 | 39 | function testOnlyMethodologist() 40 | external 41 | onlyMethodologist 42 | {} 43 | 44 | function testOnlyEOA() 45 | external 46 | onlyEOA 47 | {} 48 | 49 | function testOnlyAllowedCaller(address _caller) 50 | external 51 | onlyAllowedCaller(_caller) 52 | {} 53 | 54 | function interactManager(address _target, bytes calldata _data) external { 55 | invokeManager(_target, _data); 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/mocks/BaseGlobalExtensionMock.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 22 | 23 | import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol"; 24 | import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol"; 25 | import { IManagerCore } from "../interfaces/IManagerCore.sol"; 26 | import { ModuleMock } from "./ModuleMock.sol"; 27 | 28 | contract BaseGlobalExtensionMock is BaseGlobalExtension { 29 | 30 | /* ============ State Variables ============ */ 31 | 32 | ModuleMock public immutable module; 33 | 34 | /* ============ Constructor ============ */ 35 | 36 | constructor( 37 | IManagerCore _managerCore, 38 | ModuleMock _module 39 | ) 40 | public 41 | BaseGlobalExtension(_managerCore) 42 | { 43 | module = _module; 44 | } 45 | 46 | /* ============ External Functions ============ */ 47 | 48 | function initializeExtension( 49 | IDelegatedManager _delegatedManager 50 | ) 51 | external 52 | onlyOwnerAndValidManager(_delegatedManager) 53 | { 54 | require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending"); 55 | 56 | _initializeExtension(_delegatedManager.setToken(), _delegatedManager); 57 | } 58 | 59 | function initializeModuleAndExtension( 60 | IDelegatedManager _delegatedManager 61 | ) 62 | external 63 | onlyOwnerAndValidManager(_delegatedManager) 64 | { 65 | require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending"); 66 | 67 | ISetToken setToken = _delegatedManager.setToken(); 68 | 69 | _initializeExtension(setToken, _delegatedManager); 70 | 71 | bytes memory callData = abi.encodeWithSignature("initialize(address)", setToken); 72 | _invokeManager(_delegatedManager, address(module), callData); 73 | } 74 | 75 | function testInvokeManager(ISetToken _setToken, address _module, bytes calldata _encoded) external { 76 | _invokeManager(_manager(_setToken), _module, _encoded); 77 | } 78 | 79 | function testOnlyOwner(ISetToken _setToken) 80 | external 81 | onlyOwner(_setToken) 82 | {} 83 | 84 | function testOnlyMethodologist(ISetToken _setToken) 85 | external 86 | onlyMethodologist(_setToken) 87 | {} 88 | 89 | function testOnlyOperator(ISetToken _setToken) 90 | external 91 | onlyOperator(_setToken) 92 | {} 93 | 94 | function testOnlyOwnerAndValidManager(IDelegatedManager _delegatedManager) 95 | external 96 | onlyOwnerAndValidManager(_delegatedManager) 97 | {} 98 | 99 | function testOnlyAllowedAsset(ISetToken _setToken, address _asset) 100 | external 101 | onlyAllowedAsset(_setToken, _asset) 102 | {} 103 | 104 | function removeExtension() external override { 105 | IDelegatedManager delegatedManager = IDelegatedManager(msg.sender); 106 | ISetToken setToken = delegatedManager.setToken(); 107 | 108 | _removeExtension(setToken, delegatedManager); 109 | } 110 | } -------------------------------------------------------------------------------- /contracts/mocks/ChainlinkAggregatorMock.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | /** 22 | * Mock ChainlinkAggregator that can be passed to any contract that consumes these. 23 | * This contract was implemented for the PerpV2 fixture as a substitute for the smock-ed 24 | * aggregators they use in their own test suite. 25 | */ 26 | contract ChainlinkAggregatorMock { 27 | int256 public latestAnswer; 28 | uint80 public latestRoundId; 29 | uint256 public latestStartedAt; 30 | uint256 public latestUpdatedAt; 31 | uint80 public latestAnsweredInRound; 32 | uint8 public decimals; 33 | 34 | // Perp sets this to `6` in their fixtures... 35 | constructor(uint8 _decimals) public { 36 | decimals = _decimals; 37 | } 38 | 39 | /** 40 | * Typical usage for setting the BaseToken oracle to 100 is: 41 | * 42 | * ``` 43 | * await mockAggregator.setLatestAnswer(ethers.utils.parseUnits("100", 6)); 44 | * ``` 45 | */ 46 | function setLatestAnswer(int256 _latestAnswer) public { 47 | latestAnswer = _latestAnswer; 48 | } 49 | 50 | /** 51 | * Typical usage for setting the BaseToken oracle to 100 is: 52 | * 53 | * ``` 54 | * await mockAggregator.setRoundData(0, ethers.utils.parseUnits("100", 6),0,0,0); 55 | * ``` 56 | */ 57 | function setRoundData( 58 | uint80 _roundId, 59 | int256 _answer, 60 | uint256 _startedAt, 61 | uint256 _updatedAt, 62 | uint80 _answeredInRound 63 | ) 64 | public 65 | { 66 | latestRoundId = _roundId; 67 | latestAnswer = _answer; 68 | latestStartedAt = _startedAt; 69 | latestUpdatedAt = _updatedAt; 70 | latestAnsweredInRound = _answeredInRound; 71 | } 72 | 73 | // Consumed by PerpV2.ChainlinkPriceFeed 74 | function getRoundData(uint80 /* _roundId */) 75 | public 76 | view 77 | returns ( 78 | uint80 roundId, 79 | int256 answer, 80 | uint256 startedAt, 81 | uint256 updatedAt, 82 | uint80 answeredInRound 83 | ) 84 | { 85 | return ( 86 | latestRoundId, 87 | latestAnswer, 88 | latestStartedAt, 89 | latestUpdatedAt, 90 | latestAnsweredInRound 91 | ); 92 | } 93 | 94 | // Consumed by PerpV2.ChainlinkPriceFeed 95 | function latestRoundData() 96 | public 97 | view 98 | returns ( 99 | uint80 roundId, 100 | int256 answer, 101 | uint256 startedAt, 102 | uint256 updatedAt, 103 | uint80 answeredInRound 104 | ) 105 | { 106 | return ( 107 | latestRoundId, 108 | latestAnswer, 109 | latestStartedAt, 110 | latestUpdatedAt, 111 | latestAnsweredInRound 112 | ); 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /contracts/mocks/ManagerMock.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 22 | 23 | import { IGlobalExtension } from "../interfaces/IGlobalExtension.sol"; 24 | 25 | contract ManagerMock { 26 | ISetToken public immutable setToken; 27 | 28 | constructor( 29 | ISetToken _setToken 30 | ) 31 | public 32 | { 33 | setToken = _setToken; 34 | } 35 | 36 | function removeExtensions(address[] memory _extensions) external { 37 | for (uint256 i = 0; i < _extensions.length; i++) { 38 | address extension = _extensions[i]; 39 | IGlobalExtension(extension).removeExtension(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /contracts/mocks/ModuleMock.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | import { IController } from "@setprotocol/set-protocol-v2/contracts/interfaces/IController.sol"; 22 | import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol"; 23 | import { ModuleBase } from "@setprotocol/set-protocol-v2/contracts/protocol/lib/ModuleBase.sol"; 24 | 25 | contract ModuleMock is ModuleBase { 26 | 27 | bool public removed; 28 | 29 | /* ============ Constructor ============ */ 30 | 31 | constructor(IController _controller) public ModuleBase(_controller) {} 32 | 33 | /* ============ External Functions ============ */ 34 | 35 | function initialize( 36 | ISetToken _setToken 37 | ) 38 | external 39 | onlyValidAndPendingSet(_setToken) 40 | onlySetManager(_setToken, msg.sender) 41 | { 42 | _setToken.initializeModule(); 43 | } 44 | 45 | function removeModule() external override { 46 | removed = true; 47 | } 48 | } -------------------------------------------------------------------------------- /contracts/mocks/MutualUpgradeMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License, Version 2.0 2 | pragma solidity 0.6.10; 3 | 4 | import { MutualUpgrade } from "../lib/MutualUpgrade.sol"; 5 | 6 | 7 | // Mock contract implementation of MutualUpgrade functions 8 | contract MutualUpgradeMock is 9 | MutualUpgrade 10 | { 11 | uint256 public testUint; 12 | address public owner; 13 | address public methodologist; 14 | 15 | constructor(address _owner, address _methodologist) public { 16 | owner = _owner; 17 | methodologist = _methodologist; 18 | } 19 | 20 | function testMutualUpgrade( 21 | uint256 _testUint 22 | ) 23 | external 24 | mutualUpgrade(owner, methodologist) 25 | { 26 | testUint = _testUint; 27 | } 28 | } -------------------------------------------------------------------------------- /contracts/mocks/MutualUpgradeV2Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License, Version 2.0 2 | pragma solidity 0.6.10; 3 | 4 | import { MutualUpgradeV2 } from "../lib/MutualUpgradeV2.sol"; 5 | 6 | 7 | // Mock contract implementation of MutualUpgradeV2 functions 8 | contract MutualUpgradeV2Mock is 9 | MutualUpgradeV2 10 | { 11 | uint256 public testUint; 12 | address public owner; 13 | address public methodologist; 14 | 15 | constructor(address _owner, address _methodologist) public { 16 | owner = _owner; 17 | methodologist = _methodologist; 18 | } 19 | 20 | function testMutualUpgrade( 21 | uint256 _testUint 22 | ) 23 | external 24 | mutualUpgrade(owner, methodologist) 25 | { 26 | testUint = _testUint; 27 | } 28 | } -------------------------------------------------------------------------------- /contracts/mocks/PerpV2PriceFeedMock.sol: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Set Labs Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | SPDX-License-Identifier: Apache License, Version 2.0 17 | */ 18 | 19 | pragma solidity 0.6.10; 20 | 21 | /** 22 | * Mock PerpV2 Price Feed. 23 | */ 24 | contract PerpV2PriceFeedMock { 25 | uint8 public decimals; 26 | uint256 internal price; 27 | 28 | constructor(uint8 _decimals) public { 29 | decimals = _decimals; 30 | } 31 | 32 | /** 33 | * Typical usage for setting the BaseToken oracle to 100 is: 34 | * 35 | * ``` 36 | * await mockPriceFeed.setPrice(ethers.utils.parseUnits("100", decimals)); 37 | * ``` 38 | */ 39 | function setPrice(uint256 _price) public { 40 | price = _price; 41 | } 42 | 43 | 44 | /** 45 | * Returns the index price of the token. 46 | */ 47 | function getPrice(uint256 /*interval*/) external view returns (uint256) { 48 | return price; 49 | } 50 | } -------------------------------------------------------------------------------- /contracts/mocks/TradeAdapterMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License, Version 2.0 2 | pragma solidity 0.6.10; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | /** 7 | * Trade Adapter that doubles as a mock exchange 8 | */ 9 | contract TradeAdapterMock { 10 | 11 | /* ============ Helper Functions ============ */ 12 | 13 | function withdraw(address _token) 14 | external 15 | { 16 | uint256 balance = ERC20(_token).balanceOf(address(this)); 17 | require(ERC20(_token).transfer(msg.sender, balance), "ERC20 transfer failed"); 18 | } 19 | 20 | /* ============ Trade Functions ============ */ 21 | 22 | function trade( 23 | address _sourceToken, 24 | address _destinationToken, 25 | address _destinationAddress, 26 | uint256 _sourceQuantity, 27 | uint256 _minDestinationQuantity 28 | ) 29 | external 30 | { 31 | uint256 destinationBalance = ERC20(_destinationToken).balanceOf(address(this)); 32 | require(ERC20(_sourceToken).transferFrom(_destinationAddress, address(this), _sourceQuantity), "ERC20 TransferFrom failed"); 33 | if (_minDestinationQuantity == 1) { // byte revert case, min nonzero uint256 minimum receive quantity 34 | bytes memory data = abi.encodeWithSelector( 35 | bytes4(keccak256("trade(address,address,address,uint256,uint256)")), 36 | _sourceToken, 37 | _destinationToken, 38 | _destinationAddress, 39 | _sourceQuantity, 40 | _minDestinationQuantity 41 | ); 42 | assembly { revert(add(data, 32), mload(data)) } 43 | } 44 | if (destinationBalance >= _minDestinationQuantity) { // normal case 45 | require(ERC20(_destinationToken).transfer(_destinationAddress, destinationBalance), "ERC20 transfer failed"); 46 | } 47 | else { // string revert case, minimum destination quantity not in exchange 48 | revert("Insufficient funds in exchange"); 49 | } 50 | } 51 | 52 | /* ============ Adapter Functions ============ */ 53 | 54 | function getSpender() 55 | external 56 | view 57 | returns (address) 58 | { 59 | return address(this); 60 | } 61 | 62 | function getTradeCalldata( 63 | address _sourceToken, 64 | address _destinationToken, 65 | address _destinationAddress, 66 | uint256 _sourceQuantity, 67 | uint256 _minDestinationQuantity, 68 | bytes memory /* _data */ 69 | ) 70 | external 71 | view 72 | returns (address, uint256, bytes memory) 73 | { 74 | // Encode method data for SetToken to invoke 75 | bytes memory methodData = abi.encodeWithSignature( 76 | "trade(address,address,address,uint256,uint256)", 77 | _sourceToken, 78 | _destinationToken, 79 | _destinationAddress, 80 | _sourceQuantity, 81 | _minDestinationQuantity 82 | ); 83 | 84 | return (address(this), 0, methodData); 85 | } 86 | } -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | import chalk from "chalk"; 4 | import { HardhatUserConfig } from "hardhat/config"; 5 | import { privateKeys } from "./utils/wallets"; 6 | 7 | import "@nomiclabs/hardhat-waffle"; 8 | import "@typechain/hardhat"; 9 | import "hardhat-deploy"; 10 | import "solidity-coverage"; 11 | import "./tasks"; 12 | 13 | const forkingConfig = { 14 | url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_TOKEN}`, 15 | blockNumber: 12198000, 16 | }; 17 | 18 | const mochaConfig = { 19 | grep: "@forked-mainnet", 20 | invert: (process.env.FORK) ? false : true, 21 | timeout: (process.env.FORK) ? 100000 : 40000, 22 | } as Mocha.MochaOptions; 23 | 24 | checkForkedProviderEnvironment(); 25 | 26 | const config: HardhatUserConfig = { 27 | solidity: { 28 | version: "0.6.10", 29 | settings: { 30 | optimizer: { enabled: true, runs: 200 }, 31 | }, 32 | }, 33 | networks: { 34 | hardhat: { 35 | forking: (process.env.FORK) ? forkingConfig : undefined, 36 | accounts: getHardhatPrivateKeys(), 37 | }, 38 | localhost: { 39 | url: "http://127.0.0.1:8545", 40 | timeout: 200000, 41 | gas: 12000000, 42 | blockGasLimit: 12000000, 43 | }, 44 | kovan: { 45 | url: "https://kovan.infura.io/v3/" + process.env.INFURA_TOKEN, 46 | // @ts-ignore 47 | accounts: [`0x${process.env.KOVAN_DEPLOY_PRIVATE_KEY}`], 48 | }, 49 | staging_mainnet: { 50 | url: "https://mainnet.infura.io/v3/" + process.env.INFURA_TOKEN, 51 | // @ts-ignore 52 | accounts: [`0x${process.env.STAGING_MAINNET_DEPLOY_PRIVATE_KEY}`], 53 | }, 54 | production: { 55 | url: "https://mainnet.infura.io/v3/" + process.env.INFURA_TOKEN, 56 | // @ts-ignore 57 | accounts: [`0x${process.env.PRODUCTION_MAINNET_DEPLOY_PRIVATE_KEY}`], 58 | }, 59 | // To update coverage network configuration got o .solcover.js and update param in providerOptions field 60 | coverage: { 61 | url: "http://127.0.0.1:8555", // Coverage launches its own ganache-cli client 62 | timeout: 200000, 63 | }, 64 | }, 65 | // @ts-ignore 66 | typechain: { 67 | outDir: "typechain", 68 | target: "ethers-v5", 69 | externalArtifacts: ["external/**/*.json"], 70 | }, 71 | // Load non-strategies artifacts via hardhat-deploy:external config 72 | external: { 73 | contracts: [ 74 | { 75 | artifacts: "node_modules/@setprotocol/set-protocol-v2/artifacts", 76 | }, 77 | ], 78 | }, 79 | mocha: mochaConfig, 80 | }; 81 | 82 | function getHardhatPrivateKeys() { 83 | return privateKeys.map(key => { 84 | const ONE_MILLION_ETH = "1000000000000000000000000"; 85 | return { 86 | privateKey: key, 87 | balance: ONE_MILLION_ETH, 88 | }; 89 | }); 90 | } 91 | 92 | function checkForkedProviderEnvironment() { 93 | if (process.env.FORK && 94 | (!process.env.ALCHEMY_TOKEN || process.env.ALCHEMY_TOKEN === "fake_alchemy_token") 95 | ) { 96 | console.log(chalk.red( 97 | "You are running forked provider tests with invalid Alchemy credentials.\n" + 98 | "Update your ALCHEMY_TOKEN settings in the `.env` file." 99 | )); 100 | process.exit(1); 101 | } 102 | } 103 | 104 | export default config; 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@setprotocol/set-v2-strategies", 3 | "version": "0.0.14", 4 | "description": "", 5 | "main": "dist", 6 | "types": "dist/types", 7 | "files": [ 8 | "artifacts", 9 | "dist", 10 | "contracts", 11 | "external", 12 | "utils", 13 | "scripts", 14 | "typechain", 15 | "tsconfig.json" 16 | ], 17 | "scripts": { 18 | "build": "yarn clean && yarn compile && yarn build:typechain", 19 | "build:npm": "yarn clean && yarn compile:npm && yarn build:typechain", 20 | "build:typechain": "yarn typechain && yarn transpile-dist", 21 | "chain": "npx hardhat node", 22 | "clean": "rm -rf coverage.json .coverage_cache .coverage_contracts cache coverage typechain artifacts dist", 23 | "compile": "npx hardhat compile", 24 | "compile:npm": "SKIP_ABI_GAS_MODS=true npx hardhat compile", 25 | "coverage": "yarn clean && yarn build && yarn cov:command", 26 | "cov:command": "COVERAGE=true node --max-old-space-size=4096 ./node_modules/.bin/hardhat coverage", 27 | "etherscan:verify": "hardhat --network kovan etherscan-verify --solc-input --license 'None'", 28 | "flatten": "npx waffle flatten", 29 | "lint": "yarn run lint-sol && yarn run lint-ts", 30 | "lint-sol": "solhint 'contracts/**/*.sol'", 31 | "lint-ts": "eslint -c .eslintrc.js --ext .ts test utils tasks --fix", 32 | "precommit": "lint-staged", 33 | "prepare": "yarn build", 34 | "prepublishOnly": "yarn clean && yarn build:npm", 35 | "test": "npx hardhat test --network localhost", 36 | "test:fork:coverage": "FORK=true COVERAGE=true npx hardhat coverage", 37 | "test:fork": "FORK=true npx hardhat test", 38 | "test:fork:fast": "NO_COMPILE=true TS_NODE_TRANSPILE_ONLY=1 FORK=true npx hardhat test --no-compile", 39 | "test:clean": "yarn clean && yarn build && yarn test", 40 | "test:fast": "NO_COMPILE=true TS_NODE_TRANSPILE_ONLY=1 npx hardhat test --network localhost --no-compile", 41 | "test:fast:compile": "TS_NODE_TRANSPILE_ONLY=1 npx hardhat test --network localhost", 42 | "transpile": "tsc", 43 | "transpile-dist": "tsc -p tsconfig.dist.json", 44 | "typechain": "npx hardhat typechain" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/SetProtocol/set-v2-strategies.git" 49 | }, 50 | "author": "richardliang", 51 | "license": "MIT", 52 | "homepage": "https://github.com/SetProtocol", 53 | "devDependencies": { 54 | "@0x/utils": "^6.4.3", 55 | "@nomiclabs/hardhat-ethers": "^2.0.2", 56 | "@nomiclabs/hardhat-waffle": "^2.0.1", 57 | "@openzeppelin/contracts": "^3.1.0", 58 | "@typechain/ethers-v5": "^7.0.1", 59 | "@typechain/hardhat": "^2.3.0", 60 | "@types/chai": "^4.2.11", 61 | "@types/fs-extra": "^5.0.0", 62 | "@types/lodash": "^4.14.86", 63 | "@types/mocha": "^7.0.2", 64 | "@types/node": "^14.0.5", 65 | "@typescript-eslint/eslint-plugin": "^4.33.0", 66 | "@typescript-eslint/eslint-plugin-tslint": "^4.33.0", 67 | "@typescript-eslint/parser": "^4.33.0", 68 | "@uniswap/lib": "^4.0.1-alpha", 69 | "chai": "^4.2.0", 70 | "coveralls": "^3.0.1", 71 | "dotenv": "^8.2.0", 72 | "eslint": "^7.32.0", 73 | "eslint-plugin-jsdoc": "^36.1.0", 74 | "eslint-plugin-no-null": "^1.0.2", 75 | "ethereum-waffle": "^3.4.0", 76 | "hardhat": "^2.6.4", 77 | "hardhat-deploy": "^0.9.1", 78 | "husky": "^4.2.5", 79 | "istanbul-combine-updated": "^0.3.0", 80 | "lint-staged": "^10.2.11", 81 | "lodash": "^4.17.4", 82 | "solc": "^0.6.10", 83 | "solhint": "^3.1.0", 84 | "solidity-coverage": "^0.7.17", 85 | "ts-generator": "^0.1.1", 86 | "ts-node": "^8.10.2", 87 | "tslint": "^6.1.3", 88 | "tslint-eslint-rules": "^5.3.1", 89 | "typechain": "5.1.2", 90 | "typescript": "^4.4.3", 91 | "web3": "^1.2.9" 92 | }, 93 | "dependencies": { 94 | "@setprotocol/set-protocol-v2": "^0.10.3-hhat.1", 95 | "@uniswap/v3-sdk": "^3.5.1", 96 | "ethers": "5.5.2", 97 | "fs-extra": "^5.0.0", 98 | "jsbi": "^3.2.5", 99 | "module-alias": "^2.2.2", 100 | "replace-in-file": "^6.1.0" 101 | }, 102 | "peerDependencies": { 103 | "@nomiclabs/hardhat-ethers": "^2.0.1", 104 | "ethereum-waffle": "^3.2.1", 105 | "hardhat": "^2.2.1" 106 | }, 107 | "_moduleAliases": { 108 | "@utils": "utils", 109 | "@typechain": "typechain" 110 | }, 111 | "husky": { 112 | "hooks": { 113 | "pre-commit": "yarn precommit" 114 | } 115 | }, 116 | "lint-staged": { 117 | "contracts/**/*.sol": [ 118 | "yarn lint-sol --fix" 119 | ], 120 | "test/**/*.ts": [ 121 | "yarn lint-ts --fix" 122 | ], 123 | "utils/**/*.ts": [ 124 | "yarn lint-ts --fix" 125 | ] 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Code Review Processes 2 | ## New Feature Review 3 | Before submitting a pull request for new review, make sure the following is done: 4 | * Design doc is created and posted here: [Insert Link] 5 | * Code cleanliness and completeness is addressed via [guidelines](https://app.gitbook.com/@setprotocol-1/s/set/smart-contract-engineering/sc-code-review-process) 6 | 7 | README Checks 8 | - [] README has proper context for the reviewer to understand what the code includes, any important design considerations, and areas to pay more attention to 9 | 10 | Code Checks 11 | - [] Add explanatory comments. If there is complex code that requires specific context or understanding, note that in a comment 12 | - [] Remove unncessary comments. Any comments that do not add additional context, information, etc. should be removed 13 | - [] Add javadocs. 14 | - [] Scrub through the code for inconsistencies (e.g. removing extra spaces) 15 | - [] Ensure there are not any .onlys in spec files 16 | 17 | 18 | Broader Considerations 19 | - [] Ensure variable, function and event naming is clear, consistent, and reflective for the scope of the code. 20 | - [] Consider if certain pieces of logic should be placed in a different library, module 21 | -------------------------------------------------------------------------------- /tasks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./subtasks"; 2 | -------------------------------------------------------------------------------- /tasks/subtasks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TASK_COMPILE_SOLIDITY_GET_ARTIFACT_FROM_COMPILATION_OUTPUT, 3 | } from "hardhat/builtin-tasks/task-names"; 4 | 5 | import { subtask } from "hardhat/config"; 6 | import { addGasToAbiMethods } from "@setprotocol/set-protocol-v2/dist/utils/tasks"; 7 | 8 | // Injects network block limit (minus 1 million) in the abi so 9 | // ethers uses it instead of running gas estimation. 10 | subtask(TASK_COMPILE_SOLIDITY_GET_ARTIFACT_FROM_COMPILATION_OUTPUT) 11 | .setAction(async (_, { network }, runSuper) => { 12 | const artifact = await runSuper(); 13 | 14 | // These changes should be skipped when publishing to npm. 15 | // They override ethers' gas estimation 16 | if (!process.env.SKIP_ABI_GAS_MODS) { 17 | artifact.abi = addGasToAbiMethods(network.config, artifact.abi); 18 | } 19 | 20 | return artifact; 21 | } 22 | ); 23 | 24 | export {}; 25 | -------------------------------------------------------------------------------- /test/hooks/supplyCapIssuanceHook.spec.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | 3 | import { Address, Account } from "@utils/types"; 4 | import { SupplyCapIssuanceHook } from "@utils/contracts/index"; 5 | import { SetToken, DebtIssuanceModule } from "@setprotocol/set-protocol-v2/utils/contracts"; 6 | import DeployHelper from "@utils/deploys"; 7 | import { 8 | addSnapshotBeforeRestoreAfterEach, 9 | ether, 10 | getAccounts, 11 | getWaffleExpect, 12 | getRandomAccount, 13 | } from "@utils/index"; 14 | import { SystemFixture } from "@setprotocol/set-protocol-v2/dist/utils/fixtures"; 15 | import { getSystemFixture } from "@setprotocol/set-protocol-v2/dist/utils/test"; 16 | import { BigNumber, ContractTransaction } from "ethers"; 17 | 18 | const expect = getWaffleExpect(); 19 | 20 | describe("SupplyCapIssuanceHook", () => { 21 | let owner: Account; 22 | let hookOwner: Account; 23 | let setV2Setup: SystemFixture; 24 | 25 | let deployer: DeployHelper; 26 | let setToken: SetToken; 27 | 28 | let issuanceHook: SupplyCapIssuanceHook; 29 | let debtIssuanceModule: DebtIssuanceModule; 30 | 31 | before(async () => { 32 | [ 33 | owner, 34 | hookOwner, 35 | ] = await getAccounts(); 36 | 37 | deployer = new DeployHelper(owner.wallet); 38 | 39 | setV2Setup = getSystemFixture(owner.address); 40 | await setV2Setup.initialize(); 41 | 42 | debtIssuanceModule = await deployer.setV2.deployDebtIssuanceModule(setV2Setup.controller.address); 43 | await setV2Setup.controller.addModule(debtIssuanceModule.address); 44 | 45 | setToken = await setV2Setup.createSetToken( 46 | [setV2Setup.dai.address], 47 | [ether(1)], 48 | [debtIssuanceModule.address] 49 | ); 50 | }); 51 | 52 | addSnapshotBeforeRestoreAfterEach(); 53 | 54 | describe("#constructor", async () => { 55 | let subjectOwner: Address; 56 | let subjectSupplyCap: BigNumber; 57 | 58 | beforeEach(async () => { 59 | subjectOwner = hookOwner.address; 60 | subjectSupplyCap = ether(10); 61 | }); 62 | 63 | async function subject(): Promise { 64 | return await deployer.hooks.deploySupplyCapIssuanceHook(subjectOwner, subjectSupplyCap); 65 | } 66 | 67 | it("should set the correct SetToken address", async () => { 68 | const hook = await subject(); 69 | 70 | const actualSupplyCap = await hook.supplyCap(); 71 | expect(actualSupplyCap).to.eq(subjectSupplyCap); 72 | }); 73 | 74 | it("should set the correct owner address", async () => { 75 | const hook = await subject(); 76 | 77 | const actualOwner = await hook.owner(); 78 | expect(actualOwner).to.eq(subjectOwner); 79 | }); 80 | }); 81 | 82 | describe("#invokePreIssueHook", async () => { 83 | let subjectSetToken: Address; 84 | let subjectQuantity: BigNumber; 85 | let subjectTo: Address; 86 | 87 | beforeEach(async () => { 88 | issuanceHook = await deployer.hooks.deploySupplyCapIssuanceHook(owner.address, ether(10)); 89 | 90 | await debtIssuanceModule.initialize( 91 | setToken.address, 92 | ether(.1), 93 | ether(.01), 94 | ether(.01), 95 | owner.address, 96 | issuanceHook.address 97 | ); 98 | 99 | await setV2Setup.dai.approve(debtIssuanceModule.address, ether(100)); 100 | 101 | subjectSetToken = setToken.address; 102 | subjectQuantity = ether(5); 103 | subjectTo = owner.address; 104 | }); 105 | 106 | async function subject(): Promise { 107 | return await debtIssuanceModule.issue( 108 | subjectSetToken, 109 | subjectQuantity, 110 | subjectTo 111 | ); 112 | } 113 | 114 | it("should not revert", async () => { 115 | await expect(subject()).to.not.be.reverted; 116 | }); 117 | 118 | describe("when total issuance quantity forces supply over the limit", async () => { 119 | beforeEach(async () => { 120 | subjectQuantity = ether(11); 121 | }); 122 | 123 | it("should revert", async () => { 124 | await expect(subject()).to.be.revertedWith("Supply cap exceeded"); 125 | }); 126 | }); 127 | }); 128 | 129 | describe("#updateSupplyCap", async () => { 130 | let subjectNewCap: BigNumber; 131 | let subjectCaller: Account; 132 | 133 | beforeEach(async () => { 134 | issuanceHook = await deployer.hooks.deploySupplyCapIssuanceHook(owner.address, ether(10)); 135 | 136 | subjectNewCap = ether(20); 137 | subjectCaller = owner; 138 | }); 139 | 140 | async function subject(): Promise { 141 | return await issuanceHook.connect(subjectCaller.wallet).updateSupplyCap(subjectNewCap); 142 | } 143 | 144 | it("should update supply cap", async () => { 145 | await subject(); 146 | 147 | const actualCap = await issuanceHook.supplyCap(); 148 | 149 | expect(actualCap).to.eq(subjectNewCap); 150 | }); 151 | 152 | it("should emit the correct SupplyCapUpdated event", async () => { 153 | await expect(subject()).to.emit(issuanceHook, "SupplyCapUpdated").withArgs(subjectNewCap); 154 | }); 155 | 156 | describe("when caller is not owner", async () => { 157 | beforeEach(async () => { 158 | subjectCaller = await getRandomAccount(); 159 | }); 160 | 161 | it("should revert", async () => { 162 | await expect(subject()).to.be.revertedWith("Ownable: caller is not the owner"); 163 | }); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/lib/baseExtension.spec.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | 3 | import { BigNumber } from "ethers"; 4 | import { Account, Address, Bytes } from "@utils/types"; 5 | import { ZERO, ADDRESS_ZERO } from "@utils/constants"; 6 | import { BaseExtensionMock, BaseManager } from "@utils/contracts/index"; 7 | 8 | import DeployHelper from "@utils/deploys"; 9 | 10 | import { 11 | addSnapshotBeforeRestoreAfterEach, 12 | getAccounts, 13 | getWaffleExpect, 14 | getRandomAccount, 15 | getRandomAddress, 16 | ether, 17 | } from "@utils/index"; 18 | 19 | import { 20 | ContractCallerMock, 21 | SetToken 22 | } from "@setprotocol/set-protocol-v2/utils/contracts"; 23 | 24 | import { getSystemFixture } from "@setprotocol/set-protocol-v2/dist/utils/test"; 25 | 26 | import { 27 | SystemFixture 28 | } from "@setprotocol/set-protocol-v2/dist/utils/fixtures"; 29 | 30 | import { ContractTransaction } from "ethers"; 31 | 32 | const expect = getWaffleExpect(); 33 | 34 | describe("BaseExtension", () => { 35 | let owner: Account; 36 | let methodologist: Account; 37 | let otherAccount: Account; 38 | let deployer: DeployHelper; 39 | let setToken: SetToken; 40 | let systemSetup: SystemFixture; 41 | 42 | let baseManager: BaseManager; 43 | let baseExtensionMock: BaseExtensionMock; 44 | 45 | before(async () => { 46 | [ 47 | owner, 48 | methodologist, 49 | otherAccount, 50 | ] = await getAccounts(); 51 | 52 | deployer = new DeployHelper(owner.wallet); 53 | 54 | systemSetup = getSystemFixture(owner.address); 55 | await systemSetup.initialize(); 56 | 57 | setToken = await systemSetup.createSetToken( 58 | [systemSetup.dai.address], 59 | [ether(1)], 60 | [systemSetup.issuanceModule.address, systemSetup.streamingFeeModule.address] 61 | ); 62 | 63 | // Initialize modules 64 | await systemSetup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO); 65 | const feeRecipient = owner.address; 66 | const maxStreamingFeePercentage = ether(.1); 67 | const streamingFeePercentage = ether(.02); 68 | const streamingFeeSettings = { 69 | feeRecipient, 70 | maxStreamingFeePercentage, 71 | streamingFeePercentage, 72 | lastStreamingFeeTimestamp: ZERO, 73 | }; 74 | await systemSetup.streamingFeeModule.initialize(setToken.address, streamingFeeSettings); 75 | 76 | // Deploy BaseManager 77 | baseManager = await deployer.manager.deployBaseManager( 78 | setToken.address, 79 | owner.address, 80 | methodologist.address 81 | ); 82 | 83 | baseExtensionMock = await deployer.mocks.deployBaseExtensionMock(baseManager.address); 84 | 85 | // Transfer ownership to BaseManager 86 | await setToken.setManager(baseManager.address); 87 | await baseManager.addAdapter(baseExtensionMock.address); 88 | 89 | await baseExtensionMock.updateCallerStatus([owner.address], [true]); 90 | }); 91 | 92 | addSnapshotBeforeRestoreAfterEach(); 93 | 94 | describe("#testOnlyOperator", async () => { 95 | let subjectCaller: Account; 96 | 97 | beforeEach(async () => { 98 | subjectCaller = owner; 99 | }); 100 | 101 | async function subject(): Promise { 102 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyOperator(); 103 | } 104 | 105 | it("should succeed without revert", async () => { 106 | await subject(); 107 | }); 108 | 109 | describe("when the sender is not operator", async () => { 110 | beforeEach(async () => { 111 | subjectCaller = await getRandomAccount(); 112 | }); 113 | 114 | it("should revert", async () => { 115 | await expect(subject()).to.be.revertedWith("Must be operator"); 116 | }); 117 | }); 118 | }); 119 | 120 | describe("#testOnlyMethodologist", async () => { 121 | let subjectCaller: Account; 122 | 123 | beforeEach(async () => { 124 | subjectCaller = methodologist; 125 | }); 126 | 127 | async function subject(): Promise { 128 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyMethodologist(); 129 | } 130 | 131 | it("should succeed without revert", async () => { 132 | await subject(); 133 | }); 134 | 135 | describe("when the sender is not methodologist", async () => { 136 | beforeEach(async () => { 137 | subjectCaller = await getRandomAccount(); 138 | }); 139 | 140 | it("should revert", async () => { 141 | await expect(subject()).to.be.revertedWith("Must be methodologist"); 142 | }); 143 | }); 144 | }); 145 | 146 | describe("#testOnlyEOA", async () => { 147 | let subjectCaller: Account; 148 | 149 | beforeEach(async () => { 150 | subjectCaller = methodologist; 151 | }); 152 | 153 | async function subject(): Promise { 154 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyEOA(); 155 | } 156 | 157 | it("should succeed without revert", async () => { 158 | await subject(); 159 | }); 160 | 161 | describe("when the sender is not EOA", async () => { 162 | let subjectTarget: Address; 163 | let subjectCallData: string; 164 | let subjectValue: BigNumber; 165 | 166 | let contractCaller: ContractCallerMock; 167 | 168 | beforeEach(async () => { 169 | contractCaller = await deployer.setDeployer.mocks.deployContractCallerMock(); 170 | 171 | subjectTarget = baseExtensionMock.address; 172 | subjectCallData = baseExtensionMock.interface.encodeFunctionData("testOnlyEOA"); 173 | subjectValue = ZERO; 174 | }); 175 | 176 | async function subjectContractCaller(): Promise { 177 | return await contractCaller.invoke( 178 | subjectTarget, 179 | subjectValue, 180 | subjectCallData 181 | ); 182 | } 183 | 184 | it("the trade reverts", async () => { 185 | await expect(subjectContractCaller()).to.be.revertedWith("Caller must be EOA Address"); 186 | }); 187 | }); 188 | }); 189 | 190 | describe("#testOnlyAllowedCaller", async () => { 191 | let subjectCaller: Account; 192 | 193 | beforeEach(async () => { 194 | subjectCaller = owner; 195 | }); 196 | 197 | async function subject(): Promise { 198 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyAllowedCaller(subjectCaller.address); 199 | } 200 | 201 | it("should succeed without revert", async () => { 202 | await subject(); 203 | }); 204 | 205 | describe("when the caller is not on allowlist", async () => { 206 | beforeEach(async () => { 207 | subjectCaller = await getRandomAccount(); 208 | }); 209 | 210 | it("should revert", async () => { 211 | await expect(subject()).to.be.revertedWith("Address not permitted to call"); 212 | }); 213 | 214 | describe("when anyoneCallable is flipped to true", async () => { 215 | beforeEach(async () => { 216 | await baseExtensionMock.updateAnyoneCallable(true); 217 | }); 218 | 219 | it("should succeed without revert", async () => { 220 | await subject(); 221 | }); 222 | }); 223 | }); 224 | }); 225 | 226 | describe("#testInvokeManager", async () => { 227 | let subjectModule: Address; 228 | let subjectCallData: Bytes; 229 | let subjectCaller: Account; 230 | 231 | beforeEach(async () => { 232 | subjectModule = systemSetup.streamingFeeModule.address; 233 | subjectCallData = systemSetup.streamingFeeModule.interface.encodeFunctionData("updateFeeRecipient", [ 234 | setToken.address, 235 | otherAccount.address, 236 | ]); 237 | subjectCaller = owner; 238 | }); 239 | 240 | async function subject(): Promise { 241 | return baseExtensionMock.connect(subjectCaller.wallet).testInvokeManager(subjectModule, subjectCallData); 242 | } 243 | 244 | it("should call updateFeeRecipient on the streaming fee module from the SetToken", async () => { 245 | await subject(); 246 | const feeStates = await systemSetup.streamingFeeModule.feeStates(setToken.address); 247 | expect(feeStates.feeRecipient).to.eq(otherAccount.address); 248 | }); 249 | }); 250 | 251 | describe("#updateCallerStatus", async () => { 252 | let subjectFunctionCallers: Address[]; 253 | let subjectStatuses: boolean[]; 254 | let subjectCaller: Account; 255 | 256 | beforeEach(async () => { 257 | subjectFunctionCallers = [otherAccount.address]; 258 | subjectStatuses = [true]; 259 | subjectCaller = owner; 260 | }); 261 | 262 | async function subject(): Promise { 263 | return baseExtensionMock.connect(subjectCaller.wallet).updateCallerStatus(subjectFunctionCallers, subjectStatuses); 264 | } 265 | 266 | it("should update the callAllowList", async () => { 267 | await subject(); 268 | const callerStatus = await baseExtensionMock.callAllowList(subjectFunctionCallers[0]); 269 | expect(callerStatus).to.be.true; 270 | }); 271 | 272 | it("should emit CallerStatusUpdated event", async () => { 273 | await expect(subject()).to.emit(baseExtensionMock, "CallerStatusUpdated").withArgs( 274 | subjectFunctionCallers[0], 275 | subjectStatuses[0] 276 | ); 277 | }); 278 | 279 | describe("when the sender is not operator", async () => { 280 | beforeEach(async () => { 281 | subjectCaller = await getRandomAccount(); 282 | }); 283 | 284 | it("should revert", async () => { 285 | await expect(subject()).to.be.revertedWith("Must be operator"); 286 | }); 287 | }); 288 | 289 | describe("when the callers and statuses array lengths don't match", async () => { 290 | beforeEach(async () => { 291 | subjectFunctionCallers = [otherAccount.address, await getRandomAddress()]; 292 | }); 293 | 294 | it("should revert", async () => { 295 | await expect(subject()).to.be.revertedWith("Array length mismatch"); 296 | }); 297 | }); 298 | 299 | describe("when the arrays are empty", async () => { 300 | beforeEach(async () => { 301 | subjectFunctionCallers = []; 302 | subjectStatuses = []; 303 | }); 304 | 305 | it("should revert", async () => { 306 | await expect(subject()).to.be.revertedWith("Array length must be > 0"); 307 | }); 308 | }); 309 | 310 | describe("when there are duplicate callers listed", async () => { 311 | beforeEach(async () => { 312 | subjectFunctionCallers = [otherAccount.address, otherAccount.address]; 313 | subjectStatuses = [true, true]; 314 | }); 315 | 316 | it("should revert", async () => { 317 | await expect(subject()).to.be.revertedWith("Cannot duplicate callers"); 318 | }); 319 | }); 320 | }); 321 | 322 | describe("#updateAnyoneCallable", async () => { 323 | let subjectStatus: boolean; 324 | let subjectCaller: Account; 325 | 326 | beforeEach(async () => { 327 | subjectStatus = true; 328 | subjectCaller = owner; 329 | }); 330 | 331 | async function subject(): Promise { 332 | return baseExtensionMock.connect(subjectCaller.wallet).updateAnyoneCallable(subjectStatus); 333 | } 334 | 335 | it("should update the anyoneCallable boolean", async () => { 336 | await subject(); 337 | const callerStatus = await baseExtensionMock.anyoneCallable(); 338 | expect(callerStatus).to.be.true; 339 | }); 340 | 341 | it("should emit AnyoneCallableUpdated event", async () => { 342 | await expect(subject()).to.emit(baseExtensionMock, "AnyoneCallableUpdated").withArgs( 343 | subjectStatus 344 | ); 345 | }); 346 | 347 | describe("when the sender is not operator", async () => { 348 | beforeEach(async () => { 349 | subjectCaller = await getRandomAccount(); 350 | }); 351 | 352 | it("should revert", async () => { 353 | await expect(subject()).to.be.revertedWith("Must be operator"); 354 | }); 355 | }); 356 | }); 357 | }); -------------------------------------------------------------------------------- /test/lib/baseGlobalExtension.spec.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | 3 | import { Account, Address, Bytes } from "@utils/types"; 4 | import { ZERO, ADDRESS_ZERO } from "@utils/constants"; 5 | import { BaseGlobalExtensionMock, DelegatedManager, ManagerCore } from "@utils/contracts/index"; 6 | 7 | import DeployHelper from "@utils/deploys"; 8 | 9 | import { 10 | addSnapshotBeforeRestoreAfterEach, 11 | getAccounts, 12 | getWaffleExpect, 13 | ether, 14 | } from "@utils/index"; 15 | 16 | import { 17 | SetToken 18 | } from "@setprotocol/set-protocol-v2/utils/contracts"; 19 | 20 | import { getSystemFixture } from "@setprotocol/set-protocol-v2/dist/utils/test"; 21 | 22 | import { 23 | SystemFixture 24 | } from "@setprotocol/set-protocol-v2/dist/utils/fixtures"; 25 | 26 | import { ContractTransaction } from "ethers"; 27 | 28 | const expect = getWaffleExpect(); 29 | 30 | describe("BaseGlobalExtension", () => { 31 | let owner: Account; 32 | let methodologist: Account; 33 | let otherAccount: Account; 34 | let factory: Account; 35 | let operator: Account; 36 | 37 | let deployer: DeployHelper; 38 | let setToken: SetToken; 39 | let setV2Setup: SystemFixture; 40 | 41 | let managerCore: ManagerCore; 42 | let delegatedManager: DelegatedManager; 43 | let baseExtensionMock: BaseGlobalExtensionMock; 44 | let mockModule: Account; 45 | 46 | before(async () => { 47 | [ 48 | owner, 49 | methodologist, 50 | otherAccount, 51 | factory, 52 | operator, 53 | mockModule, 54 | ] = await getAccounts(); 55 | 56 | deployer = new DeployHelper(owner.wallet); 57 | 58 | setV2Setup = getSystemFixture(owner.address); 59 | await setV2Setup.initialize(); 60 | 61 | setToken = await setV2Setup.createSetToken( 62 | [setV2Setup.dai.address], 63 | [ether(1)], 64 | [setV2Setup.issuanceModule.address, setV2Setup.streamingFeeModule.address] 65 | ); 66 | 67 | // Initialize modules 68 | await setV2Setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO); 69 | const feeRecipient = owner.address; 70 | const maxStreamingFeePercentage = ether(.1); 71 | const streamingFeePercentage = ether(.02); 72 | const streamingFeeSettings = { 73 | feeRecipient, 74 | maxStreamingFeePercentage, 75 | streamingFeePercentage, 76 | lastStreamingFeeTimestamp: ZERO, 77 | }; 78 | await setV2Setup.streamingFeeModule.initialize(setToken.address, streamingFeeSettings); 79 | 80 | managerCore = await deployer.managerCore.deployManagerCore(); 81 | 82 | baseExtensionMock = await deployer.mocks.deployBaseGlobalExtensionMock(managerCore.address, mockModule.address); 83 | 84 | // Deploy DelegatedManager 85 | delegatedManager = await deployer.manager.deployDelegatedManager( 86 | setToken.address, 87 | factory.address, 88 | methodologist.address, 89 | [baseExtensionMock.address], 90 | [operator.address], 91 | [setV2Setup.usdc.address, setV2Setup.weth.address], 92 | true 93 | ); 94 | 95 | // Transfer ownership to DelegatedManager 96 | await setToken.setManager(delegatedManager.address); 97 | 98 | await managerCore.initialize([baseExtensionMock.address], [factory.address]); 99 | await managerCore.connect(factory.wallet).addManager(delegatedManager.address); 100 | 101 | await baseExtensionMock.initializeExtension(delegatedManager.address); 102 | }); 103 | 104 | addSnapshotBeforeRestoreAfterEach(); 105 | 106 | describe("#testOnlyOperator", async () => { 107 | let subjectSetToken: Address; 108 | let subjectCaller: Account; 109 | 110 | beforeEach(async () => { 111 | subjectSetToken = setToken.address; 112 | subjectCaller = operator; 113 | }); 114 | 115 | async function subject(): Promise { 116 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyOperator(subjectSetToken); 117 | } 118 | 119 | it("should succeed without revert", async () => { 120 | await subject(); 121 | }); 122 | 123 | describe("when the sender is not operator", async () => { 124 | beforeEach(async () => { 125 | subjectCaller = owner; 126 | }); 127 | 128 | it("should revert", async () => { 129 | await expect(subject()).to.be.revertedWith("Must be approved operator"); 130 | }); 131 | }); 132 | }); 133 | 134 | describe("#testOnlyMethodologist", async () => { 135 | let subjectSetToken: Address; 136 | let subjectCaller: Account; 137 | 138 | beforeEach(async () => { 139 | subjectSetToken = setToken.address; 140 | subjectCaller = methodologist; 141 | }); 142 | 143 | async function subject(): Promise { 144 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyMethodologist(subjectSetToken); 145 | } 146 | 147 | it("should succeed without revert", async () => { 148 | await subject(); 149 | }); 150 | 151 | describe("when the sender is not methodologist", async () => { 152 | beforeEach(async () => { 153 | subjectCaller = owner; 154 | }); 155 | 156 | it("should revert", async () => { 157 | await expect(subject()).to.be.revertedWith("Must be methodologist"); 158 | }); 159 | }); 160 | }); 161 | 162 | describe("#testOnlyOwner", async () => { 163 | let subjectSetToken: Address; 164 | let subjectCaller: Account; 165 | 166 | beforeEach(async () => { 167 | subjectSetToken = setToken.address; 168 | subjectCaller = owner; 169 | }); 170 | 171 | async function subject(): Promise { 172 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyOwner(subjectSetToken); 173 | } 174 | 175 | it("should succeed without revert", async () => { 176 | await subject(); 177 | }); 178 | 179 | describe("when the sender is not the owner", async () => { 180 | beforeEach(async () => { 181 | subjectCaller = operator; 182 | }); 183 | 184 | it("should revert", async () => { 185 | await expect(subject()).to.be.revertedWith("Must be owner"); 186 | }); 187 | }); 188 | }); 189 | 190 | describe("#testOnlyOwnerAndValidManager", async () => { 191 | let subjectDelegatedManager: Address; 192 | let subjectCaller: Account; 193 | 194 | beforeEach(async () => { 195 | await delegatedManager.connect(owner.wallet).removeExtensions([baseExtensionMock.address]); 196 | await delegatedManager.connect(owner.wallet).addExtensions([baseExtensionMock.address]); 197 | 198 | subjectDelegatedManager = delegatedManager.address; 199 | subjectCaller = owner; 200 | }); 201 | 202 | async function subject(): Promise { 203 | return baseExtensionMock.connect(subjectCaller.wallet).initializeExtension(subjectDelegatedManager); 204 | } 205 | 206 | it("should succeed without revert", async () => { 207 | await subject(); 208 | }); 209 | 210 | describe("when the sender is not the owner", async () => { 211 | beforeEach(async () => { 212 | subjectCaller = operator; 213 | }); 214 | 215 | it("should revert", async () => { 216 | await expect(subject()).to.be.revertedWith("Must be owner"); 217 | }); 218 | }); 219 | 220 | describe("when the manager is not a ManagerCore-enabled manager", async () => { 221 | beforeEach(async () => { 222 | await managerCore.connect(owner.wallet).removeManager(delegatedManager.address); 223 | }); 224 | 225 | it("should revert", async () => { 226 | await expect(subject()).to.be.revertedWith("Must be ManagerCore-enabled manager"); 227 | }); 228 | }); 229 | }); 230 | 231 | describe("#testOnlyAllowedAsset", async () => { 232 | let subjectSetToken: Address; 233 | let subjectAsset: Address; 234 | let subjectCaller: Account; 235 | 236 | beforeEach(async () => { 237 | subjectSetToken = setToken.address; 238 | subjectAsset = setV2Setup.usdc.address; 239 | subjectCaller = owner; 240 | }); 241 | 242 | async function subject(): Promise { 243 | return baseExtensionMock.connect(subjectCaller.wallet).testOnlyAllowedAsset(subjectSetToken, subjectAsset); 244 | } 245 | 246 | it("should succeed without revert", async () => { 247 | await subject(); 248 | }); 249 | 250 | describe("when the asset is not an approved asset", async () => { 251 | beforeEach(async () => { 252 | subjectAsset = setV2Setup.wbtc.address; 253 | }); 254 | 255 | it("should revert", async () => { 256 | await expect(subject()).to.be.revertedWith("Must be allowed asset"); 257 | }); 258 | }); 259 | }); 260 | 261 | describe("#testInvokeManager", async () => { 262 | let subjectSetToken: Address; 263 | let subjectModule: Address; 264 | let subjectCallData: Bytes; 265 | let subjectCaller: Account; 266 | 267 | beforeEach(async () => { 268 | subjectSetToken = setToken.address; 269 | subjectModule = setV2Setup.streamingFeeModule.address; 270 | subjectCallData = setV2Setup.streamingFeeModule.interface.encodeFunctionData("updateFeeRecipient", [ 271 | setToken.address, 272 | otherAccount.address, 273 | ]); 274 | subjectCaller = owner; 275 | }); 276 | 277 | async function subject(): Promise { 278 | return baseExtensionMock.connect(subjectCaller.wallet).testInvokeManager(subjectSetToken, subjectModule, subjectCallData); 279 | } 280 | 281 | it("should call updateFeeRecipient on the streaming fee module from the SetToken", async () => { 282 | await subject(); 283 | const feeStates = await setV2Setup.streamingFeeModule.feeStates(setToken.address); 284 | expect(feeStates.feeRecipient).to.eq(otherAccount.address); 285 | }); 286 | }); 287 | }); -------------------------------------------------------------------------------- /test/lib/mutualUpgrade.spec.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | import { solidityKeccak256 } from "ethers/lib/utils"; 3 | import { BigNumber } from "ethers"; 4 | 5 | import { Account } from "@utils/types"; 6 | import { ONE, ZERO } from "@utils/constants"; 7 | import { MutualUpgradeMock } from "@utils/contracts/index"; 8 | import DeployHelper from "@utils/deploys"; 9 | import { 10 | addSnapshotBeforeRestoreAfterEach, 11 | getAccounts, 12 | getWaffleExpect, 13 | getRandomAccount, 14 | } from "@utils/index"; 15 | import { ContractTransaction } from "ethers"; 16 | 17 | const expect = getWaffleExpect(); 18 | 19 | describe("MutualUpgrade", () => { 20 | let owner: Account; 21 | let methodologist: Account; 22 | let deployer: DeployHelper; 23 | 24 | let mutualUpgradeMock: MutualUpgradeMock; 25 | 26 | before(async () => { 27 | [ 28 | owner, 29 | methodologist, 30 | ] = await getAccounts(); 31 | 32 | deployer = new DeployHelper(owner.wallet); 33 | 34 | mutualUpgradeMock = await deployer.mocks.deployMutualUpgradeMock(owner.address, methodologist.address); 35 | }); 36 | 37 | addSnapshotBeforeRestoreAfterEach(); 38 | 39 | describe("#testMutualUpgrade", async () => { 40 | let subjectTestUint: BigNumber; 41 | let subjectCaller: Account; 42 | 43 | beforeEach(async () => { 44 | subjectTestUint = ONE; 45 | subjectCaller = owner; 46 | }); 47 | 48 | async function subject(): Promise { 49 | return mutualUpgradeMock.connect(subjectCaller.wallet).testMutualUpgrade(subjectTestUint); 50 | } 51 | 52 | describe("when the mutualUpgrade hash is not set", async () => { 53 | it("should register the initial mutual upgrade", async () => { 54 | const txHash = await subject(); 55 | 56 | const expectedHash = solidityKeccak256(["bytes", "address"], [txHash.data, subjectCaller.address]); 57 | const isLogged = await mutualUpgradeMock.mutualUpgrades(expectedHash); 58 | 59 | expect(isLogged).to.be.true; 60 | }); 61 | 62 | it("should not update the testUint", async () => { 63 | await subject(); 64 | 65 | const currentInt = await mutualUpgradeMock.testUint(); 66 | expect(currentInt).to.eq(ZERO); 67 | }); 68 | 69 | it("emits a MutualUpgradeRegistered event", async () => { 70 | await expect(subject()).to.emit(mutualUpgradeMock, "MutualUpgradeRegistered"); 71 | }); 72 | }); 73 | 74 | describe("when the mutualUpgrade hash is set", async () => { 75 | beforeEach(async () => { 76 | await subject(); 77 | subjectCaller = methodologist; 78 | }); 79 | 80 | it("should clear the mutualUpgrade hash", async () => { 81 | const txHash = await subject(); 82 | 83 | const expectedHash = solidityKeccak256(["bytes", "address"], [txHash.data, subjectCaller.address]); 84 | const isLogged = await mutualUpgradeMock.mutualUpgrades(expectedHash); 85 | 86 | expect(isLogged).to.be.false; 87 | }); 88 | 89 | it("should update the testUint", async () => { 90 | await subject(); 91 | 92 | const currentTestUint = await mutualUpgradeMock.testUint(); 93 | expect(currentTestUint).to.eq(subjectTestUint); 94 | }); 95 | 96 | describe("when the same address calls it twice", async () => { 97 | beforeEach(async () => { 98 | subjectCaller = owner; 99 | }); 100 | 101 | it("should stay logged", async () => { 102 | const txHash = await subject(); 103 | 104 | const expectedHash = solidityKeccak256(["bytes", "address"], [txHash.data, subjectCaller.address]); 105 | const isLogged = await mutualUpgradeMock.mutualUpgrades(expectedHash); 106 | 107 | expect(isLogged).to.be.true; 108 | }); 109 | 110 | it("should not change the integer value", async () => { 111 | await subject(); 112 | 113 | const currentInt = await mutualUpgradeMock.testUint(); 114 | expect(currentInt).to.eq(ZERO); 115 | }); 116 | }); 117 | }); 118 | 119 | describe("when the sender is not one of the allowed addresses", async () => { 120 | beforeEach(async () => { 121 | subjectCaller = await getRandomAccount(); 122 | }); 123 | 124 | it("should revert", async () => { 125 | await expect(subject()).to.be.revertedWith("Must be authorized address"); 126 | }); 127 | }); 128 | }); 129 | }); -------------------------------------------------------------------------------- /test/lib/mutualUpgradeV2.spec.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | import { solidityKeccak256 } from "ethers/lib/utils"; 3 | import { BigNumber } from "ethers"; 4 | 5 | import { Account } from "@utils/types"; 6 | import { ONE, ZERO } from "@utils/constants"; 7 | import { MutualUpgradeV2Mock } from "@utils/contracts/index"; 8 | import DeployHelper from "@utils/deploys"; 9 | import { 10 | addSnapshotBeforeRestoreAfterEach, 11 | getAccounts, 12 | getWaffleExpect, 13 | getRandomAccount, 14 | } from "@utils/index"; 15 | import { ContractTransaction } from "ethers"; 16 | 17 | const expect = getWaffleExpect(); 18 | 19 | describe("MutualUpgradeV2", () => { 20 | let owner: Account; 21 | let methodologist: Account; 22 | let deployer: DeployHelper; 23 | 24 | let mutualUpgradeV2Mock: MutualUpgradeV2Mock; 25 | 26 | before(async () => { 27 | [ 28 | owner, 29 | methodologist, 30 | ] = await getAccounts(); 31 | 32 | deployer = new DeployHelper(owner.wallet); 33 | 34 | mutualUpgradeV2Mock = await deployer.mocks.deployMutualUpgradeV2Mock(owner.address, methodologist.address); 35 | }); 36 | 37 | addSnapshotBeforeRestoreAfterEach(); 38 | 39 | describe("#testMutualUpgradeV2", async () => { 40 | let subjectTestUint: BigNumber; 41 | let subjectCaller: Account; 42 | let subjectMutualUpgradeV2Mock: MutualUpgradeV2Mock; 43 | 44 | beforeEach(async () => { 45 | subjectTestUint = ONE; 46 | subjectCaller = owner; 47 | subjectMutualUpgradeV2Mock = mutualUpgradeV2Mock; 48 | }); 49 | 50 | async function subject(): Promise { 51 | return subjectMutualUpgradeV2Mock.connect(subjectCaller.wallet).testMutualUpgrade(subjectTestUint); 52 | } 53 | 54 | describe("when the two mutual upgrade parties are the same", async () => { 55 | let trivialMutualUpgradeV2Mock: MutualUpgradeV2Mock; 56 | 57 | beforeEach(async () => { 58 | trivialMutualUpgradeV2Mock = await deployer.mocks.deployMutualUpgradeV2Mock(owner.address, owner.address); 59 | 60 | subjectMutualUpgradeV2Mock = trivialMutualUpgradeV2Mock; 61 | }); 62 | 63 | it("should update the testUint", async () => { 64 | await subject(); 65 | 66 | const currentTestUint = await trivialMutualUpgradeV2Mock.testUint(); 67 | expect(currentTestUint).to.eq(subjectTestUint); 68 | }); 69 | }); 70 | 71 | describe("when the mutualUpgrade hash is not set", async () => { 72 | it("should register the initial mutual upgrade", async () => { 73 | const txHash = await subject(); 74 | 75 | const expectedHash = solidityKeccak256(["bytes", "address"], [txHash.data, subjectCaller.address]); 76 | const isLogged = await mutualUpgradeV2Mock.mutualUpgrades(expectedHash); 77 | 78 | expect(isLogged).to.be.true; 79 | }); 80 | 81 | it("should not update the testUint", async () => { 82 | await subject(); 83 | 84 | const currentInt = await mutualUpgradeV2Mock.testUint(); 85 | expect(currentInt).to.eq(ZERO); 86 | }); 87 | 88 | it("emits a MutualUpgradeRegistered event", async () => { 89 | await expect(subject()).to.emit(mutualUpgradeV2Mock, "MutualUpgradeRegistered"); 90 | }); 91 | }); 92 | 93 | describe("when the mutualUpgrade hash is set", async () => { 94 | beforeEach(async () => { 95 | await subject(); 96 | subjectCaller = methodologist; 97 | }); 98 | 99 | it("should clear the mutualUpgrade hash", async () => { 100 | const txHash = await subject(); 101 | 102 | const expectedHash = solidityKeccak256(["bytes", "address"], [txHash.data, subjectCaller.address]); 103 | const isLogged = await mutualUpgradeV2Mock.mutualUpgrades(expectedHash); 104 | 105 | expect(isLogged).to.be.false; 106 | }); 107 | 108 | it("should update the testUint", async () => { 109 | await subject(); 110 | 111 | const currentTestUint = await mutualUpgradeV2Mock.testUint(); 112 | expect(currentTestUint).to.eq(subjectTestUint); 113 | }); 114 | 115 | describe("when the same address calls it twice", async () => { 116 | beforeEach(async () => { 117 | subjectCaller = owner; 118 | }); 119 | 120 | it("should stay logged", async () => { 121 | const txHash = await subject(); 122 | 123 | const expectedHash = solidityKeccak256(["bytes", "address"], [txHash.data, subjectCaller.address]); 124 | const isLogged = await mutualUpgradeV2Mock.mutualUpgrades(expectedHash); 125 | 126 | expect(isLogged).to.be.true; 127 | }); 128 | 129 | it("should not change the integer value", async () => { 130 | await subject(); 131 | 132 | const currentInt = await mutualUpgradeV2Mock.testUint(); 133 | expect(currentInt).to.eq(ZERO); 134 | }); 135 | }); 136 | }); 137 | 138 | describe("when the sender is not one of the allowed addresses", async () => { 139 | beforeEach(async () => { 140 | subjectCaller = await getRandomAccount(); 141 | }); 142 | 143 | it("should revert", async () => { 144 | await expect(subject()).to.be.revertedWith("Must be authorized address"); 145 | }); 146 | }); 147 | }); 148 | }); -------------------------------------------------------------------------------- /tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "test/**/*.ts" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "strictPropertyInitialization": false, 8 | "outDir": "dist", 9 | "resolveJsonModule": true, 10 | "declaration": true, 11 | "declarationDir": "./dist/types", 12 | "baseUrl": ".", 13 | "moduleResolution": "node", 14 | "paths": { 15 | "@utils/*": ["utils/*"], 16 | "@typechain/*": ["typechain/*"] 17 | } 18 | }, 19 | "include": [ 20 | "./test/**/*.ts", 21 | "./utils/**/*.ts", 22 | "./deploy/**/*.ts", 23 | "./typechain/**/*.ts", 24 | "./tasks/**/*.ts" 25 | ], 26 | "files": [ 27 | "hardhat.config.ts", 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /utils/common/batchTradeUtils.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { providers, BigNumber } from "ethers"; 3 | import { Address, TradeInfo, BatchTradeResult } from "@utils/types"; 4 | import { BatchTradeExtension } from "../contracts/index"; 5 | 6 | export class BatchTradeUtils { 7 | public provider: providers.Web3Provider | providers.JsonRpcProvider; 8 | 9 | constructor(_provider: providers.Web3Provider | providers.JsonRpcProvider) { 10 | this.provider = _provider; 11 | } 12 | 13 | // Helper to fetch transaction statuses 14 | public async getBatchTradeResults( 15 | batchTradeInstance: BatchTradeExtension, 16 | transactionHash: string, 17 | trades: TradeInfo[] 18 | ): Promise { 19 | 20 | const receipt = await this.provider.getTransactionReceipt(transactionHash); 21 | const results: BatchTradeResult[] = []; 22 | 23 | for (const trade of trades) { 24 | results.push({ 25 | success: true, 26 | tradeInfo: trade, 27 | }); 28 | } 29 | 30 | for (const log of receipt.logs) { 31 | try { 32 | const decodedLog = batchTradeInstance.interface.parseLog({ 33 | data: log.data, 34 | topics: log.topics, 35 | }); 36 | 37 | if (decodedLog.name === "StringTradeFailed") { 38 | const tradeIndex = (decodedLog.args as any)._index.toNumber(); 39 | results[tradeIndex].success = false; 40 | results[tradeIndex].revertReason = (decodedLog.args as any)._reason; 41 | } 42 | 43 | if (decodedLog.name === "BytesTradeFailed") { 44 | const tradeIndex = (decodedLog.args as any)._index.toNumber(); 45 | results[tradeIndex].success = false; 46 | results[tradeIndex].revertReason = (decodedLog.args as any)._reason; 47 | } 48 | } catch(e) { 49 | // ignore all non-batch trade events 50 | } 51 | } 52 | 53 | return results; 54 | } 55 | 56 | // Helper to fetch trade quotes from 0x 57 | public async getZeroExQuote( 58 | delegatedManager: Address, 59 | sellToken: Address, 60 | buyToken: Address, 61 | amount: BigNumber, 62 | slippagePercentage: number = .02 // Default to 2% 63 | ) { 64 | const url = "https://api.0x.org/swap/v1/quote"; 65 | 66 | const params = { 67 | sellToken, 68 | buyToken, 69 | slippagePercentage, 70 | sellAmount: amount.toString(), 71 | takerAddress: delegatedManager, 72 | excludedSources: [], 73 | skipValidation: true, 74 | }; 75 | 76 | const headers = { 77 | "Content-Type": "application/json", 78 | "Accept": "application/json", 79 | }; 80 | 81 | const response = await axios.get(url, { 82 | params, 83 | headers, 84 | }); 85 | 86 | return response.data; 87 | } 88 | } -------------------------------------------------------------------------------- /utils/common/blockchainUtils.ts: -------------------------------------------------------------------------------- 1 | import { providers } from "ethers"; 2 | 3 | export class Blockchain { 4 | public _provider: providers.Web3Provider | providers.JsonRpcProvider; 5 | private _snapshotId: number; 6 | 7 | constructor(_provider: providers.Web3Provider | providers.JsonRpcProvider) { 8 | this._provider = _provider; 9 | 10 | this._snapshotId = 0; 11 | } 12 | 13 | public async saveSnapshotAsync(): Promise { 14 | const response = await this.sendJSONRpcRequestAsync("evm_snapshot", []); 15 | 16 | this._snapshotId = response; 17 | return response; 18 | } 19 | 20 | public async revertAsync(): Promise { 21 | await this.sendJSONRpcRequestAsync("evm_revert", [this._snapshotId]); 22 | } 23 | 24 | public async revertByIdAsync(id: string): Promise { 25 | await this.sendJSONRpcRequestAsync("evm_revert", [id]); 26 | } 27 | 28 | public async resetAsync(): Promise { 29 | await this.sendJSONRpcRequestAsync("evm_revert", ["0x1"]); 30 | } 31 | 32 | public async increaseTimeAsync(duration: number): Promise { 33 | await this.sendJSONRpcRequestAsync("evm_increaseTime", [duration]); 34 | } 35 | 36 | public async getCurrentTimestamp(): Promise { 37 | const block = await this._provider.getBlock(await this._provider.getBlockNumber()); 38 | return block.timestamp; 39 | } 40 | 41 | public async setNextBlockTimestamp(timestamp: number): Promise { 42 | await this.sendJSONRpcRequestAsync("evm_setNextBlockTimestamp", [timestamp]); 43 | } 44 | 45 | public async waitBlocksAsync(count: number) { 46 | for (let i = 0; i < count; i++) { 47 | await this.sendJSONRpcRequestAsync("evm_mine", []); 48 | } 49 | } 50 | 51 | private async sendJSONRpcRequestAsync(method: string, params: any[]): Promise { 52 | return this._provider.send(method, params); 53 | } 54 | } -------------------------------------------------------------------------------- /utils/common/conversionUtils.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers/lib/ethers"; 2 | 3 | export const bigNumberToData = (number: BigNumber) => number.toHexString().replace("0x", "").padStart(64, "0"); -------------------------------------------------------------------------------- /utils/common/feeModuleUtils.ts: -------------------------------------------------------------------------------- 1 | import "module-alias/register"; 2 | 3 | import { BigNumber } from "@ethersproject/bignumber"; 4 | 5 | import { preciseMul, preciseMulCeilInt } from "./mathUtils"; 6 | import { ONE_YEAR_IN_SECONDS, PRECISE_UNIT } from "../constants"; 7 | import { Address } from "../types"; 8 | import { StreamingFeeModule } from "@setprotocol/set-protocol-v2/utils/contracts"; 9 | 10 | export const getStreamingFee = async( 11 | feeModule: StreamingFeeModule, 12 | setToken: Address, 13 | previousAccrueTimestamp: BigNumber, 14 | recentAccrueTimestamp: BigNumber, 15 | streamingFee?: BigNumber 16 | ): Promise => { 17 | const feeState = await feeModule.feeStates(setToken); 18 | const accrualRate = streamingFee ? streamingFee : feeState.streamingFeePercentage; 19 | 20 | const timeElapsed = recentAccrueTimestamp.sub(previousAccrueTimestamp); 21 | return timeElapsed.mul(accrualRate).div(ONE_YEAR_IN_SECONDS); 22 | }; 23 | 24 | export const getStreamingFeeInflationAmount = ( 25 | inflationPercent: BigNumber, 26 | totalSupply: BigNumber 27 | ): BigNumber => { 28 | const a = inflationPercent.mul(totalSupply); 29 | const b = PRECISE_UNIT.sub(inflationPercent); 30 | 31 | return a.div(b); 32 | }; 33 | 34 | export const getPostFeePositionUnits = ( 35 | preFeeUnits: BigNumber[], 36 | inflationPercent: BigNumber 37 | ): BigNumber[] => { 38 | const newUnits: BigNumber[] = []; 39 | for (let i = 0; i < preFeeUnits.length; i++) { 40 | if (preFeeUnits[i].gte(0)) { 41 | newUnits.push(preciseMul(preFeeUnits[i], PRECISE_UNIT.sub(inflationPercent))); 42 | } else { 43 | newUnits.push(preciseMulCeilInt(preFeeUnits[i], PRECISE_UNIT.sub(inflationPercent))); 44 | } 45 | } 46 | return newUnits; 47 | }; 48 | -------------------------------------------------------------------------------- /utils/common/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | divDown, 3 | min, 4 | preciseDiv, 5 | preciseDivCeil, 6 | preciseMul, 7 | preciseMulCeil, 8 | preciseMulCeilInt, 9 | preciseDivCeilInt, 10 | sqrt 11 | } from "./mathUtils"; 12 | export { bitcoin, ether, gWei, usdc, wbtc } from "./unitsUtils"; 13 | export { Blockchain } from "./blockchainUtils"; 14 | export { ProtocolUtils } from "./protocolUtils"; 15 | export { BatchTradeUtils } from "./batchTradeUtils"; 16 | export { 17 | convertLibraryNameToLinkId 18 | } from "./libraryUtils"; 19 | export { 20 | bigNumberToData 21 | } from "./conversionUtils"; 22 | export { 23 | getStreamingFee, 24 | getStreamingFeeInflationAmount, 25 | getPostFeePositionUnits 26 | } from "./feeModuleUtils"; 27 | -------------------------------------------------------------------------------- /utils/common/libraryUtils.ts: -------------------------------------------------------------------------------- 1 | import { utils } from "ethers"; 2 | import path from "path"; 3 | 4 | // Converts a fully qualified contract name in a bytecode link id. 5 | // (A fully qualified name looks like: `contracts/mocks/LibraryMock.sol:LibraryMock`) 6 | export function convertLibraryNameToLinkId(libraryName: string): string { 7 | if (!(libraryName.includes(path.sep) && libraryName.includes(":"))) { 8 | throw new Error( 9 | "Converting library name to link id requires a fully qualified " + 10 | "contract name. Example: `contracts/mocks/LibraryMock.sol:LibraryMock`" 11 | ); 12 | } 13 | 14 | const hashedName = utils.keccak256(utils.toUtf8Bytes(libraryName)); 15 | return `__$${hashedName.slice(2).slice(0, 34)}$__`; 16 | } 17 | -------------------------------------------------------------------------------- /utils/common/mathUtils.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | 3 | import { PRECISE_UNIT, ZERO, ONE, TWO } from "../constants"; 4 | 5 | export const preciseMul = (a: BigNumber, b: BigNumber): BigNumber => { 6 | return a.mul(b).div(PRECISE_UNIT); 7 | }; 8 | 9 | export const preciseMulCeil = (a: BigNumber, b: BigNumber): BigNumber => { 10 | if (a.eq(0) || b.eq(0)) { 11 | return ZERO; 12 | } 13 | 14 | return a.mul(b).sub(1).div(PRECISE_UNIT).add(1); 15 | }; 16 | 17 | export const preciseMulCeilInt = (a: BigNumber, b: BigNumber): BigNumber => { 18 | if (a.eq(0) || b.eq(0)) { 19 | return ZERO; 20 | } 21 | 22 | if (a.gt(0) && b.gt(0) || a.lt(0) && b.lt(0)) { 23 | return a.mul(b).sub(1).div(PRECISE_UNIT).add(1); 24 | } else { 25 | return a.mul(b).add(1).div(PRECISE_UNIT).sub(1); 26 | } 27 | }; 28 | 29 | export const preciseDiv = (a: BigNumber, b: BigNumber): BigNumber => { 30 | return a.mul(PRECISE_UNIT).div(b); 31 | }; 32 | 33 | export const preciseDivCeil = (a: BigNumber, b: BigNumber): BigNumber => { 34 | if (a.eq(0) || b.eq(0)) { 35 | return ZERO; 36 | } 37 | 38 | return a.mul(PRECISE_UNIT).sub(1).div(b).add(1); 39 | }; 40 | 41 | export const preciseDivCeilInt = (a: BigNumber, b: BigNumber): BigNumber => { 42 | if (a.eq(0) || b.eq(0)) { 43 | return ZERO; 44 | } 45 | 46 | if (a.gt(0) && b.gt(0) || a.lt(0) && b.lt(0)) { 47 | return a.mul(PRECISE_UNIT).sub(1).div(b).add(1); 48 | } else { 49 | return a.mul(PRECISE_UNIT).add(1).div(b).sub(1); 50 | } 51 | }; 52 | 53 | export const divDown = (a: BigNumber, b: BigNumber): BigNumber => { 54 | let result = a.div(b); 55 | if (a.lt(0) && b.gt(0) && !a.mod(b).isZero()) { 56 | result = result.sub(1); 57 | } else if (a.gt(0) && b.lt(0) && !a.mod(b.mul(-1)).isZero()) { 58 | result = result.sub(1); 59 | } 60 | return result; 61 | }; 62 | 63 | export const min = ( 64 | valueOne: BigNumber, 65 | valueTwo: BigNumber 66 | ): BigNumber => { 67 | return valueOne.lt(valueTwo) ? valueOne : valueTwo; 68 | }; 69 | 70 | export function sqrt(value: BigNumber) { 71 | let z = value.add(ONE).div(TWO); 72 | let y = value; 73 | while (z.sub(y).isNegative()) { 74 | y = z; 75 | z = value.div(z).add(z).div(TWO); 76 | } 77 | return y; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /utils/common/protocolUtils.ts: -------------------------------------------------------------------------------- 1 | import { ethers, providers } from "ethers"; 2 | 3 | export class ProtocolUtils { 4 | public _provider: providers.Web3Provider | providers.JsonRpcProvider; 5 | 6 | constructor(_provider: providers.Web3Provider | providers.JsonRpcProvider) { 7 | this._provider = _provider; 8 | } 9 | 10 | public async getCreatedSetTokenAddress (txnHash: string | undefined): Promise { 11 | if (!txnHash) { 12 | throw new Error("Invalid transaction hash"); 13 | } 14 | 15 | const abi = ["event SetTokenCreated(address indexed _setToken, address _manager, string _name, string _symbol)"]; 16 | const iface = new ethers.utils.Interface(abi); 17 | 18 | const topic = ethers.utils.id("SetTokenCreated(address,address,string,string)"); 19 | const logs = await this._provider.getLogs({ 20 | fromBlock: "latest", 21 | toBlock: "latest", 22 | topics: [topic], 23 | }); 24 | 25 | const parsed = iface.parseLog(logs[logs.length - 1]); 26 | return parsed.args._setToken; 27 | } 28 | } -------------------------------------------------------------------------------- /utils/common/unitsUtils.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { BigNumber } from "ethers/lib/ethers"; 3 | 4 | export const ether = (amount: number): BigNumber => { 5 | const weiString = ethers.utils.parseEther(amount.toString()); 6 | return BigNumber.from(weiString); 7 | }; 8 | 9 | export const usdc = (amount: number): BigNumber => { 10 | const weiString = BigNumber.from("1000000").mul(amount); 11 | return BigNumber.from(weiString); 12 | }; 13 | 14 | export const bitcoin = (amount: number): BigNumber => { 15 | const weiString = 100000000 * amount; 16 | return BigNumber.from(weiString); 17 | }; 18 | 19 | export const gWei = (amount: number): BigNumber => { 20 | const weiString = BigNumber.from("1000000000").mul(amount); 21 | return BigNumber.from(weiString); 22 | }; 23 | 24 | export const wbtc = (amount: number): BigNumber => { 25 | return BigNumber.from(amount).mul(BigNumber.from(10).pow(8)); 26 | }; 27 | 28 | export const UnitsUtils = { usdc, wbtc, ether, gWei }; -------------------------------------------------------------------------------- /utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { constants } from "ethers"; 2 | import { BigNumber } from "ethers"; 3 | 4 | const { AddressZero, MaxUint256, One, Two, Zero } = constants; 5 | 6 | export const EXTENSION_STATE = { 7 | "NONE": 0, 8 | "PENDING": 1, 9 | "INITIALIZED": 2, 10 | }; 11 | 12 | export const POSITION_STATE = { 13 | "DEFAULT": 0, 14 | "EXTERNAL": 1, 15 | }; 16 | 17 | export const ADDRESS_ZERO = AddressZero; 18 | export const EMPTY_BYTES = "0x"; 19 | export const ZERO_BYTES = "0x0000000000000000000000000000000000000000000000000000000000000000"; 20 | export const MAX_UINT_256 = MaxUint256; 21 | export const ONE = One; 22 | export const TWO = Two; 23 | export const THREE = BigNumber.from(3); 24 | export const ZERO = Zero; 25 | export const MAX_INT_256 = "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 26 | export const MIN_INT_256 = "-0x8000000000000000000000000000000000000000000000000000000000000000"; 27 | export const ONE_DAY_IN_SECONDS = BigNumber.from(60 * 60 * 24); 28 | export const ONE_HOUR_IN_SECONDS = BigNumber.from(60 * 60); 29 | export const ONE_YEAR_IN_SECONDS = BigNumber.from(31557600); 30 | 31 | export const PRECISE_UNIT = constants.WeiPerEther; 32 | export const ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; 33 | -------------------------------------------------------------------------------- /utils/contracts/index.ts: -------------------------------------------------------------------------------- 1 | export { BaseExtensionMock } from "../../typechain/BaseExtensionMock"; 2 | export { BaseGlobalExtensionMock } from "../../typechain/BaseGlobalExtensionMock"; 3 | export { BaseManager } from "../..//typechain/BaseManager"; 4 | export { ChainlinkAggregatorMock } from "../../typechain/ChainlinkAggregatorMock"; 5 | export { DelegatedManager } from "../../typechain/DelegatedManager"; 6 | export { DelegatedManagerFactory } from "../../typechain/DelegatedManagerFactory"; 7 | export { DeltaNeutralBasisTradingStrategyExtension } from "../../typechain/DeltaNeutralBasisTradingStrategyExtension"; 8 | export { ManagerCore } from "../../typechain/ManagerCore"; 9 | export { ManagerMock } from "../../typechain/ManagerMock"; 10 | export { ModuleMock } from "../../typechain/ModuleMock"; 11 | export { MutualUpgradeMock } from "../../typechain/MutualUpgradeMock"; 12 | export { MutualUpgradeV2Mock } from "../../typechain/MutualUpgradeV2Mock"; 13 | export { PerpV2LeverageStrategyExtension } from "../../typechain/PerpV2LeverageStrategyExtension"; 14 | export { FeeSplitExtension } from "../../typechain/FeeSplitExtension"; 15 | export { PerpV2PriceFeedMock } from "../../typechain/PerpV2PriceFeedMock"; 16 | export { SupplyCapIssuanceHook } from "../../typechain/SupplyCapIssuanceHook"; 17 | export { TradeExtension } from "../../typechain/TradeExtension"; 18 | export { IssuanceExtension } from "../../typechain/IssuanceExtension"; 19 | export { StreamingFeeSplitExtension } from "../../typechain/StreamingFeeSplitExtension"; 20 | export { BatchTradeExtension } from "../../typechain/BatchTradeExtension"; 21 | export { TradeAdapterMock } from "../../typechain/TradeAdapterMock"; 22 | export { WrapExtension } from "../../typechain/WrapExtension"; 23 | export { ClaimExtension } from "../../typechain/ClaimExtension"; -------------------------------------------------------------------------------- /utils/deploys/deployExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Signer, BigNumber } from "ethers"; 2 | import { 3 | Address, 4 | PerpV2LeverageContractSettings, 5 | PerpV2LeverageMethodologySettings, 6 | PerpV2LeverageExecutionSettings, 7 | PerpV2LeverageIncentiveSettings, 8 | PerpV2LeverageExchangeSettings, 9 | PerpV2BasisContractSettings, 10 | PerpV2BasisMethodologySettings, 11 | PerpV2BasisExecutionSettings, 12 | PerpV2BasisIncentiveSettings, 13 | PerpV2BasisExchangeSettings 14 | } from "../types"; 15 | import { 16 | DeltaNeutralBasisTradingStrategyExtension, 17 | PerpV2LeverageStrategyExtension, 18 | FeeSplitExtension 19 | } from "../contracts/index"; 20 | 21 | import { DeltaNeutralBasisTradingStrategyExtension__factory } from "../../typechain/factories/DeltaNeutralBasisTradingStrategyExtension__factory"; 22 | import { PerpV2LeverageStrategyExtension__factory } from "../../typechain/factories/PerpV2LeverageStrategyExtension__factory"; 23 | import { FeeSplitExtension__factory } from "../../typechain/factories/FeeSplitExtension__factory"; 24 | 25 | export default class DeployExtensions { 26 | private _deployerSigner: Signer; 27 | 28 | constructor(deployerSigner: Signer) { 29 | this._deployerSigner = deployerSigner; 30 | } 31 | 32 | public async deployPerpV2LeverageStrategyExtension( 33 | manager: Address, 34 | contractSettings: PerpV2LeverageContractSettings, 35 | methodologySettings: PerpV2LeverageMethodologySettings, 36 | executionSettings: PerpV2LeverageExecutionSettings, 37 | incentiveSettings: PerpV2LeverageIncentiveSettings, 38 | exchangeSettings: PerpV2LeverageExchangeSettings 39 | ): Promise { 40 | return await new PerpV2LeverageStrategyExtension__factory(this._deployerSigner).deploy( 41 | manager, 42 | contractSettings, 43 | methodologySettings, 44 | executionSettings, 45 | incentiveSettings, 46 | exchangeSettings, 47 | ); 48 | } 49 | 50 | public async deployDeltaNeutralBasisTradingStrategyExtension( 51 | manager: Address, 52 | contractSettings: PerpV2BasisContractSettings, 53 | methodologySettings: PerpV2BasisMethodologySettings, 54 | executionSettings: PerpV2BasisExecutionSettings, 55 | incentiveSettings: PerpV2BasisIncentiveSettings, 56 | exchangeSettings: PerpV2BasisExchangeSettings 57 | ): Promise { 58 | return await new DeltaNeutralBasisTradingStrategyExtension__factory(this._deployerSigner).deploy( 59 | manager, 60 | contractSettings, 61 | methodologySettings, 62 | executionSettings, 63 | incentiveSettings, 64 | exchangeSettings, 65 | ); 66 | } 67 | 68 | public async deployFeeSplitExtension( 69 | manager: Address, 70 | streamingFeeModule: Address, 71 | debtIssuanceModule: Address, 72 | operatorFeeSplit: BigNumber, 73 | operatorFeeRecipient: Address 74 | ): Promise { 75 | return await new FeeSplitExtension__factory(this._deployerSigner).deploy( 76 | manager, 77 | streamingFeeModule, 78 | debtIssuanceModule, 79 | operatorFeeSplit, 80 | operatorFeeRecipient 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /utils/deploys/deployFactories.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | import { 3 | Address 4 | } from "../types"; 5 | 6 | import { DelegatedManagerFactory } from "../contracts"; 7 | import { DelegatedManagerFactory__factory } from "../../typechain/factories/DelegatedManagerFactory__factory"; 8 | 9 | export default class DeployFactories { 10 | private _deployerSigner: Signer; 11 | 12 | constructor(deployerSigner: Signer) { 13 | this._deployerSigner = deployerSigner; 14 | } 15 | 16 | public async deployDelegatedManagerFactory( 17 | managerCore: Address, 18 | controller: Address, 19 | setTokenFactory: Address 20 | ): Promise { 21 | return await new DelegatedManagerFactory__factory(this._deployerSigner).deploy( 22 | managerCore, 23 | controller, 24 | setTokenFactory 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /utils/deploys/deployGlobalExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | import { Address } from "../types"; 3 | import { 4 | BatchTradeExtension, 5 | ClaimExtension, 6 | IssuanceExtension, 7 | StreamingFeeSplitExtension, 8 | TradeExtension, 9 | WrapExtension 10 | } from "../contracts/index"; 11 | 12 | import { BatchTradeExtension__factory } from "../../typechain/factories/BatchTradeExtension__factory"; 13 | import { ClaimExtension__factory } from "../../typechain/factories/ClaimExtension__factory"; 14 | import { IssuanceExtension__factory } from "../../typechain/factories/IssuanceExtension__factory"; 15 | import { StreamingFeeSplitExtension__factory } from "../../typechain/factories/StreamingFeeSplitExtension__factory"; 16 | import { TradeExtension__factory } from "../../typechain/factories/TradeExtension__factory"; 17 | import { WrapExtension__factory } from "../../typechain/factories/WrapExtension__factory"; 18 | 19 | export default class DeployGlobalExtensions { 20 | private _deployerSigner: Signer; 21 | 22 | constructor(deployerSigner: Signer) { 23 | this._deployerSigner = deployerSigner; 24 | } 25 | 26 | public async deployBatchTradeExtension( 27 | managerCore: Address, 28 | tradeModule: Address, 29 | integrations: string[] 30 | ): Promise { 31 | return await new BatchTradeExtension__factory(this._deployerSigner).deploy( 32 | managerCore, 33 | tradeModule, 34 | integrations 35 | ); 36 | } 37 | 38 | public async deployClaimExtension( 39 | managerCore: Address, 40 | airdropModule: Address, 41 | claimModule: Address, 42 | integrationRegistry: Address 43 | ): Promise { 44 | return await new ClaimExtension__factory(this._deployerSigner).deploy( 45 | managerCore, 46 | airdropModule, 47 | claimModule, 48 | integrationRegistry 49 | ); 50 | } 51 | 52 | public async deployIssuanceExtension( 53 | managerCore: Address, 54 | basicIssuanceModule: Address 55 | ): Promise { 56 | return await new IssuanceExtension__factory(this._deployerSigner).deploy( 57 | managerCore, 58 | basicIssuanceModule, 59 | ); 60 | } 61 | 62 | public async deployStreamingFeeSplitExtension( 63 | managerCore: Address, 64 | streamingFeeModule: Address 65 | ): Promise { 66 | return await new StreamingFeeSplitExtension__factory(this._deployerSigner).deploy( 67 | managerCore, 68 | streamingFeeModule, 69 | ); 70 | } 71 | 72 | public async deployTradeExtension( 73 | managerCore: Address, 74 | tradeModule: Address 75 | ): Promise { 76 | return await new TradeExtension__factory(this._deployerSigner).deploy( 77 | managerCore, 78 | tradeModule, 79 | ); 80 | } 81 | 82 | public async deployWrapExtension( 83 | managerCore: Address, 84 | wrapModule: Address 85 | ): Promise { 86 | return await new WrapExtension__factory(this._deployerSigner).deploy( 87 | managerCore, 88 | wrapModule, 89 | ); 90 | } 91 | } -------------------------------------------------------------------------------- /utils/deploys/deployHooks.ts: -------------------------------------------------------------------------------- 1 | import { Signer, BigNumber } from "ethers"; 2 | import { 3 | Address 4 | } from "../types"; 5 | 6 | import { SupplyCapIssuanceHook } from "../../typechain/SupplyCapIssuanceHook"; 7 | import { SupplyCapIssuanceHook__factory } from "../../typechain/factories/SupplyCapIssuanceHook__factory"; 8 | 9 | export default class DeployHooks { 10 | private _deployerSigner: Signer; 11 | 12 | constructor(deployerSigner: Signer) { 13 | this._deployerSigner = deployerSigner; 14 | } 15 | 16 | public async deploySupplyCapIssuanceHook( 17 | initialOwner: Address, 18 | supplyCap: BigNumber 19 | ): Promise { 20 | return await new SupplyCapIssuanceHook__factory(this._deployerSigner).deploy(initialOwner, supplyCap); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /utils/deploys/deployManager.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | import { Address } from "../types"; 3 | import { 4 | BaseManager, 5 | DelegatedManager 6 | } from "../contracts/index"; 7 | 8 | import { BaseManager__factory } from "../../typechain/factories/BaseManager__factory"; 9 | import { DelegatedManager__factory } from "../../typechain/factories/DelegatedManager__factory"; 10 | 11 | export default class DeployToken { 12 | private _deployerSigner: Signer; 13 | 14 | constructor(deployerSigner: Signer) { 15 | this._deployerSigner = deployerSigner; 16 | } 17 | 18 | public async deployBaseManager( 19 | set: Address, 20 | operator: Address, 21 | methodologist: Address 22 | ): Promise { 23 | return await new BaseManager__factory(this._deployerSigner).deploy( 24 | set, 25 | operator, 26 | methodologist 27 | ); 28 | } 29 | 30 | public async deployDelegatedManager( 31 | setToken: Address, 32 | factory: Address, 33 | methodologist: Address, 34 | extensions: Address[], 35 | operators: Address[], 36 | allowedAssets: Address[], 37 | useAssetAllowlist: boolean 38 | ): Promise { 39 | return await new DelegatedManager__factory(this._deployerSigner).deploy( 40 | setToken, 41 | factory, 42 | methodologist, 43 | extensions, 44 | operators, 45 | allowedAssets, 46 | useAssetAllowlist 47 | ); 48 | } 49 | 50 | /* GETTERS */ 51 | 52 | public async getDelegatedManager(managerAddress: Address): Promise { 53 | return await new DelegatedManager__factory(this._deployerSigner).attach(managerAddress); 54 | } 55 | } -------------------------------------------------------------------------------- /utils/deploys/deployManagerCore.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | 3 | import { ManagerCore } from "../contracts"; 4 | import { ManagerCore__factory } from "../../typechain/factories/ManagerCore__factory"; 5 | 6 | export default class DeployFactories { 7 | private _deployerSigner: Signer; 8 | 9 | constructor(deployerSigner: Signer) { 10 | this._deployerSigner = deployerSigner; 11 | } 12 | 13 | public async deployManagerCore(): Promise { 14 | return await new ManagerCore__factory(this._deployerSigner).deploy(); 15 | } 16 | } -------------------------------------------------------------------------------- /utils/deploys/deployMocks.ts: -------------------------------------------------------------------------------- 1 | import { Signer, BigNumber } from "ethers"; 2 | import { Address } from "../types"; 3 | import { 4 | BaseExtensionMock, 5 | BaseGlobalExtensionMock, 6 | ManagerMock, 7 | ModuleMock, 8 | MutualUpgradeMock, 9 | PerpV2PriceFeedMock, 10 | TradeAdapterMock 11 | } from "../contracts/index"; 12 | 13 | import { 14 | ChainlinkAggregatorMock, 15 | ContractCallerMock, 16 | StandardTokenMock 17 | } from "@setprotocol/set-protocol-v2/typechain"; 18 | 19 | import { BaseExtensionMock__factory } from "../../typechain/factories/BaseExtensionMock__factory"; 20 | import { BaseGlobalExtensionMock__factory } from "../../typechain/factories/BaseGlobalExtensionMock__factory"; 21 | import { ManagerMock__factory } from "../../typechain/factories/ManagerMock__factory"; 22 | import { ModuleMock__factory } from "../../typechain/factories/ModuleMock__factory"; 23 | import { ChainlinkAggregatorMock__factory } from "@setprotocol/set-protocol-v2/dist/typechain"; 24 | import { ContractCallerMock__factory } from "@setprotocol/set-protocol-v2/dist/typechain"; 25 | import { MutualUpgradeMock__factory } from "../../typechain/factories/MutualUpgradeMock__factory"; 26 | import { MutualUpgradeV2Mock__factory } from "../../typechain/factories/MutualUpgradeV2Mock__factory"; 27 | import { PerpV2PriceFeedMock__factory } from "../../typechain/factories/PerpV2PriceFeedMock__factory"; 28 | import { TradeAdapterMock__factory } from "../../typechain/factories/TradeAdapterMock__factory"; 29 | import { StandardTokenMock__factory } from "@setprotocol/set-protocol-v2/dist/typechain"; 30 | 31 | export default class DeployMocks { 32 | private _deployerSigner: Signer; 33 | 34 | constructor(deployerSigner: Signer) { 35 | this._deployerSigner = deployerSigner; 36 | } 37 | 38 | public async deployBaseExtensionMock(manager: Address): Promise { 39 | return await new BaseExtensionMock__factory(this._deployerSigner).deploy(manager); 40 | } 41 | 42 | public async deployBaseGlobalExtensionMock(managerCore: Address, module: Address): Promise { 43 | return await new BaseGlobalExtensionMock__factory(this._deployerSigner).deploy(managerCore, module); 44 | } 45 | 46 | public async deployManagerMock(setToken: Address): Promise { 47 | return await new ManagerMock__factory(this._deployerSigner).deploy(setToken); 48 | } 49 | 50 | public async deployModuleMock(controller: Address): Promise { 51 | return await new ModuleMock__factory(this._deployerSigner).deploy(controller); 52 | } 53 | 54 | public async deployMutualUpgradeMock(owner: Address, methodologist: string): Promise { 55 | return await new MutualUpgradeMock__factory(this._deployerSigner).deploy(owner, methodologist); 56 | } 57 | 58 | public async deployMutualUpgradeV2Mock(owner: Address, methodologist: string): Promise { 59 | return await new MutualUpgradeV2Mock__factory(this._deployerSigner).deploy(owner, methodologist); 60 | } 61 | 62 | public async deployStandardTokenMock(owner: Address, decimals: number): Promise { 63 | return await new StandardTokenMock__factory(this._deployerSigner).deploy( 64 | owner, 65 | BigNumber.from(1000000).mul(BigNumber.from(10).pow(decimals)), 66 | "USDCoin", 67 | "USDC", 68 | decimals 69 | ); 70 | } 71 | 72 | public async deployChainlinkAggregatorMock(decimals: number): Promise { 73 | return await new ChainlinkAggregatorMock__factory(this._deployerSigner).deploy(decimals); 74 | } 75 | 76 | public async deployContractCallerMock(): Promise { 77 | return await new ContractCallerMock__factory(this._deployerSigner).deploy(); 78 | } 79 | 80 | public async deployPerpV2PriceFeedMock(decimals: number): Promise { 81 | return await new PerpV2PriceFeedMock__factory(this._deployerSigner).deploy(decimals); 82 | } 83 | 84 | public async deployTradeAdapterMock(): Promise { 85 | return await new TradeAdapterMock__factory(this._deployerSigner).deploy(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /utils/deploys/deploySetV2.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | import { 3 | Address 4 | } from "../types"; 5 | 6 | import { SetToken } from "@setprotocol/set-protocol-v2/typechain/SetToken"; 7 | import { SetToken__factory } from "@setprotocol/set-protocol-v2/dist/typechain/factories/SetToken__factory"; 8 | import { DebtIssuanceModule } from "@setprotocol/set-protocol-v2/typechain/DebtIssuanceModule"; 9 | import { DebtIssuanceModule__factory } from "@setprotocol/set-protocol-v2/dist/typechain/factories/DebtIssuanceModule__factory"; 10 | import { IssuanceModule } from "@setprotocol/set-protocol-v2/typechain/IssuanceModule"; 11 | import { IssuanceModule__factory } from "@setprotocol/set-protocol-v2/dist/typechain/factories/IssuanceModule__factory"; 12 | 13 | export default class DeploySetV2 { 14 | private _deployerSigner: Signer; 15 | 16 | constructor(deployerSigner: Signer) { 17 | this._deployerSigner = deployerSigner; 18 | } 19 | 20 | public async deployDebtIssuanceModule( 21 | controller: Address, 22 | ): Promise { 23 | return await new DebtIssuanceModule__factory(this._deployerSigner).deploy( 24 | controller, 25 | ); 26 | } 27 | 28 | public async deployIssuanceModule( 29 | controller: Address, 30 | ): Promise { 31 | return await new IssuanceModule__factory(this._deployerSigner).deploy( 32 | controller, 33 | ); 34 | } 35 | 36 | /* GETTERS */ 37 | 38 | public async getSetToken(setTokenAddress: Address): Promise { 39 | return await new SetToken__factory(this._deployerSigner).attach(setTokenAddress); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /utils/deploys/index.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | 3 | import SetDeployHelper from "@setprotocol/set-protocol-v2/dist/utils/deploys"; 4 | 5 | import DeployManager from "./deployManager"; 6 | import DeployMocks from "./deployMocks"; 7 | import DeployExtensions from "./deployExtensions"; 8 | import DeployManagerCore from "./deployManagerCore"; 9 | import DeployGlobalExtensions from "./deployGlobalExtensions"; 10 | import DeployFactories from "./deployFactories"; 11 | import DeployHooks from "./deployHooks"; 12 | import DeploySetV2 from "./deploySetV2"; 13 | 14 | export default class DeployHelper { 15 | public extensions: DeployExtensions; 16 | public managerCore: DeployManagerCore; 17 | public globalExtensions: DeployGlobalExtensions; 18 | public factories: DeployFactories; 19 | public manager: DeployManager; 20 | public mocks: DeployMocks; 21 | public hooks: DeployHooks; 22 | public setV2: DeploySetV2; 23 | public setDeployer: SetDeployHelper; 24 | 25 | constructor(deployerSigner: Signer) { 26 | this.extensions = new DeployExtensions(deployerSigner); 27 | this.managerCore = new DeployManagerCore(deployerSigner); 28 | this.globalExtensions = new DeployGlobalExtensions(deployerSigner); 29 | this.factories = new DeployFactories(deployerSigner); 30 | this.manager = new DeployManager(deployerSigner); 31 | this.mocks = new DeployMocks(deployerSigner); 32 | this.hooks = new DeployHooks(deployerSigner); 33 | this.setV2 = new DeploySetV2(deployerSigner); 34 | this.setDeployer = new SetDeployHelper(deployerSigner); 35 | } 36 | } -------------------------------------------------------------------------------- /utils/flexibleLeverageUtils/flexibleLeverage.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "@ethersproject/bignumber"; 2 | import { PerpV2BaseToken, SetToken } from "@setprotocol/set-protocol-v2/typechain"; 3 | import { PerpV2Fixture } from "@setprotocol/set-protocol-v2/dist/utils/fixtures"; 4 | import { ether, preciseMul, preciseDiv } from "../common"; 5 | 6 | export function calculateNewLeverageRatio( 7 | currentLeverageRatio: BigNumber, 8 | targetLeverageRatio: BigNumber, 9 | minLeverageRatio: BigNumber, 10 | maxLeverageRatio: BigNumber, 11 | recenteringSpeed: BigNumber 12 | ): BigNumber { 13 | const a = preciseMul(targetLeverageRatio, recenteringSpeed); 14 | const b = preciseMul(ether(1).sub(recenteringSpeed), currentLeverageRatio); 15 | const c = a.add(b); 16 | const d = c.lt(maxLeverageRatio) ? c : maxLeverageRatio; 17 | return minLeverageRatio.gte(d) ? minLeverageRatio : d; 18 | } 19 | 20 | 21 | export function calculateNewLeverageRatioPerpV2( 22 | currentLeverageRatio: BigNumber, 23 | targetLeverageRatio: BigNumber, 24 | minLeverageRatio: BigNumber, 25 | maxLeverageRatio: BigNumber, 26 | recenteringSpeed: BigNumber 27 | ): BigNumber { 28 | const a = preciseMul(targetLeverageRatio.abs(), recenteringSpeed); 29 | const b = preciseMul(ether(1).sub(recenteringSpeed), currentLeverageRatio.abs()); 30 | const c = a.add(b); 31 | const d = c.lt(maxLeverageRatio.abs()) ? c : maxLeverageRatio.abs(); 32 | const nlr = minLeverageRatio.abs().gte(d) ? minLeverageRatio.abs() : d; 33 | return currentLeverageRatio.gt(0) ? nlr : nlr.mul(-1); 34 | } 35 | 36 | export function calculateNewLeverageRatioPerpV2Basis( 37 | currentLeverageRatio: BigNumber, 38 | methodology: { 39 | targetLeverageRatio: BigNumber; 40 | minLeverageRatio: BigNumber; 41 | maxLeverageRatio: BigNumber; 42 | recenteringSpeed: BigNumber; 43 | } 44 | ): BigNumber { 45 | const a = preciseMul(methodology.targetLeverageRatio.abs(), methodology.recenteringSpeed); 46 | const b = preciseMul(ether(1).sub(methodology.recenteringSpeed), currentLeverageRatio.abs()); 47 | const c = a.add(b); 48 | const d = c.lt(methodology.maxLeverageRatio.abs()) ? c : methodology.maxLeverageRatio.abs(); 49 | const nlr = methodology.minLeverageRatio.abs().gte(d) ? methodology.minLeverageRatio.abs() : d; 50 | return currentLeverageRatio.gt(0) ? nlr : nlr.mul(-1); 51 | } 52 | 53 | export function calculateCollateralRebalanceUnits( 54 | currentLeverageRatio: BigNumber, 55 | newLeverageRatio: BigNumber, 56 | collateralBalance: BigNumber, 57 | totalSupply: BigNumber 58 | ): BigNumber { 59 | const a = currentLeverageRatio.gt(newLeverageRatio) ? currentLeverageRatio.sub(newLeverageRatio) : newLeverageRatio.sub(currentLeverageRatio); 60 | const b = preciseDiv(a, currentLeverageRatio); 61 | const c = preciseMul(b, collateralBalance); 62 | 63 | return preciseDiv(c, totalSupply); 64 | } 65 | 66 | // export async function calculateTotalRebalanceNotionalCompound( 67 | // setToken: SetToken, 68 | // cEther: CEther, 69 | // currentLeverageRatio: BigNumber, 70 | // newLeverageRatio: BigNumber 71 | // ): Promise { 72 | 73 | // const collateralCTokenExchangeRate = await cEther.exchangeRateStored(); 74 | // const collateralCTokenBalance = await cEther.balanceOf(setToken.address); 75 | // const collateralBalance = preciseMul(collateralCTokenBalance, collateralCTokenExchangeRate); 76 | // const a = currentLeverageRatio.gt(newLeverageRatio) ? currentLeverageRatio.sub(newLeverageRatio) : newLeverageRatio.sub(currentLeverageRatio); 77 | // const b = preciseDiv(a, currentLeverageRatio); 78 | // return preciseMul(b, collateralBalance); 79 | // } 80 | 81 | // export async function calculateTotalRebalanceNotionalAave( 82 | // setToken: SetToken, 83 | // aToken: AaveV2AToken, 84 | // currentLeverageRatio: BigNumber, 85 | // newLeverageRatio: BigNumber 86 | // ): Promise { 87 | 88 | // const collateralBalance = await aToken.balanceOf(setToken.address); 89 | // const a = currentLeverageRatio.gt(newLeverageRatio) ? currentLeverageRatio.sub(newLeverageRatio) : newLeverageRatio.sub(currentLeverageRatio); 90 | // const b = preciseDiv(a, currentLeverageRatio); 91 | // return preciseMul(b, collateralBalance); 92 | // } 93 | 94 | export async function calculateTotalRebalanceNotionalPerpV2( 95 | setToken: SetToken, 96 | baseToken: PerpV2BaseToken, 97 | currentLeverageRatio: BigNumber, 98 | newLeverageRatio: BigNumber, 99 | perpV2Setup: PerpV2Fixture 100 | ): Promise { 101 | const baseBalance = await perpV2Setup.accountBalance.getBase(setToken.address, baseToken.address); 102 | const a = newLeverageRatio.sub(currentLeverageRatio); 103 | const b = preciseDiv(a, currentLeverageRatio); 104 | return preciseMul(b, baseBalance); 105 | } 106 | 107 | export function calculateMaxBorrowForDelever( 108 | collateralBalance: BigNumber, 109 | collateralFactor: BigNumber, 110 | unutilizedLeveragePercentage: BigNumber, 111 | collateralPrice: BigNumber, 112 | borrowPrice: BigNumber, 113 | borrowBalance: BigNumber, 114 | ): BigNumber { 115 | const collateralValue = preciseMul(collateralBalance, collateralPrice); 116 | const borrowValue = preciseMul(borrowBalance, borrowPrice); 117 | const netBorrowLimit = preciseMul( 118 | preciseMul(collateralValue, collateralFactor), 119 | ether(1).sub(unutilizedLeveragePercentage) 120 | ); 121 | const a = preciseMul(collateralBalance, netBorrowLimit.sub(borrowValue)); 122 | 123 | return preciseDiv(a, netBorrowLimit); 124 | } 125 | 126 | export function calculateMaxRedeemForDeleverToZero( 127 | currentLeverageRatio: BigNumber, 128 | newLeverageRatio: BigNumber, 129 | collateralBalance: BigNumber, 130 | totalSupply: BigNumber, 131 | slippageTolerance: BigNumber 132 | ): BigNumber { 133 | const a = currentLeverageRatio.gt(newLeverageRatio) ? currentLeverageRatio.sub(newLeverageRatio) : newLeverageRatio.sub(currentLeverageRatio); 134 | const b = preciseDiv(a, currentLeverageRatio); 135 | const rebalanceNotional = preciseMul(b, collateralBalance); 136 | const notionalRedeemQuantity = preciseMul(rebalanceNotional, ether(1).add(slippageTolerance)); 137 | 138 | return preciseDiv(notionalRedeemQuantity, totalSupply); 139 | } 140 | -------------------------------------------------------------------------------- /utils/flexibleLeverageUtils/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | calculateNewLeverageRatio, 3 | calculateCollateralRebalanceUnits, 4 | calculateMaxBorrowForDelever, 5 | calculateMaxRedeemForDeleverToZero, 6 | calculateTotalRebalanceNotionalPerpV2, 7 | calculateNewLeverageRatioPerpV2, 8 | calculateNewLeverageRatioPerpV2Basis 9 | } from "./flexibleLeverage"; -------------------------------------------------------------------------------- /utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { Blockchain } from "./common"; 3 | 4 | const provider = ethers.provider; 5 | export const getBlockchainUtils = () => new Blockchain(provider); 6 | 7 | export { 8 | getAccounts, 9 | getEthBalance, 10 | getLastBlockTimestamp, 11 | getProvider, 12 | getTransactionTimestamp, 13 | getWaffleExpect, 14 | addSnapshotBeforeRestoreAfterEach, 15 | getRandomAccount, 16 | getRandomAddress, 17 | increaseTimeAsync, 18 | mineBlockAsync, 19 | cacheBeforeEach, 20 | } from "./test"; 21 | 22 | export { 23 | bigNumberToData, 24 | bitcoin, 25 | divDown, 26 | ether, 27 | gWei, 28 | min, 29 | preciseDiv, 30 | preciseDivCeil, 31 | preciseMul, 32 | preciseMulCeil, 33 | preciseMulCeilInt, 34 | preciseDivCeilInt, 35 | sqrt, 36 | usdc, 37 | wbtc, 38 | } from "./common"; 39 | 40 | export { 41 | calculateNewLeverageRatio, 42 | calculateCollateralRebalanceUnits, 43 | calculateMaxBorrowForDelever, 44 | calculateMaxRedeemForDeleverToZero, 45 | calculateTotalRebalanceNotionalPerpV2, 46 | calculateNewLeverageRatioPerpV2, 47 | calculateNewLeverageRatioPerpV2Basis 48 | } from "./flexibleLeverageUtils"; -------------------------------------------------------------------------------- /utils/test/accountUtils.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { BigNumber } from "ethers"; 3 | import { Account, Address } from "../types"; 4 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; 5 | 6 | const provider = ethers.provider; 7 | 8 | export const getAccounts = async (): Promise => { 9 | const accounts: Account[] = []; 10 | 11 | const wallets = await getWallets(); 12 | for (let i = 0; i < wallets.length; i++) { 13 | accounts.push({ 14 | wallet: wallets[i], 15 | address: await wallets[i].getAddress(), 16 | }); 17 | } 18 | 19 | return accounts; 20 | }; 21 | 22 | // Use the last wallet to ensure it has Ether 23 | export const getRandomAccount = async (): Promise => { 24 | const accounts = await getAccounts(); 25 | return accounts[accounts.length - 1]; 26 | }; 27 | 28 | export const getRandomAddress = async (): Promise
=> { 29 | const wallet = ethers.Wallet.createRandom().connect(provider); 30 | return await wallet.getAddress(); 31 | }; 32 | 33 | export const getEthBalance = async (account: Address): Promise => { 34 | return await provider.getBalance(account); 35 | }; 36 | 37 | // NOTE ethers.signers may be a buidler specific function 38 | export const getWallets = async (): Promise => { 39 | return (await ethers.getSigners() as SignerWithAddress[]); 40 | }; 41 | -------------------------------------------------------------------------------- /utils/test/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | getAccounts, 3 | getEthBalance, 4 | getRandomAccount, 5 | getRandomAddress, 6 | } from "./accountUtils"; 7 | export { 8 | addSnapshotBeforeRestoreAfterEach, 9 | getLastBlockTimestamp, 10 | getProvider, 11 | getTransactionTimestamp, 12 | getWaffleExpect, 13 | increaseTimeAsync, 14 | mineBlockAsync, 15 | cacheBeforeEach 16 | } from "./testingUtils"; 17 | -------------------------------------------------------------------------------- /utils/test/testingUtils.ts: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import { solidity } from "ethereum-waffle"; 3 | 4 | chai.use(solidity); 5 | 6 | // Use HARDHAT version of providers 7 | import { ethers } from "hardhat"; 8 | import { BigNumber, providers } from "ethers"; 9 | import { Blockchain } from "../common"; 10 | 11 | const provider = ethers.provider; 12 | // const blockchain = new Blockchain(provider); 13 | 14 | // HARDHAT-SPECIFIC Provider 15 | export const getProvider = (): providers.JsonRpcProvider => { 16 | return ethers.provider; 17 | }; 18 | 19 | // HARDHAT / WAFFLE 20 | export const getWaffleExpect = (): Chai.ExpectStatic => { 21 | return chai.expect; 22 | }; 23 | 24 | // And this is our test sandboxing. It snapshots and restores between each test. 25 | // Note: if a test suite uses fastForward at all, then it MUST also use these snapshots, 26 | // otherwise it will update the block time of the EVM and future tests that expect a 27 | // starting timestamp will fail. 28 | export const addSnapshotBeforeRestoreAfterEach = () => { 29 | const blockchain = new Blockchain(provider); 30 | beforeEach(async () => { 31 | await blockchain.saveSnapshotAsync(); 32 | }); 33 | 34 | afterEach(async () => { 35 | await blockchain.revertAsync(); 36 | }); 37 | }; 38 | 39 | // This is test sandboxing for nested snapshots. Can be used like a `beforeEach` statement. 40 | // The same caveats about time noted in the comment above apply. 41 | const SNAPSHOTS: string[] = []; 42 | 43 | export function cacheBeforeEach(initializer: Mocha.AsyncFunc): void { 44 | let initialized = false; 45 | const blockchain = new Blockchain(provider); 46 | 47 | beforeEach(async function () { 48 | if (!initialized) { 49 | await initializer.call(this); 50 | SNAPSHOTS.push(await blockchain.saveSnapshotAsync()); 51 | initialized = true; 52 | } else { 53 | const snapshotId = SNAPSHOTS.pop()!; 54 | await blockchain.revertByIdAsync(snapshotId); 55 | SNAPSHOTS.push(await blockchain.saveSnapshotAsync()); 56 | } 57 | }); 58 | 59 | after(async function () { 60 | if (initialized) { 61 | SNAPSHOTS.pop(); 62 | } 63 | }); 64 | } 65 | 66 | export async function getTransactionTimestamp(asyncTxn: any): Promise { 67 | const txData = await asyncTxn; 68 | return BigNumber.from((await provider.getBlock(txData.block)).timestamp); 69 | } 70 | 71 | export async function getLastBlockTimestamp(): Promise { 72 | return BigNumber.from((await provider.getBlock("latest")).timestamp); 73 | } 74 | 75 | export async function getLastBlockTransaction(): Promise { 76 | return (await provider.getBlockWithTransactions("latest")).transactions[0]; 77 | } 78 | 79 | export async function mineBlockAsync(): Promise { 80 | await sendJSONRpcRequestAsync("evm_mine", []); 81 | } 82 | 83 | export async function increaseTimeAsync( 84 | duration: BigNumber, 85 | ): Promise { 86 | await sendJSONRpcRequestAsync("evm_increaseTime", [duration.toNumber()]); 87 | await mineBlockAsync(); 88 | } 89 | 90 | async function sendJSONRpcRequestAsync( 91 | method: string, 92 | params: any[], 93 | ): Promise { 94 | return provider.send( 95 | method, 96 | params, 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /utils/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /utils/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ContractTransaction as ContractTransactionType, 3 | Wallet as WalletType 4 | } from "ethers"; 5 | import { BigNumber } from "ethers"; 6 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; 7 | 8 | export type Account = { 9 | address: Address; 10 | wallet: SignerWithAddress; 11 | }; 12 | 13 | export type Address = string; 14 | export type Bytes = string; 15 | 16 | export type ContractTransaction = ContractTransactionType; 17 | export type Wallet = WalletType; 18 | 19 | export interface MerkleDistributorInfo { 20 | merkleRoot: string; 21 | tokenTotal: string; 22 | claims: { 23 | [account: string]: { 24 | index: number; 25 | amount: string; 26 | proof: string[]; 27 | flags?: { 28 | [flag: string]: boolean; 29 | }; 30 | }; 31 | }; 32 | } 33 | 34 | export type DistributionFormat = { address: string; earnings: BigNumber }; 35 | 36 | export interface PerpV2LeverageContractSettings { 37 | setToken: Address; 38 | perpV2LeverageModule: Address; 39 | perpV2AccountBalance: Address; 40 | baseUSDPriceOracle: Address; 41 | twapInterval: BigNumber; 42 | basePriceDecimalAdjustment: BigNumber; 43 | virtualBaseAddress: Address; 44 | virtualQuoteAddress: Address; 45 | } 46 | 47 | export interface PerpV2LeverageMethodologySettings { 48 | targetLeverageRatio: BigNumber; 49 | minLeverageRatio: BigNumber; 50 | maxLeverageRatio: BigNumber; 51 | recenteringSpeed: BigNumber; 52 | rebalanceInterval: BigNumber; 53 | } 54 | 55 | export interface PerpV2LeverageExecutionSettings { 56 | twapCooldownPeriod: BigNumber; 57 | slippageTolerance: BigNumber; 58 | } 59 | 60 | export interface PerpV2LeverageExchangeSettings { 61 | twapMaxTradeSize: BigNumber; 62 | incentivizedTwapMaxTradeSize: BigNumber; 63 | } 64 | 65 | export interface PerpV2LeverageIncentiveSettings { 66 | incentivizedTwapCooldownPeriod: BigNumber; 67 | incentivizedSlippageTolerance: BigNumber; 68 | etherReward: BigNumber; 69 | incentivizedLeverageRatio: BigNumber; 70 | } 71 | 72 | export interface PerpV2BasisContractSettings { 73 | setToken: Address; 74 | basisTradingModule: Address; 75 | tradeModule: Address; 76 | quoter: Address; 77 | perpV2AccountBalance: Address; 78 | baseUSDPriceOracle: Address; 79 | twapInterval: BigNumber; 80 | basePriceDecimalAdjustment: BigNumber; 81 | virtualBaseAddress: Address; 82 | virtualQuoteAddress: Address; 83 | spotAssetAddress: Address; 84 | } 85 | 86 | export interface PerpV2BasisMethodologySettings { 87 | targetLeverageRatio: BigNumber; 88 | minLeverageRatio: BigNumber; 89 | maxLeverageRatio: BigNumber; 90 | recenteringSpeed: BigNumber; 91 | rebalanceInterval: BigNumber; 92 | reinvestInterval: BigNumber; 93 | minReinvestUnits: BigNumber; 94 | } 95 | 96 | export interface PerpV2BasisExecutionSettings { 97 | twapCooldownPeriod: BigNumber; 98 | slippageTolerance: BigNumber; 99 | } 100 | 101 | export interface PerpV2BasisExchangeSettings { 102 | exchangeName: string; 103 | buyExactSpotTradeData: Bytes; 104 | sellExactSpotTradeData: Bytes; 105 | buySpotQuoteExactInputPath: Bytes; 106 | twapMaxTradeSize: BigNumber; 107 | incentivizedTwapMaxTradeSize: BigNumber; 108 | } 109 | 110 | export interface PerpV2BasisIncentiveSettings { 111 | incentivizedTwapCooldownPeriod: BigNumber; 112 | incentivizedSlippageTolerance: BigNumber; 113 | etherReward: BigNumber; 114 | incentivizedLeverageRatio: BigNumber; 115 | } 116 | 117 | export interface StreamingFeeState { 118 | feeRecipient: Address; 119 | streamingFeePercentage: BigNumber; 120 | maxStreamingFeePercentage: BigNumber; 121 | lastStreamingFeeTimestamp: BigNumber; 122 | } 123 | 124 | export interface TradeInfo { 125 | exchangeName: string; 126 | sendToken: Address; 127 | sendQuantity: BigNumber; 128 | receiveToken: Address; 129 | receiveQuantity: BigNumber; 130 | data: Bytes; 131 | } 132 | 133 | export interface BatchTradeResult { 134 | success: boolean; 135 | tradeInfo: TradeInfo; 136 | revertReason?: string | undefined; 137 | }; 138 | -------------------------------------------------------------------------------- /utils/wallets.ts: -------------------------------------------------------------------------------- 1 | import { ethers, providers } from "ethers"; 2 | 3 | export const privateKeys = [ 4 | "0xf2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d", 5 | "0x5d862464fe9303452126c8bc94274b8c5f9874cbd219789b3eb2128075a76f72", 6 | "0xdf02719c4df8b9b8ac7f551fcb5d9ef48fa27eef7a66453879f4d8fdc6e78fb1", 7 | "0xff12e391b79415e941a94de3bf3a9aee577aed0731e297d5cfa0b8a1e02fa1d0", 8 | "0x752dd9cf65e68cfaba7d60225cbdbc1f4729dd5e5507def72815ed0d8abc6249", 9 | "0xefb595a0178eb79a8df953f87c5148402a224cdf725e88c0146727c6aceadccd", 10 | "0x83c6d2cc5ddcf9711a6d59b417dc20eb48afd58d45290099e5987e3d768f328f", 11 | "0xbb2d3f7c9583780a7d3904a2f55d792707c345f21de1bacb2d389934d82796b2", 12 | "0xb2fd4d29c1390b71b8795ae81196bfd60293adf99f9d32a0aff06288fcdac55f", 13 | ]; 14 | 15 | export function generatedWallets(provider: providers.Provider) { 16 | return privateKeys.map((key: string) => { 17 | return new ethers.Wallet(key, provider); 18 | }); 19 | } 20 | --------------------------------------------------------------------------------