diff --git a/README.md b/README.md index 6734311d8..54489c923 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ Public facing interfaces for kyber network (folder: contracts/sol6): 2. `npm ci` ## Compilation with Buidler -1. `./cmp.sh` to compile contracts for all solidity verseions. +1. `./cmp.sh` to compile contracts for all solidity versions. 2. `./cmpSol6.sh` to compile only sol6 contracts ## Testing with Buidler -1. If contracts have not been compiled, run `./compilation.sh`. This step can be skipped subsequently. +1. If contracts have not been compiled, run `./cmp.sh`. This step can be skipped subsequently. 2. Run `./tst.sh` 3. Use `-f` for running a specific test file. 5. Use `-a` to run tests for all solidity versions. Runs only sol6 tests by default. @@ -37,4 +37,4 @@ Public facing interfaces for kyber network (folder: contracts/sol6): 2. Use `-f` for running a specific test file. ### Example Commands -`./coverage.sh -f ./test/sol6/kyberNetwork.js` (Coverage for only kyberNetwork.js) \ No newline at end of file +`./coverage.sh -f ./test/sol6/kyberNetwork.js` (Coverage for only kyberNetwork.js) diff --git a/contracts/sol6/Dao/KyberFeeHandler.sol b/contracts/sol6/Dao/KyberFeeHandler.sol index 779826092..33f060d79 100644 --- a/contracts/sol6/Dao/KyberFeeHandler.sol +++ b/contracts/sol6/Dao/KyberFeeHandler.sol @@ -92,6 +92,7 @@ contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGua ISanityRate[] internal sanityRateContract; event FeeDistributed( + IERC20 indexed token, address indexed platformWallet, uint256 platformFeeWei, uint256 rewardWei, @@ -186,6 +187,7 @@ contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGua // only platform fee paid totalPayoutBalance = totalPayoutBalance.add(platformFee); emit FeeDistributed( + ETH_TOKEN_ADDRESS, platformWallet, platformFee, 0, @@ -212,6 +214,7 @@ contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGua brrAmounts.burnWei = networkFee.sub(brrAmounts.rewardWei).sub(brrAmounts.rebateWei); emit FeeDistributed( + ETH_TOKEN_ADDRESS, platformWallet, platformFee, brrAmounts.rewardWei, @@ -265,7 +268,7 @@ contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGua emit RewardPaid(staker, epoch, ETH_TOKEN_ADDRESS, amountWei); } - /// @dev claim reabate per reserve wallet. called by any address + /// @dev claim rebate per reserve wallet. called by any address /// @param rebateWallet the wallet to claim rebates for. Total accumulated rebate sent to this wallet. /// @return amountWei amount of rebate claimed function claimReserveRebate(address rebateWallet) diff --git a/contracts/sol6/Dao/emergency/EmergencyFeeHandler.sol b/contracts/sol6/Dao/emergency/EmergencyFeeHandler.sol new file mode 100644 index 000000000..2bc3fbb75 --- /dev/null +++ b/contracts/sol6/Dao/emergency/EmergencyFeeHandler.sol @@ -0,0 +1,238 @@ +pragma solidity 0.6.6; + +import "../../IKyberFeeHandler.sol"; +import "../../utils/PermissionGroupsNoModifiers.sol"; +import "../../utils/zeppelin/ReentrancyGuard.sol"; +import "../../utils/zeppelin/SafeMath.sol"; +import "../../utils/Utils5.sol"; + +/** + * @title kyberFeeHandler + * + * @dev EmergencyFeeHandler works when dao has problem + * rebateBps and rewardBps is only set when initialization + * user can claim platformfee, rebate and reward will be distributed by admin + */ +contract EmergencyKyberFeeHandler is IKyberFeeHandler, PermissionGroupsNoModifiers, ReentrancyGuard, Utils5 { + using SafeMath for uint256; + + uint16 public immutable rewardBps; + uint16 public immutable rebateBps; + address public kyberNetwork; + + mapping(address => uint256) public feePerPlatformWallet; + uint256 public totalPlatformFeeWei; // total balance in the contract that is for platform fee + mapping(address => uint256) public rebatePerWallet; + uint256 public totalRewardWei; + + event HandleFeeFailed(address[] rebateWallets, uint256[] rebateBpsPerWallet, uint256 feeBRRWei); + + event HandleFee( + IERC20 indexed token, + address indexed platformWallet, + uint256 platformFeeWei, + address[] rebateWallets, + uint256[] rebateBpsPerWallet, + uint256 feeBRRWei + ); + + event FeeDistribution( + IERC20 indexed token, + address indexed platformWallet, + uint256 platformFeeWei, + uint256 rewardWei, + uint256 rebateWei, + address[] rebateWallets, + uint256[] rebatePercentBpsPerWallet, + uint256 burnAmountWei + ); + + event EtherWithdraw(uint256 amount, address sendTo); + + event KyberNetworkUpdated(address kyberNetwork); + + constructor( + address admin, + address _kyberNetwork, + uint256 _rewardBps, + uint256 _rebateBps, + uint256 _burnBps + ) public PermissionGroupsNoModifiers(admin) { + require(_burnBps.add(_rewardBps).add(_rebateBps) == BPS, "Bad BRR values"); + rewardBps = uint16(_rewardBps); + rebateBps = uint16(_rebateBps); + kyberNetwork = _kyberNetwork; + } + + modifier onlyKyberNetwork { + require(msg.sender == address(kyberNetwork), "only kyberNetwork"); + _; + } + + /// @dev handleFees function is called per trade on KyberNetwork. unless the trade is not involving any fees. + /// @param token Token currency of fees + /// @param rebateWallets a list of rebate wallets that will get rebate for this trade. + /// @param rebateBpsPerWallet percentage of rebate for each wallet, out of total rebate. + /// @param platformWallet Wallet address that will receive the platfrom fee. + /// @param platformFee Fee amount in wei the platfrom wallet is entitled to. + /// @param networkFee Fee amount (in wei) to be allocated for BRR + function handleFees( + IERC20 token, + address[] calldata rebateWallets, + uint256[] calldata rebateBpsPerWallet, + address platformWallet, + uint256 platformFee, + uint256 networkFee + ) external payable override onlyKyberNetwork nonReentrant { + require(token == ETH_TOKEN_ADDRESS, "token not eth"); + require(msg.value == platformFee.add(networkFee), "msg.value not equal to total fees"); + + // handle platform fee + feePerPlatformWallet[platformWallet] = feePerPlatformWallet[platformWallet].add( + platformFee + ); + totalPlatformFeeWei = totalPlatformFeeWei.add(platformFee); + emit HandleFee(ETH_TOKEN_ADDRESS, platformWallet, platformFee, rebateWallets, rebateBpsPerWallet, networkFee); + + if (networkFee == 0) { + emit FeeDistribution( + ETH_TOKEN_ADDRESS, + platformWallet, + platformFee, + 0, + 0, + rebateWallets, + rebateBpsPerWallet, + 0 + ); + return; + } + + (bool success, ) = address(this).call( + abi.encodeWithSignature( + "calculateAndRecordFeeData(address,uint256,address[],uint256[],uint256)", + platformWallet, + platformFee, + rebateWallets, + rebateBpsPerWallet, + networkFee + ) + ); + if (!success) { + emit HandleFeeFailed(rebateWallets, rebateBpsPerWallet, networkFee); + } + } + + function calculateAndRecordFeeData( + address platformWallet, + uint256 platformFee, + address[] calldata rebateWallets, + uint256[] calldata rebateBpsPerWallet, + uint256 feeBRRWei + ) external virtual { + require(msg.sender == address(this), "only Feehandler contract can call this function"); + uint256 rebateWei = feeBRRWei.mul(rebateBps).div(BPS); + uint256 rewardWei = feeBRRWei.mul(rewardBps).div(BPS); + + rebateWei = updateRebateValues(rebateWei, rebateWallets, rebateBpsPerWallet); + + totalRewardWei = totalRewardWei.add(rewardWei); + + uint burnAmountWei = feeBRRWei.sub(rewardWei).sub(rebateWei); + + emit FeeDistribution( + ETH_TOKEN_ADDRESS, + platformWallet, + platformFee, + rewardWei, + rebateWei, + rebateWallets, + rebateBpsPerWallet, + burnAmountWei + ); + } + + /// @dev claim accumulated fee per platform wallet. Called by any address + /// @param platformWallet the wallet to claim fee for. Total accumulated fee sent to this wallet. + /// @return amountWei amount of fee claimed + function claimPlatformFee(address platformWallet) + external + override + nonReentrant + returns (uint256 amountWei) + { + require(feePerPlatformWallet[platformWallet] > 1, "no fee to claim"); + // Get total amount of fees accumulated + amountWei = feePerPlatformWallet[platformWallet].sub(1); + + // redundant check, but can't happen + assert(totalPlatformFeeWei >= amountWei); + totalPlatformFeeWei = totalPlatformFeeWei.sub(amountWei); + + feePerPlatformWallet[platformWallet] = 1; // avoid zero to non zero storage cost + + (bool success, ) = platformWallet.call{value: amountWei}(""); + require(success, "platform fee transfer failed"); + + emit PlatformFeePaid(platformWallet, ETH_TOKEN_ADDRESS, amountWei); + return amountWei; + } + + function withdraw(address payable sendTo, uint256 amount) external nonReentrant { + onlyAdmin(); + + uint256 balance = address(this).balance; + // check if the remain balance is enough for withdraw and paying platform fee + require(amount <= balance.sub(totalPlatformFeeWei), "amount > available funds"); + + (bool success, ) = sendTo.call{value: amount}(""); + require(success, "withdraw transfer failed"); + emit EtherWithdraw(amount, sendTo); + } + + /// @dev claimReserveRebate is implemented for IKyberFeeHandler + function claimReserveRebate(address) external override returns (uint256) { + revert("not implemented"); + } + + /// @dev claimStakerReward is implemented for IKyberFeeHandler + function claimStakerReward(address, uint256) external override returns (uint256) { + revert("not implemented"); + } + + /// @dev set new kyberNetwork address by daoOperator + /// @param _kyberNetwork new kyberNetwork contract + function setNetworkContract(address _kyberNetwork) external { + onlyAdmin(); + require(_kyberNetwork != address(0), "kyberNetwork 0"); + if (_kyberNetwork != kyberNetwork) { + kyberNetwork = _kyberNetwork; + emit KyberNetworkUpdated(kyberNetwork); + } + } + + function updateRebateValues( + uint256 rebateWei, + address[] memory rebateWallets, + uint256[] memory rebateBpsPerWallet + ) internal returns (uint256 totalRebatePaidWei) { + uint256 totalRebateBps; + uint256 walletRebateWei; + + for (uint256 i = 0; i < rebateWallets.length; i++) { + require(rebateWallets[i] != address(0), "rebate wallet address 0"); + + walletRebateWei = rebateWei.mul(rebateBpsPerWallet[i]).div(BPS); + rebatePerWallet[rebateWallets[i]] = rebatePerWallet[rebateWallets[i]].add( + walletRebateWei + ); + + // a few wei could be left out due to rounding down. so count only paid wei + totalRebatePaidWei = totalRebatePaidWei.add(walletRebateWei); + totalRebateBps = totalRebateBps.add(rebateBpsPerWallet[i]); + } + + require(totalRebateBps <= BPS, "rebates more then 100%"); + } + +} diff --git a/contracts/sol6/Dao/mock/MockEmergencyFeeHandler.sol b/contracts/sol6/Dao/mock/MockEmergencyFeeHandler.sol new file mode 100644 index 000000000..5899b1f6a --- /dev/null +++ b/contracts/sol6/Dao/mock/MockEmergencyFeeHandler.sol @@ -0,0 +1,23 @@ +pragma solidity 0.6.6; + +import "../emergency/EmergencyFeeHandler.sol"; + +contract MockEmergencyFeeHandler is EmergencyKyberFeeHandler { + constructor( + address admin, + address _kyberNetwork, + uint256 _rewardBps, + uint256 _rebateBps, + uint256 _burnBps + ) public EmergencyKyberFeeHandler(admin, _kyberNetwork, _rewardBps, _rebateBps, _burnBps) {} + + function calculateAndRecordFeeData( + address, + uint256, + address[] calldata, + uint256[] calldata, + uint256 + ) external override { + revert(); + } +} diff --git a/contracts/sol6/KyberNetwork.sol b/contracts/sol6/KyberNetwork.sol index 48c30ea46..afceecf83 100644 --- a/contracts/sol6/KyberNetwork.sol +++ b/contracts/sol6/KyberNetwork.sol @@ -41,7 +41,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra /// If there is only 1 reserve, then it should have a value of 10000 bps /// @param srcAmounts Source amount per reserve. /// @param decimals Token decimals. Src decimals when for src -> eth, dest decimals when eth -> dest - struct ReservesData { + struct ReservesData { IKyberReserve[] addresses; bytes32[] ids; uint256[] rates; @@ -535,6 +535,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra function updateNetworkFee(uint256 expiryTimestamp, uint256 feeBps) internal { require(expiryTimestamp < 2**64, "expiry overflow"); + //+ networkFeeBps < BPS / 2 require(feeBps < BPS / 2, "fees exceed BPS"); networkFeeData.expiryTimestamp = uint64(expiryTimestamp); @@ -621,23 +622,25 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra // verify src balance only if it is not eth balanceAfter = getBalance(src, address(this)); // verify correct src amount is taken + //* srcBalanceBefore >= balanceAfter --> srcBalanceBefore - balanceAfter >= 0 if (srcBalanceBefore >= balanceAfter && srcBalanceBefore - balanceAfter > reservesData.srcAmounts[i]) { revert("reserve takes high amount"); } srcBalanceBefore = balanceAfter; } - // verify correct dest amount is received + // verify correct dest amount is received uint256 expectedDestAmount = calcDstQty( reservesData.srcAmounts[i], srcDecimals, destDecimals, reservesData.rates[i] ); - balanceAfter = getBalance(dest, address(this)); - if (balanceAfter < destBalanceBefore || balanceAfter - destBalanceBefore < expectedDestAmount) { - revert("reserve returns low amount"); - } + balanceAfter = getBalance(dest, address(this)); + //* !(balanceAfter < destBalanceBefore) => balanceAfter >= destBalanceBefore + if (balanceAfter < destBalanceBefore || balanceAfter - destBalanceBefore < expectedDestAmount) { + revert("reserve returns low amount"); + } destBalanceBefore = balanceAfter; } } @@ -672,7 +675,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra actualSrcAmount = tradeData.input.srcAmount; } - // token -> eth + // token -> eth doReserveTrades( tradeData.input.src, ETH_TOKEN_ADDRESS, @@ -747,6 +750,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra if (requiredSrcAmount < srcAmount) { // if there is "change" send back to trader if (src == ETH_TOKEN_ADDRESS) { + //* srcAmount > requiredSrcAmount. validated in: calcTradeSrcAmountFromDest (bool success, ) = trader.call{value: (srcAmount - requiredSrcAmount)}(""); require(success, "Send change failed"); } else { @@ -806,12 +810,14 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra hint ); + //* circulating eth 1e26 = 110 Million ETH. but we need this limit assumption for reverse calc. require(tradeData.tradeWei <= MAX_QTY, "Trade wei > MAX_QTY"); if (tradeData.tradeWei == 0) { return (0, 0); } // calculate fees + //* networkFeeBps * 2 + platfromFeeBps < BPS && feeAccountedBps < BPS * 2 tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS; tradeData.networkFeeWei = (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) / @@ -820,6 +826,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra assert(tradeData.tradeWei >= (tradeData.networkFeeWei + tradeData.platformFeeWei)); // eth -> token: find best reserves match and calculate trade dest amount + //* networkFeeBps * 2 + platfromFeeBps < BPS uint256 actualSrcWei = tradeData.tradeWei - tradeData.networkFeeWei - tradeData.platformFeeWei; @@ -833,11 +840,13 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra hint ); + //* networkFeeBps * 2 + platfromFeeBps < BPS tradeData.networkFeeWei = (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) / BPS; rateWithNetworkFee = calcRateFromQty( + //* networkFeeBps * 2 + platfromFeeBps < BPS tradeData.input.srcAmount * (BPS - tradeData.input.platformFeeBps) / BPS, destAmount, tradeData.tokenToEth.decimals, @@ -907,6 +916,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra // calculate dest amount and fee paying data of this part (t2e or e2t) destAmount = validateTradeCalcDestQtyAndFeeData(src, reservesData, tradeData); + // destAmount MAX = 1e53 (* numReserves). 100 reserves --> 1e55 } /// @notice Calculates source amounts per reserve. Does get rate call @@ -922,10 +932,16 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra uint256 destAmountFeeBps; if (src == ETH_TOKEN_ADDRESS) { + //* srcAmount = tradeWei * (BPS - networkFeeBps) / BPS (assuming all reserves for token to eth are fee Accounted) + //* srcAmountAfterFee = (tradeWei * (BPS - networkFeeBps) / BPS) - (tradeWei * networkFeeBps / BPS) + //* (tradeWei * (BPS - networkFeeBps) / BPS) > (tradeWei * networkFeeBps / BPS) --> + //* BPS - networkFeeBps > networkFeeBps // @notice srcAmount is after deducting fees from tradeWei // @notice using tradeWei to calculate fee so eth -> token symmetric to token -> eth srcAmountAfterFee = srcAmount - (tradeData.tradeWei * tradeData.networkFeeBps / BPS); + //+ srcAmountAfterFee < MAX_QTY + // destAmountFeeBps = 0; } else { srcAmountAfterFee = srcAmount; destAmountFeeBps = tradeData.networkFeeBps; @@ -942,6 +958,8 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra "invalid split bps" ); + //+ srcAmounts[i] <= srcAmount + //* reservesData.splitsBps[i] > BPS validated in calcSrcAmountsAndGetRates if (reservesData.isFeeAccountedFlags[i]) { reservesData.srcAmounts[i] = srcAmountAfterFee * reservesData.splitsBps[i] / BPS; feesAccountedDestBps[i] = destAmountFeeBps; @@ -956,6 +974,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra reservesData.srcAmounts[i], block.number ); + //* reservesData.rates[i] <= MAX_RATE. validated in validateTradeCalcDestQtyAndFeeData when calcDstQty is called } } @@ -1006,6 +1025,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra for (uint256 i = 0; i < reservesData.isEntitledRebateFlags.length; i++) { if (reservesData.isEntitledRebateFlags[i]) { rebateReserveIds[_index] = reservesData.ids[i]; + //* entitledRebateBps > splitsBps[i]. see validateTradeCalcDestQtyAndFeeData rebatePercentBps[_index] = (reservesData.splitsBps[i] * BPS) / entitledRebateBps; _index++; } @@ -1108,28 +1128,32 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra uint256 totalBps; uint256 srcDecimals = (src == ETH_TOKEN_ADDRESS) ? ETH_DECIMALS : reservesData.decimals; uint256 destDecimals = (src == ETH_TOKEN_ADDRESS) ? reservesData.decimals : ETH_DECIMALS; - + for (uint256 i = 0; i < reservesData.addresses.length; i++) { if (i > 0 && (uint256(reservesData.ids[i]) <= uint256(reservesData.ids[i - 1]))) { return 0; // ids are not in increasing order } + //+ totalBps <= BPS (end of this function) totalBps += reservesData.splitsBps[i]; + //+ destAmount <= MAX_QTY * MAX_RATE == 1e53 uint256 destAmount = calcDstQty( reservesData.srcAmounts[i], srcDecimals, destDecimals, reservesData.rates[i] ); - if (destAmount == 0) { + if (destAmount == 0) { return 0; } totalDestAmount += destAmount; + //+ feeAccountedBps <= BPS for each trade part (t2e, e2t) if (reservesData.isFeeAccountedFlags[i]) { tradeData.feeAccountedBps += reservesData.splitsBps[i]; if (reservesData.isEntitledRebateFlags[i]) { + //+ entitledRebateBps <= BPS tradeData.entitledRebateBps += reservesData.splitsBps[i]; tradeData.numEntitledRebateReserves++; } @@ -1151,6 +1175,8 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra { uint256 weiAfterDeductingFees; if (tradeData.input.dest != ETH_TOKEN_ADDRESS) { + //* networkFeeBps * 2 + platfromFeeBps < BPS + //* tradeData.tradeWei >= tradeData.platformFeeWei - tradeData.networkFeeWei weiAfterDeductingFees = calcTradeSrcAmount( tradeData.tradeWei - tradeData.platformFeeWei - tradeData.networkFeeWei, ETH_DECIMALS, @@ -1162,6 +1188,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra weiAfterDeductingFees = tradeData.input.maxDestAmount; } + //* weiAfterDeductingFees < MAX_QTY // reverse calculation, because we are working backwards uint256 newTradeWei = (weiAfterDeductingFees * BPS * BPS) / @@ -1170,6 +1197,11 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra tradeData.feeAccountedBps + tradeData.input.platformFeeBps * BPS)); + + // networkFeeBps * 2 + platformFeeBps < BPS + // feeAccountedBps < BPS * 2 + // ==> + // networkFeeBps * feeAccountedBps + platformFeeBps * BPS < BPS * BPS tradeData.tradeWei = minOf(newTradeWei, tradeData.tradeWei); // recalculate network and platform fees based on tradeWei tradeData.networkFeeWei = @@ -1204,7 +1236,11 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra ReservesData memory reservesData ) internal pure returns (uint256 newSrcAmount) { uint256 totalWeightedDestAmount; + //* notice: tradeWei && user srcAmount <= MAX_QTY <= 1e28, rate E2T: MAX_RATE = 1e25 + //* SUM(srcAmounts[0..i-1]) <= MAX_QTY + //* SUM(dstAmounts[0..i-1]) <= 1e53 for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) { + //* totalWeightedDestAmount <= 1e28 * 1e25 <= 1e53 totalWeightedDestAmount += reservesData.srcAmounts[i] * reservesData.rates[i]; } @@ -1220,10 +1256,16 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra "multiplication overflow"); destAmountSplit = i == (reservesData.srcAmounts.length - 1) ? (destAmount - destAmountSoFar) + //* (destAmount * currentSrcAmount * reservesData.rates[i]) = MAX_QTY * MAX_QTY * MAX_RATE => overflow check + //* SUM[reservesData.srcAmounts[ .. i - 2] * reservesData.rates[0 .. i - 2])] < totalWeightedDestAmount => destAmountSoFar < destAmount + //* currentSrcAmount * reservesData.rates[i] <= totalWeightedDestAmount : (destAmount * currentSrcAmount * reservesData.rates[i]) / totalWeightedDestAmount; destAmountSoFar += destAmountSplit; + //+ destAmountSplit <= destAmount <= MAX_QTY * MAX_RATE <= 1e53 + //+ destAmountSoFar <= totalDestAmount <= MAX_QTY * MAX_RATE + //* newSrcAmounts[i] <= MAX_QTY newSrcAmounts[i] = calcSrcQty( destAmountSplit, srcDecimals, @@ -1235,6 +1277,7 @@ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, Reentra return srcAmount; } + //* newSrcAmounts[i] <= srcAmounts[i] --> newSrcAmount <= srcAmount newSrcAmount += newSrcAmounts[i]; } // new src amounts are used only when all of them aren't greater then current srcAmounts diff --git a/package-lock.json b/package-lock.json index ebbd6b698..c655c5593 100644 --- a/package-lock.json +++ b/package-lock.json @@ -394,9 +394,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -416,23 +416,17 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true } } }, "@openzeppelin/test-helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@openzeppelin/test-helpers/-/test-helpers-0.5.5.tgz", - "integrity": "sha512-jTSCQojQ0Q7FBMN3Me7o0OIVuRnfHRR9TcE+ZlfbSfdqrHkFLwSfeDHSNWtQGlF1xPQR5r3iRI0ccsCrN+JblA==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@openzeppelin/test-helpers/-/test-helpers-0.5.6.tgz", + "integrity": "sha512-8U4sR4ed4cFmc6UKj7akUxZzQJKU9P3p/3RbF+urQuRLLhBaB8zSya1m9VB7/anYEZnBmTDk8LuVgAmYaCPs9A==", "dev": true, "requires": { "@openzeppelin/contract-loader": "^0.4.0", - "@truffle/contract": "^4.0.35", + "@truffle/contract": "^4.0.35 <4.2.2", "ansi-colors": "^3.2.3", "chai": "^4.2.0", "chai-bn": "^0.2.1", @@ -566,13 +560,13 @@ } }, "@truffle/contract": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.1.13.tgz", - "integrity": "sha512-LLtRU4rRBd9jw/ngpYKwr++pEzdR64/vu2UjSxeizP1wIDm1XAIJ7BH2QiWh4PV2i44KzAfgvPtOwgbwr2hvdw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.2.1.tgz", + "integrity": "sha512-af1rUyU/W75GYHt/i7r+NwHozwaCma7V/q/+SRZ3Cw2MFaGOQ0dA/ZGhH8P1F0fmDiUe1DBEIbKxXWai0PWFYg==", "dev": true, "requires": { "@truffle/blockchain-utils": "^0.0.18", - "@truffle/contract-schema": "^3.0.23", + "@truffle/contract-schema": "^3.1.0", "@truffle/error": "^0.0.8", "@truffle/interface-adapter": "^0.4.6", "bignumber.js": "^7.2.1", @@ -586,38 +580,6 @@ "web3-utils": "1.2.1" }, "dependencies": { - "@truffle/blockchain-utils": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.0.18.tgz", - "integrity": "sha512-XnRu5p1QO9krJizOeBY5WfzPDvEOmCnOT5u6qF8uN3Kkq9vcH3ZqW4XTuzz9ERZNpZfWb3UJx4PUosgeHLs5vw==", - "dev": true, - "requires": { - "source-map-support": "^0.5.16" - } - }, - "@truffle/contract-schema": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.0.23.tgz", - "integrity": "sha512-N4CaTMcZhOC44Vl6k2r/eua+ojUswl6mNlkVTVYMvWjfKa8GHKKClsZfkGO72aBrBzoTFsM6D75LvQIIRBy3fg==", - "dev": true, - "requires": { - "ajv": "^6.10.0", - "crypto-js": "^3.1.9-1", - "debug": "^4.1.0" - } - }, - "@truffle/interface-adapter": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.4.6.tgz", - "integrity": "sha512-FZAUb7tx/7VbxpAbo70+K2v22j1O7y4BwhWypRwYpf1YbE2C1OCo/L8zInaW5LfzRd2BEsfb2GjUgbK9VaFrDA==", - "dev": true, - "requires": { - "bn.js": "^4.11.8", - "ethers": "^4.0.32", - "source-map-support": "^0.5.16", - "web3": "1.2.1" - } - }, "bignumber.js": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", @@ -635,20 +597,6 @@ "xhr-request-promise": "^0.1.2" } }, - "ethereum-ens": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/ethereum-ens/-/ethereum-ens-0.8.0.tgz", - "integrity": "sha512-a8cBTF4AWw1Q1Y37V1LSCS9pRY4Mh3f8vCg5cbXCCEJ3eno1hbI/+Ccv9SZLISYpqQhaglP3Bxb/34lS4Qf7Bg==", - "dev": true, - "requires": { - "bluebird": "^3.4.7", - "eth-ens-namehash": "^2.0.0", - "js-sha3": "^0.5.7", - "pako": "^1.0.4", - "underscore": "^1.8.3", - "web3": "^1.0.0-beta.34" - } - }, "web3": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.1.tgz", @@ -6841,6 +6789,12 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", diff --git a/package.json b/package.json index b883673c9..5c100d584 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@nomiclabs/buidler": "1.3.0", "@nomiclabs/buidler-truffle5": "1.3.0", "@nomiclabs/buidler-web3": "1.3.0", - "@openzeppelin/test-helpers": "0.5.5", + "@openzeppelin/test-helpers": "0.5.6", "chai": "4.2.0", "chai-bn": "0.2.0", "mv": "2.1.1", diff --git a/test/sol6/emergencyFeeHandler.js b/test/sol6/emergencyFeeHandler.js new file mode 100644 index 000000000..d4104ff81 --- /dev/null +++ b/test/sol6/emergencyFeeHandler.js @@ -0,0 +1,556 @@ +const EmergencyKyberFeeHandler = artifacts.require('EmergencyKyberFeeHandler.sol') +const MockEmergencyFeeHandler = artifacts.require('MockEmergencyFeeHandler.sol') +const KyberNetwork = artifacts.require('KyberNetwork.sol') +const KyberStorage = artifacts.require('KyberStorage.sol') +const MatchingEngine = artifacts.require('KyberMatchingEngine.sol') +const TestToken = artifacts.require('Token.sol') +const NoPayableFallback = artifacts.require('NoPayableFallback.sol') + +const Helper = require('../helper.js') +const nwHelper = require('./networkHelper.js') +const BN = web3.utils.BN +const { + BPS, + precisionUnits, + ethDecimals, + ethAddress, + zeroAddress, + emptyHint, + zeroBN, + MAX_QTY, + MAX_RATE +} = require('../helper.js') +const {expectEvent, expectRevert} = require('@openzeppelin/test-helpers') +const {assert} = require('chai') + +let admin +let feeHandler +let network +let rebateBpsPerWallet +let rebateWallets +let platformWallet +let user + +let rewardBps = new BN(5000) +let rebateBps = new BN(2500) +let burnBps = new BN(2500) + +contract('EmergencyKyberFeeHandler', function (accounts) { + before('Setting global variables', async () => { + admin = accounts[1] + network = accounts[2] + rebateBpsPerWallet = [new BN(4000), new BN(6000)] + rebateWallets = [accounts[3], accounts[4]] + platformWallet = accounts[5] + user = accounts[6] + }) + + describe('valid constructor params', async () => { + it('test total BRR value should be BPS', async () => { + await expectRevert( + EmergencyKyberFeeHandler.new(admin, network, rewardBps, rebateBps, burnBps.add(new BN(1))), + 'Bad BRR values' + ) + }) + + it('test total BRR value should not be overflow', async () => { + await expectRevert.unspecified( + EmergencyKyberFeeHandler.new(admin, network, new BN(BPS), new BN(1), new BN(2).pow(new BN(256)).sub(new BN(1))) + ) + }) + }) + + describe('test set network', async () => { + let newNetwork = accounts[3] + beforeEach('init emergencyFeeHandler', async () => { + feeHandler = await EmergencyKyberFeeHandler.new(admin, network, rewardBps, rebateBps, burnBps) + }) + + it('should revert if not amin set network', async () => { + await expectRevert(feeHandler.setNetworkContract(newNetwork, {from: user}), 'only admin') + }) + + it('should revert if new network is zero', async () => { + await expectRevert(feeHandler.setNetworkContract(zeroAddress, {from: admin}), 'kyberNetwork 0') + }) + + it('should success and emit events', async () => { + let txResult = await feeHandler.setNetworkContract(newNetwork, {from: admin}) + await expectEvent(txResult, 'KyberNetworkUpdated', {kyberNetwork: newNetwork}) + // not update event if network contract is unchanged + txResult = await feeHandler.setNetworkContract(newNetwork, {from: admin}) + await expectEvent.notEmitted(txResult, 'KyberNetworkUpdated') + }) + }) + + describe('test handle fees with mock Network', async () => { + before('init emergencyFeeHandler', async () => { + feeHandler = await EmergencyKyberFeeHandler.new(admin, network, rewardBps, rebateBps, burnBps) + }) + + it('should handle Fee', async () => { + let platformFee = new BN(10).pow(new BN(17)) + let fee = new BN(10).pow(new BN(18)) + let networkFee = fee.sub(platformFee) + + let initalState = await getFeeHanlerState(feeHandler, rebateWallets, platformWallet) + let txResult = await feeHandler.handleFees( + ethAddress, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + networkFee, + { + from: network, + value: fee + } + ) + + let brrFeeResult = calculateBrrFee(networkFee, rewardBps, rebateBps, rebateWallets, rebateBpsPerWallet) + + await expectEvent(txResult, 'HandleFee', { + platformWallet, + platformFeeWei: platformFee, + rebateWallets, + feeBRRWei: networkFee + }) + + await expectEvent(txResult, 'FeeDistribution', { + platformWallet, + platformFeeWei: platformFee, + rewardWei: brrFeeResult.rewardWei, + rebateWei: brrFeeResult.rebateWei, + rebateWallets, + burnAmountWei: brrFeeResult.burnWei + }) + + await assertStateAfterHandlerFees(feeHandler, initalState, platformWallet, platformFee, brrFeeResult) + }) + + it('should handle Fee with only platform Fee', async () => { + let platformFee = new BN(10).pow(new BN(17)) + let initalState = await getFeeHanlerState(feeHandler, rebateWallets, platformWallet) + let txResult = await feeHandler.handleFees( + ethAddress, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + zeroBN, + { + from: network, + value: platformFee + } + ) + + await expectEvent(txResult, 'HandleFee', { + platformWallet, + platformFeeWei: platformFee, + feeBRRWei: new BN(0) + }) + + await expectEvent(txResult, 'FeeDistribution', { + platformWallet, + platformFeeWei: platformFee, + rewardWei: zeroBN, + rebateWei: zeroBN, + rebateWallets, + burnAmountWei: zeroBN + }) + + let brrFeeResult = calculateBrrFee(zeroBN, rewardBps, rebateBps, rebateWallets, rebateBpsPerWallet) + + await assertStateAfterHandlerFees(feeHandler, initalState, platformWallet, platformFee, brrFeeResult) + }) + + it('test failtolerance when calculateAndRecordFeeData failed', async () => { + feeHandler = await MockEmergencyFeeHandler.new(admin, network, rewardBps, rebateBps, burnBps) + let platformFee = new BN(10).pow(new BN(17)) + let fee = new BN(10).pow(new BN(18)) + + let initalFeePerPlatformWallet = await feeHandler.feePerPlatformWallet(platformWallet) + let txResult = await feeHandler.handleFees( + ethAddress, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + fee.sub(platformFee), + { + from: network, + value: fee + } + ) + + await expectEvent(txResult, 'HandleFeeFailed', { + feeBRRWei: fee.sub(platformFee) + }) + //platform fee should update as normal + let afterFeePerPlatformWallet = await feeHandler.feePerPlatformWallet(platformWallet) + Helper.assertEqual( + initalFeePerPlatformWallet.add(platformFee), + afterFeePerPlatformWallet, + 'unexpected feePerPlatformWallet' + ) + }) + + describe('test validate condition', async () => { + let platformFee = new BN(10).pow(new BN(17)) + let networkFee = new BN(10).pow(new BN(18)) + let fee = platformFee.add(networkFee) + it('test only network call handleFee', async () => { + await expectRevert( + feeHandler.handleFees( + ethAddress, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + networkFee, + { + from: admin, + value: fee + } + ), + 'only kyberNetwork' + ) + }) + + it('test calculateAndRecordFeeData only call by feehandler', async () => { + try { + await feeHandler.calculateAndRecordFeeData( + platformWallet, + platformFee, + rebateWallets, + rebateBpsPerWallet, + networkFee, + {from: admin} + ) + assert(false, 'transaction should be revert') + } catch (e) { + assert(Helper.isRevertErrorMessage(e), 'expected throw but got: ' + e) + } + }) + + it('test revert if token is not eth', async () => { + testToken = await TestToken.new('test', 'KNC', 18) + await expectRevert( + feeHandler.handleFees( + testToken.address, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + networkFee, + { + from: network, + value: fee + } + ), + 'token not eth' + ) + }) + + it('test revert if msg.value is different from total platformFee and networkFee', async() => { + await expectRevert( + feeHandler.handleFees( + ethAddress, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + networkFee, + { + from: network, + value: zeroBN + } + ), + 'msg.value not equal to total fees' + ) + }) + + it('test should record handle fee failed if rebatewallet is zero', async () => { + let txResult = await feeHandler.handleFees( + ethAddress, + [zeroAddress, zeroAddress], + rebateBpsPerWallet, + platformWallet, + platformFee, + networkFee, + { + from: network, + value: fee + } + ) + await expectEvent(txResult, 'HandleFeeFailed') + }) + + it('test should record handle fee failed if total rebate bps > BPS', async () => { + let txResult = await feeHandler.handleFees( + ethAddress, + rebateWallets, + [new BN(5000), new BN(5001)], + platformWallet, + platformFee, + networkFee, + { + from: network, + value: fee + } + ) + await expectEvent(txResult, 'HandleFeeFailed') + }) + }) + }) + + describe('test withdraw and claimPlatformFee function', async () => { + before('create new feehandler', async () => { + feeHandler = await EmergencyKyberFeeHandler.new(admin, network, rewardBps, rebateBps, burnBps) + }) + + async function createHandleFee (platformWallet) { + platformFee = new BN(10).pow(new BN(17)) + totalBalance = new BN(10).pow(new BN(18)) + await feeHandler.handleFees( + ethAddress, + rebateWallets, + rebateBpsPerWallet, + platformWallet, + platformFee, + totalBalance.sub(platformFee), + { + from: network, + value: totalBalance + } + ) + } + + it('test withdraw revert if not admin', async () => { + await createHandleFee(platformWallet) + await expectRevert(feeHandler.withdraw(user, new BN(1), {from: network}), 'only admin') + }) + + it('test withdraw revert if withdraw more than availableBalance', async () => { + await createHandleFee(platformWallet) + totalPlatformFee = await feeHandler.totalPlatformFeeWei() + totalBalance = await Helper.getBalancePromise(feeHandler.address) + availableFee = totalBalance.sub(totalPlatformFee) + await expectRevert( + feeHandler.withdraw(user, availableFee.add(new BN(1)), {from: admin}), + 'amount > available funds' + ) + }) + + it('test withdraw success', async () => { + await createHandleFee(platformWallet) + totalPlatformFee = await feeHandler.totalPlatformFeeWei() + totalBalance = await Helper.getBalancePromise(feeHandler.address) + availableFee = totalBalance.sub(totalPlatformFee) + let initialBalance = await Helper.getBalancePromise(user) + let txResult = await feeHandler.withdraw(user, availableFee, {from: admin}) + expectEvent(txResult, 'EtherWithdraw', { + amount: availableFee, + sendTo: user + }) + Helper.assertEqual(initialBalance.add(availableFee), await Helper.getBalancePromise(user)) + }) + + it('test claimPlatformFee success', async () => { + await createHandleFee(platformWallet) + let initialBalance = await Helper.getBalancePromise(platformWallet) + let initialTotalPlatformFee = await feeHandler.totalPlatformFeeWei() + let platformFee = await feeHandler.feePerPlatformWallet(platformWallet) + let txResult = await feeHandler.claimPlatformFee(platformWallet) + expectEvent(txResult, 'PlatformFeePaid', { + platformWallet: platformWallet, + amount: platformFee.sub(new BN(1)), + token: ethAddress + }) + let afterBalance = await Helper.getBalancePromise(platformWallet) + Helper.assertEqual(initialBalance.add(platformFee).sub(new BN(1)), afterBalance, 'unexpected balance') + let afterTotalPlatformFee = await feeHandler.totalPlatformFeeWei() + Helper.assertEqual( + initialTotalPlatformFee.sub(platformFee.sub(new BN(1))), + afterTotalPlatformFee, + 'total balance platform fee wei is not update as expected' + ) + await expectRevert(feeHandler.claimPlatformFee(platformWallet), 'no fee to claim') + }) + + it('reverts if platform wallet is non-payable contract', async () => { + let platformWallet = await NoPayableFallback.new() + await createHandleFee(platformWallet.address) + await expectRevert(feeHandler.claimPlatformFee(platformWallet.address), 'platform fee transfer failed') + }) + + it('reverts if withdraw to non-payable contract', async () => { + let sendTo = await NoPayableFallback.new() + await createHandleFee(platformWallet) + await expectRevert(feeHandler.withdraw(sendTo.address, new BN(1), {from: admin}), 'withdraw transfer failed') + }) + }) + + it('should revert with not implemented method from IKyberFeeHandler', async () => { + feeHandler = await EmergencyKyberFeeHandler.new(admin, network, rewardBps, rebateBps, burnBps) + await expectRevert(feeHandler.claimReserveRebate(rebateWallets[0]), 'not implemented') + await expectRevert(feeHandler.claimStakerReward(rebateWallets[0], new BN(0)), 'not implemented') + }) + + describe('test integration with real network', async () => { + // network related variables + let networkProxy + let operator + let alerter + let taker + let kyberNetwork + let storage + let reserveIdToWallet + + const gasPrice = new BN(10).pow(new BN(9)).mul(new BN(50)) + const negligibleRateDiffBps = new BN(10) //0.01% + const maxDestAmt = new BN(2).pow(new BN(255)) + const minConversionRate = new BN(0) + + let testToken + let platformFeeBps = new BN(20) // 0.2% + let networkFeeBps = new BN(25) // default fee when daoAddress is zero + + before('init network, ...', async () => { + networkProxy = accounts[6] + operator = accounts[7] + alerter = accounts[8] + taker = accounts[9] + + // init storage and network + storage = await nwHelper.setupStorage(admin) + kyberNetwork = await KyberNetwork.new(admin, storage.address) + await storage.setNetworkContract(kyberNetwork.address, {from: admin}) + await storage.addOperator(operator, {from: admin}) + + feeHandler = await EmergencyKyberFeeHandler.new(admin, kyberNetwork.address, rewardBps, rebateBps, burnBps) + // init matchingEngine + matchingEngine = await MatchingEngine.new(admin) + await matchingEngine.setNetworkContract(kyberNetwork.address, {from: admin}) + await matchingEngine.setKyberStorage(storage.address, {from: admin}) + await storage.setFeeAccountedPerReserveType(true, true, true, true, true, true, { + from: admin + }) + await storage.setEntitledRebatePerReserveType(true, true, true, true, true, true, { + from: admin + }) + + // setup network + await kyberNetwork.setContracts(feeHandler.address, matchingEngine.address, zeroAddress, { + from: admin + }) + await kyberNetwork.addOperator(operator, {from: admin}) + await kyberNetwork.addKyberProxy(networkProxy, {from: admin}) + // in the emergency case, dao address is set to zero + await kyberNetwork.setKyberDaoContract(zeroAddress, {from: admin}) + //set params, enable network + await kyberNetwork.setParams(gasPrice, negligibleRateDiffBps, {from: admin}) + await kyberNetwork.setEnable(true, {from: admin}) + + testToken = await TestToken.new('test', 'tst', new BN(18)) + let tokens = [testToken] + rebateWallets = [accounts[7], accounts[8]] + let result = await nwHelper.setupReserves(network, tokens, 2, 0, 0, 0, accounts, admin, operator, rebateWallets) + reserveIdToWallet = result.reserveIdToRebateWallet + await nwHelper.addReservesToStorage(storage, result.reserveInstances, tokens, operator) + }) + + it('network should trade, fee update as expected', async () => { + let ethSrcQty = new BN(10).pow(new BN(18)) + let initalState = await getFeeHanlerState(feeHandler, rebateWallets, platformWallet) + let txResult = await kyberNetwork.tradeWithHintAndFee( + kyberNetwork.address, + ethAddress, + ethSrcQty, + testToken.address, + taker, + maxDestAmt, + minConversionRate, + platformWallet, + platformFeeBps, + emptyHint, + {value: ethSrcQty, from: networkProxy} + ) + + let tradeEventArgs = nwHelper.getTradeEventArgs(txResult) + let tradedReserve = tradeEventArgs.e2tIds[0] + let rebateWallet = reserveIdToWallet[tradedReserve] + platformFeeWei = ethSrcQty.mul(platformFeeBps).div(BPS) + fee = ethSrcQty + .mul(networkFeeBps) + .div(BPS) + .add(platformFeeWei) + let brrFeeResult = calculateBrrFee(fee.sub(platformFeeWei), rewardBps, rebateBps, [rebateWallet], [BPS]) + await expectEvent.inTransaction(txResult.tx, feeHandler, 'HandleFee') + await expectEvent.inTransaction(txResult.tx, feeHandler, 'FeeDistribution', { + platformWallet, + platformFeeWei, + rewardWei: brrFeeResult.rewardWei, + rebateWei: brrFeeResult.rebateWei, + rebateWallets: [rebateWallet], + burnAmountWei: brrFeeResult.burnWei + }) + await assertStateAfterHandlerFees(feeHandler, initalState, platformWallet, platformFeeWei, brrFeeResult) + }) + }) +}) + +async function getFeeHanlerState (feeHandler, rebateWallets, platformWallet) { + let rebatePerWallet = [] + for (let i = 0; i < rebateWallets.length; i++) { + let rebateWei = await feeHandler.rebatePerWallet(rebateWallets[i]) + rebatePerWallet.push(rebateWei) + } + return { + rewardWei: await feeHandler.totalRewardWei(), + platformFeeWei: await feeHandler.feePerPlatformWallet(platformWallet), + rebatePerWallet + } +} + +function calculateBrrFee (networkFee, rewardBps, rebateBps, rebateWallets, rebateBpsPerWallet) { + let rewardWei = networkFee.mul(rewardBps).div(BPS) + let rebateWei = networkFee.mul(rebateBps).div(BPS) + let totalRebatePaidWei = new BN(0) + rebatePerWallets = [] + for (let i = 0; i < rebateWallets.length; i++) { + let rebatePerWallet = rebateWei.mul(rebateBpsPerWallet[i]).div(BPS) + totalRebatePaidWei = totalRebatePaidWei.add(rebatePerWallet) + rebatePerWallets.push(rebatePerWallet) + } + rebateWei = totalRebatePaidWei + let burnWei = networkFee.sub(rewardWei).sub(rebateWei) + return { + rewardWei, + burnWei, + rebateWei, + rebateWallets, + rebatePerWallets + } +} + +async function assertStateAfterHandlerFees (feeHandler, initalState, platformWallet, platformFeeWei, feeBrrResult) { + let afterState = await getFeeHanlerState(feeHandler, feeBrrResult.rebateWallets, platformWallet) + let expectedPlatformFeeWei = initalState.platformFeeWei.add(platformFeeWei) + Helper.assertEqual(expectedPlatformFeeWei, afterState.platformFeeWei, 'unexpected platform Fee') + + Helper.assertEqual(afterState.rewardWei, initalState.rewardWei.add(feeBrrResult.rewardWei), 'unexpected rewardWei') + + if (rebateWallets.length == 0) { + return + } + + for (let i = 0; i < feeBrrResult.rebateWallets.length; i++) { + let rebatePerWallet = feeBrrResult.rebatePerWallets[i] + Helper.assertEqual( + rebatePerWallet.add(initalState.rebatePerWallet[i]), + afterState.rebatePerWallet[i], + 'unpected rebatePerWallet' + ) + } +} diff --git a/test/sol6/kyberDao.js b/test/sol6/kyberDao.js index c4137a512..84edb01b0 100644 --- a/test/sol6/kyberDao.js +++ b/test/sol6/kyberDao.js @@ -387,9 +387,10 @@ contract('KyberDao', function(accounts) { let link = web3.utils.fromAscii("https://kyberswap.com"); await updateCurrentBlockAndTimestamp(); + let options = [1,2,3,4] let txResult = await submitNewCampaign(kyberDao, 0, currentBlock + 2, currentBlock + 2 + minCampPeriod, - minPercentageInPrecision, cInPrecision, tInPrecision, [1, 2, 3, 4], link, {from: daoOperator} + minPercentageInPrecision, cInPrecision, tInPrecision, options, link, {from: daoOperator} ); expectEvent(txResult, 'NewCampaignCreated', { campaignType: new BN(0), @@ -401,6 +402,14 @@ contract('KyberDao', function(accounts) { tInPrecision: new BN(tInPrecision), link: link }); + let eventLogs; + for (let i = 0; i < txResult.logs.length; i++) { + if (txResult.logs[i].event == 'NewCampaignCreated') { + eventLogs = txResult.logs[i]; + break; + } + } + Helper.assertEqualArray(eventLogs.args.options, options); await Helper.setNextBlockTimestamp(blockToTimestamp(currentBlock + 2)); // vote for first campaign diff --git a/test/sol6/kyberFeeHandler.js b/test/sol6/kyberFeeHandler.js index f9fdbc5fb..02b68ee16 100644 --- a/test/sol6/kyberFeeHandler.js +++ b/test/sol6/kyberFeeHandler.js @@ -102,6 +102,7 @@ contract('KyberFeeHandler', function(accounts) { let platformWallet = accounts[1]; let txResult = await feeHandler.handleFees(ethAddress, [], [], platformWallet, oneEth, zeroBN, {from: kyberNetwork, value: oneEth}); expectEvent(txResult, 'FeeDistributed', { + token: ethAddress, platformWallet: platformWallet, platformFeeWei: oneEth, rewardWei: zeroBN, @@ -129,6 +130,7 @@ contract('KyberFeeHandler', function(accounts) { let expectedRebateWei = oneEth.mul(currentRebateBps).div(BPS); expectEvent(txResult, 'FeeDistributed', { + token: ethAddress, platformWallet: platformWallet, platformFeeWei: oneEth, rewardWei: expectedRewardWei, @@ -468,7 +470,13 @@ contract('KyberFeeHandler', function(accounts) { it("should have updated BRR if expiryTimestamp == 2 ** 64 - 1", async() => { let maxExpiryTimestamp = new BN(2).pow(new BN(64)).sub(new BN(1)); await mockKyberDao.setMockEpochAndExpiryTimestamp(defaultEpoch, maxExpiryTimestamp); - await feeHandler.getBRR(); + let txResult = await feeHandler.getBRR(); + expectEvent(txResult, "BRRUpdated", { + rewardBps: rewardInBPS, + rebateBps: rebateInBPS, + epoch: epoch, + expiryTimestamp: maxExpiryTimestamp, + }) let result = await feeHandler.readBRRData(); Helper.assertEqual(result.expiryTimestamp, maxExpiryTimestamp, "expiry timestamp was not updated"); }); diff --git a/test/sol6/withdrawableNoModifiers.js b/test/sol6/withdrawableNoModifiers.js index 1a0fbbc2e..b17294fbf 100644 --- a/test/sol6/withdrawableNoModifiers.js +++ b/test/sol6/withdrawableNoModifiers.js @@ -14,7 +14,7 @@ let initialEtherBalance = new BN(10); let etherWithdrawAmt = new BN(3); const {zeroBN} = require("../helper.js"); -const { expectRevert } = require('@openzeppelin/test-helpers'); +const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers'); contract('WithdrawableNoModifiers', function(accounts) { before("should init globals, deploy test token", async function () { @@ -37,7 +37,12 @@ contract('WithdrawableNoModifiers', function(accounts) { Helper.assertEqual(admin, rxAdmin, "wrong admin " + rxAdmin.toString()); // withdraw the tokens from withdrawableInst - await withdrawableInst.withdrawToken(token.address, tokenWithdrawAmt, user, {from: admin}); + let txResult = await withdrawableInst.withdrawToken(token.address, tokenWithdrawAmt, user, {from: admin}); + expectEvent(txResult, "TokenWithdraw", { + token: token.address, + amount: tokenWithdrawAmt, + sendTo: user, + }) balance = await token.balanceOf(withdrawableInst.address); Helper.assertEqual(balance, initialTokenBalance.sub(tokenWithdrawAmt), "unexpected balance in withdrawble contract."); @@ -76,8 +81,11 @@ contract('WithdrawableNoModifiers', function(accounts) { it("should test withdraw ether success for admin.", async function () { // withdraw the ether from withdrawableInst - await withdrawableInst.withdrawEther(etherWithdrawAmt, user, {from: admin}); - + let txResult = await withdrawableInst.withdrawEther(etherWithdrawAmt, user, {from: admin}); + expectEvent(txResult, "EtherWithdraw", { + amount: etherWithdrawAmt, + sendTo: user, + }) let balance = await Helper.getBalancePromise(withdrawableInst.address); Helper.assertEqual(balance, initialEtherBalance.sub(etherWithdrawAmt), "unexpected balance in withdrawble contract."); }); diff --git a/web3deployment/KatalystDeploy/katalystNetworkDeployer.js b/web3deployment/KatalystDeploy/katalystNetworkDeployer.js index 971c34f56..26dbafb6d 100644 --- a/web3deployment/KatalystDeploy/katalystNetworkDeployer.js +++ b/web3deployment/KatalystDeploy/katalystNetworkDeployer.js @@ -30,9 +30,13 @@ if (printPrivateKey) { // privateKey = ""; //contract addresses: REPLACE IF SOMETHING BREAKS DURING DEPLOYMENT -let matchingEngineAddress = ""; +let networkHistoryAddress = ""; +let feeHandlerHistoryAddress = ""; +let daoHistoryAddress = ""; +let matchingEngineHistoryAddress = ""; let storageAddress = ""; let networkAddress = ""; +let matchingEngineAddress = ""; let proxyAddress = ""; let feeHandlerAddress = ""; let gasHelperAddress = ""; @@ -48,6 +52,19 @@ let chainId = chainIdInput; console.log("from",sender); +const keypress = async () => { + process.stdin.setRawMode(true) + return new Promise(resolve => process.stdin.once('data', data => { + const byteArray = [...data] + if (byteArray.length > 0 && byteArray[0] === 3) { + console.log('^C') + process.exit(1) + } + process.stdin.setRawMode(false) + resolve() + })) +} + async function sendTx(txObject, gasLimit) { const txTo = txObject._parent.options.address; @@ -86,15 +103,14 @@ async function sendTx(txObject, gasLimit) { } } -async function deployContract(solcOutput, contractName, name, ctorArgs) { +async function deployContract(artifacts, contractName, ctorArgs) { - const actualName = contractName; - const contract = solcOutput.contracts[actualName][name]; - const bytecode = contract["evm"]["bytecode"]["object"]; - const abi = contract['abi']; + const contract = artifacts[contractName]; + const bytecode = contract.bytecode; + const abi = contract.abi; const myContract = new web3.eth.Contract(abi); - const deploy = myContract.deploy({data:"0x" + bytecode, arguments: ctorArgs}); + const deploy = myContract.deploy({data: bytecode, arguments: ctorArgs}); let address = "0x" + web3.utils.sha3(RLP.encode([sender,nonce])).slice(12).substring(14); address = web3.utils.toChecksumAddress(address); @@ -106,16 +122,20 @@ async function deployContract(solcOutput, contractName, name, ctorArgs) { } //token addresses +let allTokens; let kncTokenAddress; //contracts let feeHandlerContract; +let networkHistoryContract; +let feeHandlerHistoryContract; +let daoHistoryContract; +let matchingEngineHistoryContract; +let storageContract; let matchingEngineContract; let networkContract; let proxyContract; -let stakingContract; -let storageContract; -let kyberDaoContract; +let daoContract; //permissions let matchingEnginePermissions; @@ -126,6 +146,7 @@ let daoOperator; //misc variables needed for contracts deployment, should be obtained from input json let isFeeAccounted; +let isEntitledRebate; let maxGasPrice = (new BN(50).mul(new BN(10).pow(new BN(9)))).toString(); let negDiffInBps; let burnBlockInterval; @@ -134,6 +155,7 @@ let startTimestamp; let networkFeeBps; let rewardFeeBps; let rebateFeeBps; +let contractsOutputFilename; class Reserve { constructor(jsonInput, reserveTypes) { @@ -155,14 +177,11 @@ class Reserve { let reserveDataArray = []; let walletDataArray = []; - -const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; -const zeroAddress = "0x0000000000000000000000000000000000000000"; - -function parseInput( jsonInput ) { +function parseInput(jsonInput) { const reserveTypes = jsonInput["reserveTypes"]; const reserveData = jsonInput["reserves"]; const walletData = jsonInput["wallets"]; + allTokens = jsonInput["tokens"]; // reserve array Object.values(reserveData).forEach(function(reserve) { @@ -183,6 +202,7 @@ function parseInput( jsonInput ) { //constants isFeeAccounted = jsonInput["isFeeAccounted"]; + isEntitledRebate = jsonInput["isEntitledRebate"]; maxGasPrice = jsonInput["max gas price"].toString(); negDiffInBps = jsonInput["neg diff in bps"].toString(); burnBlockInterval = jsonInput["burn block interval"].toString(); @@ -196,6 +216,7 @@ function parseInput( jsonInput ) { gasHelperAddress = jsonInput["addresses"].gasHelper; // output file name + contractsOutputFilename = jsonInput["contracts filename"]; outputFileName = jsonInput["output filename"]; }; @@ -217,14 +238,6 @@ async function setPermissions(contract, contractPermissions) { await sendTx(contract.methods.transferAdminQuickly(admin)); } -const keypress = async () => { - process.stdin.setRawMode(true) - return new Promise(resolve => process.stdin.once('data', () => { - process.stdin.setRawMode(false) - resolve() - })) -} - async function pressToContinue() { console.log("Checkpoint... Press any key to continue!"); await keypress(); @@ -236,9 +249,8 @@ async function main() { chainId = chainId || await web3.eth.net.getId() console.log('chainId', chainId); - console.log('starting compilation'); - output = await require("../compileContracts.js").compileContracts("sol5"); - console.log("finished compilation"); + console.log('compiling contracts and retrieving artifacts...'); + output = await require("../retrieveArtifacts.js").retrieveArtifacts(); //reinstantiate web3 (solc overwrites something) web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl)); @@ -252,26 +264,33 @@ async function main() { // CONTRACT INSTANTIATION / DEPLOYMENT // /////////// DO NOT TOUCH //////////////// await deployMatchingEngineContract(output); - await deployStorageContract(output); + await pressToContinue(); + await deployStorageContracts(output); + await pressToContinue(); await deployNetworkContract(output); + await pressToContinue(); await deployProxyContract(output); + await pressToContinue(); await deployFeeHandlerContract(output); - await deployStakingContract(output); - await deployKyberDaoContract(output); + await pressToContinue(); + await deployDaoContract(output); + await pressToContinue(); + await getStakingAddress(); ///////////////////////////////////////// //IF DEPLOYMENT BREAKS: // 1) Replace relevant contract addresses (if any) variables on top // 2) Use ONLY ONE of the following functions below: - // NOTE: Redeploying network == fullDeployment + exportContractAddresses(); await pressToContinue(); - await fullDeployment(); + // await fullDeployment(); ////////////////// // REDEPLOYMENT // ////////////////// - await setupMatchingEngine(); - await setupKyberDaoStuff(); + // await redeployNetwork(); + // await redeployProxy(); + // await setDaoInFeeHandler(); ///////////////////// // ADDING RESERVES // @@ -295,22 +314,26 @@ async function main() { } async function fullDeployment() { + await setStorageAddressInAllHistoricalContracts(); await setNetworkAddressInMatchingEngine(); await setNetworkAddressInStorage(); await setStorageAddressInMatchingEngine(); + await pressToContinue(); await set_Fee_MatchEngine_Gas_ContractsInNetwork(); - await setKyberDaoInNetwork(); - await setKyberDaoInFeeHandler(); - await setKyberDaoInStaking(); + await pressToContinue(); + await setDaoInNetwork(); + await setDaoInFeeHandler(); await setProxyInNetwork(); await setNetworkInProxy(); - await setTempOperatorToNetwork(); + await setHintHandlerInProxy(); + await setTempOperatorToStorage(); /////////// // BREAK // /////////// await pressToContinue(); await setFeeAccountedDataInStorage(); + await setRebateEntitledDataInStorage(); await addReserves(); await listTokensForReserves(); await configureAndEnableNetwork(); @@ -319,7 +342,7 @@ async function fullDeployment() { // BREAK // /////////// await pressToContinue(); - await removeTempOperator([networkContract]); + await removeTempOperator([storageContract]); await pressToContinue(); await setPermissionsInProxy(); await pressToContinue(); @@ -328,31 +351,29 @@ async function fullDeployment() { await setPermissionsInMatchingEngine(); await pressToContinue(); await setPermissionsInStorage(); + await setPermissionsInHistories(); +} + +function exportContractAddresses() { + console.log("Exporting contract addresses..."); + dictOutput = {}; + dictOutput["networkHistory"] = networkHistoryAddress; + dictOutput["feeHandlerHistory"] = feeHandlerHistoryAddress; + dictOutput["daoHistory"] = daoHistoryAddress; + dictOutput["matchingEngineHistory"] = matchingEngineHistoryAddress; + dictOutput["storage"] = storageAddress; + dictOutput["network"] = networkAddress; + dictOutput["matchingEngine"] = matchingEngineAddress; + dictOutput["proxy"] = proxyAddress; + dictOutput["feeHandler"] = feeHandlerAddress; + dictOutput["gasHelper"] = gasHelperAddress; + dictOutput["staking"] = stakingAddress; + dictOutput["dao"] = daoAddress; + const json = JSON.stringify(dictOutput, null, 2); + console.log(contractsOutputFilename, 'write'); + fs.writeFileSync(contractsOutputFilename, json); } -function printParams(jsonInput) { - dictOutput = {}; - dictOutput["tokens"] = jsonInput.tokens; - dictOutput["tokens"]["ETH"] = {"name" : "Ethereum", "decimals" : 18, "address" : ethAddress }; - dictOutput["addresses"] = jsonInput.addresses; - dictOutput["reserves"] = jsonInput.reserves; - dictOutput["wallets"] = jsonInput.wallets; - dictOutput["permission"] = jsonInput.permission; - dictOutput["max gas price"] = jsonInput["max gas price"]; - dictOutput["neg diff in bps"] = jsonInput["neg diff in bps"]; - dictOutput["burn block interval"] = jsonInput["burn block interval"]; - dictOutput["trade logic"] = matchingEngineAddress; - dictOutput["network"] = networkAddress; - dictOutput["proxy"] = proxyAddress; - dictOutput["fee handler"] = feeHandlerAddress; - 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) @@ -381,25 +402,77 @@ function verifyInput() { async function deployMatchingEngineContract(output) { if (matchingEngineAddress == "") { console.log("deploying matching engine"); - [matchingEngineAddress, matchingEngineContract] = await deployContract(output, "KyberMatchingEngine.sol", "KyberMatchingEngine", [sender]); + [matchingEngineAddress, matchingEngineContract] = await deployContract(output, "KyberMatchingEngine", [sender]); console.log(`matchingEngine: ${matchingEngineAddress}`); } else { console.log("Instantiating matching engine..."); matchingEngineContract = new web3.eth.Contract( - output.contracts["KyberMatchingEngine.sol"]["KyberMatchingEngine"].abi, matchingEngineAddress + output["KyberMatchingEngine"].abi, matchingEngineAddress ); } } -async function deployStorageContract(output) { +async function deployStorageContracts(output) { + // network history + if (networkHistoryAddress == "") { + console.log("deploy networkHistory"); + [networkHistoryAddress, networkHistoryContract] = await deployContract(output, "KyberHistory", [sender]); + console.log(`networkHistory: ${networkHistoryAddress}`); + } else { + console.log("instantiating networkHistory..."); + networkHistoryContract = new web3.eth.Contract( + output["KyberHistory"].abi, networkHistoryAddress + ); + } + + // feeHandlerhistory + if (feeHandlerHistoryAddress == "") { + console.log("deploy feeHandlerHistory"); + [feeHandlerHistoryAddress, feeHandlerHistoryContract] = await deployContract(output, "KyberHistory", [sender]); + console.log(`feeHandlerHistory: ${feeHandlerHistoryAddress}`); + } else { + console.log("instantiating feeHandlerHistory..."); + feeHandlerHistoryContract = new web3.eth.Contract( + output["KyberHistory"].abi, feeHandlerHistoryAddress + ); + } + + // kyberDao history + if (daoHistoryAddress == "") { + console.log("deploy daoHistory"); + [daoHistoryAddress, daoHistoryContract] = await deployContract(output, "KyberHistory", [sender]); + console.log(`daoHistory: ${daoHistoryAddress}`); + } else { + console.log("instantiating daoHistory..."); + daoHistoryContract = new web3.eth.Contract( + output["KyberHistory"].abi, daoHistoryAddress + ); + } + + // matchingEngine history + if (matchingEngineHistoryAddress == "") { + console.log("deploy matchingEngineHistory"); + [matchingEngineHistoryAddress, matchingEngineHistoryContract] = await deployContract(output, "KyberHistory", [sender]); + console.log(`matchingEngineHistory: ${matchingEngineHistoryAddress}`); + } else { + console.log("instantiating matchingEngineHistory..."); + matchingEngineHistoryContract = new web3.eth.Contract( + output["KyberHistory"].abi, matchingEngineHistoryAddress + ); + } + if (storageAddress == "") { console.log("deploying storage"); - [storageAddress, storageContract] = await deployContract(output, "KyberStorage.sol", "KyberStorage", [sender]); + [storageAddress, storageContract] = await deployContract( + output, + "KyberStorage", + [sender, networkHistoryAddress, feeHandlerHistoryAddress, daoHistoryAddress, matchingEngineHistoryAddress] + ); console.log(`storage: ${storageAddress}`); } else { console.log("Instantiating storage..."); storageContract = new web3.eth.Contract( - output.contracts["KyberStorage.sol"]["KyberStorage"].abi, storageAddress + output["KyberStorage"].abi, storageAddress ); } } @@ -407,12 +480,12 @@ async function deployStorageContract(output) { async function deployNetworkContract(output) { if (networkAddress == "") { console.log("deploying kyber network"); - [networkAddress, networkContract] = await deployContract(output, "KyberNetwork.sol", "KyberNetwork", [sender, storageAddress]); + [networkAddress, networkContract] = await deployContract(output, "KyberNetwork", [sender, storageAddress]); console.log(`network: ${networkAddress}`); } else { console.log("Instantiating network..."); networkContract = new web3.eth.Contract( - output.contracts["KyberNetwork.sol"]["KyberNetwork"].abi, networkAddress + output["KyberNetwork"].abi, networkAddress ); } } @@ -420,12 +493,12 @@ async function deployNetworkContract(output) { async function deployProxyContract(output) { if (proxyAddress == "") { console.log("deploying KNProxy"); - [proxyAddress, proxyContract] = await deployContract(output, "KyberNetworkProxy.sol", "KyberNetworkProxy", [sender]); + [proxyAddress, proxyContract] = await deployContract(output, "KyberNetworkProxy", [sender]); console.log(`KNProxy: ${proxyAddress}`); } else { console.log("Instantiating proxy..."); proxyContract = new web3.eth.Contract( - output.contracts["KyberNetworkProxy.sol"]["KyberNetworkProxy"].abi, proxyAddress + output["KyberNetworkProxy"].abi, proxyAddress ); } } @@ -434,53 +507,73 @@ async function deployFeeHandlerContract(output) { if (feeHandlerAddress == "") { console.log("deploying feeHandler"); [feeHandlerAddress, feeHandlerContract] = await deployContract( - output, "KyberFeeHandler.sol", "KyberFeeHandler", + output, "KyberFeeHandler", [sender, proxyAddress, networkAddress, kncTokenAddress, burnBlockInterval, daoOperator] ); console.log(`Fee Handler: ${feeHandlerAddress}`); } else { console.log("Instantiating feeHandler..."); feeHandlerContract = new web3.eth.Contract( - output.contracts["KyberFeeHandler.sol"]["KyberFeeHandler"].abi, feeHandlerAddress + output["KyberFeeHandler"].abi, feeHandlerAddress ); } } -async function deployStakingContract(output) { - if (stakingAddress == "") { - console.log("deploying staking contract"); - [stakingAddress, stakingContract] = await deployContract( - output, "KyberStaking.sol", "KyberStaking", - [kncTokenAddress, epochPeriod, startTimestamp, sender] - ); - console.log(`Staking: ${stakingAddress}`); - } else { - console.log("Instantiating staking..."); - stakingContract = new web3.eth.Contract( - output.contracts["KyberStaking.sol"]["KyberStaking"].abi, stakingAddress - ); - } -}; - -async function deployKyberDaoContract(output) { +async function deployDaoContract(output) { if (daoAddress == "") { - console.log("deploying KyberDao contract"); - [daoAddress, kyberDaoContract] = await deployContract( - output, "KyberDao.sol", "KyberDao", + console.log("deploying Dao and staking contracts"); + [daoAddress, daoContract] = await deployContract( + output, "KyberDao", [ - epochPeriod, startTimestamp, stakingAddress, feeHandlerAddress, kncTokenAddress, + epochPeriod, startTimestamp, kncTokenAddress, networkFeeBps, rewardFeeBps, rebateFeeBps, daoOperator ] ); - console.log(`KyberDao: ${daoAddress}`); + console.log(`Dao: ${daoAddress}`); } else { - console.log("Instantiating KyberDao..."); - kyberDaoContract = new web3.eth.Contract( - output.contracts["KyberDao.sol"]["KyberDao"].abi, daoAddress + console.log("Instantiating Dao..."); + daoContract = new web3.eth.Contract( + output["KyberDao"].abi, daoAddress ); } + // Note: Staking contract need not be instantiated, since it doesn't require any setup + console.log("\x1b[41m%s\x1b[0m" ,"Wait for tx to be mined before continuing (will call daoContract for staking address)"); }; +async function getStakingAddress() { + stakingAddress = await daoContract.methods.staking().call(); + console.log(`Staking: ${stakingAddress}`); +} + +async function waitForMatchingEngineAndStorageUpdate() { + while(true) { + let matchingEngineNetwork = await matchingEngineContract.methods.kyberNetwork().call(); + let storageNetwork = await storageContract.methods.kyberNetwork().call(); + if (matchingEngineNetwork == networkAddress && storageNetwork == networkAddress) { + return; + } else if (matchingEngineNetwork != networkAddress) { + console.log(`matching engine not pointing to network`); + console.log(`Current matchingEngine network address: ${matchingEngineNetwork}`); + console.log(`Waiting...`); + await sleep(25000); + } else { + console.log(`storage not pointing to network`); + console.log(`Current storage network address: ${storageNetwork}`); + console.log(`Waiting...`); + await sleep(25000); + } + } +} + +async function checkZeroProxies() { + let networkProxies = await storageContract.methods.getKyberProxies().call(); + if (networkProxies.length > 0) { + console.log("\x1b[41m%s\x1b[0m" ,"Existing kyberProxies in storage, remove before proceeding"); + process.exit(1); + } + return; +} + async function setNetworkAddressInMatchingEngine(tempAddress) { console.log("set network in matching engine"); if (tempAddress == undefined) { @@ -508,6 +601,18 @@ async function setStorageAddressInMatchingEngine(tempAddress) { } } +async function setStorageAddressInAllHistoricalContracts() { + console.log("set storage in historical contracts"); + await setStorageAddressInHistoricalContract(networkHistoryContract); + await setStorageAddressInHistoricalContract(feeHandlerHistoryContract); + await setStorageAddressInHistoricalContract(daoHistoryContract); + await setStorageAddressInHistoricalContract(matchingEngineHistoryContract); +} + +async function setStorageAddressInHistoricalContract(contractInstance) { + await sendTx(contractInstance.methods.setStorageContract(storageAddress)); +} + async function set_Fee_MatchEngine_Gas_ContractsInNetwork() { console.log("set feeHandler, matchingEngine and gas helper in network"); await sendTx(networkContract.methods.setContracts( @@ -515,21 +620,16 @@ async function set_Fee_MatchEngine_Gas_ContractsInNetwork() { )); } -async function setKyberDaoInNetwork() { - console.log("Setting KyberDao address in network"); +async function setDaoInNetwork() { + console.log("Setting dao address in network"); await sendTx(networkContract.methods.setKyberDaoContract(daoAddress)); } -async function setKyberDaoInFeeHandler() { - console.log("Setting KyberDao address in fee handler"); +async function setDaoInFeeHandler() { + console.log("Setting dao address in fee handler"); await sendTx(feeHandlerContract.methods.setDaoContract(daoAddress)); } -async function setKyberDaoInStaking() { - console.log("Setting KyberDao address in staking"); - await sendTx(stakingContract.methods.updateKyberDaoAddressAndRemoveSetter(daoAddress)); -} - async function setProxyInNetwork() { console.log("set proxy in network"); await sendTx(networkContract.methods.addKyberProxy(proxyAddress)); @@ -540,28 +640,40 @@ async function setNetworkInProxy() { await sendTx(proxyContract.methods.setKyberNetwork(networkAddress)); } -async function setTempOperatorToNetwork() { +async function setHintHandlerInProxy() { + console.log("setting hint handler in proxy"); + await sendTx(proxyContract.methods.setHintHandler(matchingEngineAddress)); +} + +async function setTempOperatorToStorage() { // add operator to network - console.log("set temp operator: network"); - await sendTx(networkContract.methods.addOperator(sender)); + console.log("set temp operator: storage"); + await sendTx(storageContract.methods.addOperator(sender)); } async function setFeeAccountedDataInStorage() { - console.log("set fee paying data: matching engine"); + console.log("set fee paying data: storage"); await sendTx(storageContract.methods.setFeeAccountedPerReserveType( isFeeAccounted["FPR"], isFeeAccounted["APR"], isFeeAccounted["BRIDGE"], isFeeAccounted["UTILITY"], isFeeAccounted["CUSTOM"], isFeeAccounted["ORDERBOOK"] )); } +async function setRebateEntitledDataInStorage() { + console.log("set rebate entitled data: storage"); + await sendTx(storageContract.methods.setEntitledRebatePerReserveType( + isEntitledRebate["FPR"], isEntitledRebate["APR"], isEntitledRebate["BRIDGE"], isEntitledRebate["UTILITY"], isEntitledRebate["CUSTOM"], isEntitledRebate["ORDERBOOK"] + )); +} + async function addReserves(reserveIndex) { // add reserve to network - console.log("Add reserves to network"); + console.log("Add reserves to storage"); reserveIndex = (reserveIndex == undefined) ? 0 : reserveIndex; for (let i = reserveIndex ; i < reserveDataArray.length ; i++) { const reserve = reserveDataArray[i]; console.log(`Reserve array index ${i}`); console.log(`Adding reserve ${reserve.address}`); - await sendTx(networkContract.methods.addReserve(reserve.address, reserve.id, reserve.type, reserve.wallet)); + await sendTx(storageContract.methods.addReserve(reserve.address, reserve.id, reserve.type, reserve.wallet)); await pressToContinue(); } } @@ -575,58 +687,94 @@ async function listTokensForReserves(reserveIndex, tokenIndex) { for (let j = tokenIndex ; j < tokens.length ; j++) { token = tokens[j]; console.log(`Reserve array index ${i}, token array index ${j}`); - console.log(`listing token ${token.address} for reserve ${reserve.address}`); - await sendTx(networkContract.methods.listPairForReserve(reserve.address,token.address,token.ethToToken,token.tokenToEth,true)); + console.log(`listing token ${token.address} for reserve ${reserve.id}`); + await sendTx(storageContract.methods.listPairForReserve(reserve.id,token.address,token.ethToToken,token.tokenToEth,true)); } await pressToContinue(); } } async function configureAndEnableNetwork() { - // set params - console.log("network set params"); - await sendTx(networkContract.methods.setParams(maxGasPrice, - negDiffInBps)); - - console.log("network enable"); - await sendTx(networkContract.methods.setEnable(true)); + // set params + console.log("network set params"); + await sendTx(networkContract.methods.setParams(maxGasPrice, negDiffInBps)); + + console.log("network enable"); + await sendTx(networkContract.methods.setEnable(true)); } async function removeTempOperator(contractInstances) { - for (let contractInstance of contractInstances) { - console.log(`remove temp operator`); - await sendTx(contractInstance.methods.removeOperator(sender)); - } + for (let contractInstance of contractInstances) { + console.log(`remove temp operator`); + await sendTx(contractInstance.methods.removeOperator(sender)); + } } async function setPermissionsInNetwork() { - await setPermissions(networkContract, networkPermissions); + console.log('setting permissions in network'); + await setPermissions(networkContract, networkPermissions); } async function setPermissionsInProxy() { - await setPermissions(proxyContract, proxyPermissions); + console.log('setting permissions in proxy'); + await setPermissions(proxyContract, proxyPermissions); } async function setPermissionsInMatchingEngine() { - await setPermissions(matchingEngineContract, matchingEnginePermissions); + console.log('setting permissions in matchingEngine'); + await setPermissions(matchingEngineContract, matchingEnginePermissions); } async function setPermissionsInStorage() { + console.log('setting permissions in storage'); await setPermissions(storageContract, storagePermissions); } -async function setupMatchingEngine() { - await setNetworkAddressInMatchingEngine(); - await setStorageAddressInMatchingEngine(); - await setPermissionsInMatchingEngine(); - console.log("\x1b[41m%s\x1b[0m" ,"REMINDER: Set matching engine in network contract!!"); -}; +async function setPermissionsInHistories() { + console.log('setting permissions in histories'); + await setPermissions(networkHistoryContract, storagePermissions); + await setPermissions(feeHandlerHistoryContract, storagePermissions); + await setPermissions(daoHistoryContract, storagePermissions); + await setPermissions(matchingEngineHistoryContract, storagePermissions); +} -async function setupKyberDaoStuff() { - await setKyberDaoInFeeHandler(); - await setKyberDaoInStaking(); - console.log("\x1b[41m%s\x1b[0m" ,"REMINDER: Set KyberDao in network contract!!"); -}; +async function redeployNetwork() { + await waitForMatchingEngineAndStorageUpdate(); + await pressToContinue(); + await setTempOperatorToNetwork(); + await set_Fee_MatchEngine_Gas_ContractsInNetwork(); + await pressToContinue(); + await setDaoInNetwork(); + await setProxyInNetwork(); + await pressToContinue(); + await listReservesForTokens(); + await configureAndEnableNetwork(); + await pressToContinue(); + await removeTempOperator([networkContract]); + await setPermissionsInNetwork(); +} + +async function setTempOperatorToNetwork() { + // add operator to network + console.log("set temp operator: network"); + await sendTx(networkContract.methods.addOperator(sender)); +} + +async function listReservesForTokens() { + for (let j = tokenIndex ; j < allTokens.length ; j++) { + token = allTokens[j]; + console.log(`Giving allowance to reserves for token ${token}`); + await sendTx(networkContract.methods.listReservesForToken(token, 0, 8, true)); + } +} + +async function redeployProxy() { + await checkZeroProxies(); + await setNetworkInProxy(); + await setHintHandlerInProxy(); + await pressToContinue(); + await setPermissionsInProxy(); +} function lastFewThings() { console.log("\x1b[41m%s\x1b[0m" ,"REMINDER: Don't forget to send DGX to network contract!!"); @@ -647,4 +795,3 @@ catch(err) { } main(); - diff --git a/web3deployment/KatalystDeploy/katalyst_deployer_ropsten_input.json b/web3deployment/KatalystDeploy/katalyst_deployer_ropsten_input.json index 942f3f791..7b46026cd 100644 --- a/web3deployment/KatalystDeploy/katalyst_deployer_ropsten_input.json +++ b/web3deployment/KatalystDeploy/katalyst_deployer_ropsten_input.json @@ -40,6 +40,15 @@ "ORDERBOOK": true }, + "isEntitledRebate": { + "FPR": true, + "APR": true, + "BRIDGE": false, + "UTILITY": false, + "CUSTOM": true, + "ORDERBOOK": true + }, + "reserves": [ { "address": "0x39C30B03799DB870e9C01747f68e24D2b38A30C9", @@ -112,7 +121,7 @@ "DaoOperator": "0xddf05698718ba8ed1c9aba198d38a825a64d69e2" }, "MatchingEngine" : { - "admin" : "0xea058bEa72a251039C2c9C9C103fD2a9335a781F", + "admin" : "0xee2cCc4320B6d192f4859d335F74f822C9462b16", "operators" : ["0xbDd33F411DA0B40018922a3BC69001B458227f5c","0xBE2F0354D970265BFc36D383af77F72736b81B54","0x46a77D03A76232211CD2eabaE3e10e0dfe71CddA","0x6E4843E90ecacd3a3BBcED74A5235363762D5100"], "alerters" : ["0xbDd33F411DA0B40018922a3BC69001B458227f5c","0xBE2F0354D970265BFc36D383af77F72736b81B54","0x46a77D03A76232211CD2eabaE3e10e0dfe71CddA","0x6E4843E90ecacd3a3BBcED74A5235363762D5100"] }, @@ -129,7 +138,7 @@ "alerters" : [] }, "Storage" : { - "admin" : "0xea058bEa72a251039C2c9C9C103fD2a9335a781F", + "admin" : "0xee2cCc4320B6d192f4859d335F74f822C9462b16", "operators" : [], "alerters" : [] } @@ -138,11 +147,12 @@ "max gas price" : 50000000000, "neg diff in bps" : 20, - "epoch period": 10800, - "start timestamp": 1587367900, + "epoch period": 2700, + "start timestamp": 1591848000, "network fee bps": 30, "reward fee bps": 3850, "rebate bps": 3000, "burn block interval": 10, + "contracts filename" : "katalystContracts.json", "output filename" : "katalystRopsten.json" } diff --git a/web3deployment/compileContracts.js b/web3deployment/compileContracts.js deleted file mode 100644 index c956c0453..000000000 --- a/web3deployment/compileContracts.js +++ /dev/null @@ -1,616 +0,0 @@ -const fs = require("fs-extra"); -const path = require("path"); -const solc = require("solc"); -const contractsSol4Path = path.join(__dirname, "../contracts/sol4/"); -const contractsSol5Path = path.join(__dirname, "../contracts/sol5/"); -const contractsSol6Path = path.join(__dirname, "../contracts/sol6/"); -const solc418 = "v0.4.18+commit.9cf6e910"; -const solc511 = "v0.5.11+commit.c082d0b4"; -const solc66 = "v0.6.6+commit.6c089d02"; -const solc418Path = path.join( - __dirname, - "./compilers/soljson-" + solc418 + ".js" -); -const solc511Path = path.join( - __dirname, - "./compilers/soljson-" + solc511 + ".js" -); -const solc66Path = path.join( - __dirname, - "./compilers/soljson-" + solc66 + ".js" -); -let compiler; - -const sol4SourceFiles = { - "ConversionRates.sol": { - content: fs.readFileSync( - contractsSol4Path + "reserves/fprConversionRate/ConversionRates.sol", - "utf8" - ), - }, - "ConversionRateEnhancedSteps.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/fprConversionRate/ConversionRateEnhancedSteps.sol", - "utf8" - ), - }, - "ConversionRatesInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "ConversionRatesInterface.sol", - "utf8" - ), - }, - "reserves/fprConversionRate/ConversionRates.sol": { - content: fs.readFileSync( - contractsSol4Path + "reserves/fprConversionRate/ConversionRates.sol", - "utf8" - ), - }, - "reserves/VolumeImbalanceRecorder.sol": { - content: fs.readFileSync( - contractsSol4Path + "reserves/VolumeImbalanceRecorder.sol", - "utf8" - ), - }, - "VolumeImbalanceRecorder.sol": { - content: fs.readFileSync( - contractsSol4Path + "reserves/VolumeImbalanceRecorder.sol", - "utf8" - ), - }, - "PermissionGroups.sol": { - content: fs.readFileSync( - contractsSol4Path + "PermissionGroups.sol", - "utf8" - ), - }, - "ERC20Interface.sol": { - content: fs.readFileSync( - contractsSol4Path + "ERC20Interface.sol","utf8"), - }, - "KyberNetworkInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "KyberNetworkInterface.sol", - "utf8" - ), - }, - "KyberProxyV1.sol": { - content: fs.readFileSync(contractsSol4Path + "KyberProxyV1.sol", "utf8"), - }, - "KyberNetworkProxyInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "KyberNetworkProxyInterface.sol", - "utf8" - ), - }, - "KyberReserve.sol": { - content: fs.readFileSync( - contractsSol4Path + "reserves/KyberReserve.sol", - "utf8" - ), - }, - "KyberReserveInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "KyberReserveInterface.sol", - "utf8" - ), - }, - "LiquidityConversionRates.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/aprConversionRate/LiquidityConversionRates.sol", - "utf8" - ), - }, - "LiquidityFormula.sol": { - content: fs.readFileSync( - contractsSol4Path + "reserves/aprConversionRate/LiquidityFormula.sol", - "utf8" - ), - }, - "OrderbookReserve.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderbookReserve.sol", - "utf8" - ), - }, - "OrderbookReserveInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderbookReserveInterface.sol", - "utf8" - ), - }, - "OrderIdManager.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderIdManager.sol", - "utf8" - ), - }, - "OrderList.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderList.sol", - "utf8" - ), - }, - "OrderListFactory.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderListFactory.sol", - "utf8" - ), - }, - "OrderListFactoryInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderListFactoryInterface.sol", - "utf8" - ), - }, - "OrderListInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderListInterface.sol", - "utf8" - ), - }, - "OrderListFactoryInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/orderBookReserve/permissionless/OrderListFactoryInterface.sol", - "utf8" - ), - }, - "PermissionGroups.sol": { - content: fs.readFileSync( - contractsSol4Path + "PermissionGroups.sol", - "utf8" - ), - }, - "KyberReserveInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "KyberReserveInterface.sol", - "utf8" - ), - }, - "SanityRates.sol": { - content: fs.readFileSync(contractsSol4Path + "SanityRates.sol", "utf8"), - }, - "SanityRatesInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "SanityRatesInterface.sol", - "utf8" - ), - }, - "SimpleNetworkInterface.sol": { - content: fs.readFileSync( - contractsSol4Path + "SimpleNetworkInterface.sol", - "utf8" - ), - }, - "Utils.sol": { - content: fs.readFileSync(contractsSol4Path + "Utils.sol", "utf8"), - }, - "Utils2.sol": { - content: fs.readFileSync(contractsSol4Path + "Utils2.sol", "utf8"), - }, - "Utils3.sol": { - content: fs.readFileSync(contractsSol4Path + "Utils3.sol", "utf8"), - }, - "Withdrawable.sol": { - content: fs.readFileSync(contractsSol4Path + "Withdrawable.sol", "utf8"), - }, - "KyberUniswapReserve.sol": { - content: fs.readFileSync( - contractsSol4Path + - "reserves/bridgeReserves/uniswap/KyberUniswapReserve.sol", - "utf8" - ), - }, - "WrapperBase.sol": { - content: fs.readFileSync( - contractsSol4Path + "wrappers/WrapperBase.sol", - "utf8" - ), - }, - "SetStepFunctionWrapper.sol": { - content: fs.readFileSync( - contractsSol4Path + "wrappers/SetStepFunctionWrapper.sol", - "utf8" - ), - }, - "WrapConversionRate.sol": { - content: fs.readFileSync( - contractsSol4Path + "wrappers/WrapConversionRate.sol", - "utf8" - ), - }, - "WrapReadTokenData.sol": { - content: fs.readFileSync( - contractsSol4Path + "wrappers/WrapReadTokenData.sol", - "utf8" - ), - }, -}; - -const sol5SourceFiles = { - "Eth2DaiReserve.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/eth2dai/Eth2DaiReserve.sol", "utf8"), - }, - "IBancorNetwork.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/bancor/mock/IBancorNetwork.sol", "utf8"), - }, - "IERC20.sol": { - content: fs.readFileSync(contractsSol5Path + "IERC20.sol", "utf8"), - }, - "IKyberReserve.sol": { - content: fs.readFileSync(contractsSol5Path + "IKyberReserve.sol", "utf8"), - }, - "IOtc.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/eth2dai/mock/IOtc.sol", "utf8"), - }, - "KyberBancorReserve.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/bancor/KyberBancorReserve.sol", "utf8"), - }, - "mock/IBancorNetwork.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/bancor/mock/IBancorNetwork.sol", "utf8"), - }, - "mock/IOtc.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/eth2dai/mock/IOtc.sol", "utf8"), - }, - "mock/Token.sol": { - content: fs.readFileSync(contractsSol5Path + "/mock/Token.sol", "utf8"), - }, - "MockBancorNetwork.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/bancor/mock/MockBancorNetwork.sol", "utf8"), - }, - "MockOtcOrderbook.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/eth2dai/mock/MockOtcOrderbook.sol", "utf8"), - }, - "PermissionGroups2.sol": { - content: fs.readFileSync( - contractsSol5Path + "utils/PermissionGroups2.sol", - "utf8" - ), - }, - "utils/PermissionGroups2.sol": { - content: fs.readFileSync( - contractsSol5Path + "utils/PermissionGroups2.sol", - "utf8" - ), - }, - "utils/Utils4.sol": { - content: fs.readFileSync(contractsSol5Path + "utils/Utils4.sol", "utf8"), - }, - "utils/Withdrawable2.sol": { - content: fs.readFileSync( - contractsSol5Path + "utils/Withdrawable2.sol", - "utf8" - ), - }, - "Utils4.sol": { - content: fs.readFileSync(contractsSol5Path + "utils/Utils4.sol", "utf8"), - }, - "WethToken.sol": { - content: fs.readFileSync(contractsSol5Path + "/bridges/eth2dai/mock/WethToken.sol", "utf8"), - }, - "Withdrawable2.sol": { - content: fs.readFileSync( - contractsSol5Path + "utils/Withdrawable2.sol", - "utf8" - ), - } -}; - -const sol6SourceFiles = { - "Address.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/Address.sol", - "utf8" - ), - }, - "EpochUtils.sol": { - content: fs.readFileSync(contractsSol6Path + "Dao/EpochUtils.sol", "utf8"), - }, - "GasHelper.sol": { - content: fs.readFileSync(contractsSol6Path + "mock/GasHelper.sol", "utf8"), - }, - "IGasHelper.sol": { - content: fs.readFileSync(contractsSol6Path + "IGasHelper.sol", "utf8"), - }, - "IERC20.sol": { - content: fs.readFileSync(contractsSol6Path + "IERC20.sol", "utf8"), - }, - "IBurnableToken.sol": { - content: fs.readFileSync(contractsSol6Path + "IBurnableToken.sol", "utf8"), - }, - "IKyberFeeHandler.sol": { - content: fs.readFileSync( - contractsSol6Path + "IKyberFeeHandler.sol", - "utf8" - ), - }, - "IKyberDao.sol": { - content: fs.readFileSync(contractsSol6Path + "IKyberDao.sol", "utf8"), - }, - "IKyberHint.sol": { - content: fs.readFileSync(contractsSol6Path + "IKyberHint.sol", "utf8"), - }, - "IKyberNetwork.sol": { - content: fs.readFileSync(contractsSol6Path + "IKyberNetwork.sol", "utf8"), - }, - "IKyberNetworkProxy.sol": { - content: fs.readFileSync( - contractsSol6Path + "IKyberNetworkProxy.sol", - "utf8" - ), - }, - "IKyberRateHelper.sol": { - content: fs.readFileSync( - contractsSol6Path + "IKyberRateHelper.sol", - "utf8" - ), - }, - "IKyberReserve.sol": { - content: fs.readFileSync(contractsSol6Path + "IKyberReserve.sol", "utf8"), - }, - "IKyberMatchingEngine.sol": { - content: fs.readFileSync( - contractsSol6Path + "IKyberMatchingEngine.sol", - "utf8" - ), - }, - "IKyberStaking.sol": { - content: fs.readFileSync( - contractsSol6Path + "Dao/IKyberStaking.sol", - "utf8" - ), - }, - "IKyberStorage.sol": { - content: fs.readFileSync( - contractsSol6Path + "IKyberStorage.sol", - "utf8" - ), - }, - "ISanityRate.sol": { - content: fs.readFileSync( - contractsSol6Path + "ISanityRate.sol", - "utf8" - ), - }, - "ISimpleKyberProxy.sol": { - content: fs.readFileSync( - contractsSol6Path + "ISimpleKyberProxy.sol", - "utf8" - ), - }, - "KyberDao.sol": { - content: fs.readFileSync(contractsSol6Path + "Dao/KyberDao.sol", "utf8"), - }, - "KyberFeeHandler.sol": { - content: fs.readFileSync( - contractsSol6Path + "Dao/KyberFeeHandler.sol", - "utf8" - ), - }, - "KyberHintHandler.sol": { - content: fs.readFileSync( - contractsSol6Path + "KyberHintHandler.sol", - "utf8" - ), - }, - "KyberNetwork.sol": { - content: fs.readFileSync(contractsSol6Path + "KyberNetwork.sol", "utf8"), - }, - "KyberNetworkProxy.sol": { - content: fs.readFileSync( - contractsSol6Path + "KyberNetworkProxy.sol", - "utf8" - ), - }, - "KyberMatchingEngine.sol": { - content: fs.readFileSync( - contractsSol6Path + "KyberMatchingEngine.sol", - "utf8" - ), - }, - "KyberStaking.sol": { - content: fs.readFileSync( - contractsSol6Path + "Dao/KyberStaking.sol", - "utf8" - ), - }, - "KyberStorage.sol": { - content: fs.readFileSync( - contractsSol6Path + "KyberStorage.sol", - "utf8" - ), - }, - "PermissionGroupsNoModifiers.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/PermissionGroupsNoModifiers.sol", - "utf8" - ), - }, - "ReentrancyGuard.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/ReentrancyGuard.sol", - "utf8" - ), - }, - "SafeERC20.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/SafeERC20.sol", - "utf8" - ), - }, - "SafeMath.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/SafeMath.sol", - "utf8" - ), - }, - "utils/zeppelin/Address.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/Address.sol", - "utf8" - ), - }, - "utils/PermissionGroupsNoModifiers.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/PermissionGroupsNoModifiers.sol", - "utf8" - ), - }, - "utils/Utils5.sol": { - content: fs.readFileSync(contractsSol6Path + "utils/Utils5.sol", "utf8"), - }, - "utils/WithdrawableNoModifiers.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/WithdrawableNoModifiers.sol", - "utf8" - ), - }, - "utils/zeppelin/SafeERC20.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/SafeERC20.sol", - "utf8" - ), - }, - "utils/zeppelin/SafeMath.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/SafeMath.sol", - "utf8" - ), - }, - "utils/zeppelin/ReentrancyGuard.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/zeppelin/ReentrancyGuard.sol", - "utf8" - ), - }, - "Utils5.sol": { - content: fs.readFileSync(contractsSol6Path + "utils/Utils5.sol", "utf8"), - }, - "WithdrawableNoModifiers.sol": { - content: fs.readFileSync( - contractsSol6Path + "utils/WithdrawableNoModifiers.sol", - "utf8" - ), - } -}; - -function compilingPreparations() { - const buildPath = path.resolve(__dirname, "build"); - fs.removeSync(buildPath); - return buildPath; -} - -function createConfiguration(sourceFiles) { - return { - language: "Solidity", - sources: sourceFiles, - settings: { - outputSelection: { - // return everything - "*": { - "*": ["*"], - }, - }, - // Optional: Optimizer settings - optimizer: require("../solcOptimiserSettings.js"), - }, - }; -} - -function getImports(dependency) { - console.log("Searching for dependency: ", dependency); -} - -function loadSpecificCompiler(solcVersion, solcPath) { - console.log(`Loading compiler ${solcVersion}`); - return solc.setupMethods(require(solcPath)); -} - -function errorHandling(compiledSources, versionNum) { - if (!compiledSources) { - console.error( - ">>>>>>>>>>>>>>>>>>>>>>>> ERRORS <<<<<<<<<<<<<<<<<<<<<<<<\n", - "NO OUTPUT" - ); - } else if (compiledSources.errors) { - // something went wrong. - console.error(">>>>>>>>>>>>>>>>>>>>>>>> ERRORS <<<<<<<<<<<<<<<<<<<<<<<<\n"); - compiledSources.errors.map((error) => console.log(error.formattedMessage)); - } else { - console.log(`Successfully compiled ${versionNum} contracts!`); - } -} - -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} - -module.exports.compileContracts = compileContracts; -async function compileContracts(versionNum) { - compiler = undefined; - let solcVersionNum; - let solcPath; - let sourceFiles; - if (versionNum == "sol4") { - solcVersionNum = solc418; - solcPath = solc418Path; - sourceFiles = sol4SourceFiles; - } else if (versionNum == "sol5") { - solcVersionNum = solc511; - solcPath = solc511Path; - sourceFiles = sol5SourceFiles; - } else if (versionNum == "sol6") { - solcVersionNum = solc66; - solcPath = solc66Path; - sourceFiles = sol6SourceFiles; - } else { - console.log(`invalid version number ${versionNum}`); - process.exit(0); - } - - compiler = loadSpecificCompiler(solcVersionNum, solcPath); - compilingPreparations(); - const config = createConfiguration(sourceFiles); - console.log("started compilation"); - output = JSON.parse(compiler.compile(JSON.stringify(config))); - errorHandling(output, versionNum); - return output; -} - -module.exports.compileSol4Contracts = compileSol4Contracts; -async function compileSol4Contracts() { - return await compileContracts("sol4"); -} - -module.exports.compileSol5Contracts = compileSol5Contracts; -async function compileSol5Contracts() { - return await compileContracts("sol5"); -} - -module.exports.compileSol6Contracts = compileSol6Contracts; -async function compileSol6Contracts() { - return await compileContracts("sol6"); -} - -module.exports.compileAllContracts = main; -async function main() { - let output = {contracts: {}, sources: {}}; - let v4Output = await compileContracts("sol4"); - let v5Output = await compileContracts("sol5"); - let v6Output = await compileContracts("sol6"); - return [v4Output, v5Output, v6Output]; -} - -main(); diff --git a/web3deployment/retrieveArtifacts.js b/web3deployment/retrieveArtifacts.js new file mode 100644 index 000000000..c274ecc99 --- /dev/null +++ b/web3deployment/retrieveArtifacts.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +const util = require('util'); +const readdir = util.promisify(fs.readdir); +const path = require("path"); +const artifactsPath = path.join(__dirname, "../artifacts/"); +const buidlerConfigSol5 = path.join(__dirname, "../buidlerConfigSol5.js"); +const buidlerConfigSol4 = path.join(__dirname, "../buidlerConfigSol4.js"); +const execSync = require('child_process').execSync; + +module.exports.retrieveArtifacts = main; +async function main(skipCompilation) { + if (!skipCompilation) { + compileContracts(); + } + let output = await packageArtifacts(); + return output; +} + +async function packageArtifacts() { + let result = {}; + files = await readdir(artifactsPath); + files.forEach(file => { + content = JSON.parse(fs.readFileSync(path.join(artifactsPath, file))); + result[content.contractName] = content; + }) + return result; +} + + +function compileContracts() { + console.log("Compiling contracts..."); + execSync(`npx buidler compile`, { encoding: 'utf-8' }); + execSync(`npx buidler compile --config ${buidlerConfigSol5}`, { encoding: 'utf-8'}); + execSync(`npx buidler compile --config ${buidlerConfigSol4}`, { encoding: 'utf-8'}); +} + +main();