Skip to content

Commit c3f83e0

Browse files
manhlx3006ilanDoron
authored andcommitted
Verify Network balances of src & dst tokens after each reserve trade (#928)
* Verify Network balances of src & dst tokens after each reserve trade * Update revert messages to make it more readable
1 parent ced5be2 commit c3f83e0

10 files changed

+350
-57
lines changed

contracts/sol6/KyberNetwork.sol

+94-30
Original file line numberDiff line numberDiff line change
@@ -551,8 +551,10 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
551551
IERC20 src,
552552
IERC20 dest,
553553
address payable destAddress,
554-
TradeData memory tradeData,
555-
uint256 expectedDestAmount
554+
ReservesData memory reservesData,
555+
uint256 expectedDestAmount,
556+
uint256 srcDecimals,
557+
uint256 destDecimals
556558
) internal virtual returns (bool) {
557559

558560
if (src == dest) {
@@ -564,25 +566,15 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
564566
return true;
565567
}
566568

567-
ReservesData memory reservesData = (src == ETH_TOKEN_ADDRESS)
568-
? tradeData.ethToToken
569-
: tradeData.tokenToEth;
570-
uint256 callValue;
571-
572569
for (uint256 i = 0; i < reservesData.addresses.length; i++) {
573-
callValue = (src == ETH_TOKEN_ADDRESS) ? reservesData.srcAmounts[i] : 0;
574-
575-
// reserve sends tokens/eth to network. network sends it to destination
576-
require(
577-
reservesData.addresses[i].trade{value: callValue}(
578-
src,
579-
reservesData.srcAmounts[i],
580-
dest,
581-
address(this),
582-
reservesData.rates[i],
583-
true
584-
),
585-
"trade failed"
570+
tradeAndVerifyNetworkBalance(
571+
reservesData.addresses[i],
572+
src,
573+
reservesData.srcAmounts[i],
574+
dest,
575+
reservesData.rates[i],
576+
srcDecimals,
577+
destDecimals
586578
);
587579
}
588580

@@ -594,6 +586,76 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
594586
return true;
595587
}
596588

589+
/// @dev call trade from reserve and verify balances
590+
/// return actual dest amount received from reserve
591+
/// @param reserve reserve address to trade with
592+
/// @param src src token of the trade
593+
/// @param srcAmount amount of src token to trade
594+
/// @param dest dest token of the trade
595+
/// @param conversionRate conversion rate of the trade
596+
/// @param srcDecimals src token decimals
597+
/// @param destDecimals dest token decimals
598+
function tradeAndVerifyNetworkBalance(
599+
IKyberReserve reserve,
600+
IERC20 src,
601+
uint256 srcAmount,
602+
IERC20 dest,
603+
uint256 conversionRate,
604+
uint256 srcDecimals,
605+
uint256 destDecimals
606+
) internal
607+
{
608+
uint256 destBalanceBefore = getBalance(dest, address(this));
609+
610+
uint256 srcBalanceBefore;
611+
uint256 callValue;
612+
613+
if (src == ETH_TOKEN_ADDRESS) {
614+
srcBalanceBefore = 0; // no need to verify src balance
615+
callValue = srcAmount;
616+
} else {
617+
srcBalanceBefore = getBalance(src, address(this));
618+
callValue = 0;
619+
}
620+
621+
// reserve sends tokens/eth to network. network sends it to destination
622+
uint256 expectedDestAmount = calcDstQty(
623+
srcAmount,
624+
srcDecimals,
625+
destDecimals,
626+
conversionRate
627+
);
628+
629+
require(
630+
reserve.trade{value: callValue}(
631+
src,
632+
srcAmount,
633+
dest,
634+
address(this),
635+
conversionRate,
636+
true
637+
),
638+
"trade failed"
639+
);
640+
641+
uint256 balanceAfter;
642+
if (src != ETH_TOKEN_ADDRESS) {
643+
// verify src balance only if it is not ETH
644+
balanceAfter = getBalance(src, address(this));
645+
// verify correct src amount is taken
646+
if (srcBalanceBefore >= balanceAfter && srcBalanceBefore - balanceAfter > srcAmount) {
647+
revert("reserve takes high amount");
648+
}
649+
}
650+
// verify correct dest amount is received
651+
balanceAfter = getBalance(dest, address(this));
652+
if (balanceAfter < destBalanceBefore || balanceAfter - destBalanceBefore < expectedDestAmount) {
653+
revert("reserve returns low amount");
654+
}
655+
}
656+
657+
/* solhint-disable function-max-lines */
658+
// Most of the lines here are functions calls spread over multiple lines. We find this function readable enough
597659
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
598660
/// @dev Trade API for kyber network
599661
/// @param tradeData Main trade data object for trade info to be stored
@@ -630,8 +692,10 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
630692
tradeData.input.src,
631693
ETH_TOKEN_ADDRESS,
632694
address(this),
633-
tradeData,
634-
tradeData.tradeWei
695+
tradeData.tokenToEth,
696+
tradeData.tradeWei,
697+
tradeData.tokenToEth.decimals,
698+
ETH_DECIMALS
635699
)
636700
); // tradeData.tradeWei (expectedDestAmount) not used if destAddress == address(this)
637701

@@ -641,8 +705,10 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
641705
ETH_TOKEN_ADDRESS,
642706
tradeData.input.dest,
643707
tradeData.input.destAddress,
644-
tradeData,
645-
destAmount
708+
tradeData.ethToToken,
709+
destAmount,
710+
ETH_DECIMALS,
711+
tradeData.ethToToken.decimals
646712
)
647713
);
648714

@@ -748,7 +814,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
748814
view
749815
returns (uint256 destAmount, uint256 rateWithNetworkFee)
750816
{
751-
// token to ether: find best reserve match and calculate wei amount
817+
// token to ether: find best reserves match and calculate wei amount
752818
tradeData.tradeWei = calcDestQtyAndMatchReserves(
753819
tradeData.input.src,
754820
ETH_TOKEN_ADDRESS,
@@ -762,18 +828,17 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
762828
return (0, 0);
763829
}
764830

765-
// platform fee
831+
// calculate fees
766832
tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS;
767833
tradeData.networkFeeWei =
768834
(((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
769835
BPS;
770-
// set networkFeeWei in stack. since we set it again after full flow done.
771836
require(
772837
tradeData.tradeWei >= (tradeData.networkFeeWei + tradeData.platformFeeWei),
773838
"fees exceed trade"
774839
);
775840

776-
// ether to token: find best reserve match and calculate trade dest amount
841+
// ether to token: find best reserves match and calculate trade dest amount
777842
uint256 actualSrcWei = tradeData.tradeWei -
778843
tradeData.networkFeeWei -
779844
tradeData.platformFeeWei;
@@ -1172,8 +1237,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra
11721237
);
11731238
if (newSrcAmounts[i] > currentSrcAmount) {
11741239
// revert back to use current src amounts
1175-
newSrcAmount = srcAmount;
1176-
return newSrcAmount;
1240+
return srcAmount;
11771241
}
11781242

11791243
newSrcAmount += newSrcAmounts[i];

contracts/sol6/mock/GenerousKyberNetwork.sol

+8-4
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ contract GenerousKyberNetwork is KyberNetwork {
9090
tData.input.src,
9191
ETH_TOKEN_ADDRESS,
9292
address(this),
93-
tData,
94-
tData.tradeWei
93+
tData.tokenToEth,
94+
tData.tradeWei,
95+
tData.tokenToEth.decimals,
96+
ETH_DECIMALS
9597
)
9698
); //tData.tradeWei (expectedDestAmount) not used if destAddress == address(this)
9799

@@ -100,8 +102,10 @@ contract GenerousKyberNetwork is KyberNetwork {
100102
ETH_TOKEN_ADDRESS,
101103
tData.input.dest,
102104
tData.input.destAddress,
103-
tData,
104-
destAmount
105+
tData.ethToToken,
106+
destAmount,
107+
ETH_DECIMALS,
108+
tData.ethToToken.decimals
105109
)
106110
);
107111

contracts/sol6/mock/GenerousKyberNetwork2.sol

+8-4
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ contract GenerousKyberNetwork2 is KyberNetwork {
7575
tData.input.src,
7676
ETH_TOKEN_ADDRESS,
7777
address(this),
78-
tData,
79-
tData.tradeWei
78+
tData.tokenToEth,
79+
tData.tradeWei,
80+
tData.tokenToEth.decimals,
81+
ETH_DECIMALS
8082
)
8183
); //tData.tradeWei (expectedDestAmount) not used if destAddress == address(this)
8284

@@ -85,8 +87,10 @@ contract GenerousKyberNetwork2 is KyberNetwork {
8587
ETH_TOKEN_ADDRESS,
8688
tData.input.dest,
8789
tData.input.destAddress,
88-
tData,
89-
destAmount
90+
tData.ethToToken,
91+
destAmount,
92+
ETH_DECIMALS,
93+
tData.ethToToken.decimals
9094
)
9195
);
9296

contracts/sol6/mock/MaliciousKyberNetwork.sol

+15-9
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ contract MaliciousKyberNetwork is KyberNetwork {
7272
actualSrcAmount,
7373
ETH_TOKEN_ADDRESS,
7474
address(this),
75-
tData,
76-
tData.tradeWei
75+
tData.tokenToEth,
76+
tData.tradeWei,
77+
tData.tokenToEth.decimals,
78+
ETH_DECIMALS
7779
)
7880
); //tData.tradeWei (expectedDestAmount) not used if destAddress == address(this)
7981

@@ -83,8 +85,10 @@ contract MaliciousKyberNetwork is KyberNetwork {
8385
tData.tradeWei - tData.networkFeeWei - tData.platformFeeWei,
8486
tData.input.dest,
8587
tData.input.destAddress,
86-
tData,
87-
destAmount
88+
tData.ethToToken,
89+
destAmount,
90+
ETH_DECIMALS,
91+
tData.ethToToken.decimals
8892
)
8993
);
9094
require(handleFees(tData));
@@ -109,18 +113,20 @@ contract MaliciousKyberNetwork is KyberNetwork {
109113
uint256 amount,
110114
IERC20 dest,
111115
address payable destAddress,
112-
TradeData memory tData,
113-
uint256 expectedDestAmount
116+
ReservesData memory reservesData,
117+
uint256 expectedDestAmount,
118+
uint256 srcDecimals,
119+
uint256 destDecimals
114120
) internal virtual returns (bool) {
115121
if (src == dest) {
116122
//E2E, need not do anything except for T2E, transfer ETH to destAddress
117123
if (destAddress != (address(this))) destAddress.transfer(amount - myFeeWei);
118124
return true;
119125
}
120126

121-
ReservesData memory reservesData = src == ETH_TOKEN_ADDRESS
122-
? tData.ethToToken
123-
: tData.tokenToEth;
127+
srcDecimals;
128+
destDecimals;
129+
124130
uint256 callValue;
125131
uint256 srcAmountSoFar;
126132

contracts/sol6/mock/MaliciousKyberNetwork2.sol

+7-5
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,20 @@ contract MaliciousKyberNetwork2 is MaliciousKyberNetwork {
2020
uint256 amount,
2121
IERC20 dest,
2222
address payable destAddress,
23-
TradeData memory tData,
24-
uint256 expectedDestAmount
23+
ReservesData memory reservesData,
24+
uint256 expectedDestAmount,
25+
uint256 srcDecimals,
26+
uint256 destDecimals
2527
) internal override returns (bool) {
2628
if (src == dest) {
2729
//E2E, need not do anything except for T2E, transfer ETH to destAddress
2830
if (destAddress != (address(this))) destAddress.transfer(amount - myFeeWei);
2931
return true;
3032
}
3133

32-
ReservesData memory reservesData = src == ETH_TOKEN_ADDRESS
33-
? tData.ethToToken
34-
: tData.tokenToEth;
34+
srcDecimals;
35+
destDecimals;
36+
3537
uint256 callValue;
3638
uint256 srcAmountSoFar;
3739

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
pragma solidity 0.6.6;
2+
3+
import "./MockReserve.sol";
4+
5+
6+
contract MaliciousReserve2 is MockReserve {
7+
// extraSrcAmount > 0: take more src amount from network
8+
// extraSrcAmount < 0: take less src amount from network
9+
int256 public extraSrcAmount;
10+
// extraDestAmount > 0: send more dest amount to network
11+
// extraDestAmount < 0: send less dest amount to network
12+
int256 public extraDestAmount;
13+
14+
function setExtraSrcAndDestAmounts(int256 _extraSrcAmount, int256 _extraDestAmount) public {
15+
extraSrcAmount = _extraSrcAmount;
16+
extraDestAmount = _extraDestAmount;
17+
}
18+
19+
function trade(
20+
IERC20 srcToken,
21+
uint256 srcAmount,
22+
IERC20 destToken,
23+
address payable destAddress,
24+
uint256 conversionRate,
25+
bool validate
26+
) public payable override returns (bool) {
27+
validate;
28+
if (srcToken == ETH_TOKEN_ADDRESS) {
29+
require(msg.value == srcAmount, "ETH sent != srcAmount");
30+
} else {
31+
require(msg.value == 0, "ETH was sent for token -> ETH trade");
32+
}
33+
34+
uint256 srcDecimals = getDecimals(srcToken);
35+
uint256 destDecimals = getDecimals(destToken);
36+
uint256 destAmount = calcDstQty(srcAmount, srcDecimals, destDecimals, conversionRate);
37+
38+
// collect src tokens
39+
if (srcToken != ETH_TOKEN_ADDRESS) {
40+
srcToken.safeTransferFrom(msg.sender, address(this), uint256(int256(srcAmount) + extraSrcAmount));
41+
}
42+
43+
// send dest tokens
44+
uint256 actualDestAmount = uint256(int256(destAmount) + extraDestAmount);
45+
if (destToken == ETH_TOKEN_ADDRESS) {
46+
destAddress.transfer(actualDestAmount);
47+
} else {
48+
destToken.safeTransfer(destAddress, actualDestAmount);
49+
}
50+
51+
return true;
52+
}
53+
}

contracts/sol6/mock/MockNetwork.sol

+7-3
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@ contract MockNetwork is KyberNetwork {
6262
IERC20 src,
6363
IERC20 dest,
6464
address payable destAddress,
65-
TradeData memory tradeData,
66-
uint256 expectedDestAmount
65+
ReservesData memory reservesData,
66+
uint256 expectedDestAmount,
67+
uint256 srcDecimals,
68+
uint256 destDecimals
6769
) internal override returns (bool) {
6870
src;
6971
dest;
7072
destAddress;
71-
tradeData;
73+
reservesData;
7274
expectedDestAmount;
75+
srcDecimals;
76+
destDecimals;
7377

7478
revert("must use real network");
7579
// return true;

0 commit comments

Comments
 (0)