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 { BytesLib } from "../../../../external/contracts/uniswap/v3/lib/BytesLib.sol " ;
23
+ import { ISwapRouter } from "../../../interfaces/external/ISwapRouter.sol " ;
24
+
25
+ /**
26
+ * @title UniswapV3ExchangeAdapterV2
27
+ * @author Set Protocol
28
+ *
29
+ * Exchange adapter for Uniswap V3 SwapRouter that encodes trade data. Supports multi-hop trades.
30
+ *
31
+ * CHANGE LOG:
32
+ * - Generalized ability to choose whether to swap an exact amount of source token for a min amount of
33
+ * receive token or swap a max amount of source token for an exact amount of receive token.
34
+ */
35
+ contract UniswapV3ExchangeAdapterV2 {
36
+
37
+ using BytesLib for bytes ;
38
+
39
+ /* ============ State Variables ============ */
40
+
41
+ // Address of Uniswap V3 SwapRouter contract
42
+ address public immutable swapRouter;
43
+
44
+ /* ============ Constructor ============ */
45
+
46
+ /**
47
+ * Set state variables
48
+ *
49
+ * @param _swapRouter Address of Uniswap V3 SwapRouter
50
+ */
51
+ constructor (address _swapRouter ) public {
52
+ swapRouter = _swapRouter;
53
+ }
54
+
55
+ /* ============ External Getter Functions ============ */
56
+
57
+ /**
58
+ * Return calldata for Uniswap V3 SwapRouter
59
+ *
60
+ * @param _sourceToken Address of source token to be sold
61
+ * @param _destinationToken Address of destination token to buy
62
+ * @param _destinationAddress Address that assets should be transferred to
63
+ * @param _sourceQuantity Fixed/Max amount of source token to sell
64
+ * @param _destinationQuantity Min/Fixed amount of destination token to buy
65
+ * @param _data Bytes containing trade path and bool to determine function string.
66
+ * Equals the output of the generateDataParam function
67
+ * NOTE: Path for `exactOutput` swaps are reversed
68
+ *
69
+ * @return address Target contract address
70
+ * @return uint256 Call value
71
+ * @return bytes Trade calldata
72
+ */
73
+ function getTradeCalldata (
74
+ address _sourceToken ,
75
+ address _destinationToken ,
76
+ address _destinationAddress ,
77
+ uint256 _sourceQuantity ,
78
+ uint256 _destinationQuantity ,
79
+ bytes calldata _data
80
+ )
81
+ external
82
+ view
83
+ returns (address , uint256 , bytes memory )
84
+ {
85
+ // For a single hop trade, `_data.length` is 44. 20 source/destination token address + 3 fees +
86
+ // 20 source/destination token address + 1 fixInput bool.
87
+ // For multi-hop trades, `_data.length` is greater than 44.
88
+ require (_data.length >= 44 , "Invalid data " );
89
+
90
+ bool fixInput = toBool (_data, _data.length - 1 ); // `fixInput` bool is stored at last byte
91
+
92
+ address sourceFromPath;
93
+ address destinationFromPath;
94
+
95
+ if (fixInput) {
96
+ sourceFromPath = _data.toAddress (0 );
97
+ destinationFromPath = _data.toAddress (_data.length - 21 );
98
+ } else {
99
+ // Path for exactOutput swaps are reversed
100
+ sourceFromPath = _data.toAddress (_data.length - 21 );
101
+ destinationFromPath = _data.toAddress (0 );
102
+ }
103
+
104
+ require (_sourceToken == sourceFromPath, "Source token path mismatch " );
105
+ require (_destinationToken == destinationFromPath, "Destination token path mismatch " );
106
+
107
+ bytes memory pathData = _data.slice (0 , _data.length - 1 ); // Extract path data from `_data`
108
+
109
+ bytes memory callData = fixInput
110
+ ? abi.encodeWithSelector (
111
+ ISwapRouter.exactInput.selector ,
112
+ ISwapRouter.ExactInputParams (
113
+ pathData,
114
+ _destinationAddress,
115
+ block .timestamp ,
116
+ _sourceQuantity,
117
+ _destinationQuantity
118
+ )
119
+ )
120
+ : abi.encodeWithSelector (
121
+ ISwapRouter.exactOutput.selector ,
122
+ ISwapRouter.ExactOutputParams (
123
+ pathData,
124
+ _destinationAddress,
125
+ block .timestamp ,
126
+ _destinationQuantity, // swapped vs exactInputParams
127
+ _sourceQuantity
128
+ )
129
+ );
130
+
131
+ return (swapRouter, 0 , callData);
132
+ }
133
+
134
+ /**
135
+ * Returns the address to approve source tokens to for trading. This is the Uniswap SwapRouter address
136
+ *
137
+ * @return address Address of the contract to approve tokens to
138
+ */
139
+ function getSpender () external view returns (address ) {
140
+ return swapRouter;
141
+ }
142
+
143
+ /**
144
+ * Returns the appropriate _data argument for getTradeCalldata. Equal to the encodePacked path with the
145
+ * fee of each hop between it and fixInput bool at the very end., e.g [token1, fee1, token2, fee2, token3, fixIn].
146
+ * Note: _fees.length == _path.length - 1
147
+ *
148
+ * @param _path array of addresses to use as the path for the trade
149
+ * @param _fees array of uint24 representing the pool fee to use for each hop
150
+ * @param _fixIn Boolean indicating if input amount is fixed
151
+ *
152
+ * @return bytes Bytes containing trade path and bool to determine function string.
153
+ */
154
+ function generateDataParam (
155
+ address [] calldata _path ,
156
+ uint24 [] calldata _fees ,
157
+ bool _fixIn
158
+ ) external pure returns (bytes memory ) {
159
+ bytes memory data = "" ;
160
+ for (uint256 i = 0 ; i < _path.length - 1 ; i++ ) {
161
+ data = abi.encodePacked (data, _path[i], _fees[i]);
162
+ }
163
+
164
+ // Last encode has no fee associated with it since _fees.length == _path.length - 1
165
+ data = abi.encodePacked (data, _path[_path.length - 1 ]);
166
+
167
+ // Encode fixIn
168
+ return abi.encodePacked (data, _fixIn);
169
+ }
170
+
171
+ /**
172
+ * Helper function to decode bytes to boolean. Similar to functions found in BytesLib.
173
+ * Note: Access modifier is set to public to enable complete testing.
174
+ */
175
+ function toBool (bytes memory _bytes , uint256 _start ) public pure returns (bool ) {
176
+ require (_start + 1 >= _start, "toBool_overflow " );
177
+ require (_bytes.length >= _start + 1 , "toBool_outOfBounds " );
178
+ uint8 tempUint;
179
+
180
+ assembly {
181
+ tempUint := mload (add (add (_bytes, 0x1 ), _start))
182
+ }
183
+
184
+ require (tempUint <= 1 , "Invalid bool data " ); // Should be either 0 or 1
185
+
186
+ return (tempUint == 0 ) ? false : true ;
187
+ }
188
+ }
0 commit comments