From 717b3c73ab56d2e588fb04f3d28cbe8b39c8417c Mon Sep 17 00:00:00 2001 From: Alexander Soong Date: Sun, 6 Jan 2019 15:26:47 -0800 Subject: [PATCH 1/5] Add whitelist to rebalancing set token factory constructor --- .../tokens/RebalancingSetTokenFactory.sol | 6 +++++ .../core/extensions/coreIssuance.spec.ts | 25 ++++++++++++++----- .../modules/rebalanceAuctionModule.spec.ts | 2 ++ .../core/tokens/rebalancingSetToken.spec.ts | 4 +++ .../tokens/rebalancingSetTokenFactory.spec.ts | 2 ++ utils/wrappers/coreWrapper.ts | 2 ++ 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/contracts/core/tokens/RebalancingSetTokenFactory.sol b/contracts/core/tokens/RebalancingSetTokenFactory.sol index 45d2f4e66..9330fba15 100644 --- a/contracts/core/tokens/RebalancingSetTokenFactory.sol +++ b/contracts/core/tokens/RebalancingSetTokenFactory.sol @@ -41,6 +41,9 @@ contract RebalancingSetTokenFactory { // Address of the Core contract used to verify factory when creating a Set address public core; + // Address of the WhiteList contract used to verify the tokens in a rebalance proposal + address public rebalanceComponentWhitelist; + // Minimum amount of time between rebalances in seconds uint256 public minimumRebalanceInterval; @@ -62,17 +65,20 @@ contract RebalancingSetTokenFactory { * on RebalancingSetToken * * @param _core Address of deployed core contract + * @param _componentWhitelist Address of deployed whitelist contract * @param _minimumRebalanceInterval Minimum amount of time between rebalances in seconds * @param _minimumProposalPeriod Minimum amount of time users can review proposals in seconds */ constructor( address _core, + address _componentWhitelist, uint256 _minimumRebalanceInterval, uint256 _minimumProposalPeriod ) public { core = _core; + rebalanceComponentWhitelist = _componentWhitelist; minimumRebalanceInterval = _minimumRebalanceInterval; minimumProposalPeriod = _minimumProposalPeriod; } diff --git a/test/contracts/core/extensions/coreIssuance.spec.ts b/test/contracts/core/extensions/coreIssuance.spec.ts index 0a4ecc50b..f93a3b475 100644 --- a/test/contracts/core/extensions/coreIssuance.spec.ts +++ b/test/contracts/core/extensions/coreIssuance.spec.ts @@ -18,7 +18,7 @@ import { SignatureValidatorContract, StandardTokenMockContract, TransferProxyContract, - VaultContract + VaultContract, } from '@utils/contracts'; import { ether } from '@utils/units'; import { assertTokenBalanceAsync, expectRevertError } from '@utils/tokenAssertions'; @@ -59,7 +59,6 @@ contract('CoreIssuance', accounts => { let transferProxy: TransferProxyContract; let vault: VaultContract; let setTokenFactory: SetTokenFactoryContract; - let rebalancingTokenFactory: RebalancingSetTokenFactoryContract; let signatureValidator: SignatureValidatorContract; const coreWrapper = new CoreWrapper(ownerAccount, ownerAccount); @@ -87,11 +86,7 @@ contract('CoreIssuance', accounts => { signatureValidator = await coreWrapper.deploySignatureValidatorAsync(); core = await coreWrapper.deployCoreAsync(transferProxy, vault, signatureValidator); setTokenFactory = await coreWrapper.deploySetTokenFactoryAsync(core.address); - rebalancingTokenFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( - core.address, - ); await coreWrapper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - await coreWrapper.addFactoryAsync(core, rebalancingTokenFactory); }); afterEach(async () => { @@ -363,6 +358,8 @@ contract('CoreIssuance', accounts => { let subjectQuantityToIssue: BigNumber; let subjectSetToIssue: Address; + let rebalancingTokenFactory: RebalancingSetTokenFactoryContract; + let vanillaQuantityToIssue: BigNumber; let vanillaSetToIssue: Address; @@ -373,6 +370,13 @@ contract('CoreIssuance', accounts => { let rebalancingSetToken: RebalancingSetTokenContract; beforeEach(async () => { + const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); + rebalancingTokenFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( + core.address, + rebalancingComponentWhiteList.address + ); + await coreWrapper.addFactoryAsync(core, rebalancingTokenFactory); + const setTokens = await rebalancingTokenWrapper.createSetTokensAsync( core, setTokenFactory.address, @@ -865,6 +869,8 @@ contract('CoreIssuance', accounts => { let subjectQuantityToRedeem: BigNumber; let subjectSetToRedeem: Address; + let rebalancingTokenFactory: RebalancingSetTokenFactoryContract; + let vanillaQuantityToIssue: BigNumber; let vanillaSetToIssue: Address; @@ -879,6 +885,13 @@ contract('CoreIssuance', accounts => { let rebalancingToken: RebalancingSetTokenContract; beforeEach(async () => { + const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); + rebalancingTokenFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( + core.address, + rebalancingComponentWhiteList.address + ); + await coreWrapper.addFactoryAsync(core, rebalancingTokenFactory); + const setTokens = await rebalancingTokenWrapper.createSetTokensAsync( core, setTokenFactory.address, diff --git a/test/contracts/core/modules/rebalanceAuctionModule.spec.ts b/test/contracts/core/modules/rebalanceAuctionModule.spec.ts index a74688480..d4e6c0c8c 100644 --- a/test/contracts/core/modules/rebalanceAuctionModule.spec.ts +++ b/test/contracts/core/modules/rebalanceAuctionModule.spec.ts @@ -98,8 +98,10 @@ contract('RebalanceAuctionModule', accounts => { await coreWrapper.addModuleAsync(coreMock, rebalanceAuctionModuleMock.address); factory = await coreWrapper.deploySetTokenFactoryAsync(coreMock.address); + const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); rebalancingFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( coreMock.address, + rebalancingComponentWhiteList.address, ); constantAuctionPriceCurve = await rebalancingWrapper.deployConstantAuctionPriceCurveAsync( DEFAULT_AUCTION_PRICE_NUMERATOR, diff --git a/test/contracts/core/tokens/rebalancingSetToken.spec.ts b/test/contracts/core/tokens/rebalancingSetToken.spec.ts index 765468378..918b3d13b 100644 --- a/test/contracts/core/tokens/rebalancingSetToken.spec.ts +++ b/test/contracts/core/tokens/rebalancingSetToken.spec.ts @@ -109,8 +109,10 @@ contract('RebalancingSetToken', accounts => { await coreWrapper.addModuleAsync(coreMock, rebalanceAuctionModule.address); factory = await coreWrapper.deploySetTokenFactoryAsync(coreMock.address); + const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); rebalancingFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( coreMock.address, + rebalancingComponentWhiteList.address, ); constantAuctionPriceCurve = await rebalancingWrapper.deployConstantAuctionPriceCurveAsync( DEFAULT_AUCTION_PRICE_NUMERATOR, @@ -419,8 +421,10 @@ contract('RebalancingSetToken', accounts => { const proposalPeriod = ONE_DAY_IN_SECONDS; const rebalanceInterval = ONE_DAY_IN_SECONDS; + const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); const rebalancingFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( coreMock.address, + rebalancingComponentWhiteList.address, ); await coreWrapper.addFactoryAsync(coreMock, rebalancingFactory); diff --git a/test/contracts/core/tokens/rebalancingSetTokenFactory.spec.ts b/test/contracts/core/tokens/rebalancingSetTokenFactory.spec.ts index 2d8c481a8..807c291c8 100644 --- a/test/contracts/core/tokens/rebalancingSetTokenFactory.spec.ts +++ b/test/contracts/core/tokens/rebalancingSetTokenFactory.spec.ts @@ -93,8 +93,10 @@ contract('RebalancingSetTokenFactory', accounts => { naturalUnit, ); + const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); rebalancingSetTokenFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( core.address, + rebalancingComponentWhiteList.address ); await coreWrapper.addFactoryAsync(core, rebalancingSetTokenFactory); }); diff --git a/utils/wrappers/coreWrapper.ts b/utils/wrappers/coreWrapper.ts index c2925e985..870a0c6b4 100644 --- a/utils/wrappers/coreWrapper.ts +++ b/utils/wrappers/coreWrapper.ts @@ -136,12 +136,14 @@ export class CoreWrapper { public async deployRebalancingSetTokenFactoryAsync( coreAddress: Address, + componentWhitelistAddress: Address, minimumRebalanceInterval: BigNumber = ONE_DAY_IN_SECONDS, minimumProposalPeriod: BigNumber = ONE_DAY_IN_SECONDS, from: Address = this._tokenOwnerAddress ): Promise { const truffleTokenFactory = await RebalancingSetTokenFactory.new( coreAddress, + componentWhitelistAddress, minimumRebalanceInterval, minimumProposalPeriod, { from }, From b3389616e0cce2efafec82460bd2a20ceda2b1b6 Mon Sep 17 00:00:00 2001 From: Alexander Soong Date: Sun, 6 Jan 2019 19:19:14 -0800 Subject: [PATCH 2/5] Update specs to include whitelist --- .../interfaces/IRebalancingSetFactory.sol | 10 ++ contracts/core/interfaces/IWhiteList.sol | 41 +++++++ contracts/core/tokens/RebalancingSetToken.sol | 1 + .../tokens/RebalancingSetTokenFactory.sol | 3 +- .../StandardProposeLibrary.sol | 17 +++ .../modules/rebalanceAuctionModule.spec.ts | 28 +++-- .../core/tokens/rebalancingSetToken.spec.ts | 112 +++++++++++++----- utils/wrappers/coreWrapper.ts | 25 ++++ utils/wrappers/rebalancingWrapper.ts | 71 ++++++++++- 9 files changed, 266 insertions(+), 42 deletions(-) create mode 100644 contracts/core/interfaces/IWhiteList.sol diff --git a/contracts/core/interfaces/IRebalancingSetFactory.sol b/contracts/core/interfaces/IRebalancingSetFactory.sol index 910329483..47bbcb8d4 100644 --- a/contracts/core/interfaces/IRebalancingSetFactory.sol +++ b/contracts/core/interfaces/IRebalancingSetFactory.sol @@ -17,6 +17,7 @@ pragma solidity 0.4.25; import { ISetFactory } from "./ISetFactory.sol"; +import { IWhiteList } from "./IWhiteList.sol"; /** @@ -29,6 +30,15 @@ import { ISetFactory } from "./ISetFactory.sol"; contract IRebalancingSetFactory is ISetFactory { + /** + * Getter for component WhiteList address on RebalancingSetTokenFactory + * + * @return address Address of component WhiteList contract + */ + function rebalanceComponentWhitelist() + external + returns (IWhiteList); + /** * Getter for minimumRebalanceInterval of RebalancingSetTokenFactory, used * to enforce rebalanceInterval when creating a RebalancingSetToken diff --git a/contracts/core/interfaces/IWhiteList.sol b/contracts/core/interfaces/IWhiteList.sol new file mode 100644 index 000000000..eb75d6d1a --- /dev/null +++ b/contracts/core/interfaces/IWhiteList.sol @@ -0,0 +1,41 @@ +/* + Copyright 2018 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.4.25; + +/** + * @title ISignatureValidator + * @author Set Protocol + * + * The ISignatureValidator interface provides a light-weight, structured way to interact with the + * Signature Validator contract from another contract. + */ +interface IWhiteList { + + /* ============ External Functions ============ */ + + /** + * Validates address against white list + * + * @param _address Address to check + */ + function whiteList( + address _address + ) + external + view + returns(bool); +} diff --git a/contracts/core/tokens/RebalancingSetToken.sol b/contracts/core/tokens/RebalancingSetToken.sol index 82ac34a45..1206a4105 100644 --- a/contracts/core/tokens/RebalancingSetToken.sol +++ b/contracts/core/tokens/RebalancingSetToken.sol @@ -222,6 +222,7 @@ contract RebalancingSetToken is // Validate proposal inputs and initialize auctionParameters auctionParameters = StandardProposeLibrary.propose( _nextSet, + factory, _auctionLibrary, _auctionTimeToPivot, _auctionStartPrice, diff --git a/contracts/core/tokens/RebalancingSetTokenFactory.sol b/contracts/core/tokens/RebalancingSetTokenFactory.sol index 9330fba15..58cb8a2c7 100644 --- a/contracts/core/tokens/RebalancingSetTokenFactory.sol +++ b/contracts/core/tokens/RebalancingSetTokenFactory.sol @@ -22,6 +22,7 @@ import { Bytes32 } from "../../lib/Bytes32.sol"; import { ICore } from "../interfaces/ICore.sol"; import { LibBytes } from "../../external/0x/LibBytes.sol"; import { RebalancingSetToken } from "./RebalancingSetToken.sol"; +import { IWhiteList } from "../interfaces/IWhiteList.sol"; /** @@ -78,7 +79,7 @@ contract RebalancingSetTokenFactory { public { core = _core; - rebalanceComponentWhitelist = _componentWhitelist; + rebalanceComponentWhitelist = IWhiteList(_componentWhitelist); minimumRebalanceInterval = _minimumRebalanceInterval; minimumProposalPeriod = _minimumProposalPeriod; } diff --git a/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol b/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol index 7ea26e742..5e320d56e 100644 --- a/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol +++ b/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol @@ -21,7 +21,9 @@ import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IAuctionPriceCurve } from "../../lib/auction-price-libraries/IAuctionPriceCurve.sol"; import { ICore } from "../../interfaces/ICore.sol"; +import { IRebalancingSetFactory } from "../../interfaces/IRebalancingSetFactory.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; +import { IWhiteList } from "../../interfaces/IWhiteList.sol"; import { RebalancingHelperLibrary } from "../../lib/RebalancingHelperLibrary.sol"; @@ -35,10 +37,12 @@ library StandardProposeLibrary { using SafeMath for uint256; /* ============ Constants ============ */ + uint256 constant MIN_AUCTION_TIME_TO_PIVOT = 21600; uint256 constant MAX_AUCTION_TIME_TO_PIVOT = 259200; /* ============ Structs ============ */ + struct ProposeAuctionParameters { address manager; address currentSet; @@ -54,6 +58,7 @@ library StandardProposeLibrary { * Function used to validate inputs to propose function and initialize auctionParameters struct * * @param _nextSet The Set to rebalance into + * @param _factory The Factory that created the rebalancing set token * @param _auctionLibrary The library used to calculate the Dutch Auction price * @param _auctionTimeToPivot The amount of time for the auction to go ffrom start to pivot price * @param _auctionStartPrice The price to start the auction at @@ -63,6 +68,7 @@ library StandardProposeLibrary { */ function propose( address _nextSet, + address _factory, address _auctionLibrary, uint256 _auctionTimeToPivot, uint256 _auctionStartPrice, @@ -99,6 +105,17 @@ library StandardProposeLibrary { "RebalancingSetToken.propose: Invalid or disabled proposed SetToken address" ); + // Check proposed components on whitelist. This is to ensure managers are unable to add contract addresses + // to a propose that prohibit the set from carrying out an auction i.e. a token that only the manager possesses + IWhiteList rebalanceComponentWhitelist = IRebalancingSetFactory(_factory).rebalanceComponentWhitelist(); + address[] memory components = ISetToken(_nextSet).getComponents(); + for (uint256 i = 0; i < components.length; i++) { + require( + rebalanceComponentWhitelist.whiteList(components[i]), + "RebalancingSetToken.propose: Proposed set contains invalid component token" + ); + } + // Check that the auction library is a valid priceLibrary tracked by Core require( _proposeParameters.coreInstance.validPriceLibraries(_auctionLibrary), diff --git a/test/contracts/core/modules/rebalanceAuctionModule.spec.ts b/test/contracts/core/modules/rebalanceAuctionModule.spec.ts index d4e6c0c8c..87578069b 100644 --- a/test/contracts/core/modules/rebalanceAuctionModule.spec.ts +++ b/test/contracts/core/modules/rebalanceAuctionModule.spec.ts @@ -20,6 +20,7 @@ import { SignatureValidatorContract, TransferProxyContract, VaultContract, + WhiteListContract, } from '@utils/contracts'; import { ether } from '@utils/units'; import { @@ -65,6 +66,7 @@ contract('RebalanceAuctionModule', accounts => { let signatureValidator: SignatureValidatorContract; let rebalanceAuctionModuleMock: RebalanceAuctionModuleMockContract; let factory: SetTokenFactoryContract; + let rebalancingComponentWhiteList: WhiteListContract; let rebalancingFactory: RebalancingSetTokenFactoryContract; let constantAuctionPriceCurve: ConstantAuctionPriceCurveContract; @@ -98,7 +100,7 @@ contract('RebalanceAuctionModule', accounts => { await coreWrapper.addModuleAsync(coreMock, rebalanceAuctionModuleMock.address); factory = await coreWrapper.deploySetTokenFactoryAsync(coreMock.address); - const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); + rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); rebalancingFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( coreMock.address, rebalancingComponentWhiteList.address, @@ -196,8 +198,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -212,8 +215,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -349,8 +353,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -365,8 +370,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -442,8 +448,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -458,8 +465,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -506,8 +514,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -572,8 +581,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); diff --git a/test/contracts/core/tokens/rebalancingSetToken.spec.ts b/test/contracts/core/tokens/rebalancingSetToken.spec.ts index 918b3d13b..bae777792 100644 --- a/test/contracts/core/tokens/rebalancingSetToken.spec.ts +++ b/test/contracts/core/tokens/rebalancingSetToken.spec.ts @@ -21,6 +21,7 @@ import { StandardTokenMockContract, TransferProxyContract, VaultContract, + WhiteListContract, } from '@utils/contracts'; import { Blockchain } from '@utils/blockchain'; import { ether } from '@utils/units'; @@ -77,6 +78,7 @@ contract('RebalancingSetToken', accounts => { let signatureValidator: SignatureValidatorContract; let factory: SetTokenFactoryContract; let rebalancingFactory: RebalancingSetTokenFactoryContract; + let rebalancingComponentWhiteList: WhiteListContract; let constantAuctionPriceCurve: ConstantAuctionPriceCurveContract; const coreWrapper = new CoreWrapper(deployerAccount, deployerAccount); @@ -109,7 +111,7 @@ contract('RebalancingSetToken', accounts => { await coreWrapper.addModuleAsync(coreMock, rebalanceAuctionModule.address); factory = await coreWrapper.deploySetTokenFactoryAsync(coreMock.address); - const rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); + rebalancingComponentWhiteList = await coreWrapper.deployWhiteListAsync(); rebalancingFactory = await coreWrapper.deployRebalancingSetTokenFactoryAsync( coreMock.address, rebalancingComponentWhiteList.address, @@ -536,8 +538,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -560,8 +563,9 @@ contract('RebalancingSetToken', accounts => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -694,8 +698,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -822,8 +827,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -846,8 +852,9 @@ contract('RebalancingSetToken', accounts => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -966,6 +973,13 @@ contract('RebalancingSetToken', accounts => { nextSetToken = setTokens[1]; reproposeRebalancingSetToken = setTokens[2]; + const nextSetTokenComponentAddresses = await nextSetToken.getComponents.callAsync(); + const reproposeRebalancingSetComponentAddresses = await reproposeRebalancingSetToken.getComponents.callAsync(); + const componentsToWhiteList = _.uniq( + nextSetTokenComponentAddresses.concat(reproposeRebalancingSetComponentAddresses) + ); + await coreWrapper.addTokensToWhiteList(componentsToWhiteList, rebalancingComponentWhiteList); + proposalPeriod = ONE_DAY_IN_SECONDS; rebalancingSetToken = await rebalancingWrapper.createDefaultRebalancingSetTokenAsync( coreMock, @@ -1064,6 +1078,20 @@ contract('RebalancingSetToken', accounts => { await SetTestUtils.assertLogEquivalence(formattedLogs, expectedLogs); }); + describe('when one of the components in the next set is not on the whitelist', async () => { + beforeEach(async () => { + const nextSetComponents = await nextSetToken.getComponents.callAsync(); + await rebalancingComponentWhiteList.removeAddress.sendTransactionAsync( + nextSetComponents[0], + { from: deployerAccount } + ); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + describe('but the rebalance interval has not elapsed', async () => { beforeEach(async () => { subjectTimeFastForward = ONE_DAY_IN_SECONDS.sub(10); @@ -1166,11 +1194,18 @@ contract('RebalancingSetToken', accounts => { let timeJump: BigNumber; beforeEach(async () => { - await rebalancingWrapper.defaultTransitionToProposeAsync( + const auctionTimeToPivot = new BigNumber(100000); + const auctionStartPrice = new BigNumber(500); + const auctionPivotPrice = DEFAULT_AUCTION_PRICE_NUMERATOR; + + await rebalancingWrapper.transitionToProposeAsync( coreMock, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, + auctionTimeToPivot, + auctionStartPrice, + auctionPivotPrice, managerAccount ); @@ -1198,11 +1233,18 @@ contract('RebalancingSetToken', accounts => { describe('when propose is called from Rebalance state', async () => { beforeEach(async () => { - await rebalancingWrapper.defaultTransitionToRebalanceAsync( + const auctionTimeToPivot = new BigNumber(100000); + const auctionStartPrice = new BigNumber(500); + const auctionPivotPrice = DEFAULT_AUCTION_PRICE_NUMERATOR; + + await rebalancingWrapper.transitionToRebalanceAsync( coreMock, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, + auctionTimeToPivot, + auctionStartPrice, + auctionPivotPrice, managerAccount ); }); @@ -1214,19 +1256,26 @@ contract('RebalancingSetToken', accounts => { describe('when propose is called from Drawdown State', async () => { beforeEach(async () => { - // Issue currentSetToken - await coreMock.issue.sendTransactionAsync(currentSetToken.address, ether(9), {from: deployerAccount}); - await erc20Wrapper.approveTransfersAsync([currentSetToken], transferProxy.address); + // Issue currentSetToken + await coreMock.issue.sendTransactionAsync(currentSetToken.address, ether(9), {from: deployerAccount}); + await erc20Wrapper.approveTransfersAsync([currentSetToken], transferProxy.address); - // Use issued currentSetToken to issue rebalancingSetToken - const rebalancingSetQuantityToIssue = ether(7); - await coreMock.issue.sendTransactionAsync(rebalancingSetToken.address, rebalancingSetQuantityToIssue); + // Use issued currentSetToken to issue rebalancingSetToken + const rebalancingSetQuantityToIssue = ether(7); + await coreMock.issue.sendTransactionAsync(rebalancingSetToken.address, rebalancingSetQuantityToIssue); - await rebalancingWrapper.defaultTransitionToRebalanceAsync( + const auctionTimeToPivot = new BigNumber(100000); + const auctionStartPrice = new BigNumber(500); + const auctionPivotPrice = DEFAULT_AUCTION_PRICE_NUMERATOR; + + await rebalancingWrapper.transitionToRebalanceAsync( coreMock, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, + auctionTimeToPivot, + auctionStartPrice, + auctionPivotPrice, managerAccount ); @@ -1308,8 +1357,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1493,8 +1543,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1509,8 +1560,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1589,8 +1641,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1605,8 +1658,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1704,8 +1758,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1720,8 +1775,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1801,8 +1857,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -1817,8 +1874,9 @@ contract('RebalancingSetToken', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); diff --git a/utils/wrappers/coreWrapper.ts b/utils/wrappers/coreWrapper.ts index 870a0c6b4..602b11996 100644 --- a/utils/wrappers/coreWrapper.ts +++ b/utils/wrappers/coreWrapper.ts @@ -496,6 +496,31 @@ export class CoreWrapper { return balances; } + /* ============ WhiteList ============ */ + + public async addTokensToWhiteList( + tokenAddresses: Address[], + whiteList: WhiteListContract, + from: Address = this._contractOwnerAddress, + ): Promise { + const addAddressPromises = _.map(tokenAddresses, address => { + this.addTokenToWhiteList(address, whiteList); + }); + + await Promise.all(addAddressPromises); + } + + public async addTokenToWhiteList( + address: Address, + whiteList: WhiteListContract, + from: Address = this._contractOwnerAddress, + ): Promise { + await whiteList.addAddress.sendTransactionAsync( + address, + { from }, + ); + } + /* ============ CoreFactory Extension ============ */ public async createSetTokenAsync( diff --git a/utils/wrappers/rebalancingWrapper.ts b/utils/wrappers/rebalancingWrapper.ts index c34fe8d48..779de69c0 100644 --- a/utils/wrappers/rebalancingWrapper.ts +++ b/utils/wrappers/rebalancingWrapper.ts @@ -9,7 +9,8 @@ import { LinearAuctionPriceCurveContract, SetTokenContract, RebalancingSetTokenContract, - VaultContract + VaultContract, + WhiteListContract, } from '../contracts'; import { BigNumber } from 'bignumber.js'; @@ -251,8 +252,9 @@ export class RebalancingWrapper { public async defaultTransitionToProposeAsync( core: CoreLikeContract, + rebalancingComponentWhiteList: WhiteListContract, rebalancingSetToken: RebalancingSetTokenContract, - newRebalancingSetToken: Address, + nextSetToken: SetTokenContract, auctionLibrary: Address, caller: Address ): Promise { @@ -261,6 +263,35 @@ export class RebalancingWrapper { const auctionStartPrice = new BigNumber(500); const auctionPivotPrice = DEFAULT_AUCTION_PRICE_NUMERATOR; + const nextSetTokenComponentAddresses = await nextSetToken.getComponents.callAsync(); + await this._coreWrapper.addTokensToWhiteList( + nextSetTokenComponentAddresses, + rebalancingComponentWhiteList + ); + + // Transition to propose + await this.transitionToProposeAsync( + core, + rebalancingSetToken, + nextSetToken, + auctionLibrary, + auctionTimeToPivot, + auctionStartPrice, + auctionPivotPrice, + caller, + ); + } + + public async transitionToProposeAsync( + core: CoreLikeContract, + rebalancingSetToken: RebalancingSetTokenContract, + nextSetToken: SetTokenContract, + auctionLibrary: Address, + auctionTimeToPivot: BigNumber, + auctionStartPrice: BigNumber, + auctionPivotPrice: BigNumber, + caller: Address + ): Promise { // Approve price library await core.addPriceLibrary.sendTransactionAsync( auctionLibrary, @@ -270,7 +301,7 @@ export class RebalancingWrapper { // Transition to propose await this._blockchain.increaseTimeAsync(ONE_DAY_IN_SECONDS.add(1)); await rebalancingSetToken.propose.sendTransactionAsync( - newRebalancingSetToken, + nextSetToken.address, auctionLibrary, auctionTimeToPivot, auctionStartPrice, @@ -281,17 +312,47 @@ export class RebalancingWrapper { public async defaultTransitionToRebalanceAsync( core: CoreLikeContract, + rebalancingComponentWhiteList: WhiteListContract, rebalancingSetToken: RebalancingSetTokenContract, - newRebalancingSetToken: Address, + nextSetToken: SetTokenContract, auctionLibrary: Address, caller: Address ): Promise { // Transition to propose await this.defaultTransitionToProposeAsync( + core, + rebalancingComponentWhiteList, + rebalancingSetToken, + nextSetToken, + auctionLibrary, + caller + ); + + // Transition to rebalance + await this._blockchain.increaseTimeAsync(ONE_DAY_IN_SECONDS.add(1)); + await rebalancingSetToken.startRebalance.sendTransactionAsync( + { from: caller, gas: DEFAULT_GAS } + ); + } + + public async transitionToRebalanceAsync( + core: CoreLikeContract, + rebalancingSetToken: RebalancingSetTokenContract, + nextSetToken: SetTokenContract, + auctionLibrary: Address, + auctionTimeToPivot: BigNumber, + auctionStartPrice: BigNumber, + auctionPivotPrice: BigNumber, + caller: Address + ): Promise { + await this.transitionToProposeAsync( core, rebalancingSetToken, - newRebalancingSetToken, + nextSetToken, auctionLibrary, + auctionTimeToPivot, + auctionStartPrice, + auctionPivotPrice, caller ); From 0aaba9a6f8921e0299005fb42616bbe6f68b89a4 Mon Sep 17 00:00:00 2001 From: Alexander Soong Date: Sun, 6 Jan 2019 19:21:13 -0800 Subject: [PATCH 3/5] Update interface description --- contracts/core/interfaces/IWhiteList.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/core/interfaces/IWhiteList.sol b/contracts/core/interfaces/IWhiteList.sol index eb75d6d1a..98bfb1e56 100644 --- a/contracts/core/interfaces/IWhiteList.sol +++ b/contracts/core/interfaces/IWhiteList.sol @@ -17,11 +17,10 @@ pragma solidity 0.4.25; /** - * @title ISignatureValidator + * @title IWhiteList * @author Set Protocol * - * The ISignatureValidator interface provides a light-weight, structured way to interact with the - * Signature Validator contract from another contract. + * The IWhiteList interface exposes the whitelist mapping to check components */ interface IWhiteList { From 866faba660b40d577b5ed6a371b4d3ce437fbbf7 Mon Sep 17 00:00:00 2001 From: Alexander Soong Date: Sun, 6 Jan 2019 19:33:52 -0800 Subject: [PATCH 4/5] Fix broken rebase --- .../core/modules/rebalanceAuctionModule.spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/contracts/core/modules/rebalanceAuctionModule.spec.ts b/test/contracts/core/modules/rebalanceAuctionModule.spec.ts index 87578069b..bb0fcc85a 100644 --- a/test/contracts/core/modules/rebalanceAuctionModule.spec.ts +++ b/test/contracts/core/modules/rebalanceAuctionModule.spec.ts @@ -666,8 +666,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToProposeAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -682,8 +683,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); @@ -700,8 +702,9 @@ contract('RebalanceAuctionModule', accounts => { beforeEach(async () => { await rebalancingWrapper.defaultTransitionToRebalanceAsync( coreMock, + rebalancingComponentWhiteList, rebalancingSetToken, - nextSetToken.address, + nextSetToken, constantAuctionPriceCurve.address, managerAccount ); From e1b88eae00b46a15e3d7cbfed8fdd9b3cecf0f4c Mon Sep 17 00:00:00 2001 From: Alexander Soong Date: Sun, 6 Jan 2019 20:58:43 -0800 Subject: [PATCH 5/5] Update component token verification --- .../interfaces/IRebalancingSetFactory.sol | 9 ----- contracts/core/interfaces/IWhiteList.sol | 14 ++++++++ contracts/core/tokens/RebalancingSetToken.sol | 8 +++-- .../tokens/RebalancingSetTokenFactory.sol | 1 + .../StandardProposeLibrary.sol | 17 ++++------ contracts/lib/WhiteList.sol | 24 +++++++++++++- .../core/tokens/rebalancingSetToken.spec.ts | 32 +++++++++++------- test/contracts/lib/whiteList.spec.ts | 33 +++++++++++++++++++ utils/wrappers/rebalancingWrapper.ts | 2 ++ 9 files changed, 105 insertions(+), 35 deletions(-) diff --git a/contracts/core/interfaces/IRebalancingSetFactory.sol b/contracts/core/interfaces/IRebalancingSetFactory.sol index 47bbcb8d4..4599eb6f4 100644 --- a/contracts/core/interfaces/IRebalancingSetFactory.sol +++ b/contracts/core/interfaces/IRebalancingSetFactory.sol @@ -30,15 +30,6 @@ import { IWhiteList } from "./IWhiteList.sol"; contract IRebalancingSetFactory is ISetFactory { - /** - * Getter for component WhiteList address on RebalancingSetTokenFactory - * - * @return address Address of component WhiteList contract - */ - function rebalanceComponentWhitelist() - external - returns (IWhiteList); - /** * Getter for minimumRebalanceInterval of RebalancingSetTokenFactory, used * to enforce rebalanceInterval when creating a RebalancingSetToken diff --git a/contracts/core/interfaces/IWhiteList.sol b/contracts/core/interfaces/IWhiteList.sol index 98bfb1e56..fbf2cdab3 100644 --- a/contracts/core/interfaces/IWhiteList.sol +++ b/contracts/core/interfaces/IWhiteList.sol @@ -30,6 +30,7 @@ interface IWhiteList { * Validates address against white list * * @param _address Address to check + * @return bool Whether passed in address is whitelisted */ function whiteList( address _address @@ -37,4 +38,17 @@ interface IWhiteList { external view returns(bool); + + /** + * Verifies an array of addresses against the whitelist + * + * @param _addresses Array of addresses to verify + * @return bool Whether all addresses in the list are whitelsited + */ + function areValidAddresses( + address[] _addresses + ) + external + view + returns(bool); } diff --git a/contracts/core/tokens/RebalancingSetToken.sol b/contracts/core/tokens/RebalancingSetToken.sol index 1206a4105..f122614ee 100644 --- a/contracts/core/tokens/RebalancingSetToken.sol +++ b/contracts/core/tokens/RebalancingSetToken.sol @@ -30,6 +30,7 @@ import { ICore } from "../interfaces/ICore.sol"; import { IRebalancingSetFactory } from "../interfaces/IRebalancingSetFactory.sol"; import { ISetToken } from "../interfaces/ISetToken.sol"; import { IVault } from "../interfaces/IVault.sol"; +import { IWhiteList } from "../interfaces/IWhiteList.sol"; import { RebalancingHelperLibrary } from "../lib/RebalancingHelperLibrary.sol"; import { StandardFailAuctionLibrary } from "./rebalancing-libraries/StandardFailAuctionLibrary.sol"; import { StandardPlaceBidLibrary } from "./rebalancing-libraries/StandardPlaceBidLibrary.sol"; @@ -58,7 +59,6 @@ contract RebalancingSetToken is uint256 constant MIN_AUCTION_TIME_TO_PIVOT = 21600; uint256 constant MAX_AUCTION_TIME_TO_PIVOT = 259200; - /* ============ Enums ============ */ enum State { Default, Proposal, Rebalance } @@ -73,6 +73,7 @@ contract RebalancingSetToken is // Core and Vault instances ICore private coreInstance; IVault private vaultInstance; + IWhiteList private componentWhiteListInstance; // All rebalancingSetTokens have same natural unit, still allows for // small amounts to be issued and attempts to reduce slippage as much @@ -129,6 +130,7 @@ contract RebalancingSetToken is * @param _initialUnitShares Units of currentSet that equals one share * @param _proposalPeriod Amount of time for users to inspect a rebalance proposal * @param _rebalanceInterval Minimum amount of time between rebalances + * @param _componentWhiteList Address of component WhiteList contract * @param _name The name of the new RebalancingSetToken * @param _symbol The symbol of the new RebalancingSetToken */ @@ -140,6 +142,7 @@ contract RebalancingSetToken is uint256 _initialUnitShares, uint256 _proposalPeriod, uint256 _rebalanceInterval, + address _componentWhiteList, string _name, string _symbol ) @@ -177,6 +180,7 @@ contract RebalancingSetToken is coreInstance = ICore(core); vault = coreInstance.vault(); vaultInstance = IVault(vault); + componentWhiteListInstance = IWhiteList(_componentWhiteList); factory = _factory; manager = _manager; currentSet = _initialSet; @@ -222,11 +226,11 @@ contract RebalancingSetToken is // Validate proposal inputs and initialize auctionParameters auctionParameters = StandardProposeLibrary.propose( _nextSet, - factory, _auctionLibrary, _auctionTimeToPivot, _auctionStartPrice, _auctionPivotPrice, + componentWhiteListInstance, proposeParameters ); diff --git a/contracts/core/tokens/RebalancingSetTokenFactory.sol b/contracts/core/tokens/RebalancingSetTokenFactory.sol index 58cb8a2c7..80f1265be 100644 --- a/contracts/core/tokens/RebalancingSetTokenFactory.sol +++ b/contracts/core/tokens/RebalancingSetTokenFactory.sol @@ -158,6 +158,7 @@ contract RebalancingSetTokenFactory { _units[0], parameters.proposalPeriod, parameters.rebalanceInterval, + rebalanceComponentWhitelist, _name.bytes32ToString(), _symbol.bytes32ToString() ); diff --git a/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol b/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol index 5e320d56e..1e1a98565 100644 --- a/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol +++ b/contracts/core/tokens/rebalancing-libraries/StandardProposeLibrary.sol @@ -21,7 +21,6 @@ import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IAuctionPriceCurve } from "../../lib/auction-price-libraries/IAuctionPriceCurve.sol"; import { ICore } from "../../interfaces/ICore.sol"; -import { IRebalancingSetFactory } from "../../interfaces/IRebalancingSetFactory.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { IWhiteList } from "../../interfaces/IWhiteList.sol"; import { RebalancingHelperLibrary } from "../../lib/RebalancingHelperLibrary.sol"; @@ -58,21 +57,21 @@ library StandardProposeLibrary { * Function used to validate inputs to propose function and initialize auctionParameters struct * * @param _nextSet The Set to rebalance into - * @param _factory The Factory that created the rebalancing set token * @param _auctionLibrary The library used to calculate the Dutch Auction price * @param _auctionTimeToPivot The amount of time for the auction to go ffrom start to pivot price * @param _auctionStartPrice The price to start the auction at * @param _auctionPivotPrice The price at which the price curve switches from linear to exponential + * @param _componentWhiteList Instance of component WhiteList to verify * @param _proposeParameters Rebalancing Set Token state parameters needed to execute logic * @return Struct containing auction price curve parameters */ function propose( address _nextSet, - address _factory, address _auctionLibrary, uint256 _auctionTimeToPivot, uint256 _auctionStartPrice, uint256 _auctionPivotPrice, + IWhiteList _componentWhiteList, ProposeAuctionParameters memory _proposeParameters ) internal @@ -107,14 +106,10 @@ library StandardProposeLibrary { // Check proposed components on whitelist. This is to ensure managers are unable to add contract addresses // to a propose that prohibit the set from carrying out an auction i.e. a token that only the manager possesses - IWhiteList rebalanceComponentWhitelist = IRebalancingSetFactory(_factory).rebalanceComponentWhitelist(); - address[] memory components = ISetToken(_nextSet).getComponents(); - for (uint256 i = 0; i < components.length; i++) { - require( - rebalanceComponentWhitelist.whiteList(components[i]), - "RebalancingSetToken.propose: Proposed set contains invalid component token" - ); - } + require( + _componentWhiteList.areValidAddresses(ISetToken(_nextSet).getComponents()), + "RebalancingSetToken.propose: Proposed set contains invalid component token" + ); // Check that the auction library is a valid priceLibrary tracked by Core require( diff --git a/contracts/lib/WhiteList.sol b/contracts/lib/WhiteList.sol index 9ccf20cd5..d431a7615 100644 --- a/contracts/lib/WhiteList.sol +++ b/contracts/lib/WhiteList.sol @@ -139,4 +139,26 @@ contract WhiteList is { return addresses; } -} \ No newline at end of file + + /** + * Verifies an array of addresses against the whitelist + * + * @param _addresses Array of addresses to verify + * @return bool Whether all addresses in the list are whitelsited + */ + function areValidAddresses( + address[] _addresses + ) + external + view + returns(bool) + { + for (uint256 i = 0; i < _addresses.length; i++) { + if (!whiteList[_addresses[i]]) { + return false; + } + } + + return true; + } +} diff --git a/test/contracts/core/tokens/rebalancingSetToken.spec.ts b/test/contracts/core/tokens/rebalancingSetToken.spec.ts index bae777792..53a6078ee 100644 --- a/test/contracts/core/tokens/rebalancingSetToken.spec.ts +++ b/test/contracts/core/tokens/rebalancingSetToken.spec.ts @@ -137,6 +137,7 @@ contract('RebalancingSetToken', accounts => { let subjectInitialUnitShares: BigNumber; let subjectProposalPeriod: BigNumber; let subjectRebalanceInterval: BigNumber; + let subjectComponentWhiteList: Address; const subjectName: string = 'Rebalancing Set'; const subjectSymbol: string = 'RBSET'; @@ -149,6 +150,7 @@ contract('RebalancingSetToken', accounts => { subjectInitialUnitShares = DEFAULT_UNIT_SHARES; subjectProposalPeriod = ONE_DAY_IN_SECONDS; subjectRebalanceInterval = ONE_DAY_IN_SECONDS; + subjectComponentWhiteList = rebalancingComponentWhiteList.address; }); async function subject(): Promise { @@ -159,6 +161,7 @@ contract('RebalancingSetToken', accounts => { subjectInitialUnitShares, subjectProposalPeriod, subjectRebalanceInterval, + subjectComponentWhiteList, subjectName, subjectSymbol, ); @@ -288,6 +291,7 @@ contract('RebalancingSetToken', accounts => { initialUnitShares, proposalPeriod, rebalanceInterval, + rebalancingComponentWhiteList.address, ); subjectCaller = managerAccount; @@ -326,6 +330,7 @@ contract('RebalancingSetToken', accounts => { initialUnitShares, proposalPeriod, rebalanceInterval, + rebalancingComponentWhiteList.address, ); subjectCaller = managerAccount; @@ -366,6 +371,7 @@ contract('RebalancingSetToken', accounts => { initialUnitShares, proposalPeriod, rebalanceInterval, + rebalancingComponentWhiteList.address, ); subjectCaller = managerAccount; @@ -437,6 +443,7 @@ contract('RebalancingSetToken', accounts => { initialUnitShares, proposalPeriod, rebalanceInterval, + rebalancingComponentWhiteList.address, ); subjectIssuer = deployerAccount, @@ -899,6 +906,7 @@ contract('RebalancingSetToken', accounts => { initialUnitShares, proposalPeriod, rebalanceInterval, + rebalancingComponentWhiteList.address ); subjectNewManager = otherAccount, @@ -1092,7 +1100,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the rebalance interval has not elapsed', async () => { + describe('when the rebalance interval has not elapsed', async () => { beforeEach(async () => { subjectTimeFastForward = ONE_DAY_IN_SECONDS.sub(10); }); @@ -1102,7 +1110,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but not by the token manager', async () => { + describe('when not by the token manager', async () => { beforeEach(async () => { subjectCaller = otherAccount; }); @@ -1112,7 +1120,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the auction library is not approved by Core', async () => { + describe('when the auction library is not approved by Core', async () => { beforeEach(async () => { subjectAuctionLibrary = invalidAccount; }); @@ -1122,7 +1130,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the time to pivot is less than 21600', async () => { + describe('when the time to pivot is less than 21600', async () => { beforeEach(async () => { subjectAuctionTimeToPivot = ZERO; }); @@ -1132,7 +1140,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the time to pivot is greater than 259200', async () => { + describe('when the time to pivot is greater than 259200', async () => { beforeEach(async () => { subjectAuctionTimeToPivot = new BigNumber(300000); }); @@ -1142,7 +1150,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the pivot price is less than .5', async () => { + describe('when the pivot price is less than .5', async () => { beforeEach(async () => { const pivotPrice = new BigNumber(.4); subjectAuctionPivotPrice = DEFAULT_AUCTION_PRICE_DENOMINATOR.mul(pivotPrice); @@ -1153,7 +1161,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the pivot price is greater than 5', async () => { + describe('when the pivot price is greater than 5', async () => { beforeEach(async () => { const pivotPrice = new BigNumber(6); subjectAuctionPivotPrice = DEFAULT_AUCTION_PRICE_DENOMINATOR.mul(pivotPrice); @@ -1164,7 +1172,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but the proposed nextSet is not approved by Core', async () => { + describe('when the proposed nextSet is not approved by Core', async () => { beforeEach(async () => { subjectRebalancingToken = fakeTokenAccount; }); @@ -1174,7 +1182,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe("but the new proposed set's natural unit is not a multiple of the current set", async () => { + describe("when the new proposed set's natural unit is not a multiple of the current set", async () => { before(async () => { // a setToken with natural unit ether(.003) and setToken with natural unit ether(.002) are being used naturalUnits = [ether(.003), ether(.002), ether(.001)]; @@ -1528,7 +1536,7 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but not enough time has passed before proposal period has elapsed', async () => { + describe('when not enough time has passed before proposal period has elapsed', async () => { beforeEach(async () => { subjectTimeFastForward = ONE_DAY_IN_SECONDS.sub(10); }); @@ -1940,13 +1948,13 @@ contract('RebalancingSetToken', accounts => { }); }); - describe('but pivot point has not been reached', async () => { + describe('when pivot point has not been reached', async () => { it('should revert', async () => { await expectRevertError(subject()); }); }); - describe('but auction could be settled', async () => { + describe('when auction could be settled', async () => { beforeEach(async () => { const defaultTimeToPivot = new BigNumber(100000); await blockchain.increaseTimeAsync(defaultTimeToPivot.add(1)); diff --git a/test/contracts/lib/whiteList.spec.ts b/test/contracts/lib/whiteList.spec.ts index 0597f3026..440272deb 100644 --- a/test/contracts/lib/whiteList.spec.ts +++ b/test/contracts/lib/whiteList.spec.ts @@ -31,6 +31,7 @@ contract('WhiteList', accounts => { firstTokenAddress, secondTokenAddress, thirdTokenAddress, + fourthTokenAddress, ] = accounts; let whiteList: WhiteListContract; @@ -221,4 +222,36 @@ contract('WhiteList', accounts => { }); }); }); + + describe('#areValidAddresses', async () => { + let subjectAddressesToVerify: Address[]; + + beforeEach(async () => { + whiteList = await coreWrapper.deployWhiteListAsync([firstTokenAddress, secondTokenAddress, thirdTokenAddress]); + + subjectAddressesToVerify = [firstTokenAddress, secondTokenAddress, thirdTokenAddress]; + }); + + async function subject(): Promise { + return await whiteList.areValidAddresses.callAsync(subjectAddressesToVerify); + } + + it('returns true', async () => { + const validity = await subject(); + + expect(validity).to.be.true; + }); + + describe('when one of the tokens is not whitelisted', async () => { + beforeEach(async () => { + subjectAddressesToVerify = [firstTokenAddress, secondTokenAddress, thirdTokenAddress, fourthTokenAddress]; + }); + + it('returns false', async () => { + const validity = await subject(); + + expect(validity).to.be.false; + }); + }); + }); }); diff --git a/utils/wrappers/rebalancingWrapper.ts b/utils/wrappers/rebalancingWrapper.ts index 779de69c0..f7e2c3213 100644 --- a/utils/wrappers/rebalancingWrapper.ts +++ b/utils/wrappers/rebalancingWrapper.ts @@ -66,6 +66,7 @@ export class RebalancingWrapper { initialShareRatio: BigNumber, proposalPeriod: BigNumber, rebalanceCoolOffPeriod: BigNumber, + rebalancingComponentWhiteListAddress: Address, name: string = 'Rebalancing Set', symbol: string = 'RBSET', from: Address = this._tokenOwnerAddress @@ -77,6 +78,7 @@ export class RebalancingWrapper { initialShareRatio, proposalPeriod, rebalanceCoolOffPeriod, + rebalancingComponentWhiteListAddress, name, symbol, { from, gas: DEFAULT_GAS },