Skip to content

Commit b4d95ca

Browse files
author
Sachin
authored
feat(UniswapV3ExchangeAdapterV2): Add UniswapV3ExchangeAdapterV2 exchange adapter (#240)
* Add uniswapV3ExchangeAdapterV2 contract * Fix compilation bug * Add unit tests * Fix exactOutput bug; Make toBool public and add unit tests for it * Add integration tests with TradeModule * Fix coverage: Remove .only from tests * Improve javadocs and comments; Add suggested changes
1 parent 6db2150 commit b4d95ca

File tree

5 files changed

+816
-3
lines changed

5 files changed

+816
-3
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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

Comments
 (0)