Created
September 29, 2018 04:45
-
-
Save asoong/919a2580ebfe9b2d7ff782c984351d4d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let NetworkProxy = artifacts.require("./KyberNetworkProxy.sol"); | |
let ConversionRates = artifacts.require("./mockContracts/MockConversionRate.sol"); | |
let TestToken = artifacts.require("./mockContracts/TestToken.sol"); | |
let Reserve = artifacts.require("./KyberReserve.sol"); | |
let Network = artifacts.require("./KyberNetwork.sol"); | |
let WhiteList = artifacts.require("./WhiteList.sol"); | |
let ExpectedRate = artifacts.require("./ExpectedRate.sol"); | |
let FeeBurner = artifacts.require("./FeeBurner.sol"); | |
let Helper = require("./helper.js"); | |
let BigNumber = require('bignumber.js'); | |
// Global Variables | |
let precisionUnits = (new BigNumber(10).pow(18)); | |
let ethAddress = '0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; | |
let gasPrice = (new BigNumber(10).pow(9).mul(50)); | |
let negligibleRateDiff = 11; | |
// Balances | |
let expectedReserveBalanceWei = 0; | |
let reserveTokenBalance = []; | |
let reserveTokenImbalance = []; | |
let reserveStartTokenBalance = []; | |
//permission groups | |
let admin; | |
let operator; | |
let user; | |
let walletId; | |
// Contracts | |
let conversionRates; // Previously pricing2 | |
let reserve; // Previously reserve2 | |
let whiteList; | |
let expectedRate; | |
let network; | |
let networkProxy; | |
let feeBurner; | |
// Block data | |
let priceUpdateBlock; | |
let currentBlock; | |
let validRateDurationInBlocks = 5100; | |
// Token data | |
let numTokens = 2; | |
let tokens = []; | |
let tokenAddress = []; | |
let tokenDecimals = []; | |
// imbalance data | |
let minimalRecordResolution = 2; // low resolution so I don't lose too much data. then easier to compare calculated imbalance values. | |
let maxPerBlockImbalance = 4000; | |
let maxTotalImbalance = maxPerBlockImbalance * 12; | |
// all price steps in bps (basic price steps). | |
// 100 bps means rate change will be: price * (100 + 10000) / 10000 == raise rate in 1% | |
// higher rate is better for user. will get more dst quantity for his tokens. | |
// all x values represent token imbalance. y values represent equivalent steps in bps. | |
// buyImbalance represents coin shortage. higher buy imbalance = more tokens were bought. | |
// generally. speaking, if imbalance is higher we want to have: | |
// - smaller buy bps (negative) to lower rate when buying token with ether. | |
// - bigger sell bps to have higher rate when buying ether with token. | |
//////////////////// | |
//base buy and sell rates (prices) | |
let baseBuyRate1 = []; | |
let baseBuyRate2 = []; | |
let baseSellRate1 = []; | |
let baseSellRate2 = []; | |
//quantity buy steps | |
let qtyBuyStepX = [0, 150, 350, 700, 1400]; | |
let qtyBuyStepY = [0, 0, -70, -160, -3000]; | |
//imbalance buy steps | |
let imbalanceBuyStepX = [-8500, -2800, -1500, 0, 1500, 2800, 4500]; | |
let imbalanceBuyStepY = [ 1300, 130, 43, 0, 0, -110, -1600]; | |
//sell | |
//sell price will be 1 / buy (assuming no spread) so sell is actually buy price in other direction | |
let qtySellStepX = [0, 150, 350, 700, 1400]; | |
let qtySellStepY = [0, 0, 120, 170, 3000]; | |
//sell imbalance step | |
let imbalanceSellStepX = [-8500, -2800, -1500, 0, 1500, 2800, 4500]; | |
let imbalanceSellStepY = [-1500, -320, -75, 0, 0, 110, 650]; | |
//compact data. | |
let sells = []; | |
let buys = []; | |
let indices = []; | |
let compactBuyArr = []; | |
let compactSellArr = []; | |
contract('KyberNetworkProxy', function(accounts) { | |
it("Set Accounts, deploy ConversionRates, Tokens, and enable tokens for trade with basic data per token.", async function () { | |
// set account addresses | |
admin = accounts[0]; | |
operator = accounts[1]; | |
user = accounts[3]; | |
walletId = accounts[4]; | |
currentBlock = priceUpdateBlock = await Helper.getCurrentBlock(); | |
// Initialize ConversionRates contract | |
conversionRates = await ConversionRates.new(admin, {}); | |
await conversionRates.setValidRateDurationInBlocks( | |
100000, | |
{ from: admin } | |
); | |
// Deploy tokens and add to ConversionRates | |
for (let i = 0; i < numTokens; ++i) { | |
tokenDecimals[i] = 18; | |
token = await TestToken.new("Token " + i, "TOKEN_ " + i, tokenDecimals[i]); | |
tokens[i] = token; | |
tokenAddress[i] = token.address; | |
await conversionRates.addToken(token.address); | |
await conversionRates.setTokenControlInfo( | |
token.address, | |
minimalRecordResolution, | |
maxPerBlockImbalance, | |
maxTotalImbalance | |
); | |
await conversionRates.enableTokenTrade(token.address); | |
} | |
assert.equal(tokens.length, numTokens, "Invalid number of tokens"); | |
await conversionRates.addOperator(operator); | |
}); | |
it("Set base rates, compact data rate factor, and step function for all tokens.", async function () { | |
// buy is ether to token rate. sale is token to ether rate. so sell == 1 / buy. assuming we have no spread. | |
let tokensPerEther; | |
let ethersPerToken; | |
for (i = 0; i < numTokens; ++i) { | |
tokensPerEther = (new BigNumber(precisionUnits.mul((i + 1) * 3)).floor()); | |
ethersPerToken = (new BigNumber(precisionUnits.div((i + 1) * 3)).floor()); | |
baseBuyRate1.push(tokensPerEther.valueOf()); | |
baseBuyRate2.push(tokensPerEther.valueOf() * 10100 / 10000); | |
baseSellRate1.push(ethersPerToken.valueOf()); | |
baseSellRate2.push(ethersPerToken.div(1000).mul(980)); | |
} | |
assert.equal(baseBuyRate1.length, tokens.length); | |
assert.equal(baseBuyRate2.length, tokens.length); | |
assert.equal(baseSellRate1.length, tokens.length); | |
assert.equal(baseSellRate2.length, tokens.length); | |
buys.length = sells.length = indices.length = 0; | |
// This is probably unnecessary | |
await conversionRates.setBaseRate( | |
tokenAddress, | |
baseBuyRate2, | |
baseSellRate2, | |
buys, | |
sells, | |
currentBlock, | |
indices, | |
{ from: operator } | |
); | |
// Set compact data | |
compactBuyArr = [0, 0, 0, 0, 0, 06, 07, 08, 09, 1, 0, 11, 12, 13, 14]; | |
let compactBuyHex = Helper.bytesToHex(compactBuyArr); | |
buys.push(compactBuyHex); | |
compactSellArr = [0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34]; | |
let compactSellHex = Helper.bytesToHex(compactSellArr); | |
sells.push(compactSellHex); | |
indices[0] = 0; | |
assert.equal(indices.length, sells.length, "bad sells array size"); | |
assert.equal(indices.length, buys.length, "bad buys array size"); | |
await conversionRates.setCompactData(buys, sells, currentBlock, indices, {from: operator}); | |
// Set pricing step functions | |
for (let i = 0; i < numTokens; ++i) { | |
await conversionRates.setQtyStepFunction( | |
tokenAddress[i], | |
qtyBuyStepX, | |
qtyBuyStepY, | |
qtySellStepX, | |
qtySellStepY, | |
{ from: operator } | |
); | |
await conversionRates.setImbalanceStepFunction( | |
tokenAddress[i], | |
imbalanceBuyStepX, | |
imbalanceBuyStepY, | |
imbalanceSellStepX, | |
imbalanceSellStepY, | |
{ from: operator } | |
); | |
} | |
}); | |
it("Deploy Network and Reserve Contract and set Reserve data including balances.", async function () { | |
network = await Network.new(admin); | |
await network.addOperator(operator); | |
reserve = await Reserve.new(network.address, conversionRates.address, admin); | |
await conversionRates.setReserveAddress(reserve.address); | |
for (i = 0; i < numTokens; ++i) { | |
await reserve.approveWithdrawAddress(tokenAddress[i], accounts[0], true); | |
} | |
// Set reserve balance: 10 ** 18 wei ether + per token 10**18 wei ether value according to base rate. | |
let reserveEtherInit = (new BigNumber(10)).pow(19); | |
await Helper.sendEtherWithPromise(accounts[9], reserve.address, reserveEtherInit); | |
// Transfer tokens to the reserve, each token same wei balance | |
for (let i = 0; i < numTokens; ++i) { | |
token = tokens[i]; | |
let amount2 = (new BigNumber(reserveEtherInit)).div(precisionUnits).mul(baseBuyRate2[i]).floor(); | |
reserveStartTokenBalance[i] = amount2 | |
const deployedAmount = new BigNumber(10 ** (29)); | |
await token.transfer(reserve.address, deployedAmount); | |
reserveTokenBalance.push(amount2); | |
reserveTokenImbalance.push(0); | |
} | |
}); | |
it("Deploy KyberNetworkProxy, Fee Burner, Genesis Token, Whitelist, Expected Rate, and list token pairs.", async function () { | |
// Authorize reserves | |
await network.addReserve(reserve.address, true); | |
networkProxy = await NetworkProxy.new(admin); | |
await network.setKyberProxy(networkProxy.address); | |
await networkProxy.setKyberNetworkContract(network.address); | |
// Set contracts | |
feeBurner = await FeeBurner.new(admin, tokenAddress[0], network.address); | |
let kgtToken = await TestToken.new("Kyber Genesis Token", "KGT", 0); | |
whiteList = await WhiteList.new(admin, kgtToken.address); | |
await whiteList.addOperator(operator); | |
// Max source amount for user is equal to (category cap x weiPerSgd) | |
await whiteList.setCategoryCap(0, new BigNumber('10000000000000000000'), { from: operator }); | |
await whiteList.setSgdToEthRate(30000, { from: operator }); | |
expectedRate = await ExpectedRate.new(network.address, admin); | |
await network.setWhiteList(whiteList.address); | |
await network.setExpectedRate(expectedRate.address); | |
await network.setFeeBurner(feeBurner.address); | |
await network.setParams(gasPrice.valueOf(), negligibleRateDiff); | |
await network.setEnable(true); | |
let price = await network.maxGasPrice(); | |
assert.equal(price.valueOf(), gasPrice.valueOf()); | |
// List tokens per reserve | |
for (let i = 0; i < numTokens; i++) { | |
await network.listPairForReserve( | |
reserve.address, | |
tokenAddress[i], | |
true, | |
true, | |
true | |
); | |
} | |
}); | |
it("Swaps an ERC20 token for another ERC20 token.", async function () { | |
let sourceTokenIndex = 1; | |
let destinationTokenIndex = 0; | |
let sourceToken = tokens[sourceTokenIndex]; | |
let destinationToken = tokens[destinationTokenIndex]; | |
let sourceAmountTwei = new BigNumber('30000000000000000000'); | |
let maxDestAmount = new BigNumber('8000000000000000000'); | |
// Reset max imbalance values for working with higher numbers | |
buys.length = sells.length = indices.length = 0; | |
// Set compact data | |
compactBuyArr = [0, 0, 0, 0, 0, 06, 07, 08, 09, 10, 11, 12, 13, 14]; | |
let compactBuyHex = Helper.bytesToHex(compactBuyArr); | |
buys.push(compactBuyHex); | |
compactSellArr = [0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34]; | |
let compactSellHex = Helper.bytesToHex(compactSellArr); | |
sells.push(compactSellHex); | |
indices[0] = 0; | |
currentBlock = await Helper.getCurrentBlock(); | |
await conversionRates.setBaseRate( | |
tokenAddress, | |
baseBuyRate2, | |
baseSellRate2, | |
buys, | |
sells, | |
currentBlock, | |
indices, | |
{ from: operator } | |
); | |
priceUpdateBlock = currentBlock; | |
maxPerBlockImbalance = new BigNumber('6000000000000000000000000000000'); | |
maxTotalImbalance = 12 * maxPerBlockImbalance; | |
// Set higher imbalance values and set local imbalance values to 0 since we update compact data | |
for (let i = 0; i < numTokens; ++i) { | |
await conversionRates.setTokenControlInfo( | |
tokenAddress[i], | |
minimalRecordResolution, | |
maxPerBlockImbalance, | |
maxTotalImbalance | |
); | |
const reserveBalance = await tokens[i].balanceOf(reserve.address); | |
console.log("Reserve Balance: ", reserveBalance.valueOf()); | |
// Update balance in imbalance values | |
reserveTokenBalance[i] = new BigNumber(reserveBalance); | |
reserveTokenImbalance[i] = new BigNumber(0); | |
} | |
try { | |
// Verify base rate | |
let buyRate = await networkProxy.getExpectedRate( | |
tokenAddress[sourceTokenIndex], | |
tokenAddress[destinationTokenIndex], | |
sourceAmountTwei | |
); | |
console.log('Buy Rate: ', buyRate[0].valueOf()); | |
const minimumConversionRate = maxDestAmount.div(sourceAmountTwei).mul(new BigNumber(10 ** (18))).round(); | |
console.log('Calculated Conversion Rate: ', minimumConversionRate.valueOf()); | |
console.log('================================================================'); | |
// First Token to Eth rate | |
let expected = calculateRateAmount(false, sourceTokenIndex, sourceAmountTwei, 2); | |
let expectedSellRate = expected[0]; | |
let expectedEthQtyWei = expected[1]; | |
// Eth to Token rate | |
expected = calculateRateAmount(true, destinationTokenIndex, expectedEthQtyWei, 2); | |
let expectedBuyRate = expected[0]; | |
expectedDestTokensTwei = expected[1]; | |
// Calculate combined rate | |
let combinedRate = calcCombinedRate( | |
sourceAmountTwei, | |
expectedSellRate, | |
expectedBuyRate, | |
tokenDecimals[sourceTokenIndex], | |
tokenDecimals[destinationTokenIndex], | |
expectedDestTokensTwei | |
); | |
// Check correct rate calculated | |
// assert.equal(buyRate[0].valueOf(), combinedRate.valueOf(), "Unexpected Combined Rate."); | |
// | |
// Perform trade | |
// | |
// Transfer funds to user (issuance order maker) | |
const deployedAmount = new BigNumber(10 ** (29)); | |
const amountToTransferToUser = deployedAmount; | |
await sourceToken.transfer(user, amountToTransferToUser); | |
let startBalanceSourceTokenUser = await sourceToken.balanceOf(user); | |
console.log('================================================================'); | |
console.log("Source Token on User Before Trade: ", startBalanceSourceTokenUser.toString()); | |
let startBalanceDestinationTokenUser = await destinationToken.balanceOf(user); | |
console.log("Destination Token on User Before Trade: ", startBalanceDestinationTokenUser.toString()); | |
await sourceToken.approve( | |
networkProxy.address, | |
sourceAmountTwei, | |
{ from: user } | |
); | |
let result = await networkProxy.trade( | |
tokenAddress[sourceTokenIndex], // sourceToken | |
sourceAmountTwei, // sourceTokenAmount | |
tokenAddress[destinationTokenIndex], // destinationToken | |
user, // destinationAddress | |
maxDestAmount, // maxDestAmount | |
minimumConversionRate, // minConversionRate | |
walletId, // walletId | |
{ from: user } | |
); | |
let endBalanceSourceTokenUser = await sourceToken.balanceOf(user); | |
console.log("Source Token on User After Trade: ", endBalanceSourceTokenUser.toString()); | |
let endBalanceDestinationTokenUser = await destinationToken.balanceOf(user); | |
console.log("Destination Token on User After Trade: ", endBalanceDestinationTokenUser.toString()); | |
console.log('================================================================'); | |
// | |
// Check token balances | |
// | |
// Check higher destinationToken balance on user | |
// let rate = new BigNumber(buyRate[0].valueOf()); | |
// let destinationTokenUserBalance = await destinationToken.balanceOf(user); | |
// let expectedBalancedestinationTokenuser = startBalanceDestinationTokenUser.add(expectedDestTokensTwei); | |
// assert.equal(expectedBalancedestinationTokenuser.valueOf(), destinationTokenUserBalance.valueOf(), "bad token balance"); | |
// Check lower sourceToken balance on user | |
// let sourceTokenUserBalance = await sourceToken.balanceOf(user); | |
// let expectedBalancesourceTokenUser = startBalanceSourceTokenUser.sub(sourceAmountTwei); | |
// assert.equal(sourceTokenUserBalance.valueOf(), expectedBalancesourceTokenUser.valueOf(), "bad token balance"); | |
// Check source token balance on reserve | |
// reportedBalance = await sourceToken.balanceOf(reserve.address); | |
// assert.equal(reportedBalance.valueOf(), reserveTokenBalance[sourceTokenIndex].valueOf(), "Invalid source token balance on reserve"); | |
// Check destination token balance on reserve | |
// reportedBalance = await destinationToken.balanceOf(reserve.address); | |
// assert.equal(reportedBalance.valueOf(), reserveTokenBalance[destinationTokenIndex].valueOf(), "Invalid destination token balance on reserve"); | |
// Print out contract addresses | |
console.log("KyberNetworkProxy Address: ", networkProxy.address); | |
console.log("Source Token Address: ", tokenAddress[sourceTokenIndex]); | |
console.log("Destination Token Address: ", tokenAddress[destinationTokenIndex]); | |
console.log("Buy Rate: ", buyRate[1].valueOf().toString()); | |
console.log("Source Amount: ", sourceAmountTwei.toString()); | |
console.log("Max Destination Amount: ", maxDestAmount.toString()); | |
} catch(e) { | |
throw(e); | |
} | |
}); | |
}); | |
function convertRateToConversionRatesRate (baseRate) { | |
// conversion rate in pricing is in precision units (10 ** 18) so | |
// rate 1 to 50 is 50 * 10 ** 18 | |
// rate 50 to 1 is 1 / 50 * 10 ** 18 = 10 ** 18 / 50a | |
return ((new BigNumber(10).pow(18)).mul(baseRate).floor()); | |
}; | |
function getExtraBpsForBuyQuantity(qty) { | |
for (let i = 0; i < qtyBuyStepX.length; i++) { | |
if (qty <= qtyBuyStepX[i]) return qtyBuyStepY[i]; | |
} | |
return qtyBuyStepY[qtyBuyStepY.length - 1]; | |
}; | |
function getExtraBpsForSellQuantity(qty) { | |
for (let i = 0; i < qtySellStepX.length; i++) { | |
if (qty <= qtySellStepX[i]) return qtySellStepY[i]; | |
} | |
return qtySellStepY[qtySellStepY.length - 1]; | |
}; | |
function getExtraBpsForImbalanceBuyQuantity(qty) { | |
for (let i = 0; i < imbalanceBuyStepX.length; i++) { | |
if (qty <= imbalanceBuyStepX[i]) return imbalanceBuyStepY[i]; | |
} | |
return (imbalanceBuyStepY[imbalanceBuyStepY.length - 1]); | |
}; | |
function getExtraBpsForImbalanceSellQuantity(qty) { | |
for (let i = 0; i < imbalanceSellStepX.length; i++) { | |
if (qty <= imbalanceSellStepX[i]) return imbalanceSellStepY[i]; | |
} | |
return (imbalanceSellStepY[imbalanceSellStepY.length - 1]); | |
}; | |
function addBps (rate, bps) { | |
return (rate.mul(10000 + bps).div(10000)); | |
}; | |
function compareRates (receivedRate, expectedRate) { | |
expectedRate = expectedRate - (expectedRate % 10); | |
receivedRate = receivedRate - (receivedRate % 10); | |
assert.equal(expectedRate, receivedRate, "different rates"); | |
}; | |
function calculateRateAmount(isBuy, tokenInd, srcQty, reserveIndex, maxDestAmount) { | |
let expectedRate; | |
let expectedAmount; | |
let baseArray; | |
let imbalanceArray; | |
let expected = []; | |
if (isBuy) { | |
baseArray = baseBuyRate2; | |
imbalanceArray = reserveTokenImbalance; | |
} else { | |
imbalanceArray = reserveTokenImbalance; | |
baseArray = baseSellRate2; | |
} | |
if (isBuy) { | |
expectedRate = (new BigNumber(baseArray[tokenInd])); | |
let dstQty = calcDstQty(srcQty, 18, tokenDecimals[tokenInd], expectedRate); | |
let extraBps = getExtraBpsForBuyQuantity(dstQty); | |
expectedRate = addBps(expectedRate, extraBps); | |
let relevantImbalance = imbalanceArray[tokenInd] * 1 + dstQty * 1; | |
extraBps = getExtraBpsForImbalanceBuyQuantity(relevantImbalance); | |
expectedRate = addBps(expectedRate, extraBps); | |
expectedAmount = calcDstQty(srcQty, 18, tokenDecimals[tokenInd], expectedRate); | |
} else { | |
expectedRate = (new BigNumber(baseArray[tokenInd])); | |
let extraBps = getExtraBpsForSellQuantity(srcQty); | |
expectedRate = addBps(expectedRate, extraBps); | |
let relevantImbalance = imbalanceArray[tokenInd] - srcQty; | |
extraBps = getExtraBpsForImbalanceSellQuantity(relevantImbalance.valueOf()); | |
expectedRate = addBps(expectedRate, extraBps); | |
expectedAmount = calcDstQty(srcQty, tokenDecimals[tokenInd], 18, expectedRate); | |
} | |
expectedAmount = expectedAmount.floor(); | |
expectedRate = expectedRate.floor(); | |
expected = [expectedRate, expectedAmount]; | |
return expected; | |
} | |
function calcDstQty(srcQty, srcDecimals, dstDecimals, rate) { | |
rate = new BigNumber(rate); | |
if (dstDecimals >= srcDecimals) { | |
let decimalDiff = (new BigNumber(10)).pow(dstDecimals - srcDecimals); | |
return (rate.mul(srcQty).mul(decimalDiff).div(precisionUnits)).floor(); | |
} else { | |
let decimalDiff = (new BigNumber(10)).pow(srcDecimals - dstDecimals); | |
return (rate.mul(srcQty).div(decimalDiff.mul(precisionUnits))).floor(); | |
} | |
} | |
function calcSrcQty(dstQty, srcDecimals, dstDecimals, rate) { | |
//source quantity is rounded up. to avoid dest quantity being too low. | |
let srcQty; | |
let numerator; | |
let denominator; | |
if (srcDecimals >= dstDecimals) { | |
numerator = precisionUnits.mul(dstQty).mul((new BigNumber(10)).pow(srcDecimals - dstDecimals)); | |
denominator = new BigNumber(rate); | |
} else { | |
numerator = precisionUnits.mul(dstQty); | |
denominator = (new BigNumber(rate)).mul((new BigNumber(10)).pow(dstDecimals - srcDecimals)); | |
} | |
srcQty = (numerator.add(denominator.sub(1))).div(denominator).floor(); //avoid rounding down errors | |
return srcQty; | |
} | |
function calcCombinedRate(srcQty, sellRate, buyRate, srcDecimals, dstDecimals, destQty) { | |
let rate; | |
if (false) { | |
rate = (sellRate.mul(srcQty).div(precisionUnits).floor()).mul(buyRate).div(srcQty).floor(); | |
} else { | |
if (dstDecimals >= srcDecimals) { | |
rate = (precisionUnits.mul(destQty)).div(((new BigNumber(10)).pow(dstDecimals - srcDecimals)).mul(srcQty)); | |
} else { | |
rate = (precisionUnits.mul(destQty).mul((new BigNumber(10)).pow(srcDecimals - dstDecimals))).div(srcQty); | |
} | |
} | |
return rate.floor(); | |
} | |
function log (string) { | |
console.log(string); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment