@@ -21,6 +21,7 @@ pragma experimental "ABIEncoderV2";
21
21
22
22
import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol " ;
23
23
import { ITradeModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/ITradeModule.sol " ;
24
+ import { StringArrayUtils } from "@setprotocol/set-protocol-v2/contracts/lib/StringArrayUtils.sol " ;
24
25
25
26
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol " ;
26
27
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol " ;
@@ -30,80 +31,141 @@ import { IManagerCore } from "../interfaces/IManagerCore.sol";
30
31
* @title BatchTradeExtension
31
32
* @author Set Protocol
32
33
*
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.
34
+ * Smart contract global extension which provides DelegatedManager operator(s) the ability to execute a batch of trades
35
+ * on a DEX and the owner the ability to restrict operator(s) permissions with an asset whitelist.
35
36
*/
36
37
contract BatchTradeExtension is BaseGlobalExtension {
38
+ using StringArrayUtils for string [];
37
39
38
40
/* ============ Structs ============ */
39
41
40
42
struct TradeInfo {
41
43
string exchangeName; // Human readable name of the exchange in the integrations registry
42
44
address sendToken; // Address of the token to be sent to the exchange
43
- uint256 sendQuantity; // Units of token in SetToken sent to the exchange
45
+ uint256 sendQuantity; // Max units of `sendToken` sent to the exchange
44
46
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
47
+ uint256 receiveQuantity ; // Min units of `receiveToken` to be received from the exchange
46
48
bytes data; // Arbitrary bytes to be used to construct trade call data
47
49
}
48
50
49
51
/* ============ Events ============ */
50
52
53
+ event IntegrationAdded (
54
+ string _integrationName // String name of TradeModule exchange integration to allow
55
+ );
56
+
57
+ event IntegrationRemoved (
58
+ string _integrationName // String name of TradeModule exchange integration to disallow
59
+ );
60
+
51
61
event BatchTradeExtensionInitialized (
52
- address indexed _setToken ,
53
- address indexed _delegatedManager
62
+ address indexed _setToken , // Address of the SetToken which had BatchTradeExtension initialized on their manager
63
+ address indexed _delegatedManager // Address of the DelegatedManager which initialized the BatchTradeExtension
54
64
);
55
65
56
66
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
67
+ address indexed _setToken , // Address of the SetToken which the failed trade targeted
68
+ uint256 indexed _index , // Index of trade that failed in _trades parameter of batchTrade call
69
+ string _reason , // String reason for the trade failure
70
+ TradeInfo _tradeInfo // Input TradeInfo of the failed trade
60
71
);
61
72
62
73
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
74
+ address indexed _setToken , // Address of the SetToken which the failed trade targeted
75
+ uint256 indexed _index , // Index of trade that failed in _trades parameter of batchTrade call
76
+ bytes _lowLevelData , // Bytes low level data reason for the trade failure
77
+ TradeInfo _tradeInfo // Input TradeInfo of the failed trade
66
78
);
67
79
68
80
/* ============ State Variables ============ */
69
81
70
82
// Instance of TradeModule
71
83
ITradeModule public immutable tradeModule;
72
84
85
+ // List of allowed TradeModule exchange integrations
86
+ string [] public integrations;
87
+
88
+ // Mapping to check whether string is allowed TradeModule exchange integration
89
+ mapping (string => bool ) public isIntegration;
90
+
73
91
/* ============ Modifiers ============ */
74
92
75
93
/**
76
- * Throws if any assets are not allowed to be held by the Set
94
+ * Throws if the sender is not the ManagerCore contract owner
77
95
*/
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
- }
96
+ modifier onlyManagerCoreOwner () {
97
+ require (msg .sender == managerCore.owner (), "Caller must be ManagerCore owner " );
82
98
_;
83
99
}
84
100
85
101
/* ============ Constructor ============ */
86
102
103
+ /**
104
+ * Instantiate with ManagerCore address, TradeModule address, and allowed TradeModule integration strings.
105
+ *
106
+ * @param _managerCore Address of ManagerCore contract
107
+ * @param _tradeModule Address of TradeModule contract
108
+ * @param _integrations List of TradeModule exchange integrations to allow
109
+ */
87
110
constructor (
88
111
IManagerCore _managerCore ,
89
- ITradeModule _tradeModule
112
+ ITradeModule _tradeModule ,
113
+ string [] memory _integrations
90
114
)
91
115
public
92
116
BaseGlobalExtension (_managerCore)
93
117
{
94
118
tradeModule = _tradeModule;
119
+
120
+ integrations = _integrations;
121
+ uint256 integrationsLength = _integrations.length ;
122
+ for (uint256 i = 0 ; i < integrationsLength; i++ ) {
123
+ _addIntegration (_integrations[i]);
124
+ }
95
125
}
96
126
97
127
/* ============ External Functions ============ */
98
128
129
+ /**
130
+ * MANAGER OWNER ONLY. Allows manager owner to add allowed TradeModule exchange integrations
131
+ *
132
+ * @param _integrations List of TradeModule exchange integrations to allow
133
+ */
134
+ function addIntegrations (string [] memory _integrations ) external onlyManagerCoreOwner {
135
+ uint256 integrationsLength = _integrations.length ;
136
+ for (uint256 i = 0 ; i < integrationsLength; i++ ) {
137
+ require (! isIntegration[_integrations[i]], "Integration already exists " );
138
+
139
+ integrations.push (_integrations[i]);
140
+
141
+ _addIntegration (_integrations[i]);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * MANAGER OWNER ONLY. Allows manager owner to remove allowed TradeModule exchange integrations
147
+ *
148
+ * @param _integrations List of TradeModule exchange integrations to disallow
149
+ */
150
+ function removeIntegrations (string [] memory _integrations ) external onlyManagerCoreOwner {
151
+ uint256 integrationsLength = _integrations.length ;
152
+ for (uint256 i = 0 ; i < integrationsLength; i++ ) {
153
+ require (isIntegration[_integrations[i]], "Integration does not exist " );
154
+
155
+ integrations.removeStorage (_integrations[i]);
156
+
157
+ isIntegration[_integrations[i]] = false ;
158
+
159
+ IntegrationRemoved (_integrations[i]);
160
+ }
161
+ }
162
+
99
163
/**
100
164
* ONLY OWNER: Initializes TradeModule on the SetToken associated with the DelegatedManager.
101
165
*
102
166
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
103
167
*/
104
168
function initializeModule (IDelegatedManager _delegatedManager ) external onlyOwnerAndValidManager (_delegatedManager) {
105
- require (_delegatedManager.isInitializedExtension (address (this )), "Extension must be initialized " );
106
-
107
169
_initializeModule (_delegatedManager.setToken (), _delegatedManager);
108
170
}
109
171
@@ -113,8 +175,6 @@ contract BatchTradeExtension is BaseGlobalExtension {
113
175
* @param _delegatedManager Instance of the DelegatedManager to initialize
114
176
*/
115
177
function initializeExtension (IDelegatedManager _delegatedManager ) external onlyOwnerAndValidManager (_delegatedManager) {
116
- require (_delegatedManager.isPendingExtension (address (this )), "Extension must be pending " );
117
-
118
178
ISetToken setToken = _delegatedManager.setToken ();
119
179
120
180
_initializeExtension (setToken, _delegatedManager);
@@ -123,13 +183,11 @@ contract BatchTradeExtension is BaseGlobalExtension {
123
183
}
124
184
125
185
/**
126
- * ONLY OWNER: Initializes TradeExtension to the DelegatedManager and TradeModule to the SetToken
186
+ * ONLY OWNER: Initializes BatchTradeExtension to the DelegatedManager and TradeModule to the SetToken
127
187
*
128
188
* @param _delegatedManager Instance of the DelegatedManager to initialize
129
189
*/
130
190
function initializeModuleAndExtension (IDelegatedManager _delegatedManager ) external onlyOwnerAndValidManager (_delegatedManager){
131
- require (_delegatedManager.isPendingExtension (address (this )), "Extension must be pending " );
132
-
133
191
ISetToken setToken = _delegatedManager.setToken ();
134
192
135
193
_initializeExtension (setToken, _delegatedManager);
@@ -151,53 +209,84 @@ contract BatchTradeExtension is BaseGlobalExtension {
151
209
/**
152
210
* ONLY OPERATOR: Executes a batch of trades on a supported DEX. If any individual trades fail, events are emitted.
153
211
* @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.
212
+ * sent and received is the quantity of component units multiplied by the SetToken totalSupply.
155
213
*
156
214
* @param _setToken Instance of the SetToken to trade
157
- * @param _trades Struct of information for individual trades
215
+ * @param _trades Array of TradeInfo structs containing information about trades
158
216
*/
159
217
function batchTrade (
160
218
ISetToken _setToken ,
161
219
TradeInfo[] memory _trades
162
220
)
163
221
external
164
222
onlyOperator (_setToken)
165
- onlyAllowedAssets (_setToken, _trades)
166
223
{
167
- for (uint256 i = 0 ; i < _trades.length ; i++ ) {
168
- bytes memory callData = abi.encodeWithSignature (
169
- "trade(address,string,address,uint256,address,uint256,bytes) " ,
224
+ uint256 tradesLength = _trades.length ;
225
+ IDelegatedManager manager = _manager (_setToken);
226
+ for (uint256 i = 0 ; i < tradesLength; i++ ) {
227
+ require (isIntegration[_trades[i].exchangeName], "Must be allowed integration " );
228
+ require (manager.isAllowedAsset (_trades[i].receiveToken), "Must be allowed asset " );
229
+
230
+ bytes memory callData = abi.encodeWithSelector (
231
+ ITradeModule.trade.selector ,
170
232
_setToken,
171
233
_trades[i].exchangeName,
172
234
_trades[i].sendToken,
173
235
_trades[i].sendQuantity,
174
236
_trades[i].receiveToken,
175
- _trades[i].minReceiveQuantity ,
237
+ _trades[i].receiveQuantity ,
176
238
_trades[i].data
177
239
);
178
240
179
241
// ZeroEx (for example) throws custom errors which slip through OpenZeppelin's
180
242
// functionCallWithValue error management and surface here as `bytes`. These should be
181
243
// 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);
244
+ try manager.interactManager (address (tradeModule), callData) {}
245
+ catch Error (string memory reason ) {
246
+ emit StringTradeFailed (
247
+ address (_setToken),
248
+ i,
249
+ reason,
250
+ _trades[i]
251
+ );
252
+ } catch (bytes memory lowLevelData ) {
253
+ emit BytesTradeFailed (
254
+ address (_setToken),
255
+ i,
256
+ lowLevelData,
257
+ _trades[i]
258
+ );
187
259
}
188
260
}
189
261
}
190
262
263
+ /* ============ External Getter Functions ============ */
264
+
265
+ function getIntegrations () external view returns (string [] memory ) {
266
+ return integrations;
267
+ }
268
+
191
269
/* ============ Internal Functions ============ */
192
270
271
+ /**
272
+ * Add an allowed TradeModule exchange integration to the BatchTradeExtension
273
+ *
274
+ * @param _integrationName Name of TradeModule exchange integration to allow
275
+ */
276
+ function _addIntegration (string memory _integrationName ) internal {
277
+ isIntegration[_integrationName] = true ;
278
+
279
+ emit IntegrationAdded (_integrationName);
280
+ }
281
+
193
282
/**
194
283
* Internal function to initialize TradeModule on the SetToken associated with the DelegatedManager.
195
284
*
196
285
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
197
286
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
198
287
*/
199
288
function _initializeModule (ISetToken _setToken , IDelegatedManager _delegatedManager ) internal {
200
- bytes memory callData = abi.encodeWithSignature ( " initialize(address) " , _setToken);
289
+ bytes memory callData = abi.encodeWithSelector (ITradeModule. initialize. selector , _setToken);
201
290
_invokeManager (_delegatedManager, address (tradeModule), callData);
202
291
}
203
292
}
0 commit comments