Skip to content

Instantly share code, notes, and snippets.

@asoong
Last active September 24, 2018 06:33
Show Gist options
  • Save asoong/1e7cfa412d1730fe1f05a160afac6c37 to your computer and use it in GitHub Desktop.
Save asoong/1e7cfa412d1730fe1f05a160afac6c37 to your computer and use it in GitHub Desktop.
Script for deploying Kyber network contracts for ERC20 to ERC20 transfer
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, {});
// Deploy tokens and add to ConversionRates
for (let i = 0; i < numTokens; ++i) {
tokenDecimals[i] = 15 * 1 + 1 * i;
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;
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
let zeroArr = [0];
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
await token.transfer(reserve.address, amount2.valueOf());
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);
await whiteList.setCategoryCap(0, 1000, { 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 = 1450 * 1;
let maxDestAmount = (new BigNumber(10)).pow(18);
// 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 = 60000000;
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
);
// Update balance in imbalance values
reserveTokenBalance[i] = new BigNumber(await tokens[i].balanceOf(reserve.address));
reserveTokenImbalance[i] = new BigNumber(0);
}
try {
// Verify base rate
let buyRate = await networkProxy.getExpectedRate(
tokenAddress[sourceTokenIndex],
tokenAddress[destinationTokenIndex],
sourceAmountTwei
);
// 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
//
// REQUIRED: Transfer funds to user (issuance order maker)
await sourceToken.transfer(user, sourceAmountTwei);
// REQUIRED: Approve funds to network for all trades
// await sourceToken.approve(
// networkProxy.address,
// sourceAmountTwei,
// { from: user }
// );
let startBalanceDestinationTokenUser = await destinationToken.balanceOf(user);
let startBalanceSourceTokenUser = await sourceToken.balanceOf(user);
let destinationTokenuserExistingBalance = await destinationToken.balanceOf(user);
console.log(destinationTokenuserExistingBalance.valueOf());
//
// REQUIRED: Trade
//
// let result = await networkProxy.trade(
// tokenAddress[sourceTokenIndex], // sourceToken
// sourceAmountTwei, // sourceTokenAmount
// tokenAddress[destinationTokenIndex], // destinationToken
// user, // destinationAddress
// maxDestAmount, // maxDestAmount
// buyRate[1].valueOf(), // minConversionRate
// walletId, // walletId
// { from: user }
// );
// Update Balance and Imbalance
reserveTokenBalance[sourceTokenIndex] = (reserveTokenBalance[sourceTokenIndex]).add(sourceAmountTwei);
reserveTokenImbalance[sourceTokenIndex] = reserveTokenImbalance[sourceTokenIndex].sub(sourceAmountTwei);
reserveTokenBalance[destinationTokenIndex] = reserveTokenBalance[destinationTokenIndex].sub(expectedDestTokensTwei);
reserveTokenImbalance[destinationTokenIndex] = reserveTokenImbalance[destinationTokenIndex].add(expectedDestTokensTwei); //more missing tokens
//
// 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());
console.log("Source Amount: ", sourceAmountTwei);
console.log("Max Destination Amount: ", maxDestAmount);
} 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