From e0a2d73f320db1eb3f5ff848aae0dc44522c2c95 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Thu, 29 Apr 2021 05:18:35 -0700 Subject: [PATCH 1/4] Use minimized circleci --- .circleci/config.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7381ea925..480df849c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,9 @@ version: 2 jobs: - checkout_and_compile: + install: docker: - image: circleci/node:10.16.0 - environment: - NODE_OPTIONS: --max_old_space_size=8192 - resource_class: large working_directory: ~/set-protocol-v2 steps: - checkout @@ -40,7 +37,7 @@ jobs: - setup_remote_docker: docker_layer_caching: false - restore_cache: - key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} + key: module-cache-{{ checksum "yarn.lock" }} - run: name: Set Up Environment Variables command: cp .env.default .env @@ -54,4 +51,4 @@ workflows: - checkout_and_compile - test_ovm: requires: - - checkout_and_compile \ No newline at end of file + - checkout_and_compile From 4738629252448e0c623548dbccfe3e8d1f667bda Mon Sep 17 00:00:00 2001 From: cgewecke Date: Thu, 29 Apr 2021 09:29:15 -0700 Subject: [PATCH 2/4] Restructure SetToken.sol to reduce size & make deployable to ovm --- contracts/interfaces/ISetToken.sol | 7 +- contracts/protocol/SetToken.sol | 250 +++--------------- contracts/protocol/SetTokenCreator.sol | 6 +- contracts/protocol/lib/SetTokenDataUtils.sol | 225 ++++++++++++++++ .../protocol/lib/SetTokenInternalUtils.sol | 107 ++++++++ 5 files changed, 380 insertions(+), 215 deletions(-) create mode 100644 contracts/protocol/lib/SetTokenDataUtils.sol create mode 100644 contracts/protocol/lib/SetTokenInternalUtils.sol diff --git a/contracts/interfaces/ISetToken.sol b/contracts/interfaces/ISetToken.sol index 1e2ae1977..4dbe76edb 100644 --- a/contracts/interfaces/ISetToken.sol +++ b/contracts/interfaces/ISetToken.sol @@ -114,10 +114,15 @@ interface ISetToken is IERC20 { function manager() external view returns (address); function moduleStates(address _module) external view returns (ModuleState); + function modules() external view returns (address[] memory); function getModules() external view returns (address[] memory); + function components() external view returns (address[] memory); function getDefaultPositionRealUnit(address _component) external view returns(int256); function getExternalPositionRealUnit(address _component, address _positionModule) external view returns(int256); + function getExternalPositionVirtualUnit(address _component, address _module) external view returns (int256); + function getDefaultPositionVirtualUnit(address _component) external view returns (int256); + function getComponentExternalPosition(address _component, address _positionModule) external view returns (ExternalPosition memory); function getComponents() external view returns(address[] memory); function getExternalPositionModules(address _component) external view returns(address[] memory); function getExternalPositionData(address _component, address _positionModule) external view returns(bytes memory); @@ -131,4 +136,4 @@ interface ISetToken is IERC20 { function isInitializedModule(address _module) external view returns(bool); function isPendingModule(address _module) external view returns(bool); function isLocked() external view returns (bool); -} \ No newline at end of file +} diff --git a/contracts/protocol/SetToken.sol b/contracts/protocol/SetToken.sol index c680531ec..c5efa07e1 100644 --- a/contracts/protocol/SetToken.sol +++ b/contracts/protocol/SetToken.sol @@ -31,7 +31,7 @@ import { ISetToken } from "../interfaces/ISetToken.sol"; import { Position } from "./lib/Position.sol"; import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol"; import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol"; - +import { SetTokenInternalUtils } from "./lib/SetTokenInternalUtils.sol"; /** * @title SetToken @@ -49,19 +49,10 @@ contract SetToken is ERC20 { using Address for address; using AddressArrayUtils for address[]; - /* ============ Constants ============ */ - - /* - The PositionState is the status of the Position, whether it is Default (held on the SetToken) - or otherwise held on a separate smart contract (whether a module or external source). - There are issues with cross-usage of enums, so we are defining position states - as a uint8. - */ - uint8 internal constant DEFAULT = 0; - uint8 internal constant EXTERNAL = 1; - /* ============ Events ============ */ + // Removing events leaves us w/ a .7kb margin + event Invoked(address indexed _target, uint indexed _value, bytes _data, bytes _returnValue); event ModuleAdded(address indexed _module); event ModuleRemoved(address indexed _module); @@ -206,7 +197,7 @@ contract SetToken is ERC20 { { _returnValue = _target.functionCallWithValue(_data, _value); - emit Invoked(_target, _value, _data, _returnValue); + emit Invoked(_target, _value, _data, _returnValue); return _returnValue; } @@ -215,11 +206,11 @@ contract SetToken is ERC20 { * PRIVELEGED MODULE FUNCTION. Low level function that adds a component to the components array. */ function addComponent(address _component) external onlyModule whenLockedOnlyLocker { - require(!isComponent(_component), "Must not be component"); + require(!components.contains(_component), "Must not be component"); components.push(_component); - emit ComponentAdded(_component); + emit ComponentAdded(_component); } /** @@ -228,7 +219,7 @@ contract SetToken is ERC20 { function removeComponent(address _component) external onlyModule whenLockedOnlyLocker { components.removeStorage(_component); - emit ComponentRemoved(_component); + emit ComponentRemoved(_component); } /** @@ -240,18 +231,18 @@ contract SetToken is ERC20 { componentPositions[_component].virtualUnit = virtualUnit; - emit DefaultPositionUnitEdited(_component, _realUnit); + emit DefaultPositionUnitEdited(_component, _realUnit); } /** * PRIVELEGED MODULE FUNCTION. Low level function that adds a module to a component's externalPositionModules array */ function addExternalPositionModule(address _component, address _positionModule) external onlyModule whenLockedOnlyLocker { - require(!isExternalPositionModule(_component, _positionModule), "Module already added"); + require(!_externalPositionModules(_component).contains(_positionModule), "Module already added"); componentPositions[_component].externalPositionModules.push(_positionModule); - emit PositionModuleAdded(_component, _positionModule); + emit PositionModuleAdded(_component, _positionModule); } /** @@ -270,7 +261,7 @@ contract SetToken is ERC20 { delete componentPositions[_component].externalPositions[_positionModule]; - emit PositionModuleRemoved(_component, _positionModule); + emit PositionModuleRemoved(_component, _positionModule); } /** @@ -290,7 +281,7 @@ contract SetToken is ERC20 { componentPositions[_component].externalPositions[_positionModule].virtualUnit = virtualUnit; - emit ExternalPositionUnitEdited(_component, _positionModule, _realUnit); + emit ExternalPositionUnitEdited(_component, _positionModule, _realUnit); } /** @@ -307,7 +298,7 @@ contract SetToken is ERC20 { { componentPositions[_component].externalPositions[_positionModule].data = _data; - emit ExternalPositionDataEdited(_component, _positionModule, _data); + emit ExternalPositionDataEdited(_component, _positionModule, _data); } /** @@ -315,11 +306,11 @@ contract SetToken is ERC20 { * update all the Positions' units at once in applications where inflation is awarded (e.g. subscription fees). */ function editPositionMultiplier(int256 _newMultiplier) external onlyModule whenLockedOnlyLocker { - _validateNewMultiplier(_newMultiplier); + SetTokenInternalUtils.validateNewMultiplier(address(this), _newMultiplier); positionMultiplier = _newMultiplier; - emit PositionMultiplierEdited(_newMultiplier); + emit PositionMultiplierEdited(_newMultiplier); } /** @@ -366,7 +357,7 @@ contract SetToken is ERC20 { moduleStates[_module] = ISetToken.ModuleState.PENDING; - emit ModuleAdded(_module); + emit ModuleAdded(_module); } /** @@ -383,7 +374,7 @@ contract SetToken is ERC20 { modules.removeStorage(_module); - emit ModuleRemoved(_module); + emit ModuleRemoved(_module); } /** @@ -395,7 +386,7 @@ contract SetToken is ERC20 { moduleStates[_module] = ISetToken.ModuleState.NONE; - emit PendingModuleRemoved(_module); + emit PendingModuleRemoved(_module); } /** @@ -410,7 +401,7 @@ contract SetToken is ERC20 { moduleStates[msg.sender] = ISetToken.ModuleState.INITIALIZED; modules.push(msg.sender); - emit ModuleInitialized(msg.sender); + emit ModuleInitialized(msg.sender); } /** @@ -422,125 +413,42 @@ contract SetToken is ERC20 { address oldManager = manager; manager = _manager; - emit ManagerEdited(_manager, oldManager); + emit ManagerEdited(_manager, oldManager); } - /* ============ External Getter Functions ============ */ + receive() external payable {} // solium-disable-line quotes - function getComponents() external view returns(address[] memory) { - return components; + /* ============ Public Getter Functions ============ */ + + function getDefaultPositionVirtualUnit(address _component) public view returns (int256) { + return componentPositions[_component].virtualUnit; } - function getDefaultPositionRealUnit(address _component) public view returns(int256) { - return _convertVirtualToRealUnit(_defaultPositionVirtualUnit(_component)); + function getExternalPositionVirtualUnit(address _component, address _module) external view returns (int256) { + return componentPositions[_component].externalPositions[_module].virtualUnit; } - function getExternalPositionRealUnit(address _component, address _positionModule) public view returns(int256) { - return _convertVirtualToRealUnit(_externalPositionVirtualUnit(_component, _positionModule)); + function getComponentExternalPosition( + address _component, + address _positionModule + ) + external + view + returns (ISetToken.ExternalPosition memory) + { + return componentPositions[_component].externalPositions[_positionModule]; } function getExternalPositionModules(address _component) external view returns(address[] memory) { return _externalPositionModules(_component); } - function getExternalPositionData(address _component,address _positionModule) external view returns(bytes memory) { + function getExternalPositionData(address _component, address _positionModule) external view returns(bytes memory) { return _externalPositionData(_component, _positionModule); } - function getModules() external view returns (address[] memory) { - return modules; - } - - function isComponent(address _component) public view returns(bool) { - return components.contains(_component); - } - - function isExternalPositionModule(address _component, address _module) public view returns(bool) { - return _externalPositionModules(_component).contains(_module); - } - - /** - * Only ModuleStates of INITIALIZED modules are considered enabled - */ - function isInitializedModule(address _module) external view returns (bool) { - return moduleStates[_module] == ISetToken.ModuleState.INITIALIZED; - } - - /** - * Returns whether the module is in a pending state - */ - function isPendingModule(address _module) external view returns (bool) { - return moduleStates[_module] == ISetToken.ModuleState.PENDING; - } - - /** - * Returns a list of Positions, through traversing the components. Each component with a non-zero virtual unit - * is considered a Default Position, and each externalPositionModule will generate a unique position. - * Virtual units are converted to real units. This function is typically used off-chain for data presentation purposes. - */ - function getPositions() external view returns (ISetToken.Position[] memory) { - ISetToken.Position[] memory positions = new ISetToken.Position[](_getPositionCount()); - uint256 positionCount = 0; - - for (uint256 i = 0; i < components.length; i++) { - address component = components[i]; - - // A default position exists if the default virtual unit is > 0 - if (_defaultPositionVirtualUnit(component) > 0) { - positions[positionCount] = ISetToken.Position({ - component: component, - module: address(0), - unit: getDefaultPositionRealUnit(component), - positionState: DEFAULT, - data: "" - }); - - positionCount++; - } - - address[] memory externalModules = _externalPositionModules(component); - for (uint256 j = 0; j < externalModules.length; j++) { - address currentModule = externalModules[j]; - - positions[positionCount] = ISetToken.Position({ - component: component, - module: currentModule, - unit: getExternalPositionRealUnit(component, currentModule), - positionState: EXTERNAL, - data: _externalPositionData(component, currentModule) - }); - - positionCount++; - } - } - - return positions; - } - - /** - * Returns the total Real Units for a given component, summing the default and external position units. - */ - function getTotalComponentRealUnits(address _component) external view returns(int256) { - int256 totalUnits = getDefaultPositionRealUnit(_component); - - address[] memory externalModules = _externalPositionModules(_component); - for (uint256 i = 0; i < externalModules.length; i++) { - // We will perform the summation no matter what, as an external position virtual unit can be negative - totalUnits = totalUnits.add(getExternalPositionRealUnit(_component, externalModules[i])); - } - - return totalUnits; - } - - - receive() external payable {} // solium-disable-line quotes - /* ============ Internal Functions ============ */ - function _defaultPositionVirtualUnit(address _component) internal view returns(int256) { - return componentPositions[_component].virtualUnit; - } - function _externalPositionModules(address _component) internal view returns(address[] memory) { return componentPositions[_component].externalPositionModules; } @@ -574,86 +482,6 @@ contract SetToken is ERC20 { return _virtualUnit.conservativePreciseMul(positionMultiplier); } - /** - * To prevent virtual to real unit conversion issues (where real unit may be 0), the - * product of the positionMultiplier and the lowest absolute virtualUnit value (across default and - * external positions) must be greater than 0. - */ - function _validateNewMultiplier(int256 _newMultiplier) internal view { - int256 minVirtualUnit = _getPositionsAbsMinimumVirtualUnit(); - - require(minVirtualUnit.conservativePreciseMul(_newMultiplier) > 0, "New multiplier too small"); - } - - /** - * Loops through all of the positions and returns the smallest absolute value of - * the virtualUnit. - * - * @return Min virtual unit across positions denominated as int256 - */ - function _getPositionsAbsMinimumVirtualUnit() internal view returns(int256) { - // Additional assignment happens in the loop below - uint256 minimumUnit = uint256(-1); - - for (uint256 i = 0; i < components.length; i++) { - address component = components[i]; - - // A default position exists if the default virtual unit is > 0 - uint256 defaultUnit = _defaultPositionVirtualUnit(component).toUint256(); - if (defaultUnit > 0 && defaultUnit < minimumUnit) { - minimumUnit = defaultUnit; - } - - address[] memory externalModules = _externalPositionModules(component); - for (uint256 j = 0; j < externalModules.length; j++) { - address currentModule = externalModules[j]; - - uint256 virtualUnit = _absoluteValue( - _externalPositionVirtualUnit(component, currentModule) - ); - if (virtualUnit > 0 && virtualUnit < minimumUnit) { - minimumUnit = virtualUnit; - } - } - } - - return minimumUnit.toInt256(); - } - - /** - * Gets the total number of positions, defined as the following: - * - Each component has a default position if its virtual unit is > 0 - * - Each component's external positions module is counted as a position - */ - function _getPositionCount() internal view returns (uint256) { - uint256 positionCount; - for (uint256 i = 0; i < components.length; i++) { - address component = components[i]; - - // Increment the position count if the default position is > 0 - if (_defaultPositionVirtualUnit(component) > 0) { - positionCount++; - } - - // Increment the position count by each external position module - address[] memory externalModules = _externalPositionModules(component); - if (externalModules.length > 0) { - positionCount = positionCount.add(externalModules.length); - } - } - - return positionCount; - } - - /** - * Returns the absolute value of the signed integer value - * @param _a Signed interger value - * @return Returns the absolute value in uint256 - */ - function _absoluteValue(int256 _a) internal pure returns(uint256) { - return _a >= 0 ? _a.toUint256() : (-_a).toUint256(); - } - /** * Due to reason error bloat, internal functions are used to reduce bytecode size * @@ -677,7 +505,7 @@ contract SetToken is ERC20 { function _validateWhenLockedOnlyLocker() internal view { if (isLocked) { - require(msg.sender == locker, "When locked, only the locker can call"); + require(msg.sender == locker, "Locked: only locker can call"); } } -} \ No newline at end of file +} diff --git a/contracts/protocol/SetTokenCreator.sol b/contracts/protocol/SetTokenCreator.sol index 5443cf188..dbe96592c 100644 --- a/contracts/protocol/SetTokenCreator.sol +++ b/contracts/protocol/SetTokenCreator.sol @@ -75,13 +75,13 @@ contract SetTokenCreator { returns (address) { require(_components.length > 0, "Must have at least 1 component"); - require(_components.length == _units.length, "Component and unit lengths must be the same"); - require(!_components.hasDuplicate(), "Components must not have a duplicate"); + require(_components.length == _units.length, "Comp. & unit lengths not equal"); + require(!_components.hasDuplicate(), "Duplicate component"); require(_modules.length > 0, "Must have at least 1 module"); require(_manager != address(0), "Manager must not be empty"); for (uint256 i = 0; i < _components.length; i++) { - require(_components[i] != address(0), "Component must not be null address"); + require(_components[i] != address(0), "Component is null address"); require(_units[i] > 0, "Units must be greater than 0"); } diff --git a/contracts/protocol/lib/SetTokenDataUtils.sol b/contracts/protocol/lib/SetTokenDataUtils.sol new file mode 100644 index 000000000..c8015780f --- /dev/null +++ b/contracts/protocol/lib/SetTokenDataUtils.sol @@ -0,0 +1,225 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.12; +pragma experimental "ABIEncoderV2"; + +import { Address } from "../../../external/contracts/openzeppelin/utils/Address.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; + +import { IController } from "../../interfaces/IController.sol"; +import { IModule } from "../../interfaces/IModule.sol"; +import { ISetToken } from "../../interfaces/ISetToken.sol"; +import { Position } from "../lib/Position.sol"; +import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol"; +import { AddressArrayUtils } from "../../lib/AddressArrayUtils.sol"; + + +/** + * @title SetTokenDataUtils + * @author Set Protocol + * + * Getters and status methods for contracts interacting with SetToken, packaged as an externally + * linked library to reduce contract size. + */ +library SetTokenDataUtils { + using SafeMath for uint256; + using SafeCast for int256; + using SafeCast for uint256; + using SignedSafeMath for int256; + using PreciseUnitMath for int256; + using Address for address; + using AddressArrayUtils for address[]; + + /* ============ Constants ============ */ + + /* + The PositionState is the status of the Position, whether it is Default (held on the SetToken) + or otherwise held on a separate smart contract (whether a module or external source). + There are issues with cross-usage of enums, so we are defining position states + as a uint8. + */ + uint8 internal constant DEFAULT = 0; + uint8 internal constant EXTERNAL = 1; + + /* ============ Public Getter Functions ============ */ + + function getComponents(ISetToken _setToken) external view returns(address[] memory) { + return _setToken.components(); + } + + function getDefaultPositionRealUnit(ISetToken _setToken, address _component) public view returns(int256) { + int256 virtualUnit = _setToken.getDefaultPositionVirtualUnit(_component); + return _convertVirtualToRealUnit(_setToken, virtualUnit); + } + + function getExternalPositionRealUnit( + ISetToken _setToken, + address _component, + address _positionModule + ) + public + view + returns(int256) + { + + int256 virtualUnit = _setToken.getComponentExternalPosition(_component, _positionModule).virtualUnit; + return _convertVirtualToRealUnit(_setToken, virtualUnit); + } + + function getModules(ISetToken _setToken) public view returns (address[] memory) { + return _setToken.modules(); + } + + /** + * Returns the total Real Units for a given component, summing the default and public position units. + */ + function getTotalComponentRealUnits( + ISetToken _setToken, + address _component + ) + public + view + returns(int256) + { + int256 totalUnits = getDefaultPositionRealUnit(_setToken, _component); + + address[] memory externalModules = _setToken.getExternalPositionModules(_component); + for (uint256 i = 0; i < externalModules.length; i++) { + // We will perform the summation no matter what, as an external position virtual unit can be negative + totalUnits = totalUnits.add( + getExternalPositionRealUnit(_setToken, _component, externalModules[i]) + ); + } + + return totalUnits; + } + + /** + * Only ModuleStates of INITIALIZED modules are considered enabled + */ + function isInitializedModule(ISetToken _setToken, address _module) external view returns (bool) { + return _setToken.moduleStates(_module) == ISetToken.ModuleState.INITIALIZED; + } + + /** + * Returns whether the module is in a pending state + */ + function isPendingModule(ISetToken _setToken, address _module) external view returns (bool) { + return _setToken.moduleStates(_module) == ISetToken.ModuleState.PENDING; + } + + function isComponent(ISetToken _setToken, address _component) public view returns(bool) { + return _setToken.components().contains(_component); + } + + function isExternalPositionModule( + ISetToken _setToken, + address _component, + address _module + ) + public + view + returns(bool) + { + return _setToken.getExternalPositionModules(_component).contains(_module); + } + + /** + * Returns a list of Positions, through traversing the components. Each component with a non-zero virtual unit + * is considered a Default Position, and each externalPositionModule will generate a unique position. + * Virtual units are converted to real units. This function is typically used off-chain for data presentation purposes. + */ + function getPositions(ISetToken _setToken) public view returns (ISetToken.Position[] memory) { + ISetToken.Position[] memory positions = new ISetToken.Position[]( + _getPositionCount(_setToken) + ); + uint256 positionCount = 0; + + for (uint256 i = 0; i < _setToken.components().length; i++) { + address component = _setToken.components()[i]; + + // A default position exists if the default virtual unit is > 0 + if (_setToken.getDefaultPositionVirtualUnit(component) > 0) { + positions[positionCount] = ISetToken.Position({ + component: component, + module: address(0), + unit: getDefaultPositionRealUnit(_setToken, component), + positionState: DEFAULT, + data: "" + }); + + positionCount++; + } + + address[] memory externalModules = _setToken.getExternalPositionModules(component); + for (uint256 j = 0; j < externalModules.length; j++) { + address currentModule = externalModules[j]; + + positions[positionCount] = ISetToken.Position({ + component: component, + module: currentModule, + unit: getExternalPositionRealUnit(_setToken, component, currentModule), + positionState: EXTERNAL, + data: _setToken.getExternalPositionData(component, currentModule) + }); + + positionCount++; + } + } + + return positions; + } + + /** + * Takes a virtual unit and multiplies by the position multiplier to return the real unit + */ + function _convertVirtualToRealUnit(ISetToken _setToken, int256 _virtualUnit) internal view returns(int256) { + return _virtualUnit.conservativePreciseMul(_setToken.positionMultiplier()); + } + + /** + * Gets the total number of positions, defined as the following: + * - Each component has a default position if its virtual unit is > 0 + * - Each component's external positions module is counted as a position + */ + function _getPositionCount(ISetToken _setToken) internal view returns (uint256) { + uint256 positionCount; + address[] memory components = _setToken.components(); + + for (uint256 i = 0; i < components.length; i++) { + address component = components[i]; + + // Increment the position count if the default position is > 0 + if (_setToken.getDefaultPositionVirtualUnit(component) > 0) { + positionCount++; + } + + // Increment the position count by each external position module + uint256 externalModulesLength = _setToken.getExternalPositionModules(component).length; + if (externalModulesLength > 0) { + positionCount = positionCount.add(externalModulesLength); + } + } + + return positionCount; + } +} diff --git a/contracts/protocol/lib/SetTokenInternalUtils.sol b/contracts/protocol/lib/SetTokenInternalUtils.sol new file mode 100644 index 000000000..64c821439 --- /dev/null +++ b/contracts/protocol/lib/SetTokenInternalUtils.sol @@ -0,0 +1,107 @@ +/* + Copyright 2020 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.12; +pragma experimental "ABIEncoderV2"; + +import { Address } from "../../../external/contracts/openzeppelin/utils/Address.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; + +import { IController } from "../../interfaces/IController.sol"; +import { IModule } from "../../interfaces/IModule.sol"; +import { ISetToken } from "../../interfaces/ISetToken.sol"; +import { Position } from "../lib/Position.sol"; +import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol"; +import { AddressArrayUtils } from "../../lib/AddressArrayUtils.sol"; + + +/** + * @title SetTokenInternalUtils + * @author Set Protocol + * + * Utilities SetToken can invoke on itself. These are located an externally linked library to + * reduce contract size. + */ +library SetTokenInternalUtils { + using SafeMath for uint256; + using SafeCast for int256; + using SafeCast for uint256; + using SignedSafeMath for int256; + using PreciseUnitMath for int256; + using Address for address; + using AddressArrayUtils for address[]; + + /** + * To prevent virtual to real unit conversion issues (where real unit may be 0), the + * product of the positionMultiplier and the lowest absolute virtualUnit value (across default and + * external positions) must be greater than 0. + */ + function validateNewMultiplier(address _setToken, int256 _newMultiplier) external view { + int256 minVirtualUnit = _getPositionsAbsMinimumVirtualUnit(ISetToken(_setToken)); + + require(minVirtualUnit.conservativePreciseMul(_newMultiplier) > 0, "New multiplier too small"); + } + + /** + * Loops through all of the positions and returns the smallest absolute value of + * the virtualUnit. + * + * @return Min virtual unit across positions denominated as int256 + */ + function _getPositionsAbsMinimumVirtualUnit(ISetToken _setToken) internal view returns(int256) { + // Additional assignment happens in the loop below + uint256 minimumUnit = uint256(-1); + address[] memory components = _setToken.components(); + + for (uint256 i = 0; i < components.length; i++) { + address component = components[i]; + + // A default position exists if the default virtual unit is > 0 + uint256 defaultUnit = _setToken.getDefaultPositionVirtualUnit(component).toUint256(); + if (defaultUnit > 0 && defaultUnit < minimumUnit) { + minimumUnit = defaultUnit; + } + + address[] memory externalModules = _setToken.getExternalPositionModules(component); + for (uint256 j = 0; j < externalModules.length; j++) { + address currentModule = externalModules[j]; + + uint256 virtualUnit = _absoluteValue( + _setToken.getExternalPositionVirtualUnit(component, currentModule) + ); + if (virtualUnit > 0 && virtualUnit < minimumUnit) { + minimumUnit = virtualUnit; + } + } + } + + return minimumUnit.toInt256(); + } + + /** + * Returns the absolute value of the signed integer value + * @param _a Signed interger value + * @return Returns the absolute value in uint256 + */ + function _absoluteValue(int256 _a) internal pure returns(uint256) { + return _a >= 0 ? _a.toUint256() : (-_a).toUint256(); + } +} From 05122ef5eeb344e454101d71ec00a50dd50e4a20 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Thu, 29 Apr 2021 17:24:04 -0700 Subject: [PATCH 3/4] Add library linking in deployers / fixtures --- utils/constants.ts | 2 + utils/contracts/index.ts | 1 + utils/deploys/deployCoreContracts.ts | 69 ++++++++++++++++++++++++---- utils/fixtures/systemFixture.ts | 27 +++++++++-- 4 files changed, 87 insertions(+), 12 deletions(-) diff --git a/utils/constants.ts b/utils/constants.ts index a19de5e74..b9206001d 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -30,3 +30,5 @@ export const ONE_YEAR_IN_SECONDS = BigNumber.from(31557600); export const PRECISE_UNIT = constants.WeiPerEther; export const ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +export const SET_TOKEN_INTERNAL_UTILS_LIB_PATH = "contracts/protocol/lib/SetTokenInternalUtils.sol:SetTokenInternalUtils"; +export const SET_TOKEN_DATA_UTILS_LIB_PATH = "contracts/protocol/lib/SetTokenDataUtils.sol:SetTokenDataUtils"; diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index d509201f3..df248ba84 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -62,6 +62,7 @@ export { ProtocolViewer } from "../../typechain/ProtocolViewer"; export { ResourceIdentifierMock } from "../../typechain/ResourceIdentifierMock"; export { SetToken } from "../../typechain/SetToken"; export { SetTokenCreator } from "../../typechain/SetTokenCreator"; +export { SetTokenInternalUtils } from "../../typechain/SetTokenInternalUtils"; export { SetValuer } from "../../typechain/SetValuer"; export { SingleIndexModule } from "../../typechain/SingleIndexModule"; export { SnapshotGovernanceAdapter } from "../../typechain/SnapshotGovernanceAdapter"; diff --git a/utils/deploys/deployCoreContracts.ts b/utils/deploys/deployCoreContracts.ts index 3319ab663..ff2607939 100644 --- a/utils/deploys/deployCoreContracts.ts +++ b/utils/deploys/deployCoreContracts.ts @@ -7,7 +7,8 @@ import { PriceOracle, SetToken, SetTokenCreator, - SetValuer + SetValuer, + SetTokenInternalUtils } from "./../contracts"; import { Address } from "./../types"; @@ -18,6 +19,9 @@ import { PriceOracle__factory } from "../../typechain/factories/PriceOracle__fac import { SetToken__factory } from "../../typechain/factories/SetToken__factory"; import { SetTokenCreator__factory } from "../../typechain/factories/SetTokenCreator__factory"; import { SetValuer__factory } from "../../typechain/factories/SetValuer__factory"; +import { SetTokenInternalUtils__factory } from "../../typechain/factories/SetTokenInternalUtils__factory"; + +import { convertLibraryNameToLinkId } from "../common"; export default class DeployCoreContracts { private _deployerSigner: Signer; @@ -34,12 +38,39 @@ export default class DeployCoreContracts { return await new Controller__factory(this._deployerSigner).attach(controllerAddress); } - public async deploySetTokenCreator(controller: Address): Promise { - return await new SetTokenCreator__factory(this._deployerSigner).deploy(controller); + public async deploySetTokenInternalUtils(): Promise { + return await new SetTokenInternalUtils__factory(this._deployerSigner).deploy(); } - public async getSetTokenCreator(setTokenCreatorAddress: Address): Promise { - return await new SetTokenCreator__factory(this._deployerSigner).attach(setTokenCreatorAddress); + public async deploySetTokenCreator( + controller: Address, + libraryName: string, + libraryAddress: Address + ): Promise { + const linkId = convertLibraryNameToLinkId(libraryName); + return await new SetTokenCreator__factory( + // @ts-ignore + { + [linkId]: libraryAddress, + }, + this._deployerSigner + ).deploy(controller); + } + + public async getSetTokenCreator( + setTokenCreatorAddress: Address, + libraryName: string, + libraryAddress: Address + ): Promise { + const linkId = convertLibraryNameToLinkId(libraryName); + + return await new SetTokenCreator__factory( + // @ts-ignore + { + [linkId]: libraryAddress, + }, + this._deployerSigner + ).attach(setTokenCreatorAddress); } public async deploySetToken( @@ -50,8 +81,18 @@ export default class DeployCoreContracts { _manager: Address, _name: string, _symbol: string, + _libraryName: string, + _libraryAddress: Address, ): Promise { - return await new SetToken__factory(this._deployerSigner).deploy( + const linkId = convertLibraryNameToLinkId(_libraryName); + + return await new SetToken__factory( + // @ts-ignore + { + [linkId]: _libraryAddress, + }, + this._deployerSigner + ).deploy( _components, _units, _modules, @@ -62,8 +103,20 @@ export default class DeployCoreContracts { ); } - public async getSetToken(setTokenAddress: Address): Promise { - return await new SetToken__factory(this._deployerSigner).attach(setTokenAddress); + public async getSetToken( + setTokenAddress: Address, + libraryName: string, + libraryAddress: Address + ): Promise { + const linkId = convertLibraryNameToLinkId(libraryName); + + return await new SetToken__factory( + // @ts-ignore + { + [linkId]: libraryAddress, + }, + this._deployerSigner + ).attach(setTokenAddress); } public async deployPriceOracle( diff --git a/utils/fixtures/systemFixture.ts b/utils/fixtures/systemFixture.ts index 6f1934307..2044c0fdf 100644 --- a/utils/fixtures/systemFixture.ts +++ b/utils/fixtures/systemFixture.ts @@ -14,7 +14,8 @@ import { StandardTokenMock, StreamingFeeModule, WETH9, - NavIssuanceModule + NavIssuanceModule, + SetTokenInternalUtils } from "../contracts"; import DeployHelper from "../deploys"; import { @@ -26,8 +27,11 @@ import { } from "../types"; import { MAX_UINT_256, + SET_TOKEN_INTERNAL_UTILS_LIB_PATH, } from "../constants"; +import { convertLibraryNameToLinkId } from "../common"; + import { SetToken__factory } from "../../typechain/factories/SetToken__factory"; export class SystemFixture { @@ -43,6 +47,7 @@ export class SystemFixture { public priceOracle: PriceOracle; public integrationRegistry: IntegrationRegistry; public setValuer: SetValuer; + public setTokenInternalUtils: SetTokenInternalUtils; public issuanceModule: BasicIssuanceModule; public streamingFeeModule: StreamingFeeModule; @@ -80,8 +85,13 @@ export class SystemFixture { await this.initializeStandardComponents(); this.integrationRegistry = await this._deployer.core.deployIntegrationRegistry(this.controller.address); + this.setTokenInternalUtils = await this._deployer.core.deploySetTokenInternalUtils(); - this.factory = await this._deployer.core.deploySetTokenCreator(this.controller.address); + this.factory = await this._deployer.core.deploySetTokenCreator( + this.controller.address, + SET_TOKEN_INTERNAL_UTILS_LIB_PATH, + this.setTokenInternalUtils.address, + ); this.priceOracle = await this._deployer.core.deployPriceOracle( this.controller.address, this.usdc.address, @@ -151,7 +161,14 @@ export class SystemFixture { const retrievedSetAddress = await new ProtocolUtils(this._provider).getCreatedSetTokenAddress(txHash.hash); - return new SetToken__factory(this._ownerSigner).attach(retrievedSetAddress); + const linkId = convertLibraryNameToLinkId(SET_TOKEN_INTERNAL_UTILS_LIB_PATH); + + return new SetToken__factory( + // @ts-ignore + { + [linkId]: this.setTokenInternalUtils.address, + }, + this._ownerSigner).attach(retrievedSetAddress); } public async createNonControllerEnabledSetToken( @@ -169,7 +186,9 @@ export class SystemFixture { this.controller.address, manager, name, - symbol + symbol, + SET_TOKEN_INTERNAL_UTILS_LIB_PATH, + this.setTokenInternalUtils.address ); } From 581bad48de14e889442e54cf549942da5c888051 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Fri, 30 Apr 2021 05:23:52 -0700 Subject: [PATCH 4/4] Fix circleci rebase error --- .circleci/config.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 480df849c..7381ea925 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,12 @@ version: 2 jobs: - install: + checkout_and_compile: docker: - image: circleci/node:10.16.0 + environment: + NODE_OPTIONS: --max_old_space_size=8192 + resource_class: large working_directory: ~/set-protocol-v2 steps: - checkout @@ -37,7 +40,7 @@ jobs: - setup_remote_docker: docker_layer_caching: false - restore_cache: - key: module-cache-{{ checksum "yarn.lock" }} + key: compiled-env-{{ .Environment.CIRCLE_SHA1 }} - run: name: Set Up Environment Variables command: cp .env.default .env @@ -51,4 +54,4 @@ workflows: - checkout_and_compile - test_ovm: requires: - - checkout_and_compile + - checkout_and_compile \ No newline at end of file