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
+ pragma experimental "ABIEncoderV2 " ;
21
+
22
+ import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol " ;
23
+ import { ITradeModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/ITradeModule.sol " ;
24
+
25
+ import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol " ;
26
+ import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol " ;
27
+ import { IManagerCore } from "../interfaces/IManagerCore.sol " ;
28
+
29
+ /**
30
+ * @title BatchTradeExtension
31
+ * @author Set Protocol
32
+ *
33
+ * Smart contract global extension which provides DelegatedManager privileged operator(s) the ability to execute a
34
+ * batch of trade on a DEX and the owner the ability to restrict operator(s) permissions with an asset whitelist.
35
+ */
36
+ contract BatchTradeExtension is BaseGlobalExtension {
37
+
38
+ /* ============ Structs ============ */
39
+
40
+ struct TradeInfo {
41
+ string exchangeName; // Human readable name of the exchange in the integrations registry
42
+ address sendToken; // Address of the token to be sent to the exchange
43
+ uint256 sendQuantity; // Units of token in SetToken sent to the exchange
44
+ address receiveToken; // Address of the token that will be received from the exchange
45
+ uint256 minReceiveQuantity; // Min units of token in SetToken to be received from the exchange
46
+ bytes data; // Arbitrary bytes to be used to construct trade call data
47
+ }
48
+
49
+ /* ============ Events ============ */
50
+
51
+ event BatchTradeExtensionInitialized (
52
+ address indexed _setToken ,
53
+ address indexed _delegatedManager
54
+ );
55
+
56
+ event StringTradeFailed (
57
+ address indexed _setToken , // SetToken which failed trade targeted
58
+ uint256 _index , // Index of trade that failed in _trades parameter of batchTrade call
59
+ string _reason // String reason for the failure
60
+ );
61
+
62
+ event BytesTradeFailed (
63
+ address indexed _setToken , // SetToken which failed trade targeted
64
+ uint256 _index , // Index of trade that failed in _trades parameter of batchTrade call
65
+ bytes _reason // Custom error bytes encoding reason for failure
66
+ );
67
+
68
+ /* ============ State Variables ============ */
69
+
70
+ // Instance of TradeModule
71
+ ITradeModule public immutable tradeModule;
72
+
73
+ /* ============ Modifiers ============ */
74
+
75
+ /**
76
+ * Throws if any assets are not allowed to be held by the Set
77
+ */
78
+ modifier onlyAllowedAssets (ISetToken _setToken , TradeInfo[] memory _trades ) {
79
+ for (uint256 i = 0 ; i < _trades.length ; i++ ) {
80
+ require (_manager (_setToken).isAllowedAsset (_trades[i].receiveToken), "Must be allowed asset " );
81
+ }
82
+ _;
83
+ }
84
+
85
+ /* ============ Constructor ============ */
86
+
87
+ constructor (
88
+ IManagerCore _managerCore ,
89
+ ITradeModule _tradeModule
90
+ )
91
+ public
92
+ BaseGlobalExtension (_managerCore)
93
+ {
94
+ tradeModule = _tradeModule;
95
+ }
96
+
97
+ /* ============ External Functions ============ */
98
+
99
+ /**
100
+ * ONLY OWNER: Initializes TradeModule on the SetToken associated with the DelegatedManager.
101
+ *
102
+ * @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
103
+ */
104
+ function initializeModule (IDelegatedManager _delegatedManager ) external onlyOwnerAndValidManager (_delegatedManager) {
105
+ require (_delegatedManager.isInitializedExtension (address (this )), "Extension must be initialized " );
106
+
107
+ _initializeModule (_delegatedManager.setToken (), _delegatedManager);
108
+ }
109
+
110
+ /**
111
+ * ONLY OWNER: Initializes BatchTradeExtension to the DelegatedManager.
112
+ *
113
+ * @param _delegatedManager Instance of the DelegatedManager to initialize
114
+ */
115
+ function initializeExtension (IDelegatedManager _delegatedManager ) external onlyOwnerAndValidManager (_delegatedManager) {
116
+ require (_delegatedManager.isPendingExtension (address (this )), "Extension must be pending " );
117
+
118
+ ISetToken setToken = _delegatedManager.setToken ();
119
+
120
+ _initializeExtension (setToken, _delegatedManager);
121
+
122
+ emit BatchTradeExtensionInitialized (address (setToken), address (_delegatedManager));
123
+ }
124
+
125
+ /**
126
+ * ONLY OWNER: Initializes TradeExtension to the DelegatedManager and TradeModule to the SetToken
127
+ *
128
+ * @param _delegatedManager Instance of the DelegatedManager to initialize
129
+ */
130
+ function initializeModuleAndExtension (IDelegatedManager _delegatedManager ) external onlyOwnerAndValidManager (_delegatedManager){
131
+ require (_delegatedManager.isPendingExtension (address (this )), "Extension must be pending " );
132
+
133
+ ISetToken setToken = _delegatedManager.setToken ();
134
+
135
+ _initializeExtension (setToken, _delegatedManager);
136
+ _initializeModule (setToken, _delegatedManager);
137
+
138
+ emit BatchTradeExtensionInitialized (address (setToken), address (_delegatedManager));
139
+ }
140
+
141
+ /**
142
+ * ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the BatchTradeExtension
143
+ */
144
+ function removeExtension () external override {
145
+ IDelegatedManager delegatedManager = IDelegatedManager (msg .sender );
146
+ ISetToken setToken = delegatedManager.setToken ();
147
+
148
+ _removeExtension (setToken, delegatedManager);
149
+ }
150
+
151
+ /**
152
+ * ONLY OPERATOR: Executes a batch of trades on a supported DEX. If any individual trades fail, events are emitted.
153
+ * @dev Although the SetToken units are passed in for the send and receive quantities, the total quantity
154
+ * sent and received is the quantity of SetToken units multiplied by the SetToken totalSupply.
155
+ *
156
+ * @param _setToken Instance of the SetToken to trade
157
+ * @param _trades Struct of information for individual trades
158
+ */
159
+ function batchTrade (
160
+ ISetToken _setToken ,
161
+ TradeInfo[] memory _trades
162
+ )
163
+ external
164
+ onlyOperator (_setToken)
165
+ onlyAllowedAssets (_setToken, _trades)
166
+ {
167
+ for (uint256 i = 0 ; i < _trades.length ; i++ ) {
168
+ bytes memory callData = abi.encodeWithSignature (
169
+ "trade(address,string,address,uint256,address,uint256,bytes) " ,
170
+ _setToken,
171
+ _trades[i].exchangeName,
172
+ _trades[i].sendToken,
173
+ _trades[i].sendQuantity,
174
+ _trades[i].receiveToken,
175
+ _trades[i].minReceiveQuantity,
176
+ _trades[i].data
177
+ );
178
+
179
+ // ZeroEx (for example) throws custom errors which slip through OpenZeppelin's
180
+ // functionCallWithValue error management and surface here as `bytes`. These should be
181
+ // decode-able off-chain given enough context about protocol targeted by the adapter.
182
+ try _manager (_setToken).interactManager (address (tradeModule), callData) {}
183
+ catch Error (string memory _err ) {
184
+ emit StringTradeFailed (address (_setToken), i, _err);
185
+ } catch (bytes memory _err ) {
186
+ emit BytesTradeFailed (address (_setToken), i, _err);
187
+ }
188
+ }
189
+ }
190
+
191
+ /* ============ Internal Functions ============ */
192
+
193
+ /**
194
+ * Internal function to initialize TradeModule on the SetToken associated with the DelegatedManager.
195
+ *
196
+ * @param _setToken Instance of the SetToken corresponding to the DelegatedManager
197
+ * @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
198
+ */
199
+ function _initializeModule (ISetToken _setToken , IDelegatedManager _delegatedManager ) internal {
200
+ bytes memory callData = abi.encodeWithSignature ("initialize(address) " , _setToken);
201
+ _invokeManager (_delegatedManager, address (tradeModule), callData);
202
+ }
203
+ }
0 commit comments