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