diff --git a/contracts/managers/AssetPairManager.sol b/contracts/managers/AssetPairManager.sol index 4150797..5139af8 100644 --- a/contracts/managers/AssetPairManager.sol +++ b/contracts/managers/AssetPairManager.sol @@ -33,56 +33,66 @@ import { ITrigger } from "./triggers/ITrigger.sol"; * @title AssetPairManager * @author Set Protocol * - * Rebalancing Manager contract for implementing any trading pair strategy. Allocation determinations are made - * base on output of Trigger contract. Max base asset allocation amount is passed in and used when bullish, - * allocationPrecision - bullishBaseAssetAllocation used when bearish. Additionally, all allocations are priced - * using the base contract's Allocator contract. + * Manager contract for implementing any trading pair and strategy. Allocation determinations are made + * based on output of Trigger contract. bullishBaseAssetAllocation amount is passed in and used when bullish, + * allocationDenominator - bullishBaseAssetAllocation used when bearish. */ contract AssetPairManager { using SafeMath for uint256; + /* ============ Events ============ */ + + event InitialProposeCalled( + address indexed rebalancingSetToken + ); + + /* ============ Constants ============ */ + uint256 constant private HUNDRED = 100; + /* ============ State Variables ============ */ - ICore public coreInstance; - IAllocator public allocatorInstance; - ITrigger public triggerInstance; - IAuctionPriceCurve public auctionLibraryInstance; - IRebalancingSetToken public rebalancingSetTokenInstance; + ICore public core; + IAllocator public allocator; + ITrigger public trigger; + IAuctionPriceCurve public auctionLibrary; + IRebalancingSetToken public rebalancingSetToken; uint256 public baseAssetAllocation; // Percent of base asset currently allocated in strategy - uint256 public allocationPrecision; + uint256 public allocationDenominator; uint256 public bullishBaseAssetAllocation; - uint256 public auctionStartPercentage; - uint256 public auctionEndPercentage; + uint256 public bearishBaseAssetAllocation; + uint256 public auctionStartPercentage; // Percent below fair value to start linear auction + uint256 public auctionPivotPercentage; // Percent above fair value to end linear auction uint256 public auctionTimeToPivot; - // Time to start of confirmation period in seconds + // Time until start of confirmation period after initialPropse called, in seconds uint256 public signalConfirmationMinTime; - // Time to end of confirmation period in seconds + // Time until end of confirmation period after intialPropse called, in seconds uint256 public signalConfirmationMaxTime; - uint256 public lastInitialTriggerTimestamp; + // Timestamp of last successful initialPropose call + uint256 public recentInitialProposeTimestamp; address public initializerAddress; /* * AssetPairManager constructor. * - * @param _coreInstance The address of the Core contract - * @param _allocatorInstance The address of the Allocator to be used in the strategy - * @param _triggerInstance The address of the PriceTrigger to be used in the strategy - * @param _auctionLibraryInstance The address of auction price curve to use in rebalance + * @param _core The address of the Core contract + * @param _allocator The address of the Allocator to be used in the strategy + * @param _trigger The address of the PriceTrigger to be used in the strategy + * @param _auctionLibrary The address of auction price curve to use in rebalance * @param _baseAssetAllocation Starting allocation of the Rebalancing Set in baseAsset amount - * @param _allocationPrecision Precision of allocation percentage + * @param _allocationDenominator Precision of allocation (i.e. 100 = percent, 10000 = basis point) * @param _bullishBaseAssetAllocation Base asset allocation when trigger is bullish * @param _auctionTimeToPivot Time, in seconds, spent between start and pivot price * @param _auctionPriceBounds The price bounds, in percent below and above fair value, of linear auction * @param _signalConfirmationBounds The lower and upper bounds of time, in seconds, from initialTrigger to confirm signal */ constructor( - ICore _coreInstance, - IAllocator _allocatorInstance, - ITrigger _triggerInstance, - IAuctionPriceCurve _auctionLibraryInstance, + ICore _core, + IAllocator _allocator, + ITrigger _trigger, + IAuctionPriceCurve _auctionLibrary, uint256 _baseAssetAllocation, - uint256 _allocationPrecision, + uint256 _allocationDenominator, uint256 _bullishBaseAssetAllocation, uint256 _auctionTimeToPivot, uint256[2] memory _auctionPriceBounds, @@ -90,22 +100,36 @@ contract AssetPairManager { ) public { + // Passed bullish allocation must be less than or equal to allocationDenominator + require( + _bullishBaseAssetAllocation <= _allocationDenominator, + "AssetPairManager.constructor: Passed bullishBaseAssetAllocation must be less than allocationDenominator." + ); + + bullishBaseAssetAllocation = _bullishBaseAssetAllocation; + bearishBaseAssetAllocation = _allocationDenominator.sub(_bullishBaseAssetAllocation); + + // Passed baseAssetAllocation must equal bullish or bearish allocations + require( + bullishBaseAssetAllocation == _baseAssetAllocation || bearishBaseAssetAllocation == _baseAssetAllocation, + "AssetPairManager.constructor: Passed baseAssetAllocation must equal bullish or bearish allocations." + ); + // Make sure confirmation max time is greater than confirmation min time require( _signalConfirmationBounds[1] >= _signalConfirmationBounds[0], "AssetPairManager.constructor: Confirmation max time must be greater than min time." ); - coreInstance = _coreInstance; - allocatorInstance = _allocatorInstance; - triggerInstance = _triggerInstance; - auctionLibraryInstance = _auctionLibraryInstance; + core = _core; + allocator = _allocator; + trigger = _trigger; + auctionLibrary = _auctionLibrary; baseAssetAllocation = _baseAssetAllocation; - allocationPrecision = _allocationPrecision; - bullishBaseAssetAllocation = _bullishBaseAssetAllocation; + allocationDenominator = _allocationDenominator; auctionTimeToPivot = _auctionTimeToPivot; auctionStartPercentage = _auctionPriceBounds[0]; - auctionEndPercentage = _auctionPriceBounds[1]; + auctionPivotPercentage = _auctionPriceBounds[1]; signalConfirmationMinTime = _signalConfirmationBounds[0]; signalConfirmationMaxTime = _signalConfirmationBounds[1]; initializerAddress = msg.sender; @@ -117,10 +141,10 @@ contract AssetPairManager { * This function sets the Rebalancing Set Token address that the manager is associated with. * This function is only meant to be called once during initialization by the contract deployer. * - * @param _rebalancingSetTokenInstance The address of the rebalancing Set token + * @param _rebalancingSetToken The address of the rebalancing Set token */ function initialize( - IRebalancingSetToken _rebalancingSetTokenInstance + IRebalancingSetToken _rebalancingSetToken ) external { @@ -132,12 +156,12 @@ contract AssetPairManager { // Make sure the rebalancingSetToken is tracked by Core require( // coverage-disable-line - coreInstance.validSets(address(_rebalancingSetTokenInstance)), + core.validSets(address(_rebalancingSetToken)), "AssetPairManager.initialize: Invalid or disabled RebalancingSetToken address" ); - rebalancingSetTokenInstance = _rebalancingSetTokenInstance; - // Set initializer address to 0 so that no one can update RebalancingSetTokenInstance state + rebalancingSetToken = _rebalancingSetToken; + // Set initializer address to 0 so that no one can update RebalancingSetToken state initializerAddress = address(0); } @@ -149,8 +173,14 @@ contract AssetPairManager { function initialPropose() external { + // Make sure Manager has been initialized with RebalancingSetToken + require( + address(rebalancingSetToken) != address(0), + "AssetPairManager.confirmPropose: Manager must be initialized with RebalancingSetToken." + ); + // Check enough time has passed for proposal and RebalancingSetToken in Default state - FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetTokenInstance); + FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetToken); // Make sure there is not an existing initial proposal underway require( @@ -168,7 +198,9 @@ contract AssetPairManager { ); // Set initial trigger timestamp - lastInitialTriggerTimestamp = block.timestamp; + recentInitialProposeTimestamp = block.timestamp; + + emit InitialProposeCalled(address(rebalancingSetToken)); } /* @@ -178,8 +210,14 @@ contract AssetPairManager { function confirmPropose() external { + // Make sure Manager has been initialized with RebalancingSetToken + require( + address(rebalancingSetToken) != address(0), + "AssetPairManager.confirmPropose: Manager must be initialized with RebalancingSetToken." + ); + // Check that enough time has passed for the proposal and RebalancingSetToken is in Default state - FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetTokenInstance); + FlexibleTimingManagerLibrary.validateManagerPropose(rebalancingSetToken); // Make sure in confirmation window require( @@ -197,27 +235,27 @@ contract AssetPairManager { ); // Get current collateral Set - address currentCollateralSetAddress = rebalancingSetTokenInstance.currentSet(); + ISetToken currentCollateralSet = ISetToken(rebalancingSetToken.currentSet()); // If price trigger has been met, get next Set allocation. Create new set if price difference is too // great to run good auction. Return nextSet address. - address nextSetAddress = allocatorInstance.determineNewAllocation( + ISetToken nextSet = allocator.determineNewAllocation( newBaseAssetAllocation, - allocationPrecision, - ISetToken(currentCollateralSetAddress) + allocationDenominator, + currentCollateralSet ); // Get current and next Set dollar values - uint256 currentSetDollarValue = allocatorInstance.calculateCollateralSetValue( - ISetToken(currentCollateralSetAddress) + uint256 currentSetDollarValue = allocator.calculateCollateralSetValue( + currentCollateralSet ); - uint256 nextSetDollarValue = allocatorInstance.calculateCollateralSetValue( - ISetToken(nextSetAddress) + uint256 nextSetDollarValue = allocator.calculateCollateralSetValue( + nextSet ); // Get auction price divisor - uint256 auctionPriceDivisor = auctionLibraryInstance.priceDivisor(); + uint256 auctionPriceDivisor = auctionLibrary.priceDivisor(); // Calculate the price parameters for auction ( @@ -230,9 +268,9 @@ contract AssetPairManager { ); // Propose new allocation to Rebalancing Set Token - rebalancingSetTokenInstance.propose( - nextSetAddress, - address(auctionLibraryInstance), + rebalancingSetToken.propose( + address(nextSet), + address(auctionLibrary), auctionTimeToPivot, auctionStartPrice, auctionPivotPrice @@ -288,13 +326,13 @@ contract AssetPairManager { view returns (uint256) { - return triggerInstance.isBullish() ? bullishBaseAssetAllocation : allocationPrecision.sub(bullishBaseAssetAllocation); + return trigger.isBullish() ? bullishBaseAssetAllocation : bearishBaseAssetAllocation; } /* * Calculates the auction price parameters, targetting 1% slippage every 10 minutes. Range is * defined by subtracting auctionStartPercentage * onePercentSlippage from fairValue and adding - * auctionEndPercentage * onePercentSlippage to fairValue + * auctionPivotPercentage * onePercentSlippage to fairValue * * @param _currentSetDollarAmount The 18 decimal value of one currenSet * @param _nextSetDollarAmount The 18 decimal value of one nextSet @@ -314,7 +352,7 @@ contract AssetPairManager { // Determine fair value of nextSet/currentSet and put in terms of auction library price divisor uint256 fairValue = _nextSetDollarAmount.mul(_auctionLibraryPriceDivisor).div(_currentSetDollarAmount); // Calculate how much one percent slippage from fair value is - uint256 onePercentSlippage = fairValue.div(100); + uint256 onePercentSlippage = fairValue.div(HUNDRED); // Auction start price is fair value minus half price range to center the auction at fair value uint256 auctionStartPrice = fairValue.sub( @@ -322,7 +360,7 @@ contract AssetPairManager { ); // Auction pivot price is fair value plus half price range to center the auction at fair value uint256 auctionPivotPrice = fairValue.add( - auctionEndPercentage.mul(onePercentSlippage) + auctionPivotPercentage.mul(onePercentSlippage) ); return (auctionStartPrice, auctionPivotPrice); @@ -340,12 +378,12 @@ contract AssetPairManager { returns (bool) { // Get RebalancingSetToken timing info - uint256 lastRebalanceTimestamp = rebalancingSetTokenInstance.lastRebalanceTimestamp(); - uint256 rebalanceInterval = rebalancingSetTokenInstance.rebalanceInterval(); + uint256 lastRebalanceTimestamp = rebalancingSetToken.lastRebalanceTimestamp(); + uint256 rebalanceInterval = rebalancingSetToken.rebalanceInterval(); // Require that Rebalancing Set Token is in Default state and rebalanceInterval elapsed - return block.timestamp >= lastRebalanceTimestamp.add(rebalanceInterval) && - rebalancingSetTokenInstance.rebalanceState() == RebalancingLibrary.State.Default; + return block.timestamp.sub(lastRebalanceTimestamp) >= rebalanceInterval && + rebalancingSetToken.rebalanceState() == RebalancingLibrary.State.Default; } /* @@ -358,7 +396,7 @@ contract AssetPairManager { view returns (bool) { - return block.timestamp > lastInitialTriggerTimestamp.add(signalConfirmationMaxTime); + return block.timestamp.sub(recentInitialProposeTimestamp) > signalConfirmationMaxTime; } /* @@ -371,7 +409,7 @@ contract AssetPairManager { view returns (bool) { - return block.timestamp >= lastInitialTriggerTimestamp.add(signalConfirmationMinTime) && - block.timestamp <= lastInitialTriggerTimestamp.add(signalConfirmationMaxTime); + uint256 timeSinceInitialPropose = block.timestamp.sub(recentInitialProposeTimestamp); + return timeSinceInitialPropose >= signalConfirmationMinTime && timeSinceInitialPropose <= signalConfirmationMaxTime; } } \ No newline at end of file diff --git a/contracts/managers/allocators/BinaryAllocator.sol b/contracts/managers/allocators/BinaryAllocator.sol index 7d94409..ba0175c 100644 --- a/contracts/managers/allocators/BinaryAllocator.sol +++ b/contracts/managers/allocators/BinaryAllocator.sol @@ -47,93 +47,97 @@ contract BinaryAllocator is /* ============ Events ============ */ - event NewCollateralLogged( - bytes32 indexed _hashId, - address _collateralAddress + event NewCollateralTracked( + bytes32 indexed _hash, + address indexed _collateralAddress ); /* ============ Constants ============ */ - uint256 constant MINIMUM_COLLATERAL_NATURAL_UNIT_DECIMALS = 6; + uint256 constant public MINIMUM_COLLATERAL_NATURAL_UNIT_DECIMALS = 6; /* ============ State Variables ============ */ - ICore public coreInstance; - address public setTokenFactoryAddress; + ICore public core; + address public setTokenFactory; - ERC20Detailed public baseAssetInstance; - ERC20Detailed public quoteAssetInstance; - IOracle public baseAssetOracleInstance; - IOracle public quoteAssetOracleInstance; + ERC20Detailed public baseAsset; + ERC20Detailed public quoteAsset; + IOracle public baseAssetOracle; + IOracle public quoteAssetOracle; uint8 public baseAssetDecimals; uint8 public quoteAssetDecimals; - mapping(bytes32 => address) public storedCollateral; + // Hash of collateral units, naturalUnit, and component maps to collateral address + mapping(bytes32 => ISetToken) public storedCollateral; /* * BinaryAllocator constructor. * - * @param _baseAssetInstance The baseAsset address - * @param _quoteAssetInstance The quoteAsset address - * @param _baseAssetOracleInstance The baseAsset oracle - * @param _quoteAssetOracleInstance The quoteAsset oracle - * @param _baseAssetCollateralInstance The baseAsset collateral Set - * @param _quoteAssetCollateralInstance The quoteAsset collateral Set - * @param _coreInstance The address of the Core contract - * @param _setTokenFactoryAddress The address of SetTokenFactory used to create - * new collateral + * @param _baseAsset The baseAsset address + * @param _quoteAsset The quoteAsset address + * @param _baseAssetOracle The baseAsset oracle + * @param _quoteAssetOracle The quoteAsset oracle + * @param _baseAssetCollateral The baseAsset collateral Set + * @param _quoteAssetCollateral The quoteAsset collateral Set + * @param _core The address of the Core contract + * @param _setTokenFactory The address of SetTokenFactory used to create new collateral */ constructor( - ERC20Detailed _baseAssetInstance, - ERC20Detailed _quoteAssetInstance, - IOracle _baseAssetOracleInstance, - IOracle _quoteAssetOracleInstance, - ISetToken _baseAssetCollateralInstance, - ISetToken _quoteAssetCollateralInstance, - ICore _coreInstance, - address _setTokenFactoryAddress + ERC20Detailed _baseAsset, + ERC20Detailed _quoteAsset, + IOracle _baseAssetOracle, + IOracle _quoteAssetOracle, + ISetToken _baseAssetCollateral, + ISetToken _quoteAssetCollateral, + ICore _core, + address _setTokenFactory ) public { // Get components of collateral instances - address[] memory baseAssetCollateralComponents = _baseAssetCollateralInstance.getComponents(); - address[] memory quoteAssetCollateralComponents = _quoteAssetCollateralInstance.getComponents(); + address[] memory baseAssetCollateralComponents = _baseAssetCollateral.getComponents(); + address[] memory quoteAssetCollateralComponents = _quoteAssetCollateral.getComponents(); + + // Check that component arrays only have one component + validateSingleItemArray(baseAssetCollateralComponents); + validateSingleItemArray(quoteAssetCollateralComponents); // Make sure collateral instances are using the correct base and quote asset require( - baseAssetCollateralComponents[0] == address(_baseAssetInstance), + baseAssetCollateralComponents[0] == address(_baseAsset), "BinaryAllocator.constructor: Base collateral component must match base asset." ); require( - quoteAssetCollateralComponents[0] == address(_quoteAssetInstance), + quoteAssetCollateralComponents[0] == address(_quoteAsset), "BinaryAllocator.constructor: Quote collateral component must match quote asset." ); - baseAssetInstance = _baseAssetInstance; - quoteAssetInstance = _quoteAssetInstance; + baseAsset = _baseAsset; + quoteAsset = _quoteAsset; - baseAssetOracleInstance = _baseAssetOracleInstance; - quoteAssetOracleInstance = _quoteAssetOracleInstance; + baseAssetOracle = _baseAssetOracle; + quoteAssetOracle = _quoteAssetOracle; // Query decimals of base and quote assets - baseAssetDecimals = _baseAssetInstance.decimals(); - quoteAssetDecimals = _quoteAssetInstance.decimals(); + baseAssetDecimals = _baseAsset.decimals(); + quoteAssetDecimals = _quoteAsset.decimals(); // Set Core and setTokenFactory - coreInstance = _coreInstance; - setTokenFactoryAddress = _setTokenFactoryAddress; + core = _core; + setTokenFactory = _setTokenFactory; // Store passed in collateral in mapping bytes32 baseCollateralHash = calculateCollateralIDHashFromSet( - _baseAssetCollateralInstance + _baseAssetCollateral ); bytes32 quoteCollateralHash = calculateCollateralIDHashFromSet( - _quoteAssetCollateralInstance + _quoteAssetCollateral ); - storedCollateral[baseCollateralHash] = address(_baseAssetCollateralInstance); - storedCollateral[quoteCollateralHash] = address(_quoteAssetCollateralInstance); + storedCollateral[baseCollateralHash] = _baseAssetCollateral; + storedCollateral[quoteCollateralHash] = _quoteAssetCollateral; - emit NewCollateralLogged(baseCollateralHash, address(_baseAssetCollateralInstance)); - emit NewCollateralLogged(quoteCollateralHash, address(_quoteAssetCollateralInstance)); + emit NewCollateralTracked(baseCollateralHash, address(_baseAssetCollateral)); + emit NewCollateralTracked(quoteCollateralHash, address(_quoteAssetCollateral)); } /* ============ External ============ */ @@ -154,7 +158,7 @@ contract BinaryAllocator is ISetToken _currentCollateralSet ) external - returns (address) + returns (ISetToken) { require( _targetBaseAssetAllocation == _allocationPrecision || _targetBaseAssetAllocation == 0, @@ -169,7 +173,8 @@ contract BinaryAllocator is toBaseAsset ); - // Create struct that holds relevant information for the currentSet + // Calculate currentSetValue, toBaseAsset inverted here because calculating currentSet value which would be + // using opposite collateral asset of toBaseAsset uint256 currentSetValue = calculateCollateralSetValueInternal( address(_currentCollateralSet), !toBaseAsset @@ -186,19 +191,19 @@ contract BinaryAllocator is toBaseAsset ); - address nextSetAddress = createOrSelectNextSet( + ISetToken nextSet = createOrSelectNextSet( nextSetComponent, nextSetUnit, nextSetNaturalUnit ); - return nextSetAddress; + return nextSet; } /* * Calculate value of passed collateral set. * - * @param _collateralSet Instance of current set collateralizing RebalancingSetToken + * @param _collateralSet of current set collateralizing RebalancingSetToken * @return uint256 USD value of passed Set */ function calculateCollateralSetValue( @@ -210,8 +215,13 @@ contract BinaryAllocator is { address[] memory setComponents = _collateralSet.getComponents(); - return setComponents[0] == address(baseAssetInstance) ? calculateCollateralSetValueInternal(address(_collateralSet), true) : - calculateCollateralSetValueInternal(address(_collateralSet), false); + // Check that setComponents only has one component + validateSingleItemArray(setComponents); + + return calculateCollateralSetValueInternal( + address(_collateralSet), + setComponents[0] == address(baseAsset) + ); } /* ============ Internal ============ */ @@ -232,7 +242,7 @@ contract BinaryAllocator is uint256 _nextSetNaturalUnit ) internal - returns (address) + returns (ISetToken) { // Create collateralIDHash bytes32 collateralIDHash = calculateCollateralIDHash( @@ -243,14 +253,14 @@ contract BinaryAllocator is // If collateralIDHash exists then use existing collateral set otherwise create new collateral and // store in mapping - if (storedCollateral[collateralIDHash] != address(0)) { + if (address(storedCollateral[collateralIDHash]) != address(0)) { return storedCollateral[collateralIDHash]; } else { // Determine new collateral name and symbol ( bytes32 nextCollateralName, bytes32 nextCollateralSymbol - ) = _nextSetComponent == baseAssetInstance ? (bytes32("BaseAssetCollateral"), bytes32("BACOL")) : + ) = _nextSetComponent == baseAsset ? (bytes32("BaseAssetCollateral"), bytes32("BACOL")) : (bytes32("QuoteAssetCollateral"), bytes32("QACOL")); // Create unit and component arrays for SetToken creation @@ -260,8 +270,8 @@ contract BinaryAllocator is nextSetComponents[0] = address(_nextSetComponent); // Create new collateral set with passed components, units, and naturalUnit - address nextSetAddress = coreInstance.createSet( - setTokenFactoryAddress, + address nextSetAddress = core.createSet( + setTokenFactory, nextSetComponents, nextSetUnits, _nextSetNaturalUnit, @@ -271,11 +281,11 @@ contract BinaryAllocator is ); // Store new collateral in mapping - storedCollateral[collateralIDHash] = nextSetAddress; + storedCollateral[collateralIDHash] = ISetToken(nextSetAddress); - emit NewCollateralLogged(collateralIDHash, nextSetAddress); + emit NewCollateralTracked(collateralIDHash, nextSetAddress); - return nextSetAddress; + return ISetToken(nextSetAddress); } } @@ -296,7 +306,7 @@ contract BinaryAllocator is { // Make sure passed currentSet was created by Core require( - coreInstance.validSets(address(_currentCollateralSet)), + core.validSets(address(_currentCollateralSet)), "BinaryAllocator.validateCurrentCollateralSet: Passed collateralSet must be tracked by Core." ); @@ -310,7 +320,7 @@ contract BinaryAllocator is ); // Make sure that currentSet component is opposite of expected component to be rebalanced into - address requiredComponent = _toBaseAsset ? address(quoteAssetInstance) : address(baseAssetInstance); + address requiredComponent = _toBaseAsset ? address(quoteAsset) : address(baseAsset); require( currentSetComponents[0] == requiredComponent, "BinaryAllocator.validateCurrentCollateralSet: New allocation doesn't match currentSet component." @@ -397,10 +407,10 @@ contract BinaryAllocator is ); // Intermediate step to calculate kTwo - uint256 intermediate = (uint256(10) ** uint256(18 - nextSetComponentDecimals)) - .mul(nextSetComponentPrice) - .div(_currentSetValue) - .add(1); + uint256 intermediate = AllocatorMathLibrary.roundUpDivision( + CommonMath.safePower(uint256(10), uint256(18).sub(nextSetComponentDecimals)).mul(nextSetComponentPrice), + _currentSetValue + ); // Complete kTwo calculation by taking ceil(log10()) of intermediate uint256 kTwo = AllocatorMathLibrary.ceilLog10(intermediate); @@ -419,7 +429,7 @@ contract BinaryAllocator is ); // Get nextSetComponent - ERC20Detailed nextSetComponent = _toBaseAsset ? baseAssetInstance : quoteAssetInstance; + ERC20Detailed nextSetComponent = _toBaseAsset ? baseAsset : quoteAsset; return (nextSetComponent, nextSetUnit, CommonMath.safePower(10, k)); } @@ -440,9 +450,9 @@ contract BinaryAllocator is { // If using base asset return baseAsset price and decimals and vice versa if (_usingBaseAsset) { - return (baseAssetOracleInstance.read(), baseAssetDecimals); + return (baseAssetOracle.read(), baseAssetDecimals); } else { - return (quoteAssetOracleInstance.read(), quoteAssetDecimals); + return (quoteAssetOracle.read(), quoteAssetDecimals); } } @@ -497,4 +507,21 @@ contract BinaryAllocator is ) ); } + + /* + * Check that passed component array contains one component, else revert. + * + * @param _array Array to be evaluated + */ + function validateSingleItemArray( + address[] memory _array + ) + internal + pure + { + require( + _array.length == 1, + "BinaryAllocator.validateSingleItemArray: Array contains more than one component." + ); + } } \ No newline at end of file diff --git a/contracts/managers/allocators/IAllocator.sol b/contracts/managers/allocators/IAllocator.sol index c92e1cf..5bba472 100644 --- a/contracts/managers/allocators/IAllocator.sol +++ b/contracts/managers/allocators/IAllocator.sol @@ -41,7 +41,7 @@ interface IAllocator { ISetToken _currentCollateralSet ) external - returns (address); + returns (ISetToken); /* * Calculate value of passed collateral set. diff --git a/contracts/managers/lib/AllocatorMathLibrary.sol b/contracts/managers/lib/AllocatorMathLibrary.sol index 89a8a14..3c7d5bf 100644 --- a/contracts/managers/lib/AllocatorMathLibrary.sol +++ b/contracts/managers/lib/AllocatorMathLibrary.sol @@ -42,27 +42,21 @@ library AllocatorMathLibrary { pure returns (uint256) { - // Make sure passed value is greater than 0 - require ( - _value > 0, - "AllocatorMathLibrary.roundToNearestPowerOfTwo: Value must be greater than zero." - ); - // Multiply by 1.5 to roughly approximate sqrt(2). Needed to round to nearest power of two. - uint256 scaledValue = _value.mul(3).div(2); - uint256 power = 0; + uint256 scaledValue = _value.mul(3) >> 1; + uint256 nearestValue = 1; // Calculate nearest power of two - if (scaledValue >= 0x100000000000000000000000000000000) { scaledValue >>= 128; power += 128; } - if (scaledValue >= 0x10000000000000000) { scaledValue >>= 64; power += 64; } - if (scaledValue >= 0x100000000) { scaledValue >>= 32; power += 32; } - if (scaledValue >= 0x10000) { scaledValue >>= 16; power += 16; } - if (scaledValue >= 0x100) { scaledValue >>= 8; power += 8; } - if (scaledValue >= 0x10) { scaledValue >>= 4; power += 4; } - if (scaledValue >= 0x4) { scaledValue >>= 2; power += 2; } - if (scaledValue >= 0x2) power += 1; // No need to shift x anymore - - return 2 ** power; + if (scaledValue >= 0x100000000000000000000000000000000) { scaledValue >>= 128; nearestValue <<= 128; } + if (scaledValue >= 0x10000000000000000) { scaledValue >>= 64; nearestValue <<= 64; } + if (scaledValue >= 0x100000000) { scaledValue >>= 32; nearestValue <<= 32; } + if (scaledValue >= 0x10000) { scaledValue >>= 16; nearestValue <<= 16; } + if (scaledValue >= 0x100) { scaledValue >>= 8; nearestValue <<= 8; } + if (scaledValue >= 0x10) { scaledValue >>= 4; nearestValue <<= 4; } + if (scaledValue >= 0x4) { scaledValue >>= 2; nearestValue <<= 2; } + if (scaledValue >= 0x2) nearestValue <<= 1; // No need to shift x anymore + + return nearestValue; } /* @@ -92,24 +86,24 @@ library AllocatorMathLibrary { uint256 result = 0; - if (x >= 10000000000000000000000000000000000000000000000000000000000000000) { - x /= 10000000000000000000000000000000000000000000000000000000000000000; + if (x >= 10 ** 64) { + x /= 10 ** 64; result += 64; } - if (x >= 100000000000000000000000000000000) { - x /= 100000000000000000000000000000000; + if (x >= 10 ** 32) { + x /= 10 ** 32; result += 32; } - if (x >= 10000000000000000) { - x /= 10000000000000000; + if (x >= 10 ** 16) { + x /= 10 ** 16; result += 16; } - if (x >= 100000000) { - x /= 100000000; + if (x >= 10 ** 8) { + x /= 10 ** 8; result += 8; } - if (x >= 10000) { - x /= 10000; + if (x >= 10 ** 4) { + x /= 10 ** 4; result += 4; } if (x >= 100) { @@ -117,10 +111,27 @@ library AllocatorMathLibrary { result += 2; } if (x >= 10) { - x /= 10; result += 1; } return result + 1; } + + /* + * Round up division by subtracting one from numerator, dividing, then adding one. + * + * @param _numerator Numerator of expression + * @param _denominator Denominator of expression + * @return uint256 Output value + */ + function roundUpDivision( + uint256 _numerator, + uint256 _denominator + ) + internal + pure + returns (uint256) + { + return _numerator.sub(1).div(_denominator).add(1); + } } \ No newline at end of file diff --git a/contracts/managers/lib/Oscillator.sol b/contracts/managers/lib/Oscillator.sol index ef95c72..e2d9e14 100644 --- a/contracts/managers/lib/Oscillator.sol +++ b/contracts/managers/lib/Oscillator.sol @@ -26,7 +26,7 @@ pragma experimental "ABIEncoderV2"; */ library Oscillator { - enum State { UPPER, LOWER, NEUTRAL } + enum State { NEUTRAL, UPPER, LOWER } // Oscillator bounds typically between 0 and 100 struct Bounds { @@ -37,6 +37,8 @@ library Oscillator { /* * Returns upper if value is greater or equal to upper bound. * Returns lower if lower than lower bound, and neutral if in between. + * Asymmetric bounds are due to rounding in solidity and not wanting 40.1 + * to register in LOWER state, for example. */ function getState( Bounds storage _bounds, diff --git a/contracts/managers/triggers/RSITrendingTrigger.sol b/contracts/managers/triggers/RSITrendingTrigger.sol index eaa3d06..27cbc43 100644 --- a/contracts/managers/triggers/RSITrendingTrigger.sol +++ b/contracts/managers/triggers/RSITrendingTrigger.sol @@ -51,7 +51,7 @@ contract RSITrendingTrigger is /* * RSITrendingTrigger constructor. * - * @param _rsiOracle The address of RSI oracle + * @param _rsiOracle The address of RSI oracle * @param _lowerBound Lower bound of RSI to trigger a rebalance * @param _upperBound Upper bound of RSI to trigger a rebalance * @param _rsiTimePeriod The amount of days to use in RSI calculation @@ -66,7 +66,20 @@ contract RSITrendingTrigger is { require( _upperBound >= _lowerBound, - "Constructor: Upper bound must be greater than lower bound" + "RSITrendingTrigger.constructor: Upper bound must be greater than lower bound." + ); + + // If upper bound less than 100 and above inequality holds then lowerBound + // also guaranteed to be between 0 and 100. + require( + _upperBound < 100, + "RSITrendingTrigger.constructor: Bounds must be between 0 and 100." + ); + + // RSI time period must be greater than 0 + require( + _rsiTimePeriod > 0, + "RSITrendingTrigger.constructor: RSI time period must be greater than 0." ); rsiOracle = _rsiOracle; diff --git a/contracts/mocks/managers/BinaryAllocatorMock.sol b/contracts/mocks/managers/BinaryAllocatorMock.sol index c379116..b2fae05 100644 --- a/contracts/mocks/managers/BinaryAllocatorMock.sol +++ b/contracts/mocks/managers/BinaryAllocatorMock.sol @@ -56,12 +56,12 @@ contract BinaryAllocatorMock is ISetToken _currentCollateralSet ) external - returns (address) + returns (ISetToken) { if (_targetBaseAssetAllocation == _allocationPrecision) { - return address(baseAssetCollateralInstance); + return baseAssetCollateralInstance; } else { - return address(quoteAssetCollateralInstance); + return quoteAssetCollateralInstance; } } diff --git a/package.json b/package.json index 7ec7cb9..f33f8a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "set-protocol-strategies", - "version": "1.1.13", + "version": "1.1.14", "main": "dist/artifacts/index.js", "typings": "dist/typings/artifacts/index.d.ts", "files": [ diff --git a/test/contracts/managers/allocators/binaryAllocator.spec.ts b/test/contracts/managers/allocators/binaryAllocator.spec.ts index 2957b02..835bccd 100644 --- a/test/contracts/managers/allocators/binaryAllocator.spec.ts +++ b/test/contracts/managers/allocators/binaryAllocator.spec.ts @@ -75,6 +75,7 @@ contract('BinaryAllocator', accounts => { let baseAssetCollateral: SetTokenContract; let quoteAssetCollateral: SetTokenContract; + let multiAssetCollateral: SetTokenContract; let allocator: BinaryAllocatorContract; @@ -147,6 +148,14 @@ contract('BinaryAllocator', accounts => { [new BigNumber(2 ** 7)], // 128 STABLE_COLLATERAL_NATURAL_UNIT, ); + + multiAssetCollateral = await protocolHelper.createSetTokenAsync( + core, + factory.address, + [usdcMock.address, wrappedETH.address], + [new BigNumber(2 ** 7), new BigNumber(2 ** 20)], // 128 + STABLE_COLLATERAL_NATURAL_UNIT, + ); }); afterEach(async () => { @@ -154,69 +163,69 @@ contract('BinaryAllocator', accounts => { }); describe('#constructor', async () => { - let subjectBaseAssetInstance: Address; - let subjectQuoteAssetInstance: Address; - let subjectBaseAssetOracleInstance: Address; - let subjectQuoteAssetOracleInstance: Address; - let subjectBaseAssetCollateralInstance: Address; - let subjectQuoteAssetCollateralInstance: Address; - let subjectCoreInstance: Address; - let subjectSetTokenFactoryAddress: Address; + let subjectBaseAsset: Address; + let subjectQuoteAsset: Address; + let subjectBaseAssetOracle: Address; + let subjectQuoteAssetOracle: Address; + let subjectBaseAssetCollateral: Address; + let subjectQuoteAssetCollateral: Address; + let subjectCore: Address; + let subjectSetTokenFactory: Address; beforeEach(async () => { - subjectBaseAssetInstance = wrappedETH.address; - subjectQuoteAssetInstance = usdcMock.address; - subjectBaseAssetOracleInstance = oracleProxy.address; - subjectQuoteAssetOracleInstance = usdcOracle.address; - subjectBaseAssetCollateralInstance = baseAssetCollateral.address; - subjectQuoteAssetCollateralInstance = quoteAssetCollateral.address; - subjectCoreInstance = core.address; - subjectSetTokenFactoryAddress = factory.address; + subjectBaseAsset = wrappedETH.address; + subjectQuoteAsset = usdcMock.address; + subjectBaseAssetOracle = oracleProxy.address; + subjectQuoteAssetOracle = usdcOracle.address; + subjectBaseAssetCollateral = baseAssetCollateral.address; + subjectQuoteAssetCollateral = quoteAssetCollateral.address; + subjectCore = core.address; + subjectSetTokenFactory = factory.address; }); async function subject(): Promise { return managerHelper.deployBinaryAllocatorAsync( - subjectBaseAssetInstance, - subjectQuoteAssetInstance, - subjectBaseAssetOracleInstance, - subjectQuoteAssetOracleInstance, - subjectBaseAssetCollateralInstance, - subjectQuoteAssetCollateralInstance, - subjectCoreInstance, - subjectSetTokenFactoryAddress + subjectBaseAsset, + subjectQuoteAsset, + subjectBaseAssetOracle, + subjectQuoteAssetOracle, + subjectBaseAssetCollateral, + subjectQuoteAssetCollateral, + subjectCore, + subjectSetTokenFactory ); } it('sets the correct base asset address', async () => { allocator = await subject(); - const actualBaseAssetInstance = await allocator.baseAssetInstance.callAsync(); + const actualBaseAsset = await allocator.baseAsset.callAsync(); - expect(actualBaseAssetInstance).to.equal(subjectBaseAssetInstance); + expect(actualBaseAsset).to.equal(subjectBaseAsset); }); it('sets the correct quote asset address', async () => { allocator = await subject(); - const actualQuoteAssetInstance = await allocator.quoteAssetInstance.callAsync(); + const actualQuoteAsset = await allocator.quoteAsset.callAsync(); - expect(actualQuoteAssetInstance).to.equal(subjectQuoteAssetInstance); + expect(actualQuoteAsset).to.equal(subjectQuoteAsset); }); it('sets the correct base asset oracle address', async () => { allocator = await subject(); - const actualBaseAssetOracleInstance = await allocator.baseAssetOracleInstance.callAsync(); + const actualBaseAssetOracle = await allocator.baseAssetOracle.callAsync(); - expect(actualBaseAssetOracleInstance).to.equal(subjectBaseAssetOracleInstance); + expect(actualBaseAssetOracle).to.equal(subjectBaseAssetOracle); }); it('sets the correct quote asset oracle address', async () => { allocator = await subject(); - const actualQuoteAssetOracleInstance = await allocator.quoteAssetOracleInstance.callAsync(); + const actualQuoteAssetOracle = await allocator.quoteAssetOracle.callAsync(); - expect(actualQuoteAssetOracleInstance).to.equal(subjectQuoteAssetOracleInstance); + expect(actualQuoteAssetOracle).to.equal(subjectQuoteAssetOracle); }); it('adds the correct base collateral address to storedCollateral mapping', async () => { @@ -233,7 +242,7 @@ contract('BinaryAllocator', accounts => { const actualStoredBaseAddress = await allocator.storedCollateral.callAsync(baseCollateralHash); - expect(actualStoredBaseAddress).to.equal(subjectBaseAssetCollateralInstance); + expect(actualStoredBaseAddress).to.equal(subjectBaseAssetCollateral); }); it('adds the correct quote collateral address to storedCollateral mapping', async () => { @@ -250,23 +259,23 @@ contract('BinaryAllocator', accounts => { const actualStoredQuoteAddress = await allocator.storedCollateral.callAsync(quoteCollateralHash); - expect(actualStoredQuoteAddress).to.equal(subjectQuoteAssetCollateralInstance); + expect(actualStoredQuoteAddress).to.equal(subjectQuoteAssetCollateral); }); it('sets the correct core address', async () => { allocator = await subject(); - const actualCoreAddress = await allocator.coreInstance.callAsync(); + const actualCoreAddress = await allocator.core.callAsync(); - expect(actualCoreAddress).to.equal(subjectCoreInstance); + expect(actualCoreAddress).to.equal(subjectCore); }); it('sets the correct set token factory address', async () => { allocator = await subject(); - const actualSetTokenFactoryAddress = await allocator.setTokenFactoryAddress.callAsync(); + const actualSetTokenFactory = await allocator.setTokenFactory.callAsync(); - expect(actualSetTokenFactoryAddress).to.equal(subjectSetTokenFactoryAddress); + expect(actualSetTokenFactory).to.equal(subjectSetTokenFactory); }); it('sets the correct base asset decimals', async () => { @@ -289,7 +298,7 @@ contract('BinaryAllocator', accounts => { describe('but stable asset address does not match stable collateral component', async () => { beforeEach(async () => { - subjectBaseAssetInstance = randomTokenAddress; + subjectBaseAsset = randomTokenAddress; }); it('should revert', async () => { @@ -299,7 +308,27 @@ contract('BinaryAllocator', accounts => { describe('but risk asset address does not match risk collateral component', async () => { beforeEach(async () => { - subjectQuoteAssetInstance = randomTokenAddress; + subjectQuoteAsset = randomTokenAddress; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('but passed baseCollateral has two components', async () => { + beforeEach(async () => { + subjectBaseAssetCollateral = multiAssetCollateral.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('but passed quoteCollateral has two components', async () => { + beforeEach(async () => { + subjectQuoteAssetCollateral = multiAssetCollateral.address; }); it('should revert', async () => { @@ -845,5 +874,15 @@ contract('BinaryAllocator', accounts => { expect(actualSetValue).to.bignumber.equal(expectedSetValue); }); }); + + describe('but passed collateral set has two components', async () => { + beforeEach(async () => { + subjectCollateralSet = multiAssetCollateral.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); }); \ No newline at end of file diff --git a/test/contracts/managers/assetPairManager.spec.ts b/test/contracts/managers/assetPairManager.spec.ts index 3ff6d67..2152b4d 100644 --- a/test/contracts/managers/assetPairManager.spec.ts +++ b/test/contracts/managers/assetPairManager.spec.ts @@ -3,6 +3,7 @@ require('module-alias/register'); import * as _ from 'lodash'; import * as ABIDecoder from 'abi-decoder'; import * as chai from 'chai'; +import * as setProtocolUtils from 'set-protocol-utils'; import { Address } from 'set-protocol-utils'; import { BigNumber } from 'bignumber.js'; @@ -42,6 +43,7 @@ import { } from '@utils/constants'; import { expectRevertError } from '@utils/tokenAssertions'; +import { LogInitialProposeCalled } from '@utils/contract_logs/assetPairManager'; import { getWeb3, blankTxn } from '@utils/web3Helper'; import { ERC20Helper } from '@utils/helpers/erc20Helper'; @@ -51,8 +53,11 @@ import { ProtocolHelper } from '@utils/helpers/protocolHelper'; BigNumberSetup.configure(); ChaiSetup.configure(); const web3 = getWeb3(); +const AssetPairManager = artifacts.require('AssetPairManager'); const { expect } = chai; const blockchain = new Blockchain(web3); +const { SetProtocolTestUtils: SetTestUtils } = setProtocolUtils; +const setTestUtils = new SetTestUtils(web3); contract('AssetPairManager', accounts => { const [ @@ -87,10 +92,12 @@ contract('AssetPairManager', accounts => { before(async () => { ABIDecoder.addABI(Core.abi); + ABIDecoder.addABI(AssetPairManager.abi); }); after(async () => { ABIDecoder.removeABI(Core.abi); + ABIDecoder.removeABI(AssetPairManager.abi); }); beforeEach(async () => { @@ -146,30 +153,30 @@ contract('AssetPairManager', accounts => { }); describe('#constructor', async () => { - let subjectCoreInstance: Address; - let subjectAllocatorInstance: Address; - let subjectTriggerInstance: Address; - let subjectAuctionLibraryInstance: Address; + let subjectCore: Address; + let subjectAllocator: Address; + let subjectTrigger: Address; + let subjectAuctionLibrary: Address; let subjectBaseAssetAllocation: BigNumber; - let subjectAllocationPrecision: BigNumber; + let subjectAllocationDenominator: BigNumber; let subjectBullishBaseAssetAllocation: BigNumber; let subjectAuctionStartPercentage: BigNumber; - let subjectAuctionEndPercentage: BigNumber; + let subjectAuctionPivotPercentage: BigNumber; let subjectAuctionTimeToPivot: BigNumber; let subjectSignalConfirmationMinTime: BigNumber; let subjectSignalConfirmationMaxTime: BigNumber; let subjectCaller: Address; beforeEach(async () => { - subjectCoreInstance = core.address; - subjectAllocatorInstance = allocator.address; - subjectTriggerInstance = trigger.address; - subjectAuctionLibraryInstance = linearAuctionPriceCurve.address; + subjectCore = core.address; + subjectAllocator = allocator.address; + subjectTrigger = trigger.address; + subjectAuctionLibrary = linearAuctionPriceCurve.address; subjectBaseAssetAllocation = ZERO; - subjectAllocationPrecision = new BigNumber(100); + subjectAllocationDenominator = new BigNumber(100); subjectBullishBaseAssetAllocation = new BigNumber(100); subjectAuctionStartPercentage = new BigNumber(2); - subjectAuctionEndPercentage = new BigNumber(10); + subjectAuctionPivotPercentage = new BigNumber(10); subjectAuctionTimeToPivot = ONE_HOUR_IN_SECONDS.mul(4); subjectSignalConfirmationMinTime = ONE_HOUR_IN_SECONDS.mul(6); subjectSignalConfirmationMaxTime = ONE_HOUR_IN_SECONDS.mul(12); @@ -178,15 +185,15 @@ contract('AssetPairManager', accounts => { async function subject(): Promise { return managerHelper.deployAssetPairManagerAsync( - subjectCoreInstance, - subjectAllocatorInstance, - subjectTriggerInstance, - subjectAuctionLibraryInstance, + subjectCore, + subjectAllocator, + subjectTrigger, + subjectAuctionLibrary, subjectBaseAssetAllocation, - subjectAllocationPrecision, + subjectAllocationDenominator, subjectBullishBaseAssetAllocation, subjectAuctionStartPercentage, - subjectAuctionEndPercentage, + subjectAuctionPivotPercentage, subjectAuctionTimeToPivot, subjectSignalConfirmationMinTime, subjectSignalConfirmationMaxTime, @@ -197,33 +204,33 @@ contract('AssetPairManager', accounts => { it('sets the correct core address', async () => { setManager = await subject(); - const actualCoreInstance = await setManager.coreInstance.callAsync(); + const actualCore = await setManager.core.callAsync(); - expect(actualCoreInstance).to.equal(subjectCoreInstance); + expect(actualCore).to.equal(subjectCore); }); it('sets the correct allocator address', async () => { setManager = await subject(); - const actualAllocatorInstance = await setManager.allocatorInstance.callAsync(); + const actualAllocator = await setManager.allocator.callAsync(); - expect(actualAllocatorInstance).to.equal(subjectAllocatorInstance); + expect(actualAllocator).to.equal(subjectAllocator); }); it('sets the correct trigger address', async () => { setManager = await subject(); - const actualTriggerInstance = await setManager.triggerInstance.callAsync(); + const actualTrigger = await setManager.trigger.callAsync(); - expect(actualTriggerInstance).to.equal(subjectTriggerInstance); + expect(actualTrigger).to.equal(subjectTrigger); }); it('sets the correct auctionLibrary address', async () => { setManager = await subject(); - const actualAuctionLibraryInstance = await setManager.auctionLibraryInstance.callAsync(); + const actualAuctionLibrary = await setManager.auctionLibrary.callAsync(); - expect(actualAuctionLibraryInstance).to.equal(subjectAuctionLibraryInstance); + expect(actualAuctionLibrary).to.equal(subjectAuctionLibrary); }); it('sets the correct baseAssetAllocation', async () => { @@ -234,12 +241,12 @@ contract('AssetPairManager', accounts => { expect(actualBaseAssetAllocation).to.be.bignumber.equal(subjectBaseAssetAllocation); }); - it('sets the correct allocationPrecision', async () => { + it('sets the correct allocationDenominator', async () => { setManager = await subject(); - const actualAllocationPrecision = await setManager.allocationPrecision.callAsync(); + const actualAllocationDenominator = await setManager.allocationDenominator.callAsync(); - expect(actualAllocationPrecision).to.be.bignumber.equal(subjectAllocationPrecision); + expect(actualAllocationDenominator).to.be.bignumber.equal(subjectAllocationDenominator); }); it('sets the correct bullishBaseAssetAllocation', async () => { @@ -250,6 +257,14 @@ contract('AssetPairManager', accounts => { expect(actualBullishBaseAssetAllocation).to.be.bignumber.equal(subjectBullishBaseAssetAllocation); }); + it('sets the correct bearishBaseAssetAllocation', async () => { + setManager = await subject(); + + const actualBearishBaseAssetAllocation = await setManager.bearishBaseAssetAllocation.callAsync(); + const expectedBearishBaseAssetAllocation = subjectAllocationDenominator.sub(subjectBullishBaseAssetAllocation); + expect(actualBearishBaseAssetAllocation).to.be.bignumber.equal(expectedBearishBaseAssetAllocation); + }); + it('sets the correct auctionStartPercentage', async () => { setManager = await subject(); @@ -258,12 +273,12 @@ contract('AssetPairManager', accounts => { expect(actualAuctionStartPercentage).to.be.bignumber.equal(subjectAuctionStartPercentage); }); - it('sets the correct auctionEndPercentage', async () => { + it('sets the correct auctionPivotPercentage', async () => { setManager = await subject(); - const actualAuctionEndPercentage = await setManager.auctionEndPercentage.callAsync(); + const actualAuctionPivotPercentage = await setManager.auctionPivotPercentage.callAsync(); - expect(actualAuctionEndPercentage).to.be.bignumber.equal(subjectAuctionEndPercentage); + expect(actualAuctionPivotPercentage).to.be.bignumber.equal(subjectAuctionPivotPercentage); }); it('sets the correct auctionTimeToPivot', async () => { @@ -307,6 +322,26 @@ contract('AssetPairManager', accounts => { await expectRevertError(subject()); }); }); + + describe('but bullishBaseAssetAllocation exceeds than allocationDenominator', async () => { + beforeEach(async () => { + subjectBullishBaseAssetAllocation = subjectAllocationDenominator.add(1); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('but baseAssetAllocation does not equal bullish or bearish asset allocation', async () => { + beforeEach(async () => { + subjectBaseAssetAllocation = new BigNumber(50); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('#initialize', async () => { @@ -317,9 +352,9 @@ contract('AssetPairManager', accounts => { beforeEach(async () => { const auctionStartPercentage = new BigNumber(2); - const auctionEndPercentage = new BigNumber(10); + const auctionPivotPercentage = new BigNumber(10); const auctionTimeToPivot = ONE_HOUR_IN_SECONDS.mul(4); - const allocationPrecision = new BigNumber(100); + const allocationDenominator = new BigNumber(100); const maxBaseAssetAllocation = new BigNumber(100); const signalConfirmationMinTime = ONE_HOUR_IN_SECONDS.mul(6); const signalConfirmationMaxTime = ONE_HOUR_IN_SECONDS.mul(12); @@ -330,10 +365,10 @@ contract('AssetPairManager', accounts => { trigger.address, linearAuctionPriceCurve.address, ZERO, - allocationPrecision, + allocationDenominator, maxBaseAssetAllocation, auctionStartPercentage, - auctionEndPercentage, + auctionPivotPercentage, auctionTimeToPivot, signalConfirmationMinTime, signalConfirmationMaxTime, @@ -362,9 +397,9 @@ contract('AssetPairManager', accounts => { it('sets the rebalancing set token address', async () => { await subject(); - const actualRebalancingSetTokenInstance = await setManager.rebalancingSetTokenInstance.callAsync(); + const actualRebalancingSetToken = await setManager.rebalancingSetToken.callAsync(); - expect(actualRebalancingSetTokenInstance).to.equal(subjectRebalancingSetToken); + expect(actualRebalancingSetToken).to.equal(subjectRebalancingSetToken); }); it('sets the intializer address to zero', async () => { @@ -415,18 +450,20 @@ contract('AssetPairManager', accounts => { let initialBaseAssetAllocation: BigNumber; let timeJump: BigNumber; let flipTrigger: boolean; + let shouldInitialize: boolean; before(async () => { initialBaseAssetAllocation = new BigNumber(100); flipTrigger = false; timeJump = ONE_DAY_IN_SECONDS; + shouldInitialize = true; }); beforeEach(async () => { const auctionStartPercentage = new BigNumber(2); - const auctionEndPercentage = new BigNumber(10); + const auctionPivotPercentage = new BigNumber(10); const auctionTimeToPivot = ONE_HOUR_IN_SECONDS.mul(4); - const allocationPrecision = new BigNumber(100); + const allocationDenominator = new BigNumber(100); const maxBaseAssetAllocation = new BigNumber(100); const signalConfirmationMinTime = ONE_HOUR_IN_SECONDS.mul(6); const signalConfirmationMaxTime = ONE_HOUR_IN_SECONDS.mul(12); @@ -436,10 +473,10 @@ contract('AssetPairManager', accounts => { trigger.address, linearAuctionPriceCurve.address, initialBaseAssetAllocation, - allocationPrecision, + allocationDenominator, maxBaseAssetAllocation, auctionStartPercentage, - auctionEndPercentage, + auctionPivotPercentage, auctionTimeToPivot, signalConfirmationMinTime, signalConfirmationMaxTime, @@ -467,10 +504,12 @@ contract('AssetPairManager', accounts => { await trigger.confirmTrigger.sendTransactionAsync(); } - await setManager.initialize.sendTransactionAsync( - rebalancingSetToken.address, - { from: subjectCaller, gas: DEFAULT_GAS } - ); + if (shouldInitialize) { + await setManager.initialize.sendTransactionAsync( + rebalancingSetToken.address, + { from: subjectCaller, gas: DEFAULT_GAS } + ); + } await blockchain.increaseTimeAsync(timeJump); await blankTxn(deployerAccount); @@ -492,10 +531,22 @@ contract('AssetPairManager', accounts => { const block = await web3.eth.getBlock('latest'); const expectedTimestamp = new BigNumber(block.timestamp); - const actualTimestamp = await setManager.lastInitialTriggerTimestamp.callAsync(); + const actualTimestamp = await setManager.recentInitialProposeTimestamp.callAsync(); expect(actualTimestamp).to.be.bignumber.equal(expectedTimestamp); }); + it('it emits InitialProposeCalled event', async () => { + const txHash = await subject(); + + const formattedLogs = await setTestUtils.getLogsFromTxHash(txHash); + const expectedLogs = LogInitialProposeCalled( + rebalancingSetToken.address, + setManager.address + ); + + await SetTestUtils.assertLogEquivalence(formattedLogs, expectedLogs); + }); + describe('but allocation has not changed', async () => { before(async () => { flipTrigger = true; @@ -509,6 +560,20 @@ contract('AssetPairManager', accounts => { await expectRevertError(subject()); }); }); + + describe('but manager not initialized', async () => { + before(async () => { + shouldInitialize = false; + }); + + after(async () => { + shouldInitialize = true; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('and allocating from quote asset to base asset', async () => { @@ -526,7 +591,7 @@ contract('AssetPairManager', accounts => { const block = await web3.eth.getBlock('latest'); const expectedTimestamp = new BigNumber(block.timestamp); - const actualTimestamp = await setManager.lastInitialTriggerTimestamp.callAsync(); + const actualTimestamp = await setManager.recentInitialProposeTimestamp.callAsync(); expect(actualTimestamp).to.be.bignumber.equal(expectedTimestamp); }); @@ -621,21 +686,23 @@ contract('AssetPairManager', accounts => { let initialBaseAssetAllocation: BigNumber; let flipTrigger: boolean; + let shouldInitialize: boolean; let auctionStartPercentage: BigNumber; - let auctionEndPercentage: BigNumber; + let auctionPivotPercentage: BigNumber; let auctionTimeToPivot: BigNumber; before(async () => { + shouldInitialize = true; initialBaseAssetAllocation = new BigNumber(100); flipTrigger = false; }); beforeEach(async () => { auctionStartPercentage = new BigNumber(2); - auctionEndPercentage = new BigNumber(10); + auctionPivotPercentage = new BigNumber(10); auctionTimeToPivot = ONE_HOUR_IN_SECONDS.mul(4); - const allocationPrecision = new BigNumber(100); + const allocationDenominator = new BigNumber(100); const maxBaseAssetAllocation = new BigNumber(100); const signalConfirmationMinTime = ONE_HOUR_IN_SECONDS.mul(6); const signalConfirmationMaxTime = ONE_HOUR_IN_SECONDS.mul(12); @@ -645,10 +712,10 @@ contract('AssetPairManager', accounts => { trigger.address, linearAuctionPriceCurve.address, initialBaseAssetAllocation, - allocationPrecision, + allocationDenominator, maxBaseAssetAllocation, auctionStartPercentage, - auctionEndPercentage, + auctionPivotPercentage, auctionTimeToPivot, signalConfirmationMinTime, signalConfirmationMaxTime, @@ -672,14 +739,16 @@ contract('AssetPairManager', accounts => { proposalPeriod ); - await setManager.initialize.sendTransactionAsync( - rebalancingSetToken.address, - { from: subjectCaller, gas: DEFAULT_GAS} - ); + if (shouldInitialize) { + await setManager.initialize.sendTransactionAsync( + rebalancingSetToken.address, + { from: subjectCaller, gas: DEFAULT_GAS} + ); - await blockchain.increaseTimeAsync(ONE_DAY_IN_SECONDS); + await blockchain.increaseTimeAsync(ONE_DAY_IN_SECONDS); - await setManager.initialPropose.sendTransactionAsync(); + await setManager.initialPropose.sendTransactionAsync(); + } if (flipTrigger) { await trigger.confirmTrigger.sendTransactionAsync(); @@ -735,7 +804,7 @@ contract('AssetPairManager', accounts => { baseAssetCollateralValue, quoteAssetCollateralValue, auctionStartPercentage, - auctionEndPercentage + auctionPivotPercentage ); const newAuctionParameters = await rebalancingSetToken.auctionPriceParameters.callAsync(); @@ -751,7 +820,7 @@ contract('AssetPairManager', accounts => { baseAssetCollateralValue, quoteAssetCollateralValue, auctionStartPercentage, - auctionEndPercentage + auctionPivotPercentage ); const newAuctionParameters = await rebalancingSetToken.auctionPriceParameters.callAsync(); @@ -783,6 +852,20 @@ contract('AssetPairManager', accounts => { await expectRevertError(subject()); }); }); + + describe('but manager not initialized', async () => { + before(async () => { + shouldInitialize = false; + }); + + after(async () => { + shouldInitialize = true; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('and allocating from quote asset to base asset', async () => { @@ -826,7 +909,7 @@ contract('AssetPairManager', accounts => { quoteAssetCollateralValue, baseAssetCollateralValue, auctionStartPercentage, - auctionEndPercentage + auctionPivotPercentage ); const newAuctionParameters = await rebalancingSetToken.auctionPriceParameters.callAsync(); @@ -842,7 +925,7 @@ contract('AssetPairManager', accounts => { quoteAssetCollateralValue, baseAssetCollateralValue, auctionStartPercentage, - auctionEndPercentage + auctionPivotPercentage ); const newAuctionParameters = await rebalancingSetToken.auctionPriceParameters.callAsync(); @@ -925,9 +1008,9 @@ contract('AssetPairManager', accounts => { beforeEach(async () => { const auctionStartPercentage = new BigNumber(2); - const auctionEndPercentage = new BigNumber(10); + const auctionPivotPercentage = new BigNumber(10); const auctionTimeToPivot = ONE_HOUR_IN_SECONDS.mul(4); - const allocationPrecision = new BigNumber(100); + const allocationDenominator = new BigNumber(100); const maxBaseAssetAllocation = new BigNumber(100); const signalConfirmationMinTime = ONE_HOUR_IN_SECONDS.mul(6); const signalConfirmationMaxTime = ONE_HOUR_IN_SECONDS.mul(12); @@ -937,10 +1020,10 @@ contract('AssetPairManager', accounts => { trigger.address, linearAuctionPriceCurve.address, initialBaseAssetAllocation, - allocationPrecision, + allocationDenominator, maxBaseAssetAllocation, auctionStartPercentage, - auctionEndPercentage, + auctionPivotPercentage, auctionTimeToPivot, signalConfirmationMinTime, signalConfirmationMaxTime, @@ -1134,9 +1217,9 @@ contract('AssetPairManager', accounts => { beforeEach(async () => { const auctionStartPercentage = new BigNumber(2); - const auctionEndPercentage = new BigNumber(10); + const auctionPivotPercentage = new BigNumber(10); const auctionTimeToPivot = ONE_HOUR_IN_SECONDS.mul(4); - const allocationPrecision = new BigNumber(100); + const allocationDenominator = new BigNumber(100); const maxBaseAssetAllocation = new BigNumber(100); const signalConfirmationMinTime = ONE_HOUR_IN_SECONDS.mul(6); const signalConfirmationMaxTime = ONE_HOUR_IN_SECONDS.mul(12); @@ -1146,10 +1229,10 @@ contract('AssetPairManager', accounts => { trigger.address, linearAuctionPriceCurve.address, initialBaseAssetAllocation, - allocationPrecision, + allocationDenominator, maxBaseAssetAllocation, auctionStartPercentage, - auctionEndPercentage, + auctionPivotPercentage, auctionTimeToPivot, signalConfirmationMinTime, signalConfirmationMaxTime, diff --git a/test/contracts/managers/lib/allocatorMathLibrary.spec.ts b/test/contracts/managers/lib/allocatorMathLibrary.spec.ts index 4f2f8ba..5e294b0 100644 --- a/test/contracts/managers/lib/allocatorMathLibrary.spec.ts +++ b/test/contracts/managers/lib/allocatorMathLibrary.spec.ts @@ -85,16 +85,6 @@ contract('AllocatorMathLibrary', accounts => { expect(actualOutput.toNumber()).to.equal(1); }); }); - - describe('but value is 0', async () => { - beforeEach(async () => { - subjectValue = 0; - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); }); describe('#ceilLog10', async () => { diff --git a/test/contracts/managers/triggers/rsiTrendingTrigger.spec.ts b/test/contracts/managers/triggers/rsiTrendingTrigger.spec.ts index c1e4f8d..4a27c80 100644 --- a/test/contracts/managers/triggers/rsiTrendingTrigger.spec.ts +++ b/test/contracts/managers/triggers/rsiTrendingTrigger.spec.ts @@ -28,7 +28,8 @@ import { import { DEFAULT_GAS, - ONE_DAY_IN_SECONDS + ONE_DAY_IN_SECONDS, + ZERO, } from '@utils/constants'; import { expectRevertError } from '@utils/tokenAssertions'; @@ -190,6 +191,26 @@ contract('RSITrendingTrigger', accounts => { await expectRevertError(subject()); }); }); + + describe('when time period is 0', async () => { + beforeEach(async () => { + subjectRSITimePeriod = ZERO; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('when upper bound is greater than 100', async () => { + beforeEach(async () => { + subjectUpperBound = new BigNumber(100); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('#isBullish', async () => { diff --git a/utils/contract_logs/assetPairManager.ts b/utils/contract_logs/assetPairManager.ts new file mode 100644 index 0000000..bfe2eb9 --- /dev/null +++ b/utils/contract_logs/assetPairManager.ts @@ -0,0 +1,14 @@ +import { Address, Log } from 'set-protocol-utils'; + +export function LogInitialProposeCalled( + rebalancingSetToken: Address, + contractAddress: Address, +): Log[] { + return [{ + event: 'InitialProposeCalled', + address: contractAddress, + args: { + rebalancingSetToken, + }, + }]; +} \ No newline at end of file diff --git a/utils/contract_logs/binaryAllocator.ts b/utils/contract_logs/binaryAllocator.ts index 521ebe8..4b5a802 100644 --- a/utils/contract_logs/binaryAllocator.ts +++ b/utils/contract_logs/binaryAllocator.ts @@ -1,8 +1,7 @@ - import { Address, Log } from 'set-protocol-utils'; interface NewCollateralArgs { - _hashId: Address; + _hash: Address; _collateralAddress: Address; } @@ -14,5 +13,20 @@ export function extractNewCollateralFromLogs( const createLog = logs[logs.length - 1]; const args: NewCollateralArgs = createLog.args; - return [args._hashId, args._collateralAddress]; + return [args._hash, args._collateralAddress]; +} + +export function LogNewCollateralTracked( + _hash: string, + _collateralAddress: Address, + _contractAddress: Address, +): Log[] { + return [{ + event: 'NewCollateralTracked', + address: _contractAddress, + args: { + _hash, + _collateralAddress, + }, + }]; } \ No newline at end of file