Skip to content

Commit 7e75980

Browse files
authored
Delegated Manager System audit fixes and upgrades [SIM-55] [SIM-156] (#21)
* add mutual upgrade to owner fee split update * add changelog to MutualUpgradeV2, add test case to delegatedManager * audit fix 5.3, add controller and checks for setTokens * audit fix 5.4.1, make setTokenFactory and managerCore immutable, add address zero check to setMethodologist
1 parent 6d52378 commit 7e75980

15 files changed

+446
-37
lines changed

contracts/factories/DelegatedManagerFactory.sol

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISe
2424

2525
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
2626
import { DelegatedManager } from "../manager/DelegatedManager.sol";
27+
import { IController } from "../interfaces/IController.sol";
2728
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
2829
import { IManagerCore } from "../interfaces/IManagerCore.sol";
2930
import { ISetTokenCreator } from "../interfaces/ISetTokenCreator.sol";
@@ -46,6 +47,7 @@ contract DelegatedManagerFactory {
4647
struct InitializeParams{
4748
address deployer;
4849
address owner;
50+
address methodologist;
4951
IDelegatedManager manager;
5052
bool isPending;
5153
}
@@ -79,8 +81,11 @@ contract DelegatedManagerFactory {
7981
// ManagerCore address
8082
IManagerCore public immutable managerCore;
8183

84+
// Controller address
85+
IController public immutable controller;
86+
8287
// SetTokenFactory address
83-
ISetTokenCreator public setTokenFactory;
88+
ISetTokenCreator public immutable setTokenFactory;
8489

8590
// Mapping which stores manager creation metadata between creation and initialization steps
8691
mapping(ISetToken=>InitializeParams) public initializeState;
@@ -90,15 +95,18 @@ contract DelegatedManagerFactory {
9095
/**
9196
* @dev Sets managerCore and setTokenFactory address.
9297
* @param _managerCore Address of ManagerCore protocol contract
98+
* @param _controller Address of Controller protocol contract
9399
* @param _setTokenFactory Address of SetTokenFactory protocol contract
94100
*/
95101
constructor(
96102
IManagerCore _managerCore,
103+
IController _controller,
97104
ISetTokenCreator _setTokenFactory
98-
)
99-
public
105+
)
106+
public
100107
{
101108
managerCore = _managerCore;
109+
controller = _controller;
102110
setTokenFactory = _setTokenFactory;
103111
}
104112

@@ -149,13 +157,12 @@ contract DelegatedManagerFactory {
149157

150158
DelegatedManager manager = _deployManager(
151159
setToken,
152-
_methodologist,
153160
_extensions,
154161
_operators,
155162
_assets
156163
);
157164

158-
_setInitializationState(setToken, address(manager), _owner);
165+
_setInitializationState(setToken, address(manager), _owner, _methodologist);
159166

160167
return (setToken, address(manager));
161168
}
@@ -189,19 +196,19 @@ contract DelegatedManagerFactory {
189196
external
190197
returns (address)
191198
{
199+
require(controller.isSet(address(_setToken)), "Must be controller-enabled SetToken");
192200
require(msg.sender == _setToken.manager(), "Must be manager");
193201

194202
_validateManagerParameters(_setToken.getComponents(), _extensions, _assets);
195203

196204
DelegatedManager manager = _deployManager(
197205
_setToken,
198-
_methodologist,
199206
_extensions,
200207
_operators,
201208
_assets
202209
);
203210

204-
_setInitializationState(_setToken, address(manager), _owner);
211+
_setInitializationState(_setToken, address(manager), _owner, _methodologist);
205212

206213
return address(manager);
207214
}
@@ -234,23 +241,27 @@ contract DelegatedManagerFactory {
234241
require(msg.sender == initializeState[_setToken].deployer, "Only deployer can initialize manager");
235242
_initializeTargets.validatePairsWithArray(_initializeBytecode);
236243

237-
IDelegatedManager manager = initializeState[_setToken].manager;
238-
manager.updateOwnerFeeSplit(_ownerFeeSplit);
239-
manager.updateOwnerFeeRecipient(_ownerFeeRecipient);
240-
241244
for (uint256 i = 0; i < _initializeTargets.length; i++) {
245+
address target = _initializeTargets[i];
246+
require(!controller.isSet(target), "Target must not be SetToken");
247+
242248
// Because we validate uniqueness of _initializeTargets only one transaction can be sent to each module or extension during this
243249
// transaction. Due to this no modules/extension can be used for any SetToken transactions other than initializing these contracts
244-
_initializeTargets[i].functionCallWithValue(_initializeBytecode[i], 0);
250+
target.functionCallWithValue(_initializeBytecode[i], 0);
245251
}
246252

253+
IDelegatedManager manager = initializeState[_setToken].manager;
254+
manager.updateOwnerFeeSplit(_ownerFeeSplit);
255+
manager.updateOwnerFeeRecipient(_ownerFeeRecipient);
256+
247257
// If the SetToken was factory-deployed & factory is its current `manager`, transfer
248258
// managership to the new DelegatedManager
249259
if (_setToken.manager() == address(this)) {
250260
_setToken.setManager(address(manager));
251261
}
252262

253263
manager.transferOwnership(initializeState[_setToken].owner);
264+
manager.setMethodologist(initializeState[_setToken].methodologist);
254265

255266
delete initializeState[_setToken];
256267

@@ -297,7 +308,6 @@ contract DelegatedManagerFactory {
297308
* Deploys a DelegatedManager
298309
*
299310
* @param _setToken Instance of SetToken to migrate to the DelegatedManager system
300-
* @param _methodologist Address to set as the DelegateManager's methodologist role
301311
* @param _extensions List of extensions authorized for the DelegateManager
302312
* @param _operators List of operators authorized for the DelegateManager
303313
* @param _assets List of assets DelegateManager can trade. When empty, asset allow list is not enforced
@@ -306,7 +316,6 @@ contract DelegatedManagerFactory {
306316
*/
307317
function _deployManager(
308318
ISetToken _setToken,
309-
address _methodologist,
310319
address[] memory _extensions,
311320
address[] memory _operators,
312321
address[] memory _assets
@@ -321,7 +330,7 @@ contract DelegatedManagerFactory {
321330
DelegatedManager newManager = new DelegatedManager(
322331
_setToken,
323332
address(this),
324-
_methodologist,
333+
address(this),
325334
_extensions,
326335
_operators,
327336
_assets,
@@ -347,15 +356,18 @@ contract DelegatedManagerFactory {
347356
* @param _setToken Instance of SetToken
348357
* @param _manager Address of DelegatedManager created for SetToken
349358
* @param _owner Address that will be given the `owner` DelegatedManager's role on initialization
359+
* @param _methodologist Address that will be given the `methodologist` DelegatedManager's role on initialization
350360
*/
351361
function _setInitializationState(
352362
ISetToken _setToken,
353363
address _manager,
354-
address _owner
364+
address _owner,
365+
address _methodologist
355366
) internal {
356367
initializeState[_setToken] = InitializeParams({
357368
deployer: msg.sender,
358369
owner: _owner,
370+
methodologist: _methodologist,
359371
manager: IDelegatedManager(_manager),
360372
isPending: true
361373
});

contracts/interfaces/IDelegatedManager.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ interface IDelegatedManager {
3131
function updateOwnerFeeSplit(uint256 _newFeeSplit) external;
3232

3333
function updateOwnerFeeRecipient(address _newFeeRecipient) external;
34+
35+
function setMethodologist(address _newMethodologist) external;
3436

3537
function transferOwnership(address _owner) external;
3638

contracts/lib/BaseGlobalExtension.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ abstract contract BaseGlobalExtension {
4444
/* ============ State Variables ============ */
4545

4646
// Address of the ManagerCore
47-
IManagerCore public managerCore;
47+
IManagerCore public immutable managerCore;
4848

49-
// Mapping from Set Token to DelegatedManager
49+
// Mapping from Set Token to DelegatedManager
5050
mapping(ISetToken => IDelegatedManager) public setManagers;
5151

5252
/* ============ Modifiers ============ */

contracts/lib/MutualUpgradeV2.sol

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
Copyright 2022 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
21+
/**
22+
* @title MutualUpgradeV2
23+
* @author Set Protocol
24+
*
25+
* The MutualUpgradeV2 contract contains a modifier for handling mutual upgrades between two parties
26+
*
27+
* CHANGELOG:
28+
* - Update mutualUpgrade to allow single transaction execution if the two signing addresses are the same
29+
*/
30+
contract MutualUpgradeV2 {
31+
/* ============ State Variables ============ */
32+
33+
// Mapping of upgradable units and if upgrade has been initialized by other party
34+
mapping(bytes32 => bool) public mutualUpgrades;
35+
36+
/* ============ Events ============ */
37+
38+
event MutualUpgradeRegistered(
39+
bytes32 _upgradeHash
40+
);
41+
42+
/* ============ Modifiers ============ */
43+
44+
modifier mutualUpgrade(address _signerOne, address _signerTwo) {
45+
require(
46+
msg.sender == _signerOne || msg.sender == _signerTwo,
47+
"Must be authorized address"
48+
);
49+
50+
// If the two signing addresses are the same, skip upgrade hash step
51+
if (_signerOne == _signerTwo) {
52+
_;
53+
}
54+
55+
address nonCaller = _getNonCaller(_signerOne, _signerTwo);
56+
57+
// The upgrade hash is defined by the hash of the transaction call data and sender of msg,
58+
// which uniquely identifies the function, arguments, and sender.
59+
bytes32 expectedHash = keccak256(abi.encodePacked(msg.data, nonCaller));
60+
61+
if (!mutualUpgrades[expectedHash]) {
62+
bytes32 newHash = keccak256(abi.encodePacked(msg.data, msg.sender));
63+
64+
mutualUpgrades[newHash] = true;
65+
66+
emit MutualUpgradeRegistered(newHash);
67+
68+
return;
69+
}
70+
71+
delete mutualUpgrades[expectedHash];
72+
73+
// Run the rest of the upgrades
74+
_;
75+
}
76+
77+
/* ============ Internal Functions ============ */
78+
79+
function _getNonCaller(address _signerOne, address _signerTwo) internal view returns(address) {
80+
return msg.sender == _signerOne ? _signerTwo : _signerOne;
81+
}
82+
}

contracts/manager/DelegatedManager.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { PreciseUnitMath } from "@setprotocol/set-protocol-v2/contracts/lib/Prec
2828

2929
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
3030
import { IGlobalExtension } from "../interfaces/IGlobalExtension.sol";
31+
import { MutualUpgradeV2 } from "../lib/MutualUpgradeV2.sol";
3132

3233

3334
/**
@@ -42,7 +43,7 @@ import { IGlobalExtension } from "../interfaces/IGlobalExtension.sol";
4243
* a part of the asset whitelist, hence they are a semi-trusted party. It is recommended that the owner address
4344
* be managed by a multi-sig or some form of permissioning system.
4445
*/
45-
contract DelegatedManager is Ownable {
46+
contract DelegatedManager is Ownable, MutualUpgradeV2 {
4647
using Address for address;
4748
using AddressArrayUtils for address[];
4849
using SafeERC20 for IERC20;
@@ -327,7 +328,7 @@ contract DelegatedManager is Ownable {
327328
*
328329
* @param _newFeeSplit Percent in precise units (100% = 10**18) of fees that accrue to owner
329330
*/
330-
function updateOwnerFeeSplit(uint256 _newFeeSplit) external onlyOwner {
331+
function updateOwnerFeeSplit(uint256 _newFeeSplit) external mutualUpgrade(owner(), methodologist) {
331332
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Invalid fee split");
332333

333334
ownerFeeSplit = _newFeeSplit;
@@ -354,6 +355,8 @@ contract DelegatedManager is Ownable {
354355
* @param _newMethodologist New methodologist address
355356
*/
356357
function setMethodologist(address _newMethodologist) external onlyMethodologist {
358+
require(_newMethodologist != address(0), "Null address passed");
359+
357360
methodologist = _newMethodologist;
358361

359362
emit MethodologistChanged(_newMethodologist);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: Apache License, Version 2.0
2+
pragma solidity 0.6.10;
3+
4+
import { MutualUpgradeV2 } from "../lib/MutualUpgradeV2.sol";
5+
6+
7+
// Mock contract implementation of MutualUpgradeV2 functions
8+
contract MutualUpgradeV2Mock is
9+
MutualUpgradeV2
10+
{
11+
uint256 public testUint;
12+
address public owner;
13+
address public methodologist;
14+
15+
constructor(address _owner, address _methodologist) public {
16+
owner = _owner;
17+
methodologist = _methodologist;
18+
}
19+
20+
function testMutualUpgrade(
21+
uint256 _testUint
22+
)
23+
external
24+
mutualUpgrade(owner, methodologist)
25+
{
26+
testUint = _testUint;
27+
}
28+
}

test/extensions/issuanceExtension.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,10 @@ describe("IssuanceExtension", () => {
9191
);
9292

9393
ownerFeeSplit = ether(0.1);
94-
await delegatedManager.updateOwnerFeeSplit(ownerFeeSplit);
94+
await delegatedManager.connect(owner.wallet).updateOwnerFeeSplit(ownerFeeSplit);
95+
await delegatedManager.connect(methodologist.wallet).updateOwnerFeeSplit(ownerFeeSplit);
9596
ownerFeeRecipient = owner.address;
96-
await delegatedManager.updateOwnerFeeRecipient(ownerFeeRecipient);
97+
await delegatedManager.connect(owner.wallet).updateOwnerFeeRecipient(ownerFeeRecipient);
9798

9899
await setToken.setManager(delegatedManager.address);
99100

@@ -695,6 +696,7 @@ describe("IssuanceExtension", () => {
695696
describe("when methodologist fees are 0", async () => {
696697
beforeEach(async () => {
697698
await delegatedManager.connect(owner.wallet).updateOwnerFeeSplit(ether(1));
699+
await delegatedManager.connect(methodologist.wallet).updateOwnerFeeSplit(ether(1));
698700
});
699701

700702
it("should not send fees to methodologist", async () => {
@@ -710,6 +712,7 @@ describe("IssuanceExtension", () => {
710712
describe("when owner fees are 0", async () => {
711713
beforeEach(async () => {
712714
await delegatedManager.connect(owner.wallet).updateOwnerFeeSplit(ZERO);
715+
await delegatedManager.connect(methodologist.wallet).updateOwnerFeeSplit(ZERO);
713716
});
714717

715718
it("should not send fees to owner fee recipient", async () => {

test/extensions/streamingFeeSplitExtension.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ describe("StreamingFeeSplitExtension", () => {
9595
);
9696

9797
ownerFeeSplit = ether(0.1);
98-
await delegatedManager.updateOwnerFeeSplit(ownerFeeSplit);
98+
await delegatedManager.connect(owner.wallet).updateOwnerFeeSplit(ownerFeeSplit);
99+
await delegatedManager.connect(methodologist.wallet).updateOwnerFeeSplit(ownerFeeSplit);
99100
ownerFeeRecipient = owner.address;
100-
await delegatedManager.updateOwnerFeeRecipient(ownerFeeRecipient);
101+
await delegatedManager.connect(owner.wallet).updateOwnerFeeRecipient(ownerFeeRecipient);
101102

102103
await setToken.setManager(delegatedManager.address);
103104

@@ -630,6 +631,7 @@ describe("StreamingFeeSplitExtension", () => {
630631
describe("when methodologist fees are 0", async () => {
631632
beforeEach(async () => {
632633
await delegatedManager.connect(owner.wallet).updateOwnerFeeSplit(ether(1));
634+
await delegatedManager.connect(methodologist.wallet).updateOwnerFeeSplit(ether(1));
633635
});
634636

635637
it("should not send fees to methodologist", async () => {
@@ -645,6 +647,7 @@ describe("StreamingFeeSplitExtension", () => {
645647
describe("when owner fees are 0", async () => {
646648
beforeEach(async () => {
647649
await delegatedManager.connect(owner.wallet).updateOwnerFeeSplit(ZERO);
650+
await delegatedManager.connect(methodologist.wallet).updateOwnerFeeSplit(ZERO);
648651
});
649652

650653
it("should not send fees to owner fee recipient", async () => {

0 commit comments

Comments
 (0)