diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 000000000..631889896 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,26 @@ +name: Node CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [8.x, 10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install, build, and test + run: | + npm ci + npm run build --if-present + npm test + env: + CI: true diff --git a/contracts/wrappers/SetStepFunctionWrapper.sol b/contracts/wrappers/SetStepFunctionWrapper.sol index 9fae5d803..fbd836e67 100644 --- a/contracts/wrappers/SetStepFunctionWrapper.sol +++ b/contracts/wrappers/SetStepFunctionWrapper.sol @@ -3,17 +3,18 @@ pragma solidity ^0.4.18; import "../ERC20Interface.sol"; import "../Withdrawable.sol"; + interface SetStepFunctionInterface { - function setImbalanceStepFunction( - ERC20 token, - int[] xBuy, - int[] yBuy, - int[] xSell, - int[] ySell - ) public; + function setImbalanceStepFunction( + ERC20 token, + int[] xBuy, + int[] yBuy, + int[] xSell, + int[] ySell + ) public; } -contract SetStepFunctionWrapper is Withdrawable { +contract SetStepFunctionWrapper2 is Withdrawable { SetStepFunctionInterface public rateContract; function SetStepFunctionWrapper(address admin, address operator) public { addOperator(operator); @@ -24,23 +25,34 @@ contract SetStepFunctionWrapper is Withdrawable { rateContract = _contract; } - function setImbalanceStepFunction(ERC20 token, - int[] xBuy, - int[] yBuy, - int[] xSell, - int[] ySell) public onlyOperator { + function setImbalanceStepFunction( + ERC20 token, + int[] xBuy, + int[] yBuy, + int[] xSell, + int[] ySell) + public onlyOperator + { uint i; - // check all x for buy are positive and y are negative + // check all x for buy are positive for( i = 0 ; i < xBuy.length ; i++ ) { - require(xBuy[i] >= 0 ); - require(yBuy[i] <= 0 ); + require(xBuy[i] >= 0 ); + } + + // check all y for buy are negative + for( i = 0 ; i < yBuy.length ; i++ ) { + require(yBuy[i] <= 0 ); } - // check all x for sell are negative and y are negative + // check all x for sell are negative for( i = 0 ; i < xSell.length ; i++ ) { - require(xSell[i] <= 0 ); - require(ySell[i] <= 0 ); + require(xSell[i] <= 0 ); + } + + // check all y for sell are negative + for( i = 0 ; i < ySell.length ; i++ ) { + require(ySell[i] <= 0 ); } rateContract.setImbalanceStepFunction(token,xBuy,yBuy,xSell,ySell); diff --git a/contractsV5/bridges/bancor/KyberBancorReserve.sol b/contractsV5/bridges/bancor/KyberBancorReserve.sol index 4bf12007e..ad388fad6 100644 --- a/contractsV5/bridges/bancor/KyberBancorReserve.sol +++ b/contractsV5/bridges/bancor/KyberBancorReserve.sol @@ -18,6 +18,7 @@ contract KyberBancorReserve is IKyberReserve, Withdrawable, Utils { IBancorNetwork public bancorNetwork; // 0x0e936B11c2e7b601055e58c7E32417187aF4de4a IERC20 public bancorEth; // 0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315 + IERC20 public bancorETHBNT; // 0xb1CD6e4153B2a390Cf00A6556b0fC1458C4A5533 IERC20 public bancorToken; // 0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C constructor( @@ -25,6 +26,7 @@ contract KyberBancorReserve is IKyberReserve, Withdrawable, Utils { address _kyberNetwork, uint _feeBps, address _bancorEth, + address _bancorETHBNT, address _bancorToken, address _admin ) @@ -33,6 +35,7 @@ contract KyberBancorReserve is IKyberReserve, Withdrawable, Utils { require(_bancorNetwork != address(0), "constructor: bancorNetwork address is missing"); require(_kyberNetwork != address(0), "constructor: kyberNetwork address is missing"); require(_bancorEth != address(0), "constructor: bancorEth address is missing"); + require(_bancorETHBNT != address(0), "constructor: bancorETHBNT address is missing"); require(_bancorToken != address(0), "constructor: bancorToken address is missing"); require(_admin != address(0), "constructor: admin address is missing"); require(_feeBps < BPS, "constructor: fee is too big"); @@ -40,6 +43,7 @@ contract KyberBancorReserve is IKyberReserve, Withdrawable, Utils { bancorNetwork = IBancorNetwork(_bancorNetwork); bancorToken = IERC20(_bancorToken); bancorEth = IERC20(_bancorEth); + bancorETHBNT = IERC20(_bancorETHBNT); kyberNetwork = _kyberNetwork; feeBps = _feeBps; @@ -208,21 +212,19 @@ contract KyberBancorReserve is IKyberReserve, Withdrawable, Utils { } function getConversionPath(IERC20 src, IERC20 dest) public view returns(IERC20[] memory path) { - IERC20 bntToken = bancorToken; - - if (src == bntToken) { + if (src == bancorToken) { // trade from BNT to ETH path = new IERC20[](3); - path[0] = bntToken; - path[1] = bntToken; + path[0] = bancorToken; + path[1] = bancorETHBNT; path[2] = bancorEth; return path; - } else if (dest == bntToken) { + } else if (dest == bancorToken) { // trade from ETH to BNT path = new IERC20[](3); path[0] = bancorEth; - path[1] = bntToken; - path[2] = bntToken; + path[1] = bancorETHBNT; + path[2] = bancorToken; return path; } } diff --git a/contractsV5/bridges/bancor/mock/MockBancorNetwork.sol b/contractsV5/bridges/bancor/mock/MockBancorNetwork.sol index d31b0aa9c..82368045c 100644 --- a/contractsV5/bridges/bancor/mock/MockBancorNetwork.sol +++ b/contractsV5/bridges/bancor/mock/MockBancorNetwork.sol @@ -7,14 +7,16 @@ import "../../../UtilsV5.sol"; contract MockBancorNetwork is IBancorNetwork, Utils { IERC20 public bancorETH; + IERC20 public bancorETHBNT; IERC20 public bancorBNT; uint public rateEthToBnt; uint public rateBntToETh; - constructor(address _bancorETH, address _bancorBNT) public { + constructor(address _bancorETH, address _bancorETHBNT, address _bancorBNT) public { bancorETH = IERC20(_bancorETH); bancorBNT = IERC20(_bancorBNT); + bancorETHBNT = IERC20(_bancorETHBNT); } function() external payable { } @@ -27,7 +29,7 @@ contract MockBancorNetwork is IBancorNetwork, Utils { function getReturnByPath(IERC20[] calldata _path, uint256 _amount) external view returns (uint256, uint256) { require(_amount > 0); if (_path.length != 3) { return (0, 0); } - if (_path[0] == bancorBNT && _path[1] == bancorBNT && _path[2] == bancorETH) { + if (_path[0] == bancorBNT && _path[1] == bancorETHBNT && _path[2] == bancorETH) { // rate btn to eth uint destAmount = calcDstQty(_amount, getDecimals(bancorBNT), ETH_DECIMALS, rateBntToETh); if (destAmount > address(this).balance) { @@ -35,7 +37,7 @@ contract MockBancorNetwork is IBancorNetwork, Utils { } return (destAmount, 0); } - if (_path[0] == bancorETH && _path[1] == bancorBNT && _path[2] == bancorBNT) { + if (_path[0] == bancorETH && _path[1] == bancorETHBNT && _path[2] == bancorBNT) { // rate eth to bnt uint destAmount = calcDstQty(_amount, ETH_DECIMALS, getDecimals(bancorBNT), rateEthToBnt); if (destAmount > bancorBNT.balanceOf(address(this))) { @@ -55,7 +57,7 @@ contract MockBancorNetwork is IBancorNetwork, Utils { ) external payable returns (uint256) { require(_path.length == 3); // trade eth to bnt - require(_path[0] == bancorETH && _path[1] == bancorBNT && _path[2] == bancorBNT); + require(_path[0] == bancorETH && _path[1] == bancorETHBNT && _path[2] == bancorBNT); require(msg.value == _amount && _amount > 0); require(rateEthToBnt > 0); uint destAmount = calcDstQty(_amount, ETH_DECIMALS, getDecimals(bancorBNT), rateEthToBnt); @@ -74,7 +76,7 @@ contract MockBancorNetwork is IBancorNetwork, Utils { ) external returns (uint256) { require(_path.length == 3); // trade eth to bnt - require(_path[0] == bancorBNT && _path[1] == bancorBNT && _path[2] == bancorETH); + require(_path[0] == bancorBNT && _path[1] == bancorETHBNT && _path[2] == bancorETH); // collect bnt require(_amount > 0); require(bancorBNT.transferFrom(msg.sender, address(this), _amount)); diff --git a/test/v5/kyberBancorReserve.js b/test/v5/kyberBancorReserve.js index 4b2281075..5c32f3d20 100644 --- a/test/v5/kyberBancorReserve.js +++ b/test/v5/kyberBancorReserve.js @@ -42,13 +42,15 @@ contract('KyberBancorNetwork', function(accounts) { user = accounts[4]; bancorEthToken = await TestToken.new("BancorETH", "BETH", tokenDecimal); + bancorETHBNTToken = await TestToken.new("BancorETHBNT", "BETHBNT", tokenDecimal); bancorBntToken = await TestToken.new("BancorBNT", "BBNT", tokenDecimal); - bancorNetwork = await MockBancorNetwork.new(bancorEthToken.address, bancorBntToken.address); + bancorNetwork = await MockBancorNetwork.new(bancorEthToken.address, bancorETHBNTToken.address, bancorBntToken.address); reserve = await KyberBancorReserve.new( bancorNetwork.address, network, feeBps, bancorEthToken.address, + bancorETHBNTToken.address, bancorBntToken.address, admin ); @@ -142,13 +144,14 @@ contract('KyberBancorNetwork', function(accounts) { it("Should test getConversionRate returns 0 when path is not correct", async function() { let testNewToken = await TestToken.new("Test token", "TST", tokenDecimal); - let testBancorNetwork = await MockBancorNetwork.new(testNewToken.address, bancorBntToken.address); + let testBancorNetwork = await MockBancorNetwork.new(testNewToken.address, bancorBntToken.address, bancorBntToken.address); let testReserve = await KyberBancorReserve.new( testBancorNetwork.address, network, feeBps, bancorEthToken.address, bancorBntToken.address, + bancorBntToken.address, admin ); await testBancorNetwork.setExchangeRate(ethToBntRate, bntToEthRate); @@ -160,12 +163,13 @@ contract('KyberBancorNetwork', function(accounts) { rate = await testReserve.getConversionRate(bancorBntToken.address, ethAddress, amount, 0); assert.equal(rate.valueOf(), 0, "rate should be 0 as path is incorrect"); - testBancorNetwork = await MockBancorNetwork.new(bancorEthToken.address, testNewToken.address); + testBancorNetwork = await MockBancorNetwork.new(bancorEthToken.address, testNewToken.address, testNewToken.address); testReserve = await KyberBancorReserve.new( testBancorNetwork.address, network, feeBps, bancorEthToken.address, + testNewToken.address, bancorBntToken.address, admin ); @@ -187,6 +191,7 @@ contract('KyberBancorNetwork', function(accounts) { network, feeBps.valueOf(), bancorEthToken.address, + bancorETHBNTToken.address, bancorBntToken.address, admin ); @@ -200,6 +205,7 @@ contract('KyberBancorNetwork', function(accounts) { zeroAddress, feeBps.valueOf(), bancorEthToken.address, + bancorETHBNTToken.address, bancorBntToken.address, admin ); @@ -213,6 +219,7 @@ contract('KyberBancorNetwork', function(accounts) { network, feeBps.valueOf(), zeroAddress, + bancorETHBNTToken.address, bancorBntToken.address, admin ); @@ -226,6 +233,7 @@ contract('KyberBancorNetwork', function(accounts) { network, feeBps.valueOf(), bancorEthToken.address, + bancorETHBNTToken.address, zeroAddress, admin ); @@ -239,6 +247,21 @@ contract('KyberBancorNetwork', function(accounts) { network, feeBps.valueOf(), bancorEthToken.address, + bancorETHBNTToken.address, + bancorBntToken.address, + zeroAddress + ); + assert(false, "throw was expected in line above.") + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + } + try { + _ = await KyberBancorReserve.new( + bancorNetwork.address, + network, + feeBps.valueOf(), + bancorEthToken.address, + zeroAddress.address, bancorBntToken.address, zeroAddress ); @@ -251,6 +274,7 @@ contract('KyberBancorNetwork', function(accounts) { network, feeBps.valueOf(), bancorEthToken.address, + bancorETHBNTToken.address, bancorBntToken.address, admin ); diff --git a/web3deployment/compileContracts.js b/web3deployment/compileContracts.js index 18cf68914..7e9b497c5 100644 --- a/web3deployment/compileContracts.js +++ b/web3deployment/compileContracts.js @@ -4,7 +4,11 @@ const solc = require('solc'); const contractPath = path.join(__dirname, "../contracts/"); const input = { "ConversionRates.sol" : fs.readFileSync(contractPath + 'reserves/fprConversionRate/ConversionRates.sol','utf8'), + "EnhancedStepFunctions.sol" : fs.readFileSync(contractPath + 'reserves/fprConversionRate/EnhancedStepFunctions.sol', 'utf8'), "ConversionRatesInterface.sol" : fs.readFileSync(contractPath + 'ConversionRatesInterface.sol', 'utf8'), + "reserves/fprConversionRate/ConversionRates.sol" : fs.readFileSync(contractPath + 'reserves/fprConversionRate/ConversionRates.sol','utf8'), + "reserves/VolumeImbalanceRecorder.sol" : fs.readFileSync(contractPath + 'reserves/VolumeImbalanceRecorder.sol', 'utf8'), + "VolumeImbalanceRecorder.sol" : fs.readFileSync(contractPath + 'reserves/VolumeImbalanceRecorder.sol', 'utf8'), "PermissionGroups.sol" : fs.readFileSync(contractPath + 'PermissionGroups.sol', 'utf8'), "ERC20Interface.sol" : fs.readFileSync(contractPath + 'ERC20Interface.sol', 'utf8'), "ExpectedRate.sol" : fs.readFileSync(contractPath + 'ExpectedRate.sol', 'utf8'), @@ -35,13 +39,15 @@ const input = { "Utils.sol" : fs.readFileSync(contractPath + 'Utils.sol', 'utf8'), "Utils2.sol" : fs.readFileSync(contractPath + 'Utils2.sol', 'utf8'), "Utils3.sol" : fs.readFileSync(contractPath + 'Utils3.sol', 'utf8'), - "VolumeImbalanceRecorder.sol" : fs.readFileSync(contractPath + 'reserves/VolumeImbalanceRecorder.sol', 'utf8'), "Withdrawable.sol" : fs.readFileSync(contractPath + 'Withdrawable.sol', 'utf8'), "WhiteList.sol" : fs.readFileSync(contractPath + 'WhiteList.sol', 'utf8'), "WhiteListInterface.sol" : fs.readFileSync(contractPath + 'WhiteListInterface.sol', 'utf8'), "WrapFeeBurner.sol" : fs.readFileSync(contractPath + 'wrappers/WrapFeeBurner.sol', 'utf8'), "KyberUniswapReserve.sol" : fs.readFileSync(contractPath + 'reserves/bridgeReserves/uniswap/KyberUniswapReserve.sol', 'utf8'), - "WrapperBase.sol" : fs.readFileSync(contractPath + 'wrappers/WrapperBase.sol', 'utf8') + "WrapperBase.sol" : fs.readFileSync(contractPath + 'wrappers/WrapperBase.sol', 'utf8'), + "SetStepFunctionWrapper.sol" : fs.readFileSync(contractPath + 'wrappers/SetStepFunctionWrapper.sol', 'utf8'), + "WrapConversionRate.sol" : fs.readFileSync(contractPath + 'wrappers/WrapConversionRate.sol', 'utf8'), + "WrapReadTokenData.sol" : fs.readFileSync(contractPath + 'wrappers/WrapReadTokenData.sol', 'utf8') }; module.exports.compileContracts = async function() { diff --git a/web3deployment/liquidityReserveDeployer.js b/web3deployment/liquidityReserveDeployer.js index 387b26c6f..d6f480b29 100755 --- a/web3deployment/liquidityReserveDeployer.js +++ b/web3deployment/liquidityReserveDeployer.js @@ -72,7 +72,11 @@ async function sendTx(txObject) { // don't wait for confirmation signedTxs.push(signedTx.rawTransaction) if (!dontSendTx) { - web3.eth.sendSignedTransaction(signedTx.rawTransaction, {from:sender}); + try { + web3.eth.sendSignedTransaction(signedTx.rawTransaction, {from:sender}); + } catch (e) { + console.error(e); + } } } @@ -105,9 +109,9 @@ const input = { "Utils.sol" : fs.readFileSync(contractPath + 'Utils.sol', 'utf8'), "KyberReserveInterface.sol" : fs.readFileSync(contractPath + 'KyberReserveInterface.sol', 'utf8'), "Withdrawable.sol" : fs.readFileSync(contractPath + 'Withdrawable.sol', 'utf8'), - "KyberReserve.sol" : fs.readFileSync(contractPath + 'KyberReserve.sol', 'utf8'), - "LiquidityConversionRates.sol" : fs.readFileSync(contractPath + 'LiquidityConversionRates.sol', 'utf8'), - "LiquidityFormula.sol" : fs.readFileSync(contractPath + 'LiquidityFormula.sol', 'utf8'), + "KyberReserve.sol" : fs.readFileSync(contractPath + 'reserves/KyberReserve.sol', 'utf8'), + "LiquidityConversionRates.sol" : fs.readFileSync(contractPath + '/reserves/aprConversionRate/LiquidityConversionRates.sol', 'utf8'), + "LiquidityFormula.sol" : fs.readFileSync(contractPath + '/reserves/aprConversionRate/LiquidityFormula.sol', 'utf8'), }; let reserveAddress; diff --git a/web3deployment/readConversionRate.js b/web3deployment/readConversionRate.js new file mode 100644 index 000000000..4d4d7bb4e --- /dev/null +++ b/web3deployment/readConversionRate.js @@ -0,0 +1,419 @@ +//web3 modules +const Web3 = require('web3'); + +//general purpose npm moudles +const fs = require('fs'); +const assert = require('assert'); +const solc = require('solc'); + +const utils = require("./utils.js"); +const myLog = utils.myLog; +const a2n = utils.a2n; +const addName2Add = utils.addName2Add; +const getNameFromAdd = utils.getNameFromAdd; + +process.on('unhandledRejection', console.error.bind(console)) + + + + +//// for using this script set data here +//////////////////////////////////////// + +const conversionRateAddress = ''; +const wrapConversionRate = '' +const chain = ''; // r = ropsten, m = mainnet, k = kovan +let evmNodeUrl = ''; + +////////////////////////////////// +///////////////////////////////// + +//contract sources +const contractPath = "../contracts/"; + +const input = { + "ConversionRatesInterface.sol" : fs.readFileSync(contractPath + 'ConversionRatesInterface.sol', 'utf8'), + "reserves/VolumeImbalanceRecorder.sol" : fs.readFileSync(contractPath + 'reserves/VolumeImbalanceRecorder.sol', 'utf8'), + "ConversionRates.sol" : fs.readFileSync(contractPath + 'reserves/fprConversionRate/ConversionRates.sol', 'utf8'), + "VolumeImbalanceRecorder.sol" : fs.readFileSync(contractPath + 'reserves/VolumeImbalanceRecorder.sol', 'utf8'), + "reserves/fprConversionRate/ConversionRates.sol" : fs.readFileSync(contractPath + 'reserves/fprConversionRate/ConversionRates.sol', 'utf8'), + "PermissionGroups.sol" : fs.readFileSync(contractPath + 'PermissionGroups.sol', 'utf8'), + "ERC20Interface.sol" : fs.readFileSync(contractPath + 'ERC20Interface.sol', 'utf8'), + "MockERC20.sol" : fs.readFileSync(contractPath + 'mock/MockERC20.sol', 'utf8'), +// "SanityRatesInterface.sol" : fs.readFileSync(contractPath + 'SanityRatesInterface.sol', 'utf8'), + "Utils.sol" : fs.readFileSync(contractPath + 'Utils.sol', 'utf8'), + "Utils2.sol" : fs.readFileSync(contractPath + 'Utils2.sol', 'utf8'), + "KyberReserveInterface.sol" : fs.readFileSync(contractPath + 'KyberReserveInterface.sol', 'utf8'), + "Withdrawable.sol" : fs.readFileSync(contractPath + 'Withdrawable.sol', 'utf8'), +// "KyberReserve.sol" : fs.readFileSync(contractPath + 'KyberReserve.sol', 'utf8'), + "WrapConversionRate.sol" : fs.readFileSync(contractPath + 'wrappers/WrapConversionRate.sol', 'utf8'), + "WrapperBase.sol" : fs.readFileSync(contractPath + 'wrappers/WrapperBase.sol', 'utf8'), + "WrapReadTokenData.sol" : fs.readFileSync(contractPath + 'wrappers/WrapReadTokenData.sol', 'utf8'), + /* permission less order book reserve */ +}; + +let solcOutput; + +const ethAddress = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; + + + +const ropstenReaderAddress = '0x6D3CbB74C9ad8be2d1aCa76d1156F6B3C9cF88CC'; +const mainNetReaderAddress = '0x7FA7599413E53dED64b587cc5a607c384f600C66'; +// +// code +//////// +//////// +const mainnetUrls = ['https://mainnet.infura.io', + 'https://api.mycryptoapi.com/eth', + 'https://api.myetherapi.com/eth', + 'https://mew.giveth.io/']; + +const kovanPublicNode = 'https://kovan.infura.io'; +const ropstenPublicNode = 'https://ropsten.infura.io'; + +let tokenReaderAddress = ''; + +if (chain == 'r') { + tokenReaderAddress = ropstenReaderAddress; + evmNodeUrl = ropstenPublicNode; +} else if (chain == 'm') { + tokenReaderAddress = mainNetReaderAddress; + evmNodeUrl = mainnetUrls[0]; +} + +const localURL = 'http://localhost'; +const solcOutputPath = "./solcOuput.json"; +let deployInputJsonPath = ''; + +let conversionRateContract; + +//run the code +main(); + +async function main(){ + + if((chain == '') && (evmNodeUrl == '')) { + myLog(0, 1, "please open this file and update settings in line 21") + myLog(0, 1, "must choose which chain to use. (mainnet, ropsten, kovan, etc,,,") + myLog(0, 1, "Alternatively can set must choose which chain to use. (mainnet, ropsten, kovan, etc,,,") + return; + } + + if(conversionRateAddress == '') { + myLog(0, 1, "please open this file and update settings in line 21") + myLog(0, 1, "must set conversion rate address to analyze.") + return; + } + + myLog(0, 1, 'notice, by default this script uses a public node with limited query rate') + myLog(0, 1, 'If you have a non limited node URL, please open file and set evmNodeUrl') + + await getCompiledContracts(); + + await init(evmNodeUrl); + + await readConversionRate(conversionRateAddress); + + myLog(0, 1, "") + myLog(0, 1, "") + myLog(0, 1, "And thats all for now folks") +} + +let haveTokenReader; +let tokensPerReserve; + +async function readConversionRate(conversionRateAddress) { + try { + let abi = solcOutput.contracts["ConversionRates.sol:ConversionRates"].interface; + conversionRatesABI = JSON.parse(abi); + } catch (e) { + myLog(0, 0, e); + throw e; + } + + try { + let abi = solcOutput.contracts["WrapReadTokenData.sol:WrapReadTokenData"].interface; + wrapReadTokenDataABI = JSON.parse(abi); + } catch (e) { + myLog(0, 0, e); + throw e; + } + + if (tokenReaderAddress != '') { + tokenReader = await new web3.eth.Contract(wrapReadTokenDataABI, tokenReaderAddress); + haveTokenReader = true; + } else { + haveTokenReader = false; + } + + conversionRateContract = await new web3.eth.Contract(conversionRatesABI, conversionRateAddress); + Rate = conversionRateContract; + + myLog(0, 0, ''); + myLog(0, 0, ("Conversion Rate address: " + conversionRateAddress)); + myLog(0, 0, ("--------------------------------------------------------------------")); + + //verify binary as expected. + let blockCode = await web3.eth.getCode(conversionRateAddress); + let solcCode = '0x' + (solcOutput.contracts["ConversionRates.sol:ConversionRates"].runtimeBytecode); + + if (blockCode != solcCode){ +// myLog(1, 0, "blockchain Code:"); +// myLog(0, 0, blockCode); + myLog(0, 1, "Byte code from block chain doesn't match conversion rate. checking liquidity conversion rate.") +// await readLiquidityConversionRate(conversionRateAddress, reserveAddress, index, isKyberReserve); +// return; + } else { + myLog(0, 0, "Code on blockchain matches locally compiled code"); + myLog(0, 0, ''); + } + + if (wrapConversionRate != '') { + //verify wrapper binary + let admin = (await Rate.methods.admin().call()).toLowerCase(); + myLog((admin != jsonWrapConversionRate), 0, "Admin is wrapper contract: " + (admin == jsonWrapConversionRate)); + await readConversionRateWrapper(jsonWrapConversionRate); + } + +// if(isKyberReserve) await printAdminAlertersOperators(Rate, "ConversionRates"); + + let validRateDurationInBlocks = await Rate.methods.validRateDurationInBlocks().call(); + myLog(0, 0, ("validRateDurationInBlocks: " + validRateDurationInBlocks)); + let reserveContractAdd = (await Rate.methods.reserveContract().call()).toLowerCase(); + myLog(0, 0, ("reserveContract: " + reserveContractAdd)); + tokensPerReserve = await Rate.methods.getListedTokens().call(); + let numTokens = tokensPerReserve.length; + let toks = tokensPerReserve; + let tokNames = ''; + toks.forEach(async function(name){ + tokNames += await a2n(name, true, true, solcOutput) + " "; + }); + + myLog(0, 0, "token list: " + tokNames); + myLog(0, 0, "token per reserve: " + tokensPerReserve); + + myLog(0, 0, ""); + myLog(0, 0, ""); + myLog(0, 0, ("Fetch data per token ")); + myLog(0, 0, "---------------------"); + + for (let i = 0; i < numTokens; i++) { + await readTokenDataInConversionRate(conversionRateAddress, tokensPerReserve[i]); + } +}; + +async function readTokenDataInConversionRate(conversionRateAddress, tokenAdd) { + let Rate = conversionRateContract; + tokenAdd = tokenAdd.toLowerCase(); + let someStepsNotSet = false; + + myLog(0, 0, ''); + + myLog(0, 0, ("token " + await a2n(tokenAdd, 1, true, solcOutput))); + myLog(0, 0, ("-----------------------------------------------")); + let basic = await Rate.methods.getTokenBasicData(tokenAdd).call(); + myLog((basic[0] == false), (basic[1] == false), ("listed = " + basic[0] + ". Enabled = " + basic[1])); + + //read imbalance info + let tokenName = await a2n(tokenAdd, 0); + let tokenDict = {}; + + let controlInfo = await Rate.methods.getTokenControlInfo(tokenAdd).call(); + //print resolution data + myLog(0, 0, ("minRecordResolution: " + controlInfo[0] + " = " + + await getAmountTokens(controlInfo[0], tokenAdd) + " tokens.")); + + //print max per block data + myLog(0, 0, ("maxPerBlockImbalance: " + controlInfo[1] + " = " + + await getAmountTokens(controlInfo[1], tokenAdd) + " tokens.")); + tokenDict['maxPerBlockImbalance'] = controlInfo[1].valueOf(); + + //print max total imbalance data + myLog(0, 0, ("maxTotalImbalance: " + controlInfo[2] + " = " + + await getAmountTokens(controlInfo[2], tokenAdd) + " tokens.")); + tokenDict['maxTotalImbalance'] = controlInfo[2].valueOf(); + + // get compact data + let compactData = await Rate.methods.getCompactData(tokenAdd).call(); + + myLog(0, 1, ''); + myLog(0, 1, 'compactData'); + myLog(0, 0, "compact data array index: " + compactData[0]); + myLog(0, 0, "compact data field index: " + compactData[1]); + myLog(0, 0, "compact data buy byte value: " + compactData[2]); + myLog(0, 0, "compact data sell byte value: " + compactData[3]); + + // rate update block + let rateUpdateBlock = await Rate.methods.getRateUpdateBlock(tokenAdd).call(); + + myLog(0, 0, "rate update block: " + rateUpdateBlock); + + if (haveTokenReader) { + let values = await tokenReader.methods.readQtyStepFunctions(conversionRateAddress, tokenAdd).call(); + for (let i = 0; i < values[1].length; i++) { + values[1][i] = await getAmountTokens(values[1][i], tokenAdd); + } + for (let i = 0; i < values[4].length; i++) { + values[4][i] = await getAmountTokens(values[4][i], tokenAdd); + } + myLog(values[1].length < 1, 0, ("buyRateQtyStepFunction X: " + values[1])); + myLog(values[2].length < 1, 0, ("buyRateQtyStepFunction Y: " + values[2])); + myLog(values[4].length < 1, 0, ("sellRateQtyStepFunction X: " + values[4])); + myLog(values[5].length < 1, 0, ("sellRateQtyStepFunction Y: " + values[5])); + + if(values[1].length < 1 || values[1].length < 1 || values[4].length < 1 || values[5].length < 1) { + someStepsNotSet = true; + } + + values = await tokenReader.methods.readImbalanceStepFunctions(conversionRateAddress, tokenAdd).call(); + for (let i = 0; i < values[1].length; i++) { + values[1][i] = await getAmountTokens(values[1][i], tokenAdd); + } + for (let i = 0; i < values[4].length; i++) { + values[4][i] = await getAmountTokens(values[4][i], tokenAdd); + } + if(values[1].length < 1 || values[1].length < 1 || values[4].length < 1 || values[5].length < 1) { + someStepsNotSet = true; + } + + myLog(values[1].length < 1, 0, ("buyRateImbalanceStepFunction X: " + values[1])); + myLog(values[2].length < 1, 0, ("buyRateImbalanceStepFunction Y: " + values[2])); + myLog(values[4].length < 1, 0, ("sellRateImbalanceStepFunction X: " + values[4])); + myLog(values[5].length < 1, 0, ("sellRateImbalanceStepFunction Y: " + values[5])); + tokenDict["buyRateImbalanceStepFunction X: "] = values[1]; + tokenDict["buyRateImbalanceStepFunction Y: "] = values[2]; + tokenDict["sellRateImbalanceStepFunction X: "] = values[4]; + tokenDict["sellRateImbalanceStepFunction Y: "] = values[5]; + } else { + //if no token reader contract + buyRateQtyStepFunction = await getStepFunctionXYArr(tokenAdd, 0, Rate); + assert.equal(buyRateQtyStepFunction[0].length, buyRateQtyStepFunction[1].length, "buyRateQtyStepFunction X Y different length"); + myLog(buyRateQtyStepFunction[0].length < 1, 0, ("buyRateQtyStepFunction X: " + buyRateQtyStepFunction[0])); + myLog(buyRateQtyStepFunction[1].length < 1, 0, ("buyRateQtyStepFunction Y: " + buyRateQtyStepFunction[1])); + + sellRateQtyStepFunction = await getStepFunctionXYArr(tokenAdd, 4, Rate); + assert.equal(sellRateQtyStepFunction[0].length, sellRateQtyStepFunction[1].length, "sellRateQtyStepFunction X Y different length"); + myLog(sellRateQtyStepFunction[0].length < 1, 0, ("sellRateQtyStepFunction X: " + sellRateQtyStepFunction[0])); + myLog(sellRateQtyStepFunction[1].length < 1, 0, ("sellRateQtyStepFunction Y: " + sellRateQtyStepFunction[1])); + + + buyRateImbalanceStepFunction = await getStepFunctionXYArr(tokenAdd, 8, Rate); + assert.equal(buyRateImbalanceStepFunction[0].length, buyRateImbalanceStepFunction[1].length, "buyRateImbalanceStepFunction X Y different length"); + myLog(buyRateImbalanceStepFunction[0].length < 1, 0, ("buyRateImbalanceStepFunction X: " + buyRateImbalanceStepFunction[0])); + myLog(buyRateImbalanceStepFunction[1].length < 1, 0, ("buyRateImbalanceStepFunction Y: " + buyRateImbalanceStepFunction[1])); + tokenDict["buyRateImbalanceStepFunction X: "] = buyRateImbalanceStepFunction[0]; + tokenDict["buyRateImbalanceStepFunction Y: "] = buyRateImbalanceStepFunction[1]; + + sellRateImbalanceStepFunction = await getStepFunctionXYArr(tokenAdd, 12, Rate); + assert.equal(sellRateImbalanceStepFunction[0].length, sellRateImbalanceStepFunction[1].length, "sellRateImbalanceStepFunction X Y different length"); + myLog(sellRateImbalanceStepFunction[0].length < 1, 0, ("sellRateImbalanceStepFunction X: " + sellRateImbalanceStepFunction[0])); + myLog(sellRateImbalanceStepFunction[1].length < 1, 0, ("sellRateImbalanceStepFunction Y: " + sellRateImbalanceStepFunction[1])); + tokenDict["sellRateImbalanceStepFunction X: "] = sellRateImbalanceStepFunction[0]; + tokenDict["sellRateImbalanceStepFunction Y: "] = sellRateImbalanceStepFunction[1]; + + if (buyRateQtyStepFunction[0].length < 1 || buyRateQtyStepFunction[1].length < 1 || sellRateQtyStepFunction[0].length < 1 || + sellRateQtyStepFunction[1].length < 1 || buyRateImbalanceStepFunction[0].length < 1 || + buyRateImbalanceStepFunction[1].length < 1 || sellRateImbalanceStepFunction[0].length < 1 || + sellRateImbalanceStepFunction[1].length < 1) + { + someStepsNotSet = true; + } + } + + if (someStepsNotSet) { + + myLog(1, 0, "") + myLog(1, 0, "some step not set. get rate will revert") + myLog(1, 0, "some step not set. get rate will revert") + myLog(1, 0, "") + return; + } + + //buy price + let ether = web3.utils.toBN(10).pow(web3.utils.toBN(18)); + + let precisionPartial = web3.utils.toBN(10).pow(web3.utils.toBN(12)); + let blockNum = await web3.eth.getBlockNumber(); + let buyRate1Eth = await Rate.methods.getRate(tokenAdd, blockNum, true, ether.toString()).call(); + + let etherToToken = (web3.utils.toBN(buyRate1Eth.valueOf()).div(precisionPartial)) / 1000000; + + let raiseFlag = (buyRate1Eth == 0); + myLog(raiseFlag, 0, ("for 1 eth. eth to " + await a2n(tokenAdd, 0) + " rate is: " + buyRate1Eth + + " (1 eth = " + etherToToken + " " + await a2n(tokenAdd, 0) + ")")); + + //sell price + let hundredTokensInTwei = web3.utils.toBN(10).pow(web3.utils.toBN(await getTokenDecimals(tokenAdd) * 1 + 4 * 1)); + let sellRateXTwei = await Rate.methods.getRate(tokenAdd, blockNum, false, hundredTokensInTwei.toString()).call(); + tokensTweixToEth = (web3.utils.toBN(sellRateXTwei).div(precisionPartial)) / 10000; + raiseFlag = (sellRateXTwei == 0); + myLog(raiseFlag, 0, ("for 10000 " + await a2n(tokenAdd, 0) + " tokens. Token to eth rate is " + + sellRateXTwei + " (10000 " + await a2n(tokenAdd, 0) + " tokens = " + tokensTweixToEth + " ether)")); +} + +async function init(nodeUrl){ + //web3 instance + + web3 = new Web3(new Web3.providers.HttpProvider(nodeUrl)); + + myLog(0, 0, ("web 3 version " + web3.version)); + let isListening; + try { + isListening = await web3.eth.net.isListening(); + } catch (e) { + myLog(1, 0, ("can't connect to node: " + nodeUrl + ". check your internet connection. Or possibly check status of this node.")); + myLog(0, 0, ("exception: " + e)); + throw(e); + } + numPeers = await web3.eth.net.getPeerCount(); + myLog(0, 1, ( "node " + nodeUrl + " listening: " + isListening.toString() + " with " + numPeers + " peers")); +}; + +let decimalsPerToken = {}; + +async function getTokenDecimals (token) { + if (decimalsPerToken[token] == undefined) { + let abi = solcOutput.contracts["MockERC20.sol:MockERC20"].interface; + let ERC20 = await new web3.eth.Contract(JSON.parse(abi), token); + + decimalsPerToken[token] = await ERC20.methods.decimals().call(); + } + + return decimalsPerToken[token]; +} + +async function getAmountTokens(amountTwei, tokenAdd) { + let digits = await getTokenDecimals(tokenAdd); +// myLog(0, 0, "decimals " + digits + "amountTwei " + amountTwei) + let stringAmount = amountTwei.toString(10); + let integer = stringAmount.substring(0,stringAmount.length - digits); +// myLog(0, 0, "integer " + integer) + let fraction = stringAmount.substring(stringAmount.length - digits); + if( fraction.length < digits) { + fraction = web3.utils.toBN(10).pow(web3.utils.toBN(fraction.length - digits)).toString(10).substring(1) + fraction; + } + + fraction = fraction.replace(/0+$/,''); + fraction = fraction.slice(0, 4); //enough 4 decimals. + if (fraction == '') fraction = '0'; + if (integer == '') integer = '0'; + + return integer + "." + fraction; +}; + +async function getCompiledContracts() { + myLog(0, 0, "starting compilation"); + solcOutput = await solc.compile({ sources: input }, 1); + console.log(solcOutput.errors); +// console.log(solcOutput); + myLog(0, 0, "finished compilation"); +// let solcOutJson = JSON.stringify(solcOutput, null, 2); +// fs.writeFileSync(solcOutputPath, solcOutJson, function(err) { +// if(err) { +// return console.log(err); +// } +// +// console.log("Saved solc output to: " + solcOutputPath); +// }); +}; diff --git a/web3deployment/reserveEnhancedStepsDeployer.js b/web3deployment/reserveEnhancedStepsDeployer.js new file mode 100644 index 000000000..9f8f23114 --- /dev/null +++ b/web3deployment/reserveEnhancedStepsDeployer.js @@ -0,0 +1,336 @@ +#!/usr/bin/env node + +const Web3 = require("web3"); +const fs = require("fs"); +const path = require('path'); +const RLP = require('rlp'); +const BigNumber = require('bignumber.js') + +process.on('unhandledRejection', console.error.bind(console)) + +const { configPath, gasPriceGwei, printPrivateKey, rpcUrl, signedTxOutput, dontSendTx, networkAddress, chainId: chainIdInput } = require('yargs') + .usage('Usage: $0 --config-path [path] --gas-price-gwei [gwei] --print-private-key [bool] --rpc-url [url] --signed-tx-output [path] --dont-send-tx [bool] --network-address [address] --chain-id') + .demandOption(['gasPriceGwei', 'rpcUrl']) + .boolean('printPrivateKey') + .boolean('dontSendTx') + .argv; +const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl)); +const solc = require('solc') + +const rand = web3.utils.randomHex(7); +const privateKey = web3.utils.sha3("js sucks" + rand); +//console.log("privateKey", privateKey); + +if (printPrivateKey) { + console.log("privateKey", privateKey); + let path = "privatekey_" + web3.utils.randomHex(7) + ".txt"; + fs.writeFileSync(path, privateKey, function(err) { + if(err) { + return console.log(err); + } + }); +} +const account = web3.eth.accounts.privateKeyToAccount(privateKey); +const sender = account.address; +const gasPrice = BigNumber(gasPriceGwei).mul(10 ** 9); +const signedTxs = []; +let nonce; +let chainId = chainIdInput; + +console.log("from",sender); + +async function sendTx(txObject) { + const txTo = txObject._parent.options.address; + + let gasLimit; + try { + gasLimit = await txObject.estimateGas(); + } catch (e) { + gasLimit = 500 * 1000; + } + + if(txTo !== null) { + gasLimit = 500 * 1000; + } + + gasLimit *= 1.2; + gasLimit -= gasLimit % 1; + //console.log(gasLimit); + const txData = txObject.encodeABI(); + const txFrom = account.address; + const txKey = account.privateKey; + + const tx = { + from : txFrom, + to : txTo, + nonce : nonce, + data : txData, + gas : gasLimit, + chainId, + gasPrice + }; + + const signedTx = await web3.eth.accounts.signTransaction(tx, txKey); + nonce++; + // don't wait for confirmation + signedTxs.push(signedTx.rawTransaction) + if (!dontSendTx) { + web3.eth.sendSignedTransaction(signedTx.rawTransaction, {from:sender}); + } +} + +async function deployContract(solcOutput, contractName, ctorArgs) { + + const actualName = contractName; + const bytecode = solcOutput.contracts[actualName].bytecode; + + const abi = solcOutput.contracts[actualName].interface; + const myContract = new web3.eth.Contract(JSON.parse(abi)); + const deploy = myContract.deploy({data:"0x" + bytecode, arguments: ctorArgs}); + let address = "0x" + web3.utils.sha3(RLP.encode([sender,nonce])).slice(12).substring(14); + address = web3.utils.toChecksumAddress(address); + + await sendTx(deploy); + + myContract.options.address = address; + + + return [address,myContract]; +} + +const contractPath = path.join(__dirname, "../contracts/"); + +let reserveAddress; +let conversionRatesAddress; + +let reserveContract; +let conversionRatesContract; + +let reservePermissions; +let conversionRatesPermissions; + +const depositAddresses = []; +let validDurationBlock = 24; +let taxWalletAddress = 0x0; + +const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + +const tokens = []; +const tokenControlInfo = {}; +const tokenNameToAddress = { "ETH" : ethAddress }; + + +function parseInput( jsonInput ) { + // tokens + const tokenInfo = jsonInput["tokens"]; + Object.keys(tokenInfo).forEach(function(key) { + const val = tokenInfo[key]; + const symbol = key; + const name = val["name"]; + const address = val["address"]; + + tokenNameToAddress[symbol] = address; + + tokens.push(address); + const dict = { + minimalRecordResolution : val["minimalRecordResolution"], + maxPerBlockImbalance : val["maxPerBlockImbalance"], + maxTotalImbalance : val["maxTotalImbalance"] + }; + tokenControlInfo[address] = dict; + }); + + // exchanges + const exchangeInfo = jsonInput["exchanges"]; + Object.keys(exchangeInfo).forEach(function(exchange) { + Object.keys(exchangeInfo[exchange]).forEach(function(token){ + const depositAddress = exchangeInfo[exchange][token]; + const dict = {}; + dict[token] = depositAddress; + depositAddresses.push(dict); + }); + }); + + reservePermissions = jsonInput.permission["KyberReserve"]; + conversionRatesPermissions = jsonInput.permission["ConversionRates"]; + validDurationBlock = jsonInput["valid duration block"]; + + // output file name + outputFileName = jsonInput["output filename"]; +}; + +async function setPermissions(contract, alerters, operators, admin) { + console.log("set operator(s) " + contract.address); + for(let i = 0 ; i < operators.length ; i++ ) { + const operator = operators[i]; + console.log(operator); + await sendTx(contract.methods.addOperator(operator)); + } + + console.log("set alerter(s)"); + for(let i = 0 ; i < alerters.length ; i++ ) { + const alerter = alerters[i]; + console.log(alerter); + await sendTx(contract.methods.addAlerter(alerter)); + } + + console.log("transferAdminQuickly"); + console.log(admin); + await sendTx(contract.methods.transferAdminQuickly(admin)); +} + +async function main() { + nonce = await web3.eth.getTransactionCount(sender); + console.log("nonce",nonce); + + chainId = chainId || await web3.eth.net.getId() + console.log('chainId', chainId); + console.log('starting compilation'); + output = await require("./compileContracts.js").compileContracts(); + console.log(output.errors) + console.log("finished compilation"); + + if (!dontSendTx) { + await waitForEth(); + } + + let networkAddress = '0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd'; + console.log('network address: ', networkAddress); + + console.log("deploying EnhancedStepFunctions"); + [conversionRatesAddress,conversionRatesContract] = await deployContract(output, "EnhancedStepFunctions.sol:EnhancedStepFunctions", [sender]); + console.log("enhanced steps fpr pricing contract", conversionRatesAddress); + + console.log("deploying kyber reserve"); + [reserveAddress,reserveContract] = await deployContract(output, "KyberReserve.sol:KyberReserve", [networkAddress,conversionRatesAddress,sender]); + + console.log("reserve", reserveAddress); + + console.log("deploying enhanced step functions wrapper"); + [wrapperAddress, wrapperContract] = await deployContract(output, "WrapConversionRate.sol:WrapConversionRate", [conversionRatesAddress]); + + console.log("wrapperAddress", wrapperAddress); + + let stepsSetterContract; + let stepsSetterAddress; + + console.log("deploying step function setter."); + [stepsSetterAddress, stepsSetterContract] = await deployContract(output, "WrapConversionRate.sol:WrapConversionRate", [conversionRatesAddress]); + + operators = ['0xF76d38Da26c0c0a4ce8344370D7Ae4c34B031dea','0xf3d872b9e8d314820dc8e99dafbe1a3feedc27d5'] + admin = '0xf3d872b9e8d314820dc8e99dafbe1a3feedc27d5'; + + await setPermissions(reserveContract, operators, operators, admin); + await setPermissions(wrapperContract, operators, operators, admin); + await setPermissions(stepsSetterContract, operators, operators, admin); + + operators.push(stepsSetterAddress); + await setPermissions(conversionRatesContract, operators, operators, admin); +console.log(operators) + console.log("done for now...") +return; + + // conversion rates + console.log("conversion rate - add token"); + for( let i = 0 ; i < tokens.length ; i++ ) { + console.log(tokens[i]); + await sendTx(conversionRatesContract.methods.addToken(tokens[i])); + } + + console.log("conversion rate - set valid duration block"); + await sendTx(conversionRatesContract.methods.setValidRateDurationInBlocks(validDurationBlock)); + console.log("conversion rate - setReserveAddress"); + await sendTx(conversionRatesContract.methods.setReserveAddress(reserveAddress)); + + console.log("conversion rate - set control info"); + for( let i = 0 ; i < tokens.length ; i++ ) { + console.log(tokens[i]); + const dict = tokenControlInfo[tokens[i]]; + await sendTx(conversionRatesContract.methods.setTokenControlInfo(tokens[i], + dict.minimalRecordResolution, + dict.maxPerBlockImbalance, + dict.maxTotalImbalance)); + } + + console.log("conversion rate - enable token trade"); + for( let i = 0 ; i < tokens.length ; i++ ) { + console.log(tokens[i]); + const dict = tokenControlInfo[tokens[i]]; + await sendTx(conversionRatesContract.methods.enableTokenTrade(tokens[i])); + } + + console.log("conversion rate - add temp operator"); + await sendTx(conversionRatesContract.methods.addOperator(sender)); + console.log("conversion rate - set qty step function to 0"); + for( let i = 0 ; i < tokens.length ; i++ ) { + console.log(tokens[i]); + await sendTx(conversionRatesContract.methods.setQtyStepFunction(tokens[i], + [0], + [0], + [0], + [0])); + } + + console.log("conversion rate - set imbalance step function to 0"); + for( let i = 0 ; i < tokens.length ; i++ ) { + console.log(tokens[i]); + await sendTx(conversionRatesContract.methods.setImbalanceStepFunction(tokens[i], + [0], + [0], + [0], + [0])); + } + + console.log("conversion rate - remove temp operator"); + await sendTx(conversionRatesContract.methods.removeOperator(sender)); + + await setPermissions(conversionRatesContract, conversionRatesPermissions); + + console.log("last nonce is", nonce); + + if (signedTxOutput) { + fs.writeFileSync(signedTxOutput, signedTxsJson); + } +} + +function printParams(jsonInput) { + dictOutput = {}; + dictOutput["tokens"] = jsonInput.tokens; + dictOutput["tokens"]["ETH"] = {"name" : "Ethereum", "decimals" : 18, "address" : ethAddress }; + dictOutput["exchanges"] = jsonInput.exchanges; + dictOutput["permission"] = jsonInput.permission; + dictOutput["valid duration block"] = jsonInput["valid duration block"]; + dictOutput["reserve"] = reserveAddress; + dictOutput["pricing"] = conversionRatesAddress; + dictOutput["network"] = networkAddress; + const json = JSON.stringify(dictOutput, null, 2); + console.log(json); + const outputFileName = jsonInput["output filename"]; + console.log(outputFileName, 'write'); + fs.writeFileSync(outputFileName, json); +} + + +function sleep(ms){ + return new Promise(resolve=>{ + setTimeout(resolve,ms) + }) +} + +async function waitForEth() { + while(true) { + const balance = await web3.eth.getBalance(sender); + console.log("waiting for balance to account " + sender); + if(balance.toString() !== "0") { + console.log("received " + balance.toString() + " wei"); + return; + } + else await sleep(10000) + } +} + + +let filename; +let content; + +main();