Skip to content

Commit a9342f7

Browse files
ducquangkstnilanDoron
andauthoredAug 6, 2020
uniswap v2 bridge reserve and add permission groups with modifiers
* add uniswapV2 reserve Co-authored-by: Ilan Doron <ilan.on.the.road@gmail.com>
1 parent 4f2bd9b commit a9342f7

13 files changed

+1958
-20
lines changed
 

‎.prettierrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"useTabs": false,
2020
"singleQuote": true,
2121
"bracketSpacing": false,
22-
"explicitTypes": "always"
22+
"explicitTypes": "always",
23+
"semi": true
2324
}
2425
}
2526
]

‎.solhint.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"func-name-mixedcase": "error",
1515
"func-order": "error",
1616
"imports-on-top": "error",
17-
"mark-callable-contracts": "error",
17+
"mark-callable-contracts": "off",
1818
"max-line-length": ["error", 99],
1919
"max-states-count": ["error", 20],
2020
"multiple-sends": "error",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
pragma solidity 0.6.6;
2+
3+
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
4+
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
5+
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
6+
7+
import "../../IKyberReserve.sol";
8+
import "../../IERC20.sol";
9+
import "../../utils/Withdrawable3.sol";
10+
import "../../utils/Utils5.sol";
11+
import "../../utils/zeppelin/SafeERC20.sol";
12+
13+
contract KyberUniswapV2Reserve is IKyberReserve, Withdrawable3, Utils5 {
14+
using SafeERC20 for IERC20;
15+
16+
uint256 public constant DEFAULT_FEE_BPS = 0;
17+
uint256 public constant DEADLINE = 2**255;
18+
19+
address public kyberNetwork;
20+
// fee deducted for each trade
21+
uint256 public feeBps = DEFAULT_FEE_BPS;
22+
23+
bool public tradeEnabled = true;
24+
25+
IUniswapV2Router01 public immutable uniswapRouter;
26+
IUniswapV2Factory public immutable uniswapFactory;
27+
address public immutable weth;
28+
29+
mapping(IERC20 => bool) public tokenListed;
30+
mapping(IERC20 => address[][]) public e2tSwapPaths;
31+
mapping(IERC20 => address[][]) public t2eSwapPaths;
32+
33+
event TradeExecute(
34+
address indexed sender,
35+
IERC20 indexed srcToken,
36+
uint256 srcAmount,
37+
IERC20 indexed destToken,
38+
uint256 destAmount,
39+
address destAddress
40+
);
41+
42+
event TokenListed(IERC20 indexed token, bool add);
43+
44+
event TokenPathAdded(IERC20 indexed token, address[] path, bool isEthToToken, bool add);
45+
46+
event TradeEnabled(bool enable);
47+
48+
event FeeUpdated(uint256 feeBps);
49+
50+
event EtherReceival(address indexed sender, uint256 amount);
51+
52+
event KyberNetworkSet(address kyberNetwork);
53+
54+
constructor(
55+
IUniswapV2Router01 _uniswapRouter,
56+
address _weth,
57+
address _admin,
58+
address _kyberNetwork
59+
) public Withdrawable3(_admin) {
60+
require(address(_uniswapRouter) != address(0), "uniswapRouter 0");
61+
require(_weth != address(0), "weth 0");
62+
require(_kyberNetwork != address(0), "kyberNetwork 0");
63+
64+
uniswapRouter = _uniswapRouter;
65+
uniswapFactory = IUniswapV2Factory(_uniswapRouter.factory());
66+
weth = _weth;
67+
kyberNetwork = _kyberNetwork;
68+
}
69+
70+
receive() external payable {
71+
emit EtherReceival(msg.sender, msg.value);
72+
}
73+
74+
/**
75+
conversionRate: expected conversion rate should be >= this value.
76+
*/
77+
function trade(
78+
IERC20 srcToken,
79+
uint256 srcAmount,
80+
IERC20 destToken,
81+
address payable destAddress,
82+
uint256 conversionRate,
83+
bool /* validate */
84+
) external override payable returns (bool) {
85+
require(tradeEnabled, "trade is disabled");
86+
require(msg.sender == kyberNetwork, "only kyberNetwork");
87+
require(isValidTokens(srcToken, destToken), "only use eth and listed token");
88+
89+
require(conversionRate > 0, "conversionRate 0");
90+
if (srcToken == ETH_TOKEN_ADDRESS) {
91+
require(msg.value == srcAmount, "msg.value != srcAmount");
92+
} else {
93+
require(msg.value == 0, "msg.value is not 0");
94+
}
95+
96+
uint256 expectedDestAmount = calcDestAmount(
97+
srcToken,
98+
destToken,
99+
srcAmount,
100+
conversionRate
101+
);
102+
103+
uint256 destAmount;
104+
(uint256 actualRate, address[] memory path) = calcUniswapConversion(
105+
srcToken,
106+
destToken,
107+
srcAmount
108+
);
109+
require(conversionRate <= actualRate, "expected conversionRate <= actualRate");
110+
111+
if (srcToken == ETH_TOKEN_ADDRESS) {
112+
// Deduct fees (in ETH) before converting
113+
uint256 quantity = deductFee(srcAmount);
114+
115+
uint256[] memory amounts = uniswapRouter.swapExactETHForTokens{value: quantity}(
116+
expectedDestAmount,
117+
path,
118+
destAddress,
119+
DEADLINE
120+
);
121+
destAmount = amounts[amounts.length - 1];
122+
require(destAmount >= expectedDestAmount, "Returned trade amount too low");
123+
} else {
124+
srcToken.safeTransferFrom(msg.sender, address(this), srcAmount);
125+
uint256[] memory amounts = uniswapRouter.swapExactTokensForETH(
126+
srcAmount,
127+
expectedDestAmount,
128+
path,
129+
address(this),
130+
DEADLINE
131+
);
132+
133+
destAmount = amounts[amounts.length - 1];
134+
// Deduct fees (in ETH) after converting
135+
destAmount = deductFee(destAmount);
136+
require(destAmount >= expectedDestAmount, "Returned trade amount too low");
137+
// Transfer user-expected dest amount
138+
destAddress.transfer(expectedDestAmount);
139+
}
140+
141+
emit TradeExecute(
142+
msg.sender,
143+
srcToken,
144+
srcAmount,
145+
destToken,
146+
expectedDestAmount,
147+
destAddress
148+
);
149+
return true;
150+
}
151+
152+
function setFee(uint256 _feeBps) external onlyAdmin {
153+
require(_feeBps < BPS, "fee >= BPS");
154+
if (_feeBps != feeBps) {
155+
feeBps = _feeBps;
156+
emit FeeUpdated(_feeBps);
157+
}
158+
}
159+
160+
function setKyberNetwork(address _kyberNetwork) external onlyAdmin {
161+
require(_kyberNetwork != address(0));
162+
if (kyberNetwork != _kyberNetwork) {
163+
kyberNetwork = _kyberNetwork;
164+
emit KyberNetworkSet(kyberNetwork);
165+
}
166+
}
167+
168+
function listToken(
169+
IERC20 token,
170+
bool addDefaultPaths,
171+
bool validate
172+
) external onlyOperator {
173+
require(token != IERC20(0), "token 0");
174+
175+
require(!tokenListed[token], "token is listed");
176+
tokenListed[token] = true;
177+
// list the direct path
178+
if (addDefaultPaths) {
179+
address[] memory paths = new address[](2);
180+
paths[0] = weth;
181+
paths[1] = address(token);
182+
addPath(token, paths, true);
183+
paths[0] = address(token);
184+
paths[1] = weth;
185+
addPath(token, paths, false);
186+
}
187+
// check if any path exists for this token
188+
if (validate && !addDefaultPaths) {
189+
require(e2tSwapPaths[token].length != 0, "no path exists for e2t");
190+
require(t2eSwapPaths[token].length != 0, "no path exists for t2e");
191+
}
192+
193+
token.safeApprove(address(uniswapRouter), MAX_ALLOWANCE);
194+
195+
setDecimals(token);
196+
197+
emit TokenListed(token, true);
198+
}
199+
200+
function delistToken(IERC20 token) external onlyOperator {
201+
require(tokenListed[token], "token is not listed");
202+
delete tokenListed[token];
203+
// clear all paths data
204+
delete t2eSwapPaths[token];
205+
delete e2tSwapPaths[token];
206+
207+
token.safeApprove(address(uniswapRouter), 0);
208+
emit TokenListed(token, false);
209+
}
210+
211+
function removePath(
212+
IERC20 token,
213+
bool isEthTotoken,
214+
uint256 index
215+
) external onlyOperator {
216+
address[][] storage allPaths;
217+
if (isEthTotoken) {
218+
allPaths = e2tSwapPaths[token];
219+
} else {
220+
allPaths = t2eSwapPaths[token];
221+
}
222+
require(index < allPaths.length, "invalid index");
223+
address[] memory path = allPaths[index];
224+
allPaths[index] = allPaths[allPaths.length - 1];
225+
allPaths.pop();
226+
227+
emit TokenPathAdded(token, path, isEthTotoken, false);
228+
}
229+
230+
function enableTrade() external onlyAdmin returns (bool) {
231+
tradeEnabled = true;
232+
emit TradeEnabled(true);
233+
return true;
234+
}
235+
236+
function disableTrade() external onlyAlerter returns (bool) {
237+
tradeEnabled = false;
238+
emit TradeEnabled(false);
239+
return true;
240+
}
241+
242+
/**
243+
* @dev called by kybernetwork to get settlement rate
244+
*/
245+
function getConversionRate(
246+
IERC20 src,
247+
IERC20 dest,
248+
uint256 srcQty,
249+
uint256 /* blockNumber */
250+
) external override view returns (uint256) {
251+
if (!isValidTokens(src, dest)) return 0;
252+
if (!tradeEnabled) return 0;
253+
if (srcQty == 0) return 0;
254+
255+
(uint256 rate, ) = calcUniswapConversion(src, dest, srcQty);
256+
return rate;
257+
}
258+
259+
function addPath(
260+
IERC20 token,
261+
address[] memory path,
262+
bool isEthToToken
263+
) public onlyOperator {
264+
address[][] storage allPaths;
265+
266+
require(path.length >= 2, "path is too short");
267+
if (isEthToToken) {
268+
require(path[0] == weth, "start address of path is not weth");
269+
require(path[path.length - 1] == address(token), "end address of path is not token");
270+
allPaths = e2tSwapPaths[token];
271+
} else {
272+
require(path[0] == address(token), "start address of path is not token");
273+
require(path[path.length - 1] == weth, "end address of path is not weth");
274+
allPaths = t2eSwapPaths[token];
275+
}
276+
// verify the pair existed and the pair has liquidity
277+
for (uint256 i = 0; i < path.length - 1; i++) {
278+
address uniswapPair = uniswapFactory.getPair(path[i], path[i + 1]);
279+
require(uniswapPair != address(0), "uniswapPair not found");
280+
(uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(uniswapPair).getReserves();
281+
require(reserve0 > 0 && reserve1 > 0, "insufficient liquidity");
282+
}
283+
allPaths.push(path);
284+
285+
emit TokenPathAdded(token, path, isEthToToken, true);
286+
}
287+
288+
function deductFee(uint256 amount) internal view returns (uint256) {
289+
return (amount * (BPS - feeBps)) / BPS;
290+
}
291+
292+
function isValidTokens(IERC20 src, IERC20 dest) internal view returns (bool) {
293+
return ((src == ETH_TOKEN_ADDRESS && tokenListed[dest]) ||
294+
(tokenListed[src] && dest == ETH_TOKEN_ADDRESS));
295+
}
296+
297+
function calcUniswapConversion(
298+
IERC20 src,
299+
IERC20 dest,
300+
uint256 srcQty
301+
) internal view returns (uint256 rate, address[] memory path) {
302+
uint256 destQty = 0;
303+
address[][] storage allPaths;
304+
if (src == ETH_TOKEN_ADDRESS) {
305+
uint256 amountLessFee = deductFee(srcQty);
306+
if (amountLessFee == 0) {
307+
return (rate, path);
308+
}
309+
310+
allPaths = e2tSwapPaths[dest];
311+
for (uint256 i = 0; i < allPaths.length; i++) {
312+
address[] memory currentPath = allPaths[i];
313+
uint256[] memory amounts = uniswapRouter.getAmountsOut(amountLessFee, currentPath);
314+
if (amounts[amounts.length - 1] > destQty) {
315+
destQty = amounts[amounts.length - 1];
316+
path = currentPath;
317+
}
318+
}
319+
} else {
320+
allPaths = t2eSwapPaths[src];
321+
for (uint256 i = 0; i < allPaths.length; i++) {
322+
address[] memory currentPath = allPaths[i];
323+
uint256[] memory amounts = uniswapRouter.getAmountsOut(srcQty, currentPath);
324+
if (amounts[amounts.length - 1] > destQty) {
325+
destQty = amounts[amounts.length - 1];
326+
path = currentPath;
327+
}
328+
}
329+
destQty = deductFee(destQty);
330+
}
331+
if (destQty == 0) return (rate, path);
332+
rate = calcRateFromQty(srcQty, destQty, getDecimals(src), getDecimals(dest));
333+
}
334+
}

0 commit comments

Comments
 (0)
Please sign in to comment.