-
Notifications
You must be signed in to change notification settings - Fork 108
Expand file tree
/
Copy pathGeneralIndexModuleV2.sol
More file actions
1221 lines (1087 loc) · 55.9 KB
/
GeneralIndexModuleV2.sol
File metadata and controls
1221 lines (1087 loc) · 55.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { AddressArrayUtils } from "../../lib/AddressArrayUtils.sol";
import { IController } from "../../interfaces/IController.sol";
import { IIndexExchangeAdapter } from "../../interfaces/IIndexExchangeAdapter.sol";
import { Invoke } from "../lib/Invoke.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { IWETH } from "../../interfaces/external/IWETH.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
import { Position } from "../lib/Position.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
import { Uint256ArrayUtils } from "../../lib/Uint256ArrayUtils.sol";
/**
* @title GeneralIndexModuleV2
* @author Set Protocol
*
* Smart contract that facilitates rebalances for indices. Manager can update allocation by calling startRebalance().
* There is no "end" to a rebalance, however once there are no more tokens to sell the rebalance is effectively over
* until the manager calls startRebalance() again with a new allocation. Once a new allocation is passed in, allowed
* traders can submit rebalance transactions by calling trade() and specifying the component they wish to rebalance.
* All parameterizations for a trade are set by the manager ahead of time, including max trade size, coolOffPeriod bet-
* ween trades, and exchange to trade on. WETH is used as the quote asset for all trades, near the end of rebalance
* tradeRemaingingWETH() or raiseAssetTargets() can be called to clean up any excess WETH positions. Once a component's
* target allocation is met any further attempted trades of that component will revert.
*
* SECURITY ASSUMPTION:
* - Works with following modules: StreamingFeeModule, BasicIssuanceModule (any other module additions to Sets using
* this module need to be examined separately)
*
* Note: TradeModuleV2 will allow governance to specify a protocol fee and rebate split percentage
* which is sent to the manager's address. The fee rebates will be automatic per trade.
*
*/
contract GeneralIndexModuleV2 is ModuleBase, ReentrancyGuard {
using SafeCast for int256;
using SafeCast for uint256;
using SafeMath for uint256;
using Position for uint256;
using Math for uint256;
using Position for ISetToken;
using Invoke for ISetToken;
using AddressArrayUtils for address[];
using AddressArrayUtils for IERC20[];
using Uint256ArrayUtils for uint256[];
/* ============ Struct ============ */
struct TradeExecutionParams {
uint256 targetUnit; // Target unit of component for Set
uint256 maxSize; // Max trade size in precise units
uint256 coolOffPeriod; // Required time between trades for the asset
uint256 lastTradeTimestamp; // Timestamp of last trade
string exchangeName; // Name of exchange adapter
bytes exchangeData; // Arbitrary data that can be used to encode exchange specific settings (fee tier) or features (multi-hop)
}
struct TradePermissionInfo {
bool anyoneTrade; // Boolean indicating if anyone can execute a trade
address[] tradersHistory; // Tracks permissioned traders to be deleted on module removal
mapping(address => bool) tradeAllowList; // Mapping indicating which addresses are allowed to execute trade
}
struct RebalanceInfo {
uint256 positionMultiplier; // Position multiplier at the beginning of rebalance
uint256 raiseTargetPercentage; // Amount to raise all unit targets by if allowed (in precise units)
address[] rebalanceComponents; // Array of components involved in rebalance
}
struct TradeInfo {
ISetToken setToken; // Instance of SetToken
IIndexExchangeAdapter exchangeAdapter; // Instance of Exchange Adapter
address sendToken; // Address of token being sold
address receiveToken; // Address of token being bought
bool isSendTokenFixed; // Boolean indicating fixed asset is send token
uint256 setTotalSupply; // Total supply of Set (in precise units)
uint256 totalFixedQuantity; // Total quantity of fixed asset being traded
uint256 sendQuantity; // Units of component sent to the exchange
uint256 floatingQuantityLimit; // Max/min amount of floating token spent/received during trade
uint256 preTradeSendTokenBalance; // Total initial balance of token being sold
uint256 preTradeReceiveTokenBalance; // Total initial balance of token being bought
bytes exchangeData; // Arbitrary data for executing trade on given exchange
}
/* ============ Events ============ */
event TradeMaximumUpdated(ISetToken indexed _setToken, address indexed _component, uint256 _newMaximum);
event AssetExchangeUpdated(ISetToken indexed _setToken, address indexed _component, string _newExchangeName);
event CoolOffPeriodUpdated(ISetToken indexed _setToken, address indexed _component, uint256 _newCoolOffPeriod);
event ExchangeDataUpdated(ISetToken indexed _setToken, address indexed _component, bytes _newExchangeData);
event RaiseTargetPercentageUpdated(ISetToken indexed _setToken, uint256 indexed _raiseTargetPercentage);
event AssetTargetsRaised(ISetToken indexed _setToken, uint256 indexed positionMultiplier);
event AnyoneTradeUpdated(ISetToken indexed _setToken, bool indexed _status);
event TraderStatusUpdated(ISetToken indexed _setToken, address indexed _trader, bool _status);
event TradeExecuted(
ISetToken indexed _setToken,
address indexed _sellComponent,
address indexed _buyComponent,
IIndexExchangeAdapter _exchangeAdapter,
address _executor,
uint256 _netAmountSold,
uint256 _netAmountReceived,
uint256 _protocolFee,
uint256 _managerRebate
);
event RebalanceStarted(
ISetToken indexed _setToken,
address[] aggregateComponents,
uint256[] aggregateTargetUnits,
uint256 indexed positionMultiplier
);
event FeeRecipientUpdated(ISetToken indexed _setToken, address _newFeeRecipient);
/* ============ Constants ============ */
uint256 constant internal GENERAL_INDEX_MODULE_V2_TOTAL_FEE_INDEX = 0; // 0 index stores the total fee % charged in the trade function
uint256 constant internal GENERAL_INDEX_MODULE_V2_MANAGER_REBATE_SPLIT_INDEX = 1; // 1 index stores the % of total fees that the manager receives back as rebates
/* ============ State Variables ============ */
mapping(ISetToken => address) public managerRebateRecipient; // Mapping to efficiently identify a manager rebate recipient address for a given SetToken
mapping(ISetToken => mapping(IERC20 => TradeExecutionParams)) public executionInfo; // Mapping of SetToken to execution parameters of each asset on SetToken
mapping(ISetToken => TradePermissionInfo) public permissionInfo; // Mapping of SetToken to trading permissions
mapping(ISetToken => RebalanceInfo) public rebalanceInfo; // Mapping of SetToken to relevant data for current rebalance
IWETH public immutable weth; // Weth contract address
/* ============ Modifiers ============ */
modifier onlyAllowedTrader(ISetToken _setToken) {
_validateOnlyAllowedTrader(_setToken);
_;
}
modifier onlyEOAIfUnrestricted(ISetToken _setToken) {
_validateOnlyEOAIfUnrestricted(_setToken);
_;
}
/* ============ Constructor ============ */
constructor(IController _controller, IWETH _weth) public ModuleBase(_controller) {
weth = _weth;
}
/* ============ External Functions ============ */
/**
* MANAGER ONLY: Changes the target allocation of the Set, opening it up for trading by the Sets designated traders. The manager
* must pass in any new components and their target units (units defined by the amount of that component the manager wants in 10**18
* units of a SetToken). Old component target units must be passed in, in the current order of the components array on the
* SetToken. If a component is being removed it's index in the _oldComponentsTargetUnits should be set to 0. Additionally, the
* positionMultiplier is passed in, in order to adjust the target units in the event fees are accrued or some other activity occurs
* that changes the positionMultiplier of the Set. This guarantees the same relative allocation between all the components.
*
* @param _setToken Address of the SetToken to be rebalanced
* @param _newComponents Array of new components to add to allocation
* @param _newComponentsTargetUnits Array of target units at end of rebalance for new components, maps to same index of _newComponents array
* @param _oldComponentsTargetUnits Array of target units at end of rebalance for old component, maps to same index of
* _setToken.getComponents() array, if component being removed set to 0.
* @param _positionMultiplier Position multiplier when target units were calculated, needed in order to adjust target units
* if fees accrued
*/
function startRebalance(
ISetToken _setToken,
address[] calldata _newComponents,
uint256[] calldata _newComponentsTargetUnits,
uint256[] calldata _oldComponentsTargetUnits,
uint256 _positionMultiplier
)
external
onlyManagerAndValidSet(_setToken)
{
( address[] memory aggregateComponents, uint256[] memory aggregateTargetUnits ) = _getAggregateComponentsAndUnits(
_setToken.getComponents(),
_newComponents,
_newComponentsTargetUnits,
_oldComponentsTargetUnits
);
for (uint256 i = 0; i < aggregateComponents.length; i++) {
require(!_setToken.hasExternalPosition(aggregateComponents[i]), "External positions not allowed");
executionInfo[_setToken][IERC20(aggregateComponents[i])].targetUnit = aggregateTargetUnits[i];
}
rebalanceInfo[_setToken].rebalanceComponents = aggregateComponents;
rebalanceInfo[_setToken].positionMultiplier = _positionMultiplier;
emit RebalanceStarted(_setToken, aggregateComponents, aggregateTargetUnits, _positionMultiplier);
}
/**
* ACCESS LIMITED: Calling trade() pushes the current component units closer to the target units defined by the manager in startRebalance().
* Only approved addresses can call, if anyoneTrade is false then contracts are allowed to call otherwise calling address must be EOA.
*
* Trade can be called at anytime but will revert if the passed component's target unit is met or cool off period hasn't passed. Trader can pass
* in a max/min amount of ETH spent/received in the trade based on if the component is being bought/sold in order to prevent sandwich attacks.
* The parameters defined by the manager are used to determine which exchange will be used and the size of the trade. Trade size will default
* to max trade size unless the max trade size would exceed the target, then an amount that would match the target unit is traded. Protocol fees,
* if enabled, are collected in the token received in a trade.
*
* @param _setToken Address of the SetToken
* @param _component Address of SetToken component to trade
* @param _ethQuantityLimit Max/min amount of ETH spent/received during trade
*/
function trade(
ISetToken _setToken,
IERC20 _component,
uint256 _ethQuantityLimit
)
external
nonReentrant
onlyAllowedTrader(_setToken)
onlyEOAIfUnrestricted(_setToken)
virtual
{
_validateTradeParameters(_setToken, _component);
TradeInfo memory tradeInfo = _createTradeInfo(_setToken, _component, _ethQuantityLimit);
_executeTrade(tradeInfo);
(uint256 protocolFee, uint256 managerRebate) = _accrueFees(tradeInfo);
(uint256 netSendAmount, uint256 netReceiveAmount) = _updatePositionStateAndTimestamp(tradeInfo, _component);
emit TradeExecuted(
tradeInfo.setToken,
tradeInfo.sendToken,
tradeInfo.receiveToken,
tradeInfo.exchangeAdapter,
msg.sender,
netSendAmount,
netReceiveAmount,
protocolFee,
managerRebate
);
}
/**
* ACCESS LIMITED: Only callable when 1) there are no more components to be sold and, 2) entire remaining WETH amount (above WETH target) can be
* traded such that resulting inflows won't exceed component's maxTradeSize nor overshoot the target unit. To be used near the end of rebalances
* when a component's calculated trade size is greater in value than remaining WETH.
*
* Only approved addresses can call, if anyoneTrade is false then contracts are allowed to call otherwise calling address must be EOA. Trade
* can be called at anytime but will revert if the passed component's target unit is met or cool off period hasn't passed. Like with trade()
* a minimum component receive amount can be set.
*
* @param _setToken Address of the SetToken
* @param _component Address of the SetToken component to trade
* @param _minComponentReceived Min amount of component received during trade
*/
function tradeRemainingWETH(
ISetToken _setToken,
IERC20 _component,
uint256 _minComponentReceived
)
external
nonReentrant
onlyAllowedTrader(_setToken)
onlyEOAIfUnrestricted(_setToken)
virtual
{
require(_noTokensToSell(_setToken), "Sell other set components first");
require(
executionInfo[_setToken][weth].targetUnit < _getDefaultPositionRealUnit(_setToken, weth),
"WETH is below target unit"
);
_validateTradeParameters(_setToken, _component);
TradeInfo memory tradeInfo = _createTradeRemainingInfo(_setToken, _component, _minComponentReceived);
_executeTrade(tradeInfo);
(uint256 protocolFee, uint256 managerRebate) = _accrueFees(tradeInfo);
(uint256 netSendAmount, uint256 netReceiveAmount) = _updatePositionStateAndTimestamp(tradeInfo, _component);
require(
netReceiveAmount.add(protocolFee).add(managerRebate) < executionInfo[_setToken][_component].maxSize,
"Trade amount > max trade size"
);
_validateComponentPositionUnit(_setToken, _component);
emit TradeExecuted(
tradeInfo.setToken,
tradeInfo.sendToken,
tradeInfo.receiveToken,
tradeInfo.exchangeAdapter,
msg.sender,
netSendAmount,
netReceiveAmount,
protocolFee,
managerRebate
);
}
/**
* ACCESS LIMITED: For situation where all target units met and remaining WETH, uniformly raise targets by same percentage by applying
* to logged positionMultiplier in RebalanceInfo struct, in order to allow further trading. Can be called multiple times if necessary,
* targets are increased by amount specified by raiseAssetTargetsPercentage as set by manager. In order to reduce tracking error
* raising the target by a smaller amount allows greater granularity in finding an equilibrium between the excess ETH and components
* that need to be bought. Raising the targets too much could result in vastly under allocating to WETH as more WETH than necessary is
* spent buying the components to meet their new target.
*
* @param _setToken Address of the SetToken
*/
function raiseAssetTargets(ISetToken _setToken) external onlyAllowedTrader(_setToken) virtual {
require(
_allTargetsMet(_setToken)
&& _getDefaultPositionRealUnit(_setToken, weth) > _getNormalizedTargetUnit(_setToken, weth),
"Targets not met or ETH =~ 0"
);
// positionMultiplier / (10^18 + raiseTargetPercentage)
// ex: (10 ** 18) / ((10 ** 18) + ether(.0025)) => 997506234413965087
rebalanceInfo[_setToken].positionMultiplier = rebalanceInfo[_setToken].positionMultiplier.preciseDiv(
PreciseUnitMath.preciseUnit().add(rebalanceInfo[_setToken].raiseTargetPercentage)
);
emit AssetTargetsRaised(_setToken, rebalanceInfo[_setToken].positionMultiplier);
}
/**
* MANAGER ONLY: Set trade maximums for passed components of the SetToken. Can be called at anytime.
* Note: Trade maximums must be set before rebalance can begin properly - they are zero by
* default and trades will not execute if a component's trade size is greater than the maximum.
*
* @param _setToken Address of the SetToken
* @param _components Array of components
* @param _tradeMaximums Array of trade maximums mapping to correct component
*/
function setTradeMaximums(
ISetToken _setToken,
address[] memory _components,
uint256[] memory _tradeMaximums
)
external
onlyManagerAndValidSet(_setToken)
{
_components.validatePairsWithArray(_tradeMaximums);
for (uint256 i = 0; i < _components.length; i++) {
executionInfo[_setToken][IERC20(_components[i])].maxSize = _tradeMaximums[i];
emit TradeMaximumUpdated(_setToken, _components[i], _tradeMaximums[i]);
}
}
/**
* MANAGER ONLY: Set exchange for passed components of the SetToken. Can be called at anytime.
*
* @param _setToken Address of the SetToken
* @param _components Array of components
* @param _exchangeNames Array of exchange names mapping to correct component
*/
function setExchanges(
ISetToken _setToken,
address[] memory _components,
string[] memory _exchangeNames
)
external
onlyManagerAndValidSet(_setToken)
{
_components.validatePairsWithArray(_exchangeNames);
for (uint256 i = 0; i < _components.length; i++) {
if (_components[i] != address(weth)) {
require(
controller.getIntegrationRegistry().isValidIntegration(address(this), _exchangeNames[i]),
"Unrecognized exchange name"
);
executionInfo[_setToken][IERC20(_components[i])].exchangeName = _exchangeNames[i];
emit AssetExchangeUpdated(_setToken, _components[i], _exchangeNames[i]);
}
}
}
/**
* MANAGER ONLY: Set cool off periods for passed components of the SetToken. Can be called at any time.
*
* @param _setToken Address of the SetToken
* @param _components Array of components
* @param _coolOffPeriods Array of cool off periods to correct component
*/
function setCoolOffPeriods(
ISetToken _setToken,
address[] memory _components,
uint256[] memory _coolOffPeriods
)
external
onlyManagerAndValidSet(_setToken)
{
_components.validatePairsWithArray(_coolOffPeriods);
for (uint256 i = 0; i < _components.length; i++) {
executionInfo[_setToken][IERC20(_components[i])].coolOffPeriod = _coolOffPeriods[i];
emit CoolOffPeriodUpdated(_setToken, _components[i], _coolOffPeriods[i]);
}
}
/**
* MANAGER ONLY: Set arbitrary byte data on a per asset basis that can be used to pass exchange specific settings (i.e. specifying
* fee tiers) or exchange specific features (enabling multi-hop trades). Can be called at any time.
*
* @param _setToken Address of the SetToken
* @param _components Array of components
* @param _exchangeData Array of exchange specific arbitrary bytes data
*/
function setExchangeData(
ISetToken _setToken,
address[] memory _components,
bytes[] memory _exchangeData
)
external
onlyManagerAndValidSet(_setToken)
{
_components.validatePairsWithArray(_exchangeData);
for (uint256 i = 0; i < _components.length; i++) {
executionInfo[_setToken][IERC20(_components[i])].exchangeData = _exchangeData[i];
emit ExchangeDataUpdated(_setToken, _components[i], _exchangeData[i]);
}
}
/**
* MANAGER ONLY: Set amount by which all component's targets units would be raised. Can be called at any time.
*
* @param _setToken Address of the SetToken
* @param _raiseTargetPercentage Amount to raise all component's unit targets by (in precise units)
*/
function setRaiseTargetPercentage(
ISetToken _setToken,
uint256 _raiseTargetPercentage
)
external
onlyManagerAndValidSet(_setToken)
{
require(_raiseTargetPercentage > 0, "Target percentage must be > 0");
rebalanceInfo[_setToken].raiseTargetPercentage = _raiseTargetPercentage;
emit RaiseTargetPercentageUpdated(_setToken, _raiseTargetPercentage);
}
/**
* MANAGER ONLY: Toggles ability for passed addresses to call trade() or tradeRemainingWETH(). Can be called at any time.
*
* @param _setToken Address of the SetToken
* @param _traders Array trader addresses to toggle status
* @param _statuses Booleans indicating if matching trader can trade
*/
function setTraderStatus(
ISetToken _setToken,
address[] memory _traders,
bool[] memory _statuses
)
external
onlyManagerAndValidSet(_setToken)
{
_traders.validatePairsWithArray(_statuses);
for (uint256 i = 0; i < _traders.length; i++) {
_updateTradersHistory(_setToken, _traders[i], _statuses[i]);
permissionInfo[_setToken].tradeAllowList[_traders[i]] = _statuses[i];
emit TraderStatusUpdated(_setToken, _traders[i], _statuses[i]);
}
}
/**
* MANAGER ONLY: Toggle whether anyone can trade, if true bypasses the traderAllowList. Can be called at anytime.
*
* @param _setToken Address of the SetToken
* @param _status Boolean indicating if anyone can trade
*/
function setAnyoneTrade(ISetToken _setToken, bool _status) external onlyManagerAndValidSet(_setToken) {
permissionInfo[_setToken].anyoneTrade = _status;
emit AnyoneTradeUpdated(_setToken, _status);
}
/**
* MANAGER ONLY: Updates address receiving manager rebate fees for a given SetToken.
*
* @param _setToken Instance of the SetToken to update fee recipient
* @param _newRebateRecipient New rebate fee recipient address
*/
function updateFeeRecipient(
ISetToken _setToken,
address _newRebateRecipient
)
external
onlyManagerAndValidSet(_setToken)
{
require(_newRebateRecipient != address(0), "Recipient must be non-zero address.");
require(_newRebateRecipient != managerRebateRecipient[_setToken], "Same fee recipient passed");
managerRebateRecipient[_setToken] = _newRebateRecipient;
emit FeeRecipientUpdated(_setToken, _newRebateRecipient);
}
/**
* MANAGER ONLY: Called to initialize module to SetToken in order to allow GeneralIndexModuleV2 access for rebalances.
* Grabs the current units for each asset in the Set and set's the targetUnit to that unit in order to prevent any
* trading until startRebalance() is explicitly called. Position multiplier is also logged in order to make sure any
* position multiplier changes don't unintentionally open the Set for rebalancing.
*
* @param _setToken Address of the Set Token
*/
function initialize(
ISetToken _setToken,
address _managerRebateRecipient
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndPendingSet(_setToken)
{
require(_managerRebateRecipient != address(0), "Recipient must be non-zero address.");
managerRebateRecipient[_setToken] = _managerRebateRecipient;
ISetToken.Position[] memory positions = _setToken.getPositions();
for (uint256 i = 0; i < positions.length; i++) {
ISetToken.Position memory position = positions[i];
require(position.positionState == 0, "External positions not allowed");
executionInfo[_setToken][IERC20(position.component)].targetUnit = position.unit.toUint256();
executionInfo[_setToken][IERC20(position.component)].lastTradeTimestamp = 0;
}
rebalanceInfo[_setToken].positionMultiplier = _setToken.positionMultiplier().toUint256();
_setToken.initializeModule();
}
/**
* Called by a SetToken to notify that this module was removed from the SetToken. Remove the manager rebate recipient address.
* Clears the rebalanceInfo and permissionsInfo of the calling SetToken.
* IMPORTANT: SetToken's execution settings, including trade maximums and exchange names,
* are NOT DELETED. Restoring a previously removed module requires that care is taken to
* initialize execution settings appropriately.
*/
function removeModule() external override {
TradePermissionInfo storage tokenPermissionInfo = permissionInfo[ISetToken(msg.sender)];
for (uint i = 0; i < tokenPermissionInfo.tradersHistory.length; i++) {
tokenPermissionInfo.tradeAllowList[tokenPermissionInfo.tradersHistory[i]] = false;
}
delete rebalanceInfo[ISetToken(msg.sender)];
delete permissionInfo[ISetToken(msg.sender)];
delete managerRebateRecipient[ISetToken(msg.sender)];
}
/* ============ External View Functions ============ */
/**
* Get the array of SetToken components involved in rebalance.
*
* @param _setToken Address of the SetToken
*
* @return address[] Array of _setToken components involved in rebalance
*/
function getRebalanceComponents(ISetToken _setToken)
external
view
onlyValidAndInitializedSet(_setToken)
returns (address[] memory)
{
return rebalanceInfo[_setToken].rebalanceComponents;
}
/**
* Calculates the amount of a component that is going to be traded and whether the component is being bought
* or sold. If currentUnit and targetUnit are the same, function will revert.
*
* @param _setToken Instance of the SetToken to rebalance
* @param _component IERC20 component to trade
*
* @return isSendTokenFixed Boolean indicating fixed asset is send token
* @return componentQuantity Amount of component being traded
*/
function getComponentTradeQuantityAndDirection(
ISetToken _setToken,
IERC20 _component
)
external
view
onlyValidAndInitializedSet(_setToken)
returns (bool, uint256)
{
require(
rebalanceInfo[_setToken].rebalanceComponents.contains(address(_component)),
"Component not recognized"
);
uint256 totalSupply = _setToken.totalSupply();
return _calculateTradeSizeAndDirection(_setToken, _component, totalSupply);
}
/**
* Get if a given address is an allowed trader.
*
* @param _setToken Address of the SetToken
* @param _trader Address of the trader
*
* @return bool True if _trader is allowed to trade, else false
*/
function getIsAllowedTrader(ISetToken _setToken, address _trader)
external
view
onlyValidAndInitializedSet(_setToken)
returns (bool)
{
return _isAllowedTrader(_setToken, _trader);
}
/**
* Get the list of traders who are allowed to call trade(), tradeRemainingWeth(), and raiseAssetTarget()
*
* @param _setToken Address of the SetToken
*
* @return address[]
*/
function getAllowedTraders(ISetToken _setToken)
external
view
onlyValidAndInitializedSet(_setToken)
returns (address[] memory)
{
return permissionInfo[_setToken].tradersHistory;
}
/* ============ Internal Functions ============ */
/**
* A rebalance is a multi-step process in which current Set components are sold for a
* bridge asset (WETH) before buying target components in the correct amount to achieve
* the desired balance between elements in the set.
*
* Step 1 | Step 2
* -------------------------------------------
* Component --> WETH | WETH --> Component
* -------------------------------------------
*
* The syntax we use frames this as trading from a "fixed" amount of one component to a
* "fixed" amount of another via a "floating limit" which is *either* the maximum size of
* the trade we want to make (trades may be tranched to avoid moving markets) OR the minimum
* amount of tokens we expect to receive. The different meanings of the floating limit map to
* the trade sequence as below:
*
* Step 1: Component --> WETH
* ----------------------------------------------------------
* | Fixed | Floating limit |
* ----------------------------------------------------------
* send (Component) | YES | |
* recieve (WETH) | | Min WETH to receive |
* ----------------------------------------------------------
*
* Step 2: WETH --> Component
* ----------------------------------------------------------
* | Fixed | Floating limit |
* ----------------------------------------------------------
* send (WETH) | NO | Max WETH to send |
* recieve (Component) | YES | |
* ----------------------------------------------------------
*
* Additionally, there is an edge case where price volatility during a rebalance
* results in remaining WETH which needs to be allocated proportionately. In this case
* the values are as below:
*
* Edge case: Remaining WETH --> Component
* ----------------------------------------------------------
* | Fixed | Floating limit |
* ----------------------------------------------------------
* send (WETH) | YES | |
* recieve (Component) | | Min component to receive |
* ----------------------------------------------------------
*/
/**
* Create and return TradeInfo struct. This function reverts if the target has already been met.
* If this is a trade from component into WETH, sell the total fixed component quantity
* and expect to receive an ETH amount the user has specified (or more). If it's a trade from
* WETH into a component, sell the lesser of: the user's WETH limit OR the SetToken's
* remaining WETH balance and expect to receive a fixed component quantity.
*
* @param _setToken Instance of the SetToken to rebalance
* @param _component IERC20 component to trade
* @param _ethQuantityLimit Max/min amount of weth spent/received during trade
*
* @return tradeInfo Struct containing data for trade
*/
function _createTradeInfo(
ISetToken _setToken,
IERC20 _component,
uint256 _ethQuantityLimit
)
internal
view
virtual
returns (TradeInfo memory tradeInfo)
{
tradeInfo = _getDefaultTradeInfo(_setToken, _component, true);
if (tradeInfo.isSendTokenFixed){
tradeInfo.sendQuantity = tradeInfo.totalFixedQuantity;
tradeInfo.floatingQuantityLimit = _ethQuantityLimit;
} else {
tradeInfo.sendQuantity = _ethQuantityLimit.min(tradeInfo.preTradeSendTokenBalance);
tradeInfo.floatingQuantityLimit = tradeInfo.totalFixedQuantity;
}
}
/**
* Create and return TradeInfo struct. This function does NOT check if the WETH target has been met.
*
* @param _setToken Instance of the SetToken to rebalance
* @param _component IERC20 component to trade
* @param _minComponentReceived Min amount of component received during trade
*
* @return tradeInfo Struct containing data for tradeRemaining info
*/
function _createTradeRemainingInfo(
ISetToken _setToken,
IERC20 _component,
uint256 _minComponentReceived
)
internal
view
returns (TradeInfo memory tradeInfo)
{
tradeInfo = _getDefaultTradeInfo(_setToken, _component, false);
(,,
uint256 currentNotional,
uint256 targetNotional
) = _getUnitsAndNotionalAmounts(_setToken, weth, tradeInfo.setTotalSupply);
tradeInfo.sendQuantity = currentNotional.sub(targetNotional);
tradeInfo.floatingQuantityLimit = _minComponentReceived;
tradeInfo.isSendTokenFixed = true;
}
/**
* Create and returns a partial TradeInfo struct with all fields that overlap between `trade`
* and `tradeRemaining` info constructors filled in. Values for `sendQuantity` and `floatingQuantityLimit`
* are derived separately, outside this method. `trade` requires that trade size and direction are
* calculated, whereas `tradeRemaining` automatically sets WETH as the sendToken and _component
* as receiveToken.
*
* @param _setToken Instance of the SetToken to rebalance
* @param _component IERC20 component to trade
* @param calculateTradeDirection Indicates whether method should calculate trade size and direction
*
* @return tradeInfo Struct containing partial data for trade
*/
function _getDefaultTradeInfo(ISetToken _setToken, IERC20 _component, bool calculateTradeDirection)
internal
view
returns (TradeInfo memory tradeInfo)
{
tradeInfo.setToken = _setToken;
tradeInfo.setTotalSupply = _setToken.totalSupply();
tradeInfo.exchangeAdapter = _getExchangeAdapter(_setToken, _component);
tradeInfo.exchangeData = executionInfo[_setToken][_component].exchangeData;
if(calculateTradeDirection){
(
tradeInfo.isSendTokenFixed,
tradeInfo.totalFixedQuantity
) = _calculateTradeSizeAndDirection(_setToken, _component, tradeInfo.setTotalSupply);
}
if (tradeInfo.isSendTokenFixed){
tradeInfo.sendToken = address(_component);
tradeInfo.receiveToken = address(weth);
} else {
tradeInfo.sendToken = address(weth);
tradeInfo.receiveToken = address(_component);
}
tradeInfo.preTradeSendTokenBalance = IERC20(tradeInfo.sendToken).balanceOf(address(_setToken));
tradeInfo.preTradeReceiveTokenBalance = IERC20(tradeInfo.receiveToken).balanceOf(address(_setToken));
}
/**
* Function handles all interactions with exchange. All GeneralIndexModuleV2 adapters must allow for selling or buying a fixed
* quantity of a token in return for a non-fixed (floating) quantity of a token. If `isSendTokenFixed` is true then the adapter
* will choose the exchange interface associated with inputting a fixed amount, otherwise it will select the interface used for
* receiving a fixed amount. Any other exchange specific data can also be created by calling generateDataParam function.
*
* @param _tradeInfo Struct containing trade information used in internal functions
*/
function _executeTrade(TradeInfo memory _tradeInfo) internal virtual {
_tradeInfo.setToken.invokeApprove(
_tradeInfo.sendToken,
_tradeInfo.exchangeAdapter.getSpender(),
_tradeInfo.sendQuantity
);
(
address targetExchange,
uint256 callValue,
bytes memory methodData
) = _tradeInfo.exchangeAdapter.getTradeCalldata(
_tradeInfo.sendToken,
_tradeInfo.receiveToken,
address(_tradeInfo.setToken),
_tradeInfo.isSendTokenFixed,
_tradeInfo.sendQuantity,
_tradeInfo.floatingQuantityLimit,
_tradeInfo.exchangeData
);
_tradeInfo.setToken.invoke(targetExchange, callValue, methodData);
}
/**
* Retrieve fee from controller and calculate total protocol fee and send from SetToken to protocol recipient.
* The protocol fee is collected from the amount of received token in the trade.
*
* @param _tradeInfo Struct containing trade information used in internal functions
*
* @return protocolFee Amount of receive token taken as protocol fee
* @return managerRebate Amount of receive token taken as manager rebate fee
*/
function _accrueFees(TradeInfo memory _tradeInfo) internal returns (uint256 protocolFee, uint256 managerRebate) {
uint256 exchangedQuantity = IERC20(_tradeInfo.receiveToken)
.balanceOf(address(_tradeInfo.setToken))
.sub(_tradeInfo.preTradeReceiveTokenBalance);
uint256 totalFeePercentage = controller.getModuleFee(address(this), GENERAL_INDEX_MODULE_V2_TOTAL_FEE_INDEX);
uint256 managerRebateSplitPercentage = controller.getModuleFee(address(this), GENERAL_INDEX_MODULE_V2_MANAGER_REBATE_SPLIT_INDEX);
managerRebate = totalFeePercentage.preciseMul(exchangedQuantity).preciseMul(managerRebateSplitPercentage);
protocolFee = totalFeePercentage.preciseMul(exchangedQuantity).sub(managerRebate);
payProtocolFeeFromSetToken(_tradeInfo.setToken, _tradeInfo.receiveToken, protocolFee);
if (managerRebate > 0) {
_tradeInfo.setToken.strictInvokeTransfer(
_tradeInfo.receiveToken,
managerRebateRecipient[_tradeInfo.setToken],
managerRebate
);
}
}
/**
* Update SetToken positions and executionInfo's last trade timestamp. This function is intended
* to be called after the fees have been accrued, hence it returns the amount of tokens bought net of fees.
*
* @param _tradeInfo Struct containing trade information used in internal functions
* @param _component IERC20 component which was traded
*
* @return netSendAmount Amount of sendTokens used in the trade
* @return netReceiveAmount Amount of receiveTokens received in the trade (net of fees)
*/
function _updatePositionStateAndTimestamp(TradeInfo memory _tradeInfo, IERC20 _component)
internal
returns (uint256 netSendAmount, uint256 netReceiveAmount)
{
(uint256 postTradeSendTokenBalance,,) = _tradeInfo.setToken.calculateAndEditDefaultPosition(
_tradeInfo.sendToken,
_tradeInfo.setTotalSupply,
_tradeInfo.preTradeSendTokenBalance
);
(uint256 postTradeReceiveTokenBalance,,) = _tradeInfo.setToken.calculateAndEditDefaultPosition(
_tradeInfo.receiveToken,
_tradeInfo.setTotalSupply,
_tradeInfo.preTradeReceiveTokenBalance
);
netSendAmount = _tradeInfo.preTradeSendTokenBalance.sub(postTradeSendTokenBalance);
netReceiveAmount = postTradeReceiveTokenBalance.sub(_tradeInfo.preTradeReceiveTokenBalance);
executionInfo[_tradeInfo.setToken][_component].lastTradeTimestamp = block.timestamp;
}
/**
* Adds or removes newly permissioned trader to/from permissionsInfo traderHistory. It's
* necessary to verify that traderHistory contains the address because AddressArrayUtils will
* throw when attempting to remove a non-element and it's possible someone can set a new
* trader's status to false.
*
* @param _setToken Instance of the SetToken
* @param _trader Trader whose permission is being set
* @param _status Boolean permission being set
*/
function _updateTradersHistory(ISetToken _setToken, address _trader, bool _status) internal {
if (_status && !permissionInfo[_setToken].tradersHistory.contains(_trader)) {
permissionInfo[_setToken].tradersHistory.push(_trader);
} else if(!_status && permissionInfo[_setToken].tradersHistory.contains(_trader)) {
permissionInfo[_setToken].tradersHistory.removeStorage(_trader);
}
}
/**
* Calculates the amount of a component is going to be traded and whether the component is being bought or sold.
* If currentUnit and targetUnit are the same, function will revert.
*
* @param _setToken Instance of the SetToken to rebalance
* @param _component IERC20 component to trade
* @param _totalSupply Total supply of _setToken
*
* @return isSendTokenFixed Boolean indicating fixed asset is send token
* @return totalFixedQuantity Amount of fixed token to send or receive
*/
function _calculateTradeSizeAndDirection(
ISetToken _setToken,
IERC20 _component,
uint256 _totalSupply
)
internal
view
returns (bool isSendTokenFixed, uint256 totalFixedQuantity)
{
uint256 totalFeePercentage = controller.getModuleFee(address(this), GENERAL_INDEX_MODULE_V2_TOTAL_FEE_INDEX);
uint256 componentMaxSize = executionInfo[_setToken][_component].maxSize;
(
uint256 currentUnit,
uint256 targetUnit,
uint256 currentNotional,
uint256 targetNotional
) = _getUnitsAndNotionalAmounts(_setToken, _component, _totalSupply);
require(currentUnit != targetUnit, "Target already met");
isSendTokenFixed = targetNotional < currentNotional;
// In order to account for fees taken by protocol when buying the notional difference between currentUnit
// and targetUnit is divided by (1 - totalFeePercentage) to make sure that targetUnit can be met. Failure to
// do so would lead to never being able to meet target of components that need to be bought.
//
// ? - lesserOf: (componentMaxSize, (currentNotional - targetNotional))
// : - lesserOf: (componentMaxSize, (targetNotional - currentNotional) / 10 ** 18 - totalFeePercentage)
totalFixedQuantity = isSendTokenFixed
? componentMaxSize.min(currentNotional.sub(targetNotional))
: componentMaxSize.min(targetNotional.sub(currentNotional).preciseDiv(PreciseUnitMath.preciseUnit().sub(totalFeePercentage)));
}
/**
* Check if all targets are met.
*
* @param _setToken Instance of the SetToken to be rebalanced
*
* @return bool True if all component's target units have been met, otherwise false
*/
function _allTargetsMet(ISetToken _setToken) internal view returns (bool) {
address[] memory rebalanceComponents = rebalanceInfo[_setToken].rebalanceComponents;
for (uint256 i = 0; i < rebalanceComponents.length; i++) {
if (_targetUnmet(_setToken, rebalanceComponents[i])) { return false; }
}
return true;
}
/**
* Determine if passed address is allowed to call trade for the SetToken. If anyoneTrade set to true anyone can call otherwise
* needs to be approved.
*
* @param _setToken Instance of SetToken to be rebalanced
* @param _trader Address of the trader who called contract function
*
* @return bool True if trader is an approved trader for the SetToken
*/
function _isAllowedTrader(ISetToken _setToken, address _trader) internal view returns (bool) {
TradePermissionInfo storage permissions = permissionInfo[_setToken];
return permissions.anyoneTrade || permissions.tradeAllowList[_trader];
}