Skip to content

Commit 24d8078

Browse files
authored
fix(BatchTradeExtension): BatchTradeExtension audit fixes [SIM-269] (#34)
* add BatchTradeExtension gas optimizations and javadoc fixes * add allowed TradeModule integrations to BatchTradeExtension * emit TradeInfo struct in failed trade events, add onlyManagerCoreOwner modifier, clean comments
1 parent 29db4a3 commit 24d8078

File tree

6 files changed

+327
-57
lines changed

6 files changed

+327
-57
lines changed

contracts/extensions/BatchTradeExtension.sol

Lines changed: 127 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pragma experimental "ABIEncoderV2";
2121

2222
import { ISetToken } from "@setprotocol/set-protocol-v2/contracts/interfaces/ISetToken.sol";
2323
import { ITradeModule } from "@setprotocol/set-protocol-v2/contracts/interfaces/ITradeModule.sol";
24+
import { StringArrayUtils } from "@setprotocol/set-protocol-v2/contracts/lib/StringArrayUtils.sol";
2425

2526
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
2627
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
@@ -30,80 +31,141 @@ import { IManagerCore } from "../interfaces/IManagerCore.sol";
3031
* @title BatchTradeExtension
3132
* @author Set Protocol
3233
*
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.
3536
*/
3637
contract BatchTradeExtension is BaseGlobalExtension {
38+
using StringArrayUtils for string[];
3739

3840
/* ============ Structs ============ */
3941

4042
struct TradeInfo {
4143
string exchangeName; // Human readable name of the exchange in the integrations registry
4244
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
4446
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
4648
bytes data; // Arbitrary bytes to be used to construct trade call data
4749
}
4850

4951
/* ============ Events ============ */
5052

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+
5161
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
5464
);
5565

5666
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
6071
);
6172

6273
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
6678
);
6779

6880
/* ============ State Variables ============ */
6981

7082
// Instance of TradeModule
7183
ITradeModule public immutable tradeModule;
7284

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+
7391
/* ============ Modifiers ============ */
7492

7593
/**
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
7795
*/
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");
8298
_;
8399
}
84100

85101
/* ============ Constructor ============ */
86102

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+
*/
87110
constructor(
88111
IManagerCore _managerCore,
89-
ITradeModule _tradeModule
112+
ITradeModule _tradeModule,
113+
string[] memory _integrations
90114
)
91115
public
92116
BaseGlobalExtension(_managerCore)
93117
{
94118
tradeModule = _tradeModule;
119+
120+
integrations = _integrations;
121+
uint256 integrationsLength = _integrations.length;
122+
for (uint256 i = 0; i < integrationsLength; i++) {
123+
_addIntegration(_integrations[i]);
124+
}
95125
}
96126

97127
/* ============ External Functions ============ */
98128

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+
99163
/**
100164
* ONLY OWNER: Initializes TradeModule on the SetToken associated with the DelegatedManager.
101165
*
102166
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
103167
*/
104168
function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
105-
require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized");
106-
107169
_initializeModule(_delegatedManager.setToken(), _delegatedManager);
108170
}
109171

@@ -113,8 +175,6 @@ contract BatchTradeExtension is BaseGlobalExtension {
113175
* @param _delegatedManager Instance of the DelegatedManager to initialize
114176
*/
115177
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
116-
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
117-
118178
ISetToken setToken = _delegatedManager.setToken();
119179

120180
_initializeExtension(setToken, _delegatedManager);
@@ -123,13 +183,11 @@ contract BatchTradeExtension is BaseGlobalExtension {
123183
}
124184

125185
/**
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
127187
*
128188
* @param _delegatedManager Instance of the DelegatedManager to initialize
129189
*/
130190
function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){
131-
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
132-
133191
ISetToken setToken = _delegatedManager.setToken();
134192

135193
_initializeExtension(setToken, _delegatedManager);
@@ -151,53 +209,84 @@ contract BatchTradeExtension is BaseGlobalExtension {
151209
/**
152210
* ONLY OPERATOR: Executes a batch of trades on a supported DEX. If any individual trades fail, events are emitted.
153211
* @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.
155213
*
156214
* @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
158216
*/
159217
function batchTrade(
160218
ISetToken _setToken,
161219
TradeInfo[] memory _trades
162220
)
163221
external
164222
onlyOperator(_setToken)
165-
onlyAllowedAssets(_setToken, _trades)
166223
{
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,
170232
_setToken,
171233
_trades[i].exchangeName,
172234
_trades[i].sendToken,
173235
_trades[i].sendQuantity,
174236
_trades[i].receiveToken,
175-
_trades[i].minReceiveQuantity,
237+
_trades[i].receiveQuantity,
176238
_trades[i].data
177239
);
178240

179241
// ZeroEx (for example) throws custom errors which slip through OpenZeppelin's
180242
// functionCallWithValue error management and surface here as `bytes`. These should be
181243
// 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+
);
187259
}
188260
}
189261
}
190262

263+
/* ============ External Getter Functions ============ */
264+
265+
function getIntegrations() external view returns (string[] memory) {
266+
return integrations;
267+
}
268+
191269
/* ============ Internal Functions ============ */
192270

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+
193282
/**
194283
* Internal function to initialize TradeModule on the SetToken associated with the DelegatedManager.
195284
*
196285
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
197286
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
198287
*/
199288
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);
201290
_invokeManager(_delegatedManager, address(tradeModule), callData);
202291
}
203292
}

contracts/interfaces/IManagerCore.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ interface IManagerCore {
2222
function isExtension(address _extension) external view returns(bool);
2323
function isFactory(address _factory) external view returns(bool);
2424
function isManager(address _manager) external view returns(bool);
25+
function owner() external view returns(address);
2526
}

0 commit comments

Comments
 (0)