Skip to content

Commit 4038cae

Browse files
authored
Set higher max rate for Kyber reserve + conversion rate enhanced steps. (#1052)
* We align MAX_RATE parameter with Katalyst, from 10**24 to 10 ** 25. * Create a mock Contract to enable easy checking of steps logic,
1 parent ac3829b commit 4038cae

10 files changed

+2103
-1
lines changed

buidler.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ module.exports = {
145145

146146
paths: {
147147
sources: "./contracts/sol6",
148-
tests: "./test/sol6",
148+
tests: "./test/",
149149
},
150150

151151
mocha: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
pragma solidity 0.4.18;
2+
3+
import "../reserves/fprConversionRate/ConversionRateEnhancedSteps.sol";
4+
5+
/**
6+
* @title ConversionRateEnhancedOpen
7+
* Inherits ConversionRateEnhancedSteps.
8+
* Supports API to check new getRate logic by exposing internal data.
9+
* Additional API enables 2 options:
10+
* - get rate queries that also show steps data (accumulated Y value).
11+
* - query get rate with fake imbalance.
12+
*/
13+
contract ConversionRateEnhancedOpen is ConversionRateEnhancedSteps {
14+
15+
function ConversionRateEnhancedOpen(address _admin) public
16+
ConversionRateEnhancedSteps(_admin)
17+
{ } // solhint-disable-line no-empty-blocks
18+
19+
///@dev enables calling get rate and watching extra BPS from step function.
20+
///@dev doesn't check: token listed, valid rate duration.
21+
/// this logic isn't required since only used to show getRate internal values. extra logic isn't
22+
///@dev rateWithSteps value should be equal to rate value from getRate call
23+
function getRateOpenData(
24+
ERC20 token,
25+
bool buy,
26+
uint qty
27+
) public view returns(
28+
uint rateWithSteps,
29+
uint rateWithoutSteps,
30+
int extraBpsYdata
31+
) {
32+
// get rate update block
33+
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
34+
35+
uint updateRateBlock = getLast4Bytes(compactData);
36+
37+
// check imbalance
38+
int totalImbalance;
39+
int blockImbalance;
40+
(totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, block.number);
41+
42+
return getRateDataFakeImbalance(token, buy, qty, totalImbalance);
43+
}
44+
45+
///@dev enables calling get rate and watching extra BPS from step function.
46+
///@dev can supply fake imbalance and see steps values with this fake imbalance.
47+
function getRateDataFakeImbalance(
48+
ERC20 token,
49+
bool buy,
50+
uint qty,
51+
int fakeImbalance
52+
) public view returns (
53+
uint rateWithSteps,
54+
uint rateWithoutSteps,
55+
int extraBpsYdata)
56+
{
57+
// get rate update block
58+
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
59+
60+
// calculate actual rate
61+
int imbalanceQty;
62+
int8 rateUpdate;
63+
64+
if (buy) {
65+
// start with base rate
66+
rateWithoutSteps = tokenData[token].baseBuyRate;
67+
68+
// add rate update
69+
rateUpdate = getRateByteFromCompactData(compactData, token, true);
70+
extraBpsYdata = int(rateUpdate) * 10;
71+
rateWithoutSteps = addBps(rateWithoutSteps, extraBpsYdata);
72+
73+
// compute token qty
74+
qty = getTokenQty(token, qty, rateWithoutSteps);
75+
imbalanceQty = int(qty);
76+
77+
// add imbalance overhead
78+
extraBpsYdata = executeStepFunction(
79+
tokenData[token].buyRateImbalanceStepFunction,
80+
fakeImbalance,
81+
fakeImbalance + imbalanceQty
82+
);
83+
rateWithSteps = addBps(rateWithoutSteps, extraBpsYdata);
84+
} else {
85+
// start with base rate
86+
rateWithoutSteps = tokenData[token].baseSellRate;
87+
88+
// add rate update
89+
rateUpdate = getRateByteFromCompactData(compactData, token, false);
90+
extraBpsYdata = int(rateUpdate) * 10;
91+
rateWithoutSteps = addBps(rateWithoutSteps, extraBpsYdata);
92+
93+
// compute token qty
94+
imbalanceQty = -1 * int(qty);
95+
96+
// add imbalance overhead
97+
extraBpsYdata = executeStepFunction(
98+
tokenData[token].sellRateImbalanceStepFunction,
99+
fakeImbalance + imbalanceQty,
100+
fakeImbalance
101+
);
102+
rateWithSteps = addBps(rateWithoutSteps, extraBpsYdata);
103+
}
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
pragma solidity ^0.4.18;
2+
3+
import "../reserves/fprConversionRate/ConversionRateEnhancedSteps.sol";
4+
5+
6+
contract MockConversionRateEnhancedSteps is ConversionRateEnhancedSteps {
7+
function MockConversionRateEnhancedSteps(address admin) ConversionRateEnhancedSteps(admin) public {
8+
9+
}
10+
11+
function mockGetImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock) public view
12+
returns(int totalImbalance, int currentBlockImbalance)
13+
{
14+
(totalImbalance, currentBlockImbalance) = getImbalance(token, rateUpdateBlock, currentBlock);
15+
return(totalImbalance, currentBlockImbalance);
16+
}
17+
18+
function mockGetMaxTotalImbalance(ERC20 token) public view returns(uint) {
19+
return getMaxTotalImbalance(token);
20+
}
21+
22+
function getUpdateRateBlockFromCompact (ERC20 token) public view returns(uint updateRateBlock) {
23+
// get rate update block
24+
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
25+
updateRateBlock = getLast4Bytes(compactData);
26+
}
27+
28+
function mockAddBps(uint rate, int bps) public pure returns(uint) {
29+
return addBps(rate, bps);
30+
}
31+
32+
function mockIsTokenTradeEnabled(address token) public view returns (bool) {
33+
return tokenData[token].enabled;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
pragma solidity 0.4.18;
2+
3+
import "../reserves/KyberReserveHighRate.sol";
4+
5+
/// @title Kyber Reserve contract
6+
/// reuses KyberReserve.sol contract while overriding a few functions / values.
7+
/// Update MAX_RATE to higher value and should have maximum code reuse
8+
contract MockKyberReserveHighRate is KyberReserveHighRate {
9+
10+
function MockKyberReserveHighRate(address _kyberNetwork, ConversionRatesInterface _ratesContract,
11+
address _admin) public KyberReserveHighRate(_kyberNetwork, _ratesContract, _admin)
12+
{ }
13+
14+
function MockCalcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate)
15+
public pure returns(uint)
16+
{
17+
return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);
18+
}
19+
20+
function MockCalcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate)
21+
public pure returns(uint)
22+
{
23+
return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
pragma solidity 0.4.18;
2+
3+
import "./KyberReserve.sol";
4+
5+
/// @title Kyber Reserve contract
6+
/// reuses KyberReserve.sol contract while overriding a few functions / values.
7+
/// Update MAX_RATE to higher value and should have maximum code reuse
8+
contract KyberReserveHighRate is KyberReserve {
9+
10+
uint constant internal MAX_RATE = (PRECISION * 10 ** 7); // 10M tokens per ETH
11+
12+
function KyberReserveHighRate(address _kyberNetwork, ConversionRatesInterface _ratesContract,
13+
address _admin) public KyberReserve(_kyberNetwork, _ratesContract, _admin)
14+
{ }
15+
16+
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate)
17+
internal pure returns(uint)
18+
{
19+
require(srcQty <= MAX_QTY);
20+
require(rate <= MAX_RATE);
21+
22+
if (dstDecimals >= srcDecimals) {
23+
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
24+
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
25+
} else {
26+
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
27+
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
28+
}
29+
}
30+
31+
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate)
32+
internal pure returns(uint)
33+
{
34+
require(dstQty <= MAX_QTY);
35+
require(rate <= MAX_RATE);
36+
37+
//source quantity is rounded up. to avoid dest quantity being too low.
38+
uint numerator;
39+
uint denominator;
40+
if (srcDecimals >= dstDecimals) {
41+
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
42+
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
43+
denominator = rate;
44+
} else {
45+
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
46+
numerator = (PRECISION * dstQty);
47+
denominator = (rate * (10**(dstDecimals - srcDecimals)));
48+
}
49+
return (numerator + denominator - 1) / denominator; //avoid rounding down
50+
}
51+
}

contracts/sol4/reserves/fprConversionRate/ConversionRateEnhancedSteps.sol

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import "./ConversionRates.sol";
1212

1313
contract ConversionRateEnhancedSteps is ConversionRates {
1414

15+
uint constant internal MAX_RATE = (PRECISION * 10 ** 7); // up to 10M tokens per ETH
1516
uint constant internal MAX_STEPS_IN_FUNCTION = 16;
1617
int constant internal MAX_IMBALANCE = 2 ** 255 - 1;
1718
uint constant internal POW_2_128 = 2 ** 128;
@@ -241,6 +242,15 @@ contract ConversionRateEnhancedSteps is ConversionRates {
241242
return getImbalance(token, rateUpdateBlock, usedBlock);
242243
}
243244

245+
function addBps(uint rate, int bps) internal pure returns(uint) {
246+
require(rate <= MAX_RATE);
247+
require(bps >= MIN_BPS_ADJUSTMENT);
248+
require(bps <= MAX_BPS_ADJUSTMENT);
249+
250+
uint maxBps = 100 * 100;
251+
return (rate * uint(int(maxBps) + bps)) / maxBps;
252+
}
253+
244254
function executeStepFunction(StepFunction storage f, int from, int to) internal view returns(int) {
245255

246256
uint len = f.x.length;
@@ -266,6 +276,7 @@ contract ConversionRateEnhancedSteps is ConversionRates {
266276
change += (stepXValue - fromVal) * stepYValue;
267277
fromVal = stepXValue;
268278
}
279+
// max change < MAX_QTY * BPS * 16 = (10 ** 28) * 10000 * 16 = 1.6e33
269280
}
270281

271282
return change / (to - from);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
pragma solidity 0.4.18;
2+
3+
import "./WrapConversionRate.sol";
4+
5+
6+
contract WrapConversionRateEnhancedSteps is WrapConversionRate {
7+
8+
//general functions
9+
function WrapConversionRateEnhancedSteps(ConversionRates _conversionRates) public
10+
WrapConversionRate(_conversionRates)
11+
{ /* empty block */ }
12+
13+
function addToken(
14+
ERC20 token,
15+
uint minRecordResolution,
16+
uint maxPerBlockImbalance,
17+
uint maxTotalImbalance
18+
) public onlyAdmin
19+
{
20+
require(token != address(0));
21+
require(minRecordResolution != 0);
22+
require(maxPerBlockImbalance != 0);
23+
require(maxTotalImbalance != 0);
24+
25+
conversionRates.addToken(token);
26+
27+
//token control info
28+
conversionRates.setTokenControlInfo(
29+
token,
30+
minRecordResolution,
31+
maxPerBlockImbalance,
32+
maxTotalImbalance
33+
);
34+
35+
//step functions
36+
int[] memory emptyArr = new int[](0);
37+
int[] memory zeroArr = new int[](1);
38+
zeroArr[0] = 0;
39+
40+
conversionRates.setImbalanceStepFunction(token, emptyArr, zeroArr, emptyArr, zeroArr);
41+
42+
conversionRates.enableTokenTrade(token);
43+
}
44+
}

test/sol4/conversionRateEnhancedSteps.js

+37
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,43 @@ contract('ConversionRateEnhancedSteps', function(accounts) {
12411241
contractBps = await convRatesInst.mockExecuteStepFunction(token, 0, 31);
12421242
Helper.assertEqual(contractBps, bps, "bad bps");
12431243
});
1244+
1245+
it("should verify add bps reverts for illegal values", async function () {
1246+
let minLegalBps = -100 * 100;
1247+
let maxLegalBps = new BN(100 * 100);
1248+
let legalRate = new BN(10).pow(new BN(25));
1249+
let illegalRate = legalRate.add(new BN(1));
1250+
let illegalBpsMinSide = minLegalBps - 1*1;
1251+
let illegalBpsMaxSide = maxLegalBps.add(new BN(1));
1252+
1253+
await convRatesInst.mockAddBps(legalRate, minLegalBps);
1254+
await convRatesInst.mockAddBps(legalRate, maxLegalBps);
1255+
1256+
//see fail with illegal rate
1257+
try {
1258+
await convRatesInst.mockAddBps(illegalRate, minLegalBps);
1259+
assert(false, "throw was expected in line above.")
1260+
} catch(e){
1261+
assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e);
1262+
}
1263+
1264+
//see fail with illegal bps (min side)
1265+
try {
1266+
await convRatesInst.mockAddBps(legalRate, illegalBpsMinSide);
1267+
assert(false, "throw was expected in line above.")
1268+
} catch(e){
1269+
assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e);
1270+
}
1271+
1272+
//see fail with illegal bps (max side)
1273+
try {
1274+
await convRatesInst.mockAddBps(legalRate, illegalBpsMaxSide);
1275+
assert(false, "throw was expected in line above.")
1276+
} catch(e){
1277+
assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e);
1278+
}
1279+
1280+
});
12441281
});
12451282

12461283
function convertRateToPricingRate (baseRate) {

0 commit comments

Comments
 (0)