From 730121d34f2fc60636f812112ac5676ebbf033db Mon Sep 17 00:00:00 2001 From: Le Xuan Manh Date: Thu, 21 Mar 2019 16:26:14 +0700 Subject: [PATCH 1/6] Add nonReentrant guard to swap in network contract (#3) --- contracts/Network.sol | 28 +++++++++++++++++++- contracts/WrapContracts/WrapExpectedRate.sol | 28 +++++++++++++++++++- contracts/WrapContracts/WrapNetwork.sol | 28 +++++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/contracts/Network.sol b/contracts/Network.sol index d99b631..ea0683a 100644 --- a/contracts/Network.sol +++ b/contracts/Network.sol @@ -8,10 +8,34 @@ import "./Utils2.sol"; import "./WhiteListInterface.sol"; import "./ExpectedRateInterface.sol"; +/** + * @title Helps contracts guard against reentrancy attacks. + */ +contract ReentrancyGuard { + + /// @dev counter to allow mutex lock with only one SSTORE operation + uint256 private guardCounter = 1; + + /** + * @dev Prevents a function from calling itself, directly or indirectly. + * Calling one `nonReentrant` function from + * another is not supported. Instead, you can implement a + * `private` function doing the actual work, and an `external` + * wrapper marked as `nonReentrant`. + */ + modifier nonReentrant() { + guardCounter += 1; + uint256 localCounter = guardCounter; + _; + require(localCounter == guardCounter); + } +} + + //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Network main contract -contract Network is Withdrawable, Utils2, NetworkInterface { +contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01% ReserveInterface[] public reserves; @@ -69,6 +93,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface { address walletId ) public + nonReentrant payable returns(uint) { @@ -97,6 +122,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface { uint minConversionRate ) public + nonReentrant payable returns(uint) { diff --git a/contracts/WrapContracts/WrapExpectedRate.sol b/contracts/WrapContracts/WrapExpectedRate.sol index c894cd6..573b9ba 100644 --- a/contracts/WrapContracts/WrapExpectedRate.sol +++ b/contracts/WrapContracts/WrapExpectedRate.sol @@ -320,9 +320,33 @@ contract Utils2 is Utils { } } +/** + * @title Helps contracts guard against reentrancy attacks. + */ +contract ReentrancyGuard { + + /// @dev counter to allow mutex lock with only one SSTORE operation + uint256 private guardCounter = 1; + + /** + * @dev Prevents a function from calling itself, directly or indirectly. + * Calling one `nonReentrant` function from + * another is not supported. Instead, you can implement a + * `private` function doing the actual work, and an `external` + * wrapper marked as `nonReentrant`. + */ + modifier nonReentrant() { + guardCounter += 1; + uint256 localCounter = guardCounter; + _; + require(localCounter == guardCounter); + } +} + + //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Network main contract -contract Network is Withdrawable, Utils2, NetworkInterface { +contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01% ReserveInterface[] public reserves; @@ -380,6 +404,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface { address walletId ) public + nonReentrant payable returns(uint) { @@ -408,6 +433,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface { uint minConversionRate ) public + nonReentrant payable returns(uint) { diff --git a/contracts/WrapContracts/WrapNetwork.sol b/contracts/WrapContracts/WrapNetwork.sol index bb8b0b1..01964d9 100644 --- a/contracts/WrapContracts/WrapNetwork.sol +++ b/contracts/WrapContracts/WrapNetwork.sol @@ -319,9 +319,33 @@ contract Utils2 is Utils { } } +/** + * @title Helps contracts guard against reentrancy attacks. + */ +contract ReentrancyGuard { + + /// @dev counter to allow mutex lock with only one SSTORE operation + uint256 private guardCounter = 1; + + /** + * @dev Prevents a function from calling itself, directly or indirectly. + * Calling one `nonReentrant` function from + * another is not supported. Instead, you can implement a + * `private` function doing the actual work, and an `external` + * wrapper marked as `nonReentrant`. + */ + modifier nonReentrant() { + guardCounter += 1; + uint256 localCounter = guardCounter; + _; + require(localCounter == guardCounter); + } +} + + //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Network main contract -contract Network is Withdrawable, Utils2, NetworkInterface { +contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01% ReserveInterface[] public reserves; @@ -379,6 +403,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface { address walletId ) public + nonReentrant payable returns(uint) { @@ -407,6 +432,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface { uint minConversionRate ) public + nonReentrant payable returns(uint) { From 7756ee0c2340088ed09427f5a1bac1194e49d0a1 Mon Sep 17 00:00:00 2001 From: manhlx Date: Thu, 21 Mar 2019 17:19:19 +0700 Subject: [PATCH 2/6] Add fee sharing contract and update network with fee sharing program --- contracts/FeeSharing.sol | 110 +++++ contracts/FeeSharingInterface.sol | 5 + contracts/Network.sol | 88 ++-- contracts/WrapContracts/WrapExpectedRate.sol | 90 ++-- contracts/WrapContracts/WrapFeeSharing.sol | 419 +++++++++++++++++++ contracts/WrapContracts/WrapNetwork.sol | 90 ++-- 6 files changed, 699 insertions(+), 103 deletions(-) create mode 100644 contracts/FeeSharing.sol create mode 100644 contracts/FeeSharingInterface.sol create mode 100644 contracts/WrapContracts/WrapFeeSharing.sol diff --git a/contracts/FeeSharing.sol b/contracts/FeeSharing.sol new file mode 100644 index 0000000..a099f1c --- /dev/null +++ b/contracts/FeeSharing.sol @@ -0,0 +1,110 @@ +pragma solidity 0.4.25; + + +import "./FeeSharingInterface.sol"; +import "./Withdrawable.sol"; +import "./Utils2.sol"; +import "./NetworkInterface.sol"; + +/** + * @title Helps contracts guard against reentrancy attacks. + */ +contract ReentrancyGuard { + + /// @dev counter to allow mutex lock with only one SSTORE operation + uint256 private guardCounter = 1; + + /** + * @dev Prevents a function from calling itself, directly or indirectly. + * Calling one `nonReentrant` function from + * another is not supported. Instead, you can implement a + * `private` function doing the actual work, and an `external` + * wrapper marked as `nonReentrant`. + */ + modifier nonReentrant() { + guardCounter += 1; + uint256 localCounter = guardCounter; + _; + require(localCounter == guardCounter); + } +} + +contract FeeSharing is Withdrawable, FeeSharingInterface, Utils2, ReentrancyGuard { + + mapping(address=>uint) public walletFeesInBps; // commission (percentage) of a wallet + mapping(address=>uint) public walletFeesToPay; // amount of fee to pay for a wallet + mapping(address=>uint) public feePayedPerWallet; // total fee payed to a wallet + + NetworkInterface public network; + + function FeeBurner( + address _admin, + NetworkInterface _network + ) + public + { + require(_admin != address(0)); + require(_network != address(0)); + network = _network; + admin = _admin; + } + + event SetNewNetworkContract(NetworkInterface _network); + function setNetworkContract(NetworkInterface _network) public onlyAdmin { + require(_network != address(0)); + network = _network; + emit SetNewNetworkContract(_network); + } + + event WalletFeesSet(address wallet, uint feesInBps); + function setWalletFees(address wallet, uint feesInBps) public onlyAdmin { + require(feesInBps < 10000); // under 100% + walletFeesInBps[wallet] = feesInBps; + emit WalletFeesSet(wallet, feesInBps); + } + + event AssignFeeToWallet(address wallet, uint walletFee); + function handleFees(address wallet) + public + nonReentrant + payable + returns(bool) + { + require(msg.sender == address(network)); + require(msg.value <= MAX_QTY); + uint fee = msg.value; + + uint walletFee = fee * walletFeesInBps[wallet] / 10000; + require(fee >= walletFee); + + if (walletFee > 0) { + walletFeesToPay[wallet] += walletFee; + emit AssignFeeToWallet(wallet, walletFee); + } + return true; + } + + event SendWalletFees(address indexed wallet, address sender); + // this function is callable by anyone + function sendFeeToWallet(address wallet) public nonReentrant { + uint feeAmount = walletFeesToPay[wallet]; + require(feeAmount > 0); + + uint thisBalance = address(this).balance; + uint walletBalance = wallet.balance; + + require(thisBalance >= feeAmount); + + walletFeesToPay[wallet] = 0; + wallet.transfer(feeAmount); + feePayedPerWallet[wallet] += feeAmount; + + uint newThisBalance = address(this).balance; + require(newThisBalance == thisBalance - feeAmount); + + uint newWalletBalance = wallet.balance; + require(newWalletBalance == walletBalance + feeAmount); + + emit SendWalletFees(wallet, msg.sender); + } +} diff --git a/contracts/FeeSharingInterface.sol b/contracts/FeeSharingInterface.sol new file mode 100644 index 0000000..1f29327 --- /dev/null +++ b/contracts/FeeSharingInterface.sol @@ -0,0 +1,5 @@ +pragma solidity 0.4.25; + +interface FeeSharingInterface { + function handleFees(address wallet) external payable returns(bool); +} diff --git a/contracts/Network.sol b/contracts/Network.sol index ebcd5bc..f4a7f26 100644 --- a/contracts/Network.sol +++ b/contracts/Network.sol @@ -7,6 +7,7 @@ import "./Withdrawable.sol"; import "./Utils2.sol"; import "./WhiteListInterface.sol"; import "./ExpectedRateInterface.sol"; +import "./FeeSharingInterface.sol"; /** * @title Helps contracts guard against reentrancy attacks. @@ -32,7 +33,6 @@ contract ReentrancyGuard { } - //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Network main contract contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { @@ -52,12 +52,11 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { // only 1 reserve for a token mapping(address=>address) public reservePerTokenFee; mapping(address=>uint) public feeForReserve; - address public feeHolder; + FeeSharingInterface public feeSharing; constructor(address _admin) public { require(_admin != address(0)); admin = _admin; - feeHolder = address(this); } event EtherReceival(address indexed sender, uint amount); @@ -269,20 +268,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { emit NetworkProxySet(networkProxy, msg.sender); } + event FeeSharingSet(address fee, address sender); + function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { + require(_feeSharing != address(0)); + feeSharing = _feeSharing; + emit FeeSharingSet(_feeSharing, msg.sender); + } + /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { return reserves.length; } - event FeeHolderSet(address holder); - function setFeeHolder(address holder) public onlyAdmin { - require(holder != address(0)); - feeHolder = holder; - emit FeeHolderSet(holder); - } - - event FeeForReserveSet(address reserve, uint percent); function setFeePercent(address reserve, uint newPercent) public onlyAdmin { require(isReserve[reserve]); @@ -301,10 +299,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { return maxGasPriceValue; } - function getFeeHolder() public view returns(address) { - return feeHolder; - } - function getExpectedRate(TRC20 src, TRC20 dest, uint srcQty) public view returns(uint expectedRate, uint slippageRate) @@ -542,6 +536,30 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); + uint feeInWei; + uint balanceBefore; + uint balanceAfter; + + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + //not a "fake" trade tomo to tomo + feeInWei = weiAmount * feeForReserve[rateResult.reserve1] / 10000; + balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + + if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + //not a "fake" trade tomo to tomo + feeInWei = weiAmount * feeForReserve[rateResult.reserve2] / 10000; + balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -607,6 +625,16 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { expectedRate, true)); + uint feeInWei = actualDestAmount * feeForReserve[reserve] / 10000; + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeInWei > 0) { + //not a "fake" trade tomo to tomo + uint balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + emit TradeFee(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -662,6 +690,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { bool validate ) internal + nonReentrant returns(bool) { uint callValue = 0; @@ -681,24 +710,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint tomoValue = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; uint feeInWei = tomoValue * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% - // Logics: expected to receive exactly feeInWei amount of TOMO as fee from reserve. - // - If feeHolder and src == TOMO - // + callValue of TOMO will be transfered from network -> reserve - // - If feeHolder is the destAdress and dest token is TOMO - // + expectedDestAmount of TOMO will be transfered from reserve -> destAdress - uint expectedFeeHolderTomoBal = feeHolder.balance; - if (feeHolder == address(this) && src == TOMO_TOKEN_ADDRESS) { + uint expectedNetworkBalance = address(this).balance; + if (src == TOMO_TOKEN_ADDRESS) { // callValue amount of Tomo will be transfered to reserve - require(expectedFeeHolderTomoBal >= callValue); - expectedFeeHolderTomoBal -= callValue; + require(expectedNetworkBalance >= callValue); + expectedNetworkBalance -= callValue; } - if (feeHolder == destAddress && dest == TOMO_TOKEN_ADDRESS) { - // expectedDestAmount of Tomo will be transfered to destAdress (which is also feeHolder) - expectedFeeHolderTomoBal += expectedDestAmount; + if (address(this) == destAddress && dest == TOMO_TOKEN_ADDRESS) { + // expectedDestAmount of Tomo will be transfered to destAdress (which is also network) + expectedNetworkBalance += expectedDestAmount; } // receive feeInWei amount of Tomo as fee - expectedFeeHolderTomoBal += feeInWei; + expectedNetworkBalance += feeInWei; // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, feeInWei, validate), "doReserveTrade: reserve trade failed"); @@ -711,15 +735,9 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { require(dest.transfer(destAddress, expectedDestAmount), "doReserveTrade: transfer token failed"); } } - if (feeHolder != address(this)) { - require(address(this).balance >= feeInWei); - // transfer fee to feeHolder - feeHolder.transfer(feeInWei); - } // Expected to receive exact amount fee in TOMO - require(feeHolder.balance == expectedFeeHolderTomoBal); - + require(address(this).balance == expectedNetworkBalance); return true; } diff --git a/contracts/WrapContracts/WrapExpectedRate.sol b/contracts/WrapContracts/WrapExpectedRate.sol index 573b9ba..abbab8d 100644 --- a/contracts/WrapContracts/WrapExpectedRate.sol +++ b/contracts/WrapContracts/WrapExpectedRate.sol @@ -343,6 +343,9 @@ contract ReentrancyGuard { } } +interface FeeSharingInterface { + function handleFees(address wallet) external payable returns(bool); +} //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Network main contract @@ -363,12 +366,11 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { // only 1 reserve for a token mapping(address=>address) public reservePerTokenFee; mapping(address=>uint) public feeForReserve; - address public feeHolder; + FeeSharingInterface public feeSharing; constructor(address _admin) public { require(_admin != address(0)); admin = _admin; - feeHolder = address(this); } event EtherReceival(address indexed sender, uint amount); @@ -582,20 +584,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { emit NetworkProxySet(networkProxy, msg.sender); } + event FeeSharingSet(address fee, address sender); + function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { + require(_feeSharing != address(0)); + feeSharing = _feeSharing; + emit FeeSharingSet(_feeSharing, msg.sender); + } + /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { return reserves.length; } - event FeeHolderSet(address holder); - function setFeeHolder(address holder) public onlyAdmin { - require(holder != address(0)); - feeHolder = holder; - emit FeeHolderSet(holder); - } - - event FeeForReserveSet(address reserve, uint percent); function setFeePercent(address reserve, uint newPercent) public onlyAdmin { require(isReserve[reserve]); @@ -614,10 +615,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { return maxGasPriceValue; } - function getFeeHolder() public view returns(address) { - return feeHolder; - } - function getExpectedRate(TRC20 src, TRC20 dest, uint srcQty) public view returns(uint expectedRate, uint slippageRate) @@ -854,6 +851,30 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); + uint feeInWei; + uint balanceBefore; + uint balanceAfter; + + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + //not a "fake" trade tomo to tomo + feeInWei = weiAmount * feeForReserve[rateResult.reserve1] / 10000; + balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + + if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + //not a "fake" trade tomo to tomo + feeInWei = weiAmount * feeForReserve[rateResult.reserve2] / 10000; + balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -919,6 +940,16 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { expectedRate, true)); + uint feeInWei = actualDestAmount * feeForReserve[reserve] / 10000; + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeInWei > 0) { + //not a "fake" trade tomo to tomo + uint balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + emit TradeFee(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -974,6 +1005,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { bool validate ) internal + nonReentrant returns(bool) { uint callValue = 0; @@ -993,24 +1025,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint tomoValue = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; uint feeInWei = tomoValue * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% - // Logics: expected to receive exactly feeInWei amount of TOMO as fee from reserve. - // - If feeHolder and src == TOMO - // + callValue of TOMO will be transfered from network -> reserve - // - If feeHolder is the destAdress and dest token is TOMO - // + expectedDestAmount of TOMO will be transfered from reserve -> destAdress - uint expectedFeeHolderTomoBal = feeHolder.balance; - if (feeHolder == address(this) && src == TOMO_TOKEN_ADDRESS) { + uint expectedNetworkBalance = address(this).balance; + if (src == TOMO_TOKEN_ADDRESS) { // callValue amount of Tomo will be transfered to reserve - require(expectedFeeHolderTomoBal >= callValue); - expectedFeeHolderTomoBal -= callValue; + require(expectedNetworkBalance >= callValue); + expectedNetworkBalance -= callValue; } - if (feeHolder == destAddress && dest == TOMO_TOKEN_ADDRESS) { - // expectedDestAmount of Tomo will be transfered to destAdress (which is also feeHolder) - expectedFeeHolderTomoBal += expectedDestAmount; + if (address(this) == destAddress && dest == TOMO_TOKEN_ADDRESS) { + // expectedDestAmount of Tomo will be transfered to destAdress (which is also network) + expectedNetworkBalance += expectedDestAmount; } // receive feeInWei amount of Tomo as fee - expectedFeeHolderTomoBal += feeInWei; + expectedNetworkBalance += feeInWei; // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, feeInWei, validate), "doReserveTrade: reserve trade failed"); @@ -1023,15 +1050,9 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { require(dest.transfer(destAddress, expectedDestAmount), "doReserveTrade: transfer token failed"); } } - if (feeHolder != address(this)) { - require(address(this).balance >= feeInWei); - // transfer fee to feeHolder - feeHolder.transfer(feeInWei); - } // Expected to receive exact amount fee in TOMO - require(feeHolder.balance == expectedFeeHolderTomoBal); - + require(address(this).balance == expectedNetworkBalance); return true; } @@ -1065,6 +1086,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } } + contract ExpectedRate is Withdrawable, ExpectedRateInterface, Utils2 { Network public network; diff --git a/contracts/WrapContracts/WrapFeeSharing.sol b/contracts/WrapContracts/WrapFeeSharing.sol new file mode 100644 index 0000000..007f7ef --- /dev/null +++ b/contracts/WrapContracts/WrapFeeSharing.sol @@ -0,0 +1,419 @@ +pragma solidity 0.4.25; + +// https://github.com/ethereum/EIPs/issues/20 +interface TRC20 { + function totalSupply() external view returns (uint supply); + function balanceOf(address _owner) external view returns (uint balance); + function transfer(address _to, uint _value) external returns (bool success); + function transferFrom(address _from, address _to, uint _value) external returns (bool success); + function approve(address _spender, uint _value) external returns (bool success); + function allowance(address _owner, address _spender) external view returns (uint remaining); + function decimals() external view returns(uint digits); + event Approval(address indexed _owner, address indexed _spender, uint _value); +} + +interface FeeSharingInterface { + function handleFees(address wallet) external payable returns(bool); +} + +/// @title Network interface +interface NetworkProxyInterface { + function maxGasPrice() external view returns(uint); + function getUserCapInWei(address user) external view returns(uint); + function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); + function enabled() external view returns(bool); + function info(bytes32 id) external view returns(uint); + + function getExpectedRate(TRC20 src, TRC20 dest, uint srcQty) external view + returns (uint expectedRate, uint slippageRate); + function getExpectedFeeRate(TRC20 token, uint srcQty) external view + returns (uint expectedRate, uint slippageRate); + + function swap(TRC20 src, uint srcAmount, TRC20 dest, address destAddress, uint maxDestAmount, + uint minConversionRate, address walletId) external payable returns(uint); + function payTxFee(TRC20 src, uint srcAmount, address destAddress, uint maxDestAmount, + uint minConversionRate) external payable returns(uint); + function payTxFeeFast(TRC20 src, uint srcAmount, address destAddress) external payable returns(uint); +} + +/// @title Kyber Network interface +interface NetworkInterface { + function maxGasPrice() external view returns(uint); + function getFeeHolder() external view returns(address); + function getUserCapInWei(address user) external view returns(uint); + function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); + function enabled() external view returns(bool); + function info(bytes32 id) external view returns(uint); + + function getExpectedRate(TRC20 src, TRC20 dest, uint srcQty) external view + returns (uint expectedRate, uint slippageRate); + function getExpectedFeeRate(TRC20 token, uint srcQty) external view + returns (uint expectedRate, uint slippageRate); + + function swap(address trader, TRC20 src, uint srcAmount, TRC20 dest, address destAddress, + uint maxDestAmount, uint minConversionRate, address walletId) external payable returns(uint); + function payTxFee(address trader, TRC20 src, uint srcAmount, address destAddress, + uint maxDestAmount, uint minConversionRate) external payable returns(uint); +} + + +/// @title constants contract +contract Utils { + + TRC20 constant internal TOMO_TOKEN_ADDRESS = TRC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); + uint constant internal PRECISION = (10**18); + uint constant internal MAX_QTY = (10**28); // 10B tokens + uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH + uint constant internal MAX_DECIMALS = 18; + uint constant internal TOMO_DECIMALS = 18; + mapping(address=>uint) internal decimals; + + function setDecimals(TRC20 token) internal { + if (token == TOMO_TOKEN_ADDRESS) decimals[token] = TOMO_DECIMALS; + else decimals[token] = token.decimals(); + } + + function getDecimals(TRC20 token) internal view returns(uint) { + if (token == TOMO_TOKEN_ADDRESS) return TOMO_DECIMALS; // save storage access + uint tokenDecimals = decimals[token]; + // technically, there might be token with decimals 0 + // moreover, very possible that old tokens have decimals 0 + // these tokens will just have higher gas fees. + if(tokenDecimals == 0) return token.decimals(); + + return tokenDecimals; + } + + function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { + require(srcQty <= MAX_QTY); + require(rate <= MAX_RATE); + + if (dstDecimals >= srcDecimals) { + require((dstDecimals - srcDecimals) <= MAX_DECIMALS); + return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; + } else { + require((srcDecimals - dstDecimals) <= MAX_DECIMALS); + return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); + } + } + + function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { + require(dstQty <= MAX_QTY); + require(rate <= MAX_RATE); + + //source quantity is rounded up. to avoid dest quantity being too low. + uint numerator; + uint denominator; + if (srcDecimals >= dstDecimals) { + require((srcDecimals - dstDecimals) <= MAX_DECIMALS); + numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); + denominator = rate; + } else { + require((dstDecimals - srcDecimals) <= MAX_DECIMALS); + numerator = (PRECISION * dstQty); + denominator = (rate * (10**(dstDecimals - srcDecimals))); + } + return (numerator + denominator - 1) / denominator; //avoid rounding down errors + } +} + + +contract Utils2 is Utils { + + /// @dev get the balance of a user. + /// @param token The token type + /// @return The balance + function getBalance(TRC20 token, address user) public view returns(uint) { + if (token == TOMO_TOKEN_ADDRESS) + return user.balance; + else + return token.balanceOf(user); + } + + function getDecimalsSafe(TRC20 token) internal returns(uint) { + + if (decimals[token] == 0) { + setDecimals(token); + } + + return decimals[token]; + } + + function calcDestAmount(TRC20 src, TRC20 dest, uint srcAmount, uint rate) internal view returns(uint) { + return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate); + } + + function calcSrcAmount(TRC20 src, TRC20 dest, uint destAmount, uint rate) internal view returns(uint) { + return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate); + } + + function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals) + internal pure returns(uint) + { + require(srcAmount <= MAX_QTY); + require(destAmount <= MAX_QTY); + + if (dstDecimals >= srcDecimals) { + require((dstDecimals - srcDecimals) <= MAX_DECIMALS); + return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount)); + } else { + require((srcDecimals - dstDecimals) <= MAX_DECIMALS); + return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount); + } + } +} + +contract PermissionGroups { + + address public admin; + address public pendingAdmin; + mapping(address=>bool) internal operators; + mapping(address=>bool) internal alerters; + address[] internal operatorsGroup; + address[] internal alertersGroup; + uint constant internal MAX_GROUP_SIZE = 50; + + constructor() public { + admin = msg.sender; + } + + modifier onlyAdmin() { + require(msg.sender == admin); + _; + } + + modifier onlyOperator() { + require(operators[msg.sender]); + _; + } + + modifier onlyAlerter() { + require(alerters[msg.sender]); + _; + } + + function getOperators () external view returns(address[] memory) { + return operatorsGroup; + } + + function getAlerters () external view returns(address[] memory) { + return alertersGroup; + } + + event TransferAdminPending(address pendingAdmin); + + /** + * @dev Allows the current admin to set the pendingAdmin address. + * @param newAdmin The address to transfer ownership to. + */ + function transferAdmin(address newAdmin) public onlyAdmin { + require(newAdmin != address(0)); + emit TransferAdminPending(pendingAdmin); + pendingAdmin = newAdmin; + } + + /** + * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. + * @param newAdmin The address to transfer ownership to. + */ + function transferAdminQuickly(address newAdmin) public onlyAdmin { + require(newAdmin != address(0)); + emit TransferAdminPending(newAdmin); + emit AdminClaimed(newAdmin, admin); + admin = newAdmin; + } + + event AdminClaimed( address newAdmin, address previousAdmin); + + /** + * @dev Allows the pendingAdmin address to finalize the change admin process. + */ + function claimAdmin() public { + require(pendingAdmin == msg.sender); + emit AdminClaimed(pendingAdmin, admin); + admin = pendingAdmin; + pendingAdmin = address(0); + } + + event AlerterAdded (address newAlerter, bool isAdd); + + function addAlerter(address newAlerter) public onlyAdmin { + require(!alerters[newAlerter]); // prevent duplicates. + require(alertersGroup.length < MAX_GROUP_SIZE); + + emit AlerterAdded(newAlerter, true); + alerters[newAlerter] = true; + alertersGroup.push(newAlerter); + } + + function removeAlerter (address alerter) public onlyAdmin { + require(alerters[alerter]); + alerters[alerter] = false; + + for (uint i = 0; i < alertersGroup.length; ++i) { + if (alertersGroup[i] == alerter) { + alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; + alertersGroup.length--; + emit AlerterAdded(alerter, false); + break; + } + } + } + + event OperatorAdded(address newOperator, bool isAdd); + + function addOperator(address newOperator) public onlyAdmin { + require(!operators[newOperator]); // prevent duplicates. + require(operatorsGroup.length < MAX_GROUP_SIZE); + + emit OperatorAdded(newOperator, true); + operators[newOperator] = true; + operatorsGroup.push(newOperator); + } + + function removeOperator (address operator) public onlyAdmin { + require(operators[operator]); + operators[operator] = false; + + for (uint i = 0; i < operatorsGroup.length; ++i) { + if (operatorsGroup[i] == operator) { + operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; + operatorsGroup.length -= 1; + emit OperatorAdded(operator, false); + break; + } + } + } +} + + +/** + * @title Contracts that should be able to recover tokens or ethers + */ +contract Withdrawable is PermissionGroups { + + event TokenWithdraw(TRC20 token, uint amount, address sendTo); + + /** + * @dev Withdraw all TRC20 compatible tokens + * @param token TRC20 The address of the token contract + */ + function withdrawToken(TRC20 token, uint amount, address sendTo) external onlyAdmin { + require(token.transfer(sendTo, amount)); + emit TokenWithdraw(token, amount, sendTo); + } + + event EtherWithdraw(uint amount, address sendTo); + + /** + * @dev Withdraw Ethers + */ + function withdrawEther(uint amount, address sendTo) external onlyAdmin { + sendTo.transfer(amount); + emit EtherWithdraw(amount, sendTo); + } +} + + +/** + * @title Helps contracts guard against reentrancy attacks. + */ +contract ReentrancyGuard { + + /// @dev counter to allow mutex lock with only one SSTORE operation + uint256 private guardCounter = 1; + + /** + * @dev Prevents a function from calling itself, directly or indirectly. + * Calling one `nonReentrant` function from + * another is not supported. Instead, you can implement a + * `private` function doing the actual work, and an `external` + * wrapper marked as `nonReentrant`. + */ + modifier nonReentrant() { + guardCounter += 1; + uint256 localCounter = guardCounter; + _; + require(localCounter == guardCounter); + } +} + +contract FeeSharing is Withdrawable, FeeSharingInterface, Utils2, ReentrancyGuard { + + mapping(address=>uint) public walletFeesInBps; // commission (percentage) of a wallet + mapping(address=>uint) public walletFeesToPay; // amount of fee to pay for a wallet + mapping(address=>uint) public feePayedPerWallet; // total fee payed to a wallet + + NetworkInterface public network; + + function FeeBurner( + address _admin, + NetworkInterface _network + ) + public + { + require(_admin != address(0)); + require(_network != address(0)); + network = _network; + admin = _admin; + } + + event SetNewNetworkContract(NetworkInterface _network); + function setNetworkContract(NetworkInterface _network) public onlyAdmin { + require(_network != address(0)); + network = _network; + emit SetNewNetworkContract(_network); + } + + event WalletFeesSet(address wallet, uint feesInBps); + function setWalletFees(address wallet, uint feesInBps) public onlyAdmin { + require(feesInBps < 10000); // under 100% + walletFeesInBps[wallet] = feesInBps; + emit WalletFeesSet(wallet, feesInBps); + } + + event AssignFeeToWallet(address wallet, uint walletFee); + function handleFees(address wallet) + public + nonReentrant + payable + returns(bool) + { + require(msg.sender == address(network)); + require(msg.value <= MAX_QTY); + uint fee = msg.value; + + uint walletFee = fee * walletFeesInBps[wallet] / 10000; + require(fee >= walletFee); + + if (walletFee > 0) { + walletFeesToPay[wallet] += walletFee; + emit AssignFeeToWallet(wallet, walletFee); + } + return true; + } + + event SendWalletFees(address indexed wallet, address sender); + // this function is callable by anyone + function sendFeeToWallet(address wallet) public nonReentrant { + uint feeAmount = walletFeesToPay[wallet]; + require(feeAmount > 0); + + uint thisBalance = address(this).balance; + uint walletBalance = wallet.balance; + + require(thisBalance >= feeAmount); + + walletFeesToPay[wallet] = 0; + wallet.transfer(feeAmount); + feePayedPerWallet[wallet] += feeAmount; + + uint newThisBalance = address(this).balance; + require(newThisBalance == thisBalance - feeAmount); + + uint newWalletBalance = wallet.balance; + require(newWalletBalance == walletBalance + feeAmount); + + emit SendWalletFees(wallet, msg.sender); + } +} diff --git a/contracts/WrapContracts/WrapNetwork.sol b/contracts/WrapContracts/WrapNetwork.sol index 01964d9..0eb33b0 100644 --- a/contracts/WrapContracts/WrapNetwork.sol +++ b/contracts/WrapContracts/WrapNetwork.sol @@ -343,6 +343,10 @@ contract ReentrancyGuard { } +interface FeeSharingInterface { + function handleFees(address wallet) external payable returns(bool); +} + //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Network main contract contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { @@ -362,12 +366,11 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { // only 1 reserve for a token mapping(address=>address) public reservePerTokenFee; mapping(address=>uint) public feeForReserve; - address public feeHolder; + FeeSharingInterface public feeSharing; constructor(address _admin) public { require(_admin != address(0)); admin = _admin; - feeHolder = address(this); } event EtherReceival(address indexed sender, uint amount); @@ -581,20 +584,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { emit NetworkProxySet(networkProxy, msg.sender); } + event FeeSharingSet(address fee, address sender); + function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { + require(_feeSharing != address(0)); + feeSharing = _feeSharing; + emit FeeSharingSet(_feeSharing, msg.sender); + } + /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { return reserves.length; } - event FeeHolderSet(address holder); - function setFeeHolder(address holder) public onlyAdmin { - require(holder != address(0)); - feeHolder = holder; - emit FeeHolderSet(holder); - } - - event FeeForReserveSet(address reserve, uint percent); function setFeePercent(address reserve, uint newPercent) public onlyAdmin { require(isReserve[reserve]); @@ -613,10 +615,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { return maxGasPriceValue; } - function getFeeHolder() public view returns(address) { - return feeHolder; - } - function getExpectedRate(TRC20 src, TRC20 dest, uint srcQty) public view returns(uint expectedRate, uint slippageRate) @@ -853,6 +851,30 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); + uint feeInWei; + uint balanceBefore; + uint balanceAfter; + + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + //not a "fake" trade tomo to tomo + feeInWei = weiAmount * feeForReserve[rateResult.reserve1] / 10000; + balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + + if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + //not a "fake" trade tomo to tomo + feeInWei = weiAmount * feeForReserve[rateResult.reserve2] / 10000; + balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -918,6 +940,16 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { expectedRate, true)); + uint feeInWei = actualDestAmount * feeForReserve[reserve] / 10000; + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeInWei > 0) { + //not a "fake" trade tomo to tomo + uint balanceBefore = address(this).balance; + require(balanceBefore >= feeInWei); + require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - feeInWei); + } + emit TradeFee(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -973,6 +1005,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { bool validate ) internal + nonReentrant returns(bool) { uint callValue = 0; @@ -992,24 +1025,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint tomoValue = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; uint feeInWei = tomoValue * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% - // Logics: expected to receive exactly feeInWei amount of TOMO as fee from reserve. - // - If feeHolder and src == TOMO - // + callValue of TOMO will be transfered from network -> reserve - // - If feeHolder is the destAdress and dest token is TOMO - // + expectedDestAmount of TOMO will be transfered from reserve -> destAdress - uint expectedFeeHolderTomoBal = feeHolder.balance; - if (feeHolder == address(this) && src == TOMO_TOKEN_ADDRESS) { + uint expectedNetworkBalance = address(this).balance; + if (src == TOMO_TOKEN_ADDRESS) { // callValue amount of Tomo will be transfered to reserve - require(expectedFeeHolderTomoBal >= callValue); - expectedFeeHolderTomoBal -= callValue; + require(expectedNetworkBalance >= callValue); + expectedNetworkBalance -= callValue; } - if (feeHolder == destAddress && dest == TOMO_TOKEN_ADDRESS) { - // expectedDestAmount of Tomo will be transfered to destAdress (which is also feeHolder) - expectedFeeHolderTomoBal += expectedDestAmount; + if (address(this) == destAddress && dest == TOMO_TOKEN_ADDRESS) { + // expectedDestAmount of Tomo will be transfered to destAdress (which is also network) + expectedNetworkBalance += expectedDestAmount; } // receive feeInWei amount of Tomo as fee - expectedFeeHolderTomoBal += feeInWei; + expectedNetworkBalance += feeInWei; // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, feeInWei, validate), "doReserveTrade: reserve trade failed"); @@ -1022,15 +1050,9 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { require(dest.transfer(destAddress, expectedDestAmount), "doReserveTrade: transfer token failed"); } } - if (feeHolder != address(this)) { - require(address(this).balance >= feeInWei); - // transfer fee to feeHolder - feeHolder.transfer(feeInWei); - } // Expected to receive exact amount fee in TOMO - require(feeHolder.balance == expectedFeeHolderTomoBal); - + require(address(this).balance == expectedNetworkBalance); return true; } From 9c798e3db5b1439cf7a4b4475b90f25e184e6897 Mon Sep 17 00:00:00 2001 From: manhlx Date: Wed, 3 Apr 2019 17:13:49 +0700 Subject: [PATCH 3/6] Fix sharing fee --- contracts/FeeSharing.sol | 15 +- contracts/Network.sol | 46 +- contracts/NetworkInterface.sol | 1 - contracts/NetworkProxy.sol | 2 - contracts/SimpleConversionRates.sol | 292 ----------- contracts/WrapContracts/WrapExpectedRate.sol | 43 +- contracts/WrapContracts/WrapFeeSharing.sol | 16 +- contracts/WrapContracts/WrapNetwork.sol | 42 +- contracts/WrapContracts/WrapNetworkProxy.sol | 3 - contracts/WrapContracts/WrapReserve.sol | 1 - contracts/mockContracts/GenerousNetwork.sol | 21 + contracts/mockContracts/MaliciousNetwork.sol | 26 +- contracts/mockContracts/MaliciousNetwork2.sol | 6 - test/network.js | 464 +++++++++--------- 14 files changed, 343 insertions(+), 635 deletions(-) delete mode 100644 contracts/SimpleConversionRates.sol diff --git a/contracts/FeeSharing.sol b/contracts/FeeSharing.sol index a099f1c..de3ab1a 100644 --- a/contracts/FeeSharing.sol +++ b/contracts/FeeSharing.sol @@ -37,16 +37,11 @@ contract FeeSharing is Withdrawable, FeeSharingInterface, Utils2, ReentrancyGuar NetworkInterface public network; - function FeeBurner( - address _admin, - NetworkInterface _network - ) - public - { - require(_admin != address(0)); - require(_network != address(0)); - network = _network; - admin = _admin; + constructor(address _admin, NetworkInterface _network) public { + require(_admin != address(0)); + require(_network != address(0)); + network = _network; + admin = _admin; } event SetNewNetworkContract(NetworkInterface _network); diff --git a/contracts/Network.sol b/contracts/Network.sol index f4a7f26..ec71b9f 100644 --- a/contracts/Network.sol +++ b/contracts/Network.sol @@ -536,29 +536,25 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); - uint feeInWei; - uint balanceBefore; - uint balanceAfter; - - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - //not a "fake" trade tomo to tomo - feeInWei = weiAmount * feeForReserve[rateResult.reserve1] / 10000; - balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); - } - - if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - //not a "fake" trade tomo to tomo - feeInWei = weiAmount * feeForReserve[rateResult.reserve2] / 10000; - balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); - } + // uint totalFeeInWei = 0; + // + // if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + // //not a "fake" trade tomo to tomo + // totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; + // } + // + // if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + // //not a "fake" trade tomo to tomo + // totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; + // } + // + // if (totalFeeInWei > 0) { + // uint balanceBefore = address(this).balance; + // require(balanceBefore >= totalFeeInWei); + // require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); + // uint balanceAfter = address(this).balance; + // require(balanceAfter == balanceBefore - totalFeeInWei); + // } emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -707,8 +703,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // calculate expected fee for this transaction based on amount of Tomo - uint tomoValue = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; - uint feeInWei = tomoValue * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% + // feePercent = 25 -> fee = 25/10000 = 0.25% + uint feeInWei = (src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount) * feeForReserve[reserve] / 10000; uint expectedNetworkBalance = address(this).balance; if (src == TOMO_TOKEN_ADDRESS) { diff --git a/contracts/NetworkInterface.sol b/contracts/NetworkInterface.sol index e95cc13..6173e74 100644 --- a/contracts/NetworkInterface.sol +++ b/contracts/NetworkInterface.sol @@ -6,7 +6,6 @@ import "./TRC20Interface.sol"; /// @title Kyber Network interface interface NetworkInterface { function maxGasPrice() external view returns(uint); - function getFeeHolder() external view returns(address); function getUserCapInWei(address user) external view returns(uint); function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); function enabled() external view returns(bool); diff --git a/contracts/NetworkProxy.sol b/contracts/NetworkProxy.sol index fadf60d..ca6d798 100644 --- a/contracts/NetworkProxy.sol +++ b/contracts/NetworkProxy.sol @@ -88,7 +88,6 @@ contract NetworkProxy is NetworkProxyInterface, SimpleNetworkInterface, Withdraw { require(src == TOMO_TOKEN_ADDRESS || msg.value == 0); require(payFeeCallers[msg.sender] == true, "payTxFee: Sender is not callable this function"); - require(msg.sender != networkContract.getFeeHolder()); TRC20 dest = TOMO_TOKEN_ADDRESS; UserBalance memory userBalanceBefore; @@ -235,7 +234,6 @@ contract NetworkProxy is NetworkProxyInterface, SimpleNetworkInterface, Withdraw returns(uint) { require(src == TOMO_TOKEN_ADDRESS || msg.value == 0); - require(msg.sender != networkContract.getFeeHolder()); UserBalance memory userBalanceBefore; diff --git a/contracts/SimpleConversionRates.sol b/contracts/SimpleConversionRates.sol deleted file mode 100644 index d29e084..0000000 --- a/contracts/SimpleConversionRates.sol +++ /dev/null @@ -1,292 +0,0 @@ -pragma solidity 0.4.25; - - -import "./TRC20Interface.sol"; -import "./VolumeImbalanceRecorder.sol"; -import "./Utils.sol"; -import "./ConversionRatesInterface.sol"; - -contract SimpleConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils { - - // bps - basic rate steps. one step is 1 / 10000 of the rate. - struct StepFunction { - int[] x; // quantity for each step. Quantity of each step includes previous steps. - int[] y; // rate change per quantity step in bps. - } - - struct TokenData { - bool listed; // was added to reserve - bool enabled; // whether trade is enabled - uint lastUpdatedBlock; - - // rate data. base and changes according to quantity and reserve balance. - // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction. - uint baseBuyRate; // in PRECISION units. see KyberConstants - uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate - StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate. - StepFunction sellRateQtyStepFunction;// in bps. higher the qua - StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate. - StepFunction sellRateImbalanceStepFunction; - } - - uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks - TRC20[] internal listedTokens; - mapping(address=>TokenData) internal tokenData; - address public reserveContract; - uint constant internal MAX_STEPS_IN_FUNCTION = 10; - int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B % - int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100% - - constructor(address _admin) public VolumeImbalanceRecorder(_admin) - { } // solhint-disable-line no-empty-blocks - - function addToken(TRC20 token) public onlyAdmin { - - require(!tokenData[token].listed); - tokenData[token].listed = true; - listedTokens.push(token); - - setGarbageToVolumeRecorder(token); - - setDecimals(token); - } - - function setBaseRates( - TRC20[] memory tokens, - uint[] memory baseBuy, - uint[] memory baseSell, - uint blockNumber - ) - public - onlyOperator - { - require(tokens.length == baseBuy.length); - require(tokens.length == baseSell.length); - - for (uint ind = 0; ind < tokens.length; ind++) { - require(tokenData[tokens[ind]].listed); - tokenData[tokens[ind]].baseBuyRate = baseBuy[ind]; - tokenData[tokens[ind]].baseSellRate = baseSell[ind]; - tokenData[tokens[ind]].lastUpdatedBlock = blockNumber; - } - } - - function setQtyStepFunction( - TRC20 token, - int[] memory xBuy, - int[] memory yBuy, - int[] memory xSell, - int[] memory ySell - ) - public - onlyOperator - { - require(xBuy.length == yBuy.length); - require(xSell.length == ySell.length); - require(xBuy.length <= MAX_STEPS_IN_FUNCTION); - require(xSell.length <= MAX_STEPS_IN_FUNCTION); - require(tokenData[token].listed); - - tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy); - tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell); - } - - function setImbalanceStepFunction( - TRC20 token, - int[] memory xBuy, - int[] memory yBuy, - int[] memory xSell, - int[] memory ySell - ) - public - onlyOperator - { - require(xBuy.length == yBuy.length); - require(xSell.length == ySell.length); - require(xBuy.length <= MAX_STEPS_IN_FUNCTION); - require(xSell.length <= MAX_STEPS_IN_FUNCTION); - require(tokenData[token].listed); - - tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy); - tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell); - } - - function setValidRateDurationInBlocks(uint duration) public onlyAdmin { - validRateDurationInBlocks = duration; - } - - function enableTokenTrade(TRC20 token) public onlyAdmin { - require(tokenData[token].listed); - require(tokenControlInfo[token].minimalRecordResolution != 0); - tokenData[token].enabled = true; - } - - function disableTokenTrade(TRC20 token) public onlyAlerter { - require(tokenData[token].listed); - tokenData[token].enabled = false; - } - - function setReserveAddress(address reserve) public onlyAdmin { - reserveContract = reserve; - } - - function recordImbalance( - TRC20 token, - int buyAmount, - uint rateUpdateBlock, - uint currentBlock - ) - public - { - require(msg.sender == reserveContract); - - if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token); - - return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock); - } - - /* solhint-disable function-max-lines */ - function getRate(TRC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) { - // check if trade is enabled - if (!tokenData[token].enabled) return 0; - if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set - - uint updateRateBlock = tokenData[token].lastUpdatedBlock; - if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired - // check imbalance - int totalImbalance; - int blockImbalance; - (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber); - - // calculate actual rate - int imbalanceQty; - int extraBps; - uint rate; - - if (buy) { - // start with base rate - rate = tokenData[token].baseBuyRate; - - // compute token qty - qty = getTokenQty(token, rate, qty); - imbalanceQty = int(qty); - totalImbalance += imbalanceQty; - - // add qty overhead - extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty)); - rate = addBps(rate, extraBps); - - // add imbalance overhead - extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance); - rate = addBps(rate, extraBps); - } else { - // start with base rate - rate = tokenData[token].baseSellRate; - - // compute token qty - imbalanceQty = -1 * int(qty); - totalImbalance += imbalanceQty; - - // add qty overhead - extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty)); - rate = addBps(rate, extraBps); - - // add imbalance overhead - extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance); - rate = addBps(rate, extraBps); - } - - if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0; - if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0; - - return rate; - } - /* solhint-enable function-max-lines */ - - function getBasicRate(TRC20 token, bool buy) public view returns(uint) { - if (buy) - return tokenData[token].baseBuyRate; - else - return tokenData[token].baseSellRate; - } - - function getTokenBasicData(TRC20 token) public view returns(bool, bool) { - return (tokenData[token].listed, tokenData[token].enabled); - } - - /* solhint-disable code-complexity */ - function getStepFunctionData(TRC20 token, uint command, uint param) public view returns(int) { - if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length); - if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param]; - if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length); - if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param]; - - if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length); - if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param]; - if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length); - if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param]; - - if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length); - if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param]; - if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length); - if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param]; - - if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length); - if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param]; - if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length); - if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param]; - - revert(); - } - /* solhint-enable code-complexity */ - - function getRateUpdateBlock(TRC20 token) public view returns(uint) { - require(tokenData[token].listed); - return tokenData[token].lastUpdatedBlock; - } - - function getListedTokensAtIndex(uint id) public view returns(TRC20) { - require(id < listedTokens.length); - return listedTokens[id]; - } - - function getListedTokens() public view returns(TRC20[] memory) { - return listedTokens; - } - - function getNumListedTokens() public view returns(uint) { - return listedTokens.length; - } - - function getTokenQty(TRC20 token, uint tomoQty, uint rate) internal view returns(uint) { - uint dstDecimals = getDecimals(token); - uint srcDecimals = TOMO_DECIMALS; - - return calcDstQty(tomoQty, srcDecimals, dstDecimals, rate); - } - - function executeStepFunction(StepFunction memory f, int x) internal pure returns(int) { - uint len = f.y.length; - for (uint ind = 0; ind < len; ind++) { - if (x <= f.x[ind]) return f.y[ind]; - } - - return f.y[len-1]; - } - - function addBps(uint rate, int bps) internal pure returns(uint) { - require(rate <= MAX_RATE); - require(bps >= MIN_BPS_ADJUSTMENT); - require(bps <= MAX_BPS_ADJUSTMENT); - - uint maxBps = 100 * 100; - return (rate * uint(int(maxBps) + bps)) / maxBps; - } - - function abs(int x) internal pure returns(uint) { - if (x < 0) - return uint(-1 * x); - else - return uint(x); - } -} diff --git a/contracts/WrapContracts/WrapExpectedRate.sol b/contracts/WrapContracts/WrapExpectedRate.sol index abbab8d..93418e4 100644 --- a/contracts/WrapContracts/WrapExpectedRate.sol +++ b/contracts/WrapContracts/WrapExpectedRate.sol @@ -42,7 +42,6 @@ interface ReserveInterface { /// @title Kyber Network interface interface NetworkInterface { function maxGasPrice() external view returns(uint); - function getFeeHolder() external view returns(address); function getUserCapInWei(address user) external view returns(uint); function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); function enabled() external view returns(bool); @@ -529,9 +528,9 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (tokenToTomo) { listPairs(reserve, token, true, add); if (add) { - token.approve(reserve, 2**255); // approve infinity + require(token.approve(reserve, 2**255)); // approve infinity } else { - token.approve(reserve, 0); + require(token.approve(reserve, 0)); } emit ListReservePairs(reserve, token, TOMO_TOKEN_ADDRESS, add); @@ -541,7 +540,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } function setWhiteList(WhiteListInterface whiteList) public onlyAdmin { - require(whiteList != address(0)); whiteListContract = whiteList; } @@ -565,7 +563,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { function setEnable(bool _enable) public onlyAdmin { if (_enable) { - require(whiteListContract != address(0)); require(expectedRateContract != address(0)); require(networkProxyContract != address(0)); } @@ -633,6 +630,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } function getUserCapInWei(address user) public view returns(uint) { + if (whiteListContract == address(0)) { return 2**255; } return whiteListContract.getUserCapInWei(user); } @@ -821,7 +819,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (tradeInput.src == TOMO_TOKEN_ADDRESS) { tradeInput.trader.transfer(tradeInput.srcAmount - actualSrcAmount); } else { - tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); + require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); } } @@ -851,28 +849,24 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); - uint feeInWei; - uint balanceBefore; - uint balanceAfter; + uint totalFeeInWei = 0; if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { //not a "fake" trade tomo to tomo - feeInWei = weiAmount * feeForReserve[rateResult.reserve1] / 10000; - balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; } if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { //not a "fake" trade tomo to tomo - feeInWei = weiAmount * feeForReserve[rateResult.reserve2] / 10000; - balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; + } + + if (totalFeeInWei > 0) { + uint balanceBefore = address(this).balance; + require(balanceBefore >= totalFeeInWei); + require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - totalFeeInWei); } emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, @@ -923,7 +917,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (actualSrcAmount < tradeInput.srcAmount) { // if there is "change" send back to trader - tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); + require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); } // verify trade size is smaller than user cap, dest is always TOMO @@ -1022,8 +1016,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // calculate expected fee for this transaction based on amount of Tomo - uint tomoValue = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; - uint feeInWei = tomoValue * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% + uint feeInWei = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; + feeInWei = feeInWei * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% uint expectedNetworkBalance = address(this).balance; if (src == TOMO_TOKEN_ADDRESS) { @@ -1086,7 +1080,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } } - contract ExpectedRate is Withdrawable, ExpectedRateInterface, Utils2 { Network public network; diff --git a/contracts/WrapContracts/WrapFeeSharing.sol b/contracts/WrapContracts/WrapFeeSharing.sol index 007f7ef..f23084a 100644 --- a/contracts/WrapContracts/WrapFeeSharing.sol +++ b/contracts/WrapContracts/WrapFeeSharing.sol @@ -39,7 +39,6 @@ interface NetworkProxyInterface { /// @title Kyber Network interface interface NetworkInterface { function maxGasPrice() external view returns(uint); - function getFeeHolder() external view returns(address); function getUserCapInWei(address user) external view returns(uint); function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); function enabled() external view returns(bool); @@ -346,16 +345,11 @@ contract FeeSharing is Withdrawable, FeeSharingInterface, Utils2, ReentrancyGuar NetworkInterface public network; - function FeeBurner( - address _admin, - NetworkInterface _network - ) - public - { - require(_admin != address(0)); - require(_network != address(0)); - network = _network; - admin = _admin; + constructor(address _admin, NetworkInterface _network) public { + require(_admin != address(0)); + require(_network != address(0)); + network = _network; + admin = _admin; } event SetNewNetworkContract(NetworkInterface _network); diff --git a/contracts/WrapContracts/WrapNetwork.sol b/contracts/WrapContracts/WrapNetwork.sol index 0eb33b0..d10bedb 100644 --- a/contracts/WrapContracts/WrapNetwork.sol +++ b/contracts/WrapContracts/WrapNetwork.sol @@ -34,7 +34,6 @@ interface ReserveInterface { /// @title Kyber Network interface interface NetworkInterface { function maxGasPrice() external view returns(uint); - function getFeeHolder() external view returns(address); function getUserCapInWei(address user) external view returns(uint); function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); function enabled() external view returns(bool); @@ -529,9 +528,9 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (tokenToTomo) { listPairs(reserve, token, true, add); if (add) { - token.approve(reserve, 2**255); // approve infinity + require(token.approve(reserve, 2**255)); // approve infinity } else { - token.approve(reserve, 0); + require(token.approve(reserve, 0)); } emit ListReservePairs(reserve, token, TOMO_TOKEN_ADDRESS, add); @@ -541,7 +540,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } function setWhiteList(WhiteListInterface whiteList) public onlyAdmin { - require(whiteList != address(0)); whiteListContract = whiteList; } @@ -565,7 +563,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { function setEnable(bool _enable) public onlyAdmin { if (_enable) { - require(whiteListContract != address(0)); require(expectedRateContract != address(0)); require(networkProxyContract != address(0)); } @@ -633,6 +630,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } function getUserCapInWei(address user) public view returns(uint) { + if (whiteListContract == address(0)) { return 2**255; } return whiteListContract.getUserCapInWei(user); } @@ -821,7 +819,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (tradeInput.src == TOMO_TOKEN_ADDRESS) { tradeInput.trader.transfer(tradeInput.srcAmount - actualSrcAmount); } else { - tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); + require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); } } @@ -851,28 +849,24 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); - uint feeInWei; - uint balanceBefore; - uint balanceAfter; + uint totalFeeInWei = 0; if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { //not a "fake" trade tomo to tomo - feeInWei = weiAmount * feeForReserve[rateResult.reserve1] / 10000; - balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; } if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { //not a "fake" trade tomo to tomo - feeInWei = weiAmount * feeForReserve[rateResult.reserve2] / 10000; - balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; + } + + if (totalFeeInWei > 0) { + uint balanceBefore = address(this).balance; + require(balanceBefore >= totalFeeInWei); + require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - totalFeeInWei); } emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, @@ -923,7 +917,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (actualSrcAmount < tradeInput.srcAmount) { // if there is "change" send back to trader - tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); + require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); } // verify trade size is smaller than user cap, dest is always TOMO @@ -1022,8 +1016,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // calculate expected fee for this transaction based on amount of Tomo - uint tomoValue = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; - uint feeInWei = tomoValue * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% + uint feeInWei = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; + feeInWei = feeInWei * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% uint expectedNetworkBalance = address(this).balance; if (src == TOMO_TOKEN_ADDRESS) { diff --git a/contracts/WrapContracts/WrapNetworkProxy.sol b/contracts/WrapContracts/WrapNetworkProxy.sol index 88d281f..b02b071 100644 --- a/contracts/WrapContracts/WrapNetworkProxy.sol +++ b/contracts/WrapContracts/WrapNetworkProxy.sol @@ -34,7 +34,6 @@ interface ReserveInterface { /// @title Kyber Network interface interface NetworkInterface { function maxGasPrice() external view returns(uint); - function getFeeHolder() external view returns(address); function getUserCapInWei(address user) external view returns(uint); function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); function enabled() external view returns(bool); @@ -425,7 +424,6 @@ contract NetworkProxy is NetworkProxyInterface, SimpleNetworkInterface, Withdraw returns(uint) { require(src == TOMO_TOKEN_ADDRESS || msg.value == 0); - require(msg.sender != networkContract.getFeeHolder()); require(payFeeCallers[msg.sender] == true, "payTxFee: Sender is not callable this function"); TRC20 dest = TOMO_TOKEN_ADDRESS; @@ -573,7 +571,6 @@ contract NetworkProxy is NetworkProxyInterface, SimpleNetworkInterface, Withdraw returns(uint) { require(src == TOMO_TOKEN_ADDRESS || msg.value == 0); - require(msg.sender != networkContract.getFeeHolder()); UserBalance memory userBalanceBefore; diff --git a/contracts/WrapContracts/WrapReserve.sol b/contracts/WrapContracts/WrapReserve.sol index 45605af..5293aa5 100644 --- a/contracts/WrapContracts/WrapReserve.sol +++ b/contracts/WrapContracts/WrapReserve.sol @@ -34,7 +34,6 @@ interface ReserveInterface { /// @title Kyber Network interface interface NetworkInterface { function maxGasPrice() external view returns(uint); - function getFeeHolder() external view returns(address); function getUserCapInWei(address user) external view returns(uint); function getUserCapInTokenWei(address user, TRC20 token) external view returns(uint); function enabled() external view returns(bool); diff --git a/contracts/mockContracts/GenerousNetwork.sol b/contracts/mockContracts/GenerousNetwork.sol index e53d7b6..5fb1d15 100644 --- a/contracts/mockContracts/GenerousNetwork.sol +++ b/contracts/mockContracts/GenerousNetwork.sol @@ -76,6 +76,27 @@ contract GenerousNetwork is Network { rateResult.rateTomoToDest, true)); + + uint totalFeeInWei = 0; + + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + //not a "fake" trade tomo to tomo + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; + } + + if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + //not a "fake" trade tomo to tomo + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; + } + + if (totalFeeInWei > 0) { + uint balanceBefore = address(this).balance; + require(balanceBefore >= totalFeeInWei); + require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - totalFeeInWei); + } + return (actualDestAmount); } /* solhint-enable function-max-lines */ diff --git a/contracts/mockContracts/MaliciousNetwork.sol b/contracts/mockContracts/MaliciousNetwork.sol index 46bbcd9..d399f1a 100644 --- a/contracts/mockContracts/MaliciousNetwork.sol +++ b/contracts/mockContracts/MaliciousNetwork.sol @@ -72,6 +72,26 @@ contract MaliciousNetwork is Network { rateResult.rateTomoToDest, true)); + uint totalFeeInWei = 0; + + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + //not a "fake" trade tomo to tomo + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; + } + + if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + //not a "fake" trade tomo to tomo + totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; + } + + if (totalFeeInWei > 0) { + uint balanceBefore = address(this).balance; + require(balanceBefore >= totalFeeInWei); + require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - totalFeeInWei); + } + return (actualDestAmount - myFeeWei); } /* solhint-enable function-max-lines */ @@ -135,12 +155,6 @@ contract MaliciousNetwork is Network { } } - if (feeHolder != address(this)) { - require(address(this).balance >= feeInWei); - // transfer fee to feeHolder - feeHolder.transfer(feeInWei); - } - return true; } } diff --git a/contracts/mockContracts/MaliciousNetwork2.sol b/contracts/mockContracts/MaliciousNetwork2.sol index 5bacf2e..773fd54 100644 --- a/contracts/mockContracts/MaliciousNetwork2.sol +++ b/contracts/mockContracts/MaliciousNetwork2.sol @@ -73,12 +73,6 @@ contract MaliciousNetwork2 is Network { } } - if (feeHolder != address(this)) { - require(address(this).balance >= feeInWei); - // transfer fee to feeHolder - feeHolder.transfer(feeInWei); - } - return true; } } diff --git a/test/network.js b/test/network.js index bc13720..65134ab 100644 --- a/test/network.js +++ b/test/network.js @@ -5,6 +5,7 @@ const TestTokenTransferFailing = artifacts.require("TestTokenTransferFailing.sol const Reserve = artifacts.require("Reserve.sol"); const Network = artifacts.require("Network.sol"); const WhiteList = artifacts.require("WhiteList.sol"); +const FeeSharing = artifacts.require("FeeSharing.sol"); const ExpectedRate = artifacts.require("ExpectedRate.sol"); const Helper = require("./helper.js"); @@ -124,8 +125,6 @@ let compactSellArr = []; let oldBaseBuy; let oldBaseSell; -let fixedFeeHolder; - contract('Network', function(accounts) { it("should init globals. init 2 ConversionRates Inst, init tokens and add to pricing inst. set basic data per token.", async function () { // set account addresses @@ -136,7 +135,6 @@ contract('Network', function(accounts) { user1 = accounts[4]; user2 = accounts[5]; walletId = accounts[6]; - fixedFeeHolder = accounts[7] maker1 = accounts[8]; currentBlock = priceUpdateBlock = await Helper.getCurrentBlock(); @@ -2716,232 +2714,240 @@ contract('Network', function(accounts) { log("average gas usage " + numTrades + " buys. token to token: " + avgGas.floor().valueOf()); }); }); - describe("transaction fee", function() { - it("should test set fee percent for added reserve and not for not added reserve", async function () { - let feePercent = 25; - await network.setFeePercent(reserve1.address, feePercent, {from: admin}); - // console.log("Set fee success"); - let recordedFeePercent = await network.feeForReserve(reserve1.address); - // console.log("Get fee percent: " + recordedFeePercent); - assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve1 is not set correctly"); - - await network.setFeePercent(reserve2.address, feePercent, {from: admin}); - recordedFeePercent = await network.feeForReserve(reserve2.address); - assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve2 is not set correctly"); - - // remove reserve3 - await network.addReserve(reserve3.address, false, {from: admin}); - - // fee should be 0 as we have set it to 0 - recordedFeePercent = await network.feeForReserve(reserve3.address); - assert.equal(recordedFeePercent, 0, "1. Fee percent for reserve3 is not set correctly"); - - try { - await network.setFeePercent(reserve3.address, feePercent, {from: admin}); - assert.equal(false, "expect to throw error the line above"); - } catch (e) { - assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); - } - - recordedFeePercent = await network.feeForReserve(reserve3.address); - assert.equal(0, (new BigNumber(recordedFeePercent)).valueOf(), "2. Fee percent for reserve3 is not set correctly"); - - await network.setFeePercent(reserve1.address, feePercent, {from: admin}); - }); - - it("Should test default fee holder is network and receive fee as expected", async function() { - let tokenInd = 0; - let token = tokens[tokenInd]; //choose some token - let amountTwei = 4000; - let maxDestAmount = 100000000; - - let feeHolder = await network.getFeeHolder(); - assert.equal(feeHolder.toLowerCase(), network.address.toLowerCase(), "Fee holder should be network itself"); - - let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - let minRate = rates[0].valueOf(); - - // transfer funds to user and approve funds to network - await token.transfer(network.address, amountTwei); - // await token.approve(network.address, amountTwei, {from:user1}) - - let networkBalBefore = await Helper.getBalancePromise(network.address); - //perform full amount trade. see token balance on user 1 zero - let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, - minRate, walletId, {from:networkProxy}); - - let feePercent = 25; - let tokenDecimal = await token.decimals(); - let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - - let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); - - let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - - let networkBalAfter = await Helper.getBalancePromise(network.address); - - const expectedDiffInPct = new BigNumber(0.03); // diff 3% - Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - }); - - it("Should test receive fee as expected when network is the dest address", async function() { - let tokenInd = 0; - let token = tokens[tokenInd]; //choose some token - let amountTwei = 4000; - let maxDestAmount = 100000000; - - let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - let minRate = rates[0].valueOf(); - - // transfer funds to user and approve funds to network - await token.transfer(network.address, amountTwei); - // await token.approve(network.address, amountTwei, {from:user1}) - - let networkBalBefore = await Helper.getBalancePromise(network.address); - //perform full amount trade. see token balance on user 1 zero - let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, network.address, maxDestAmount, - minRate, walletId, {from:networkProxy}); - - let feePercent = 25; - let tokenDecimal = await token.decimals(); - let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - - let expectedWeiAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); - let expectedFee = new BigNumber(expectedWeiAmount).mul(feePercent).div(10000).round(); - - let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedWeiAmount); - - let networkBalAfter = await Helper.getBalancePromise(network.address); - - const expectedDiffInPct = new BigNumber(0.03); // diff 3% - Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - }); - - it("Should test receive fee as expected for buy transaction", async function() { - let tokenInd = 0; - let token = tokens[tokenInd]; //choose some token - let amountTwei = 4000; - let maxDestAmount = 100000000; - - let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); - let minRate = rates[0].valueOf(); - - let networkBalBefore = await Helper.getBalancePromise(network.address); - //perform full amount trade. see token balance on user 1 zero - let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, - minRate, walletId, {from:networkProxy, value: 4000}); - - let feePercent = 25; - - let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); - - let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - - let networkBalAfter = await Helper.getBalancePromise(network.address); - - const expectedDiffInPct = new BigNumber(0.03); // diff 3% - Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - }); - - it("Should test can set fee holder and receive fee as expected", async function() { - let feeHolder = await network.getFeeHolder(); - assert.equal(feeHolder.toLowerCase(), network.address.toLowerCase(), "Fee holder should be network itself"); - - await network.setFeeHolder(fixedFeeHolder, {from: admin}); - console.log("Successfully set fee holder address"); - feeHolder = await network.getFeeHolder(); - assert.equal(feeHolder.toLowerCase(), fixedFeeHolder.toLowerCase(), "Fee holder should be network itself"); - - let tokenInd = 0; - let token = tokens[tokenInd]; //choose some token - let amountTwei = 4000; - let maxDestAmount = 100000000; - - let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - let minRate = rates[0].valueOf(); - - // transfer funds to user and approve funds to network - await token.transfer(network.address, amountTwei); - // await token.approve(network.address, amountTwei, {from:user1}) - - let networkBalBefore = await Helper.getBalancePromise(feeHolder); - //perform full amount trade. see token balance on user 1 zero - let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, - minRate, walletId, {from:networkProxy}); - - let feePercent = 25; - let tokenDecimal = await token.decimals(); - let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - - let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); - - let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - - let networkBalAfter = await Helper.getBalancePromise(feeHolder); - - const expectedDiffInPct = new BigNumber(0.03); // diff 3% - Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - }); - - it("should TOMO balance changes correctly for fee holder if it is receiver of swap tx", async function() { - let tokenInd = 0; - let token = tokens[tokenInd]; //choose some token - let amountTwei = 4000; - let maxDestAmount = 100000000; - - let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - let minRate = rates[0].valueOf(); - - // transfer funds to user and approve funds to network - await token.transfer(network.address, amountTwei); - // await token.approve(network.address, amountTwei, {from:user1}) - - let networkBalBefore = await Helper.getBalancePromise(fixedFeeHolder); - //perform full amount trade. see token balance on user 1 zero - let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, fixedFeeHolder, maxDestAmount, - minRate, walletId, {from:networkProxy}); - - let feePercent = 25; - let tokenDecimal = await token.decimals(); - let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - - let expectedDestAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); - let expectedFee = new BigNumber(expectedDestAmount).mul(feePercent).div(10000).round(); - - let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedDestAmount); - - let networkBalAfter = await Helper.getBalancePromise(fixedFeeHolder); - - const expectedDiffInPct = new BigNumber(0.03); // diff 3% - Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - }); - - it("Should test receive fee in holder wallet as expected for buy transaction", async function() { - let tokenInd = 0; - let token = tokens[tokenInd]; //choose some token - let amountTwei = 4000; - let maxDestAmount = 100000000; - - let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); - let minRate = rates[0].valueOf(); - - let networkBalBefore = await Helper.getBalancePromise(fixedFeeHolder); - //perform full amount trade. see token balance on user 1 zero - let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, - minRate, walletId, {from:networkProxy, value: 4000}); - - let feePercent = 25; - - let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); - - let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - - let networkBalAfter = await Helper.getBalancePromise(fixedFeeHolder); - - const expectedDiffInPct = new BigNumber(0.03); // diff 3% - Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - }); - }); + // describe("transaction fee", function() { + // it("should test set fee percent for added reserve and not for not added reserve", async function () { + // let feePercent = 25; + // await network.setFeePercent(reserve1.address, feePercent, {from: admin}); + // // console.log("Set fee success"); + // let recordedFeePercent = await network.feeForReserve(reserve1.address); + // // console.log("Get fee percent: " + recordedFeePercent); + // assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve1 is not set correctly"); + // + // await network.setFeePercent(reserve2.address, feePercent, {from: admin}); + // recordedFeePercent = await network.feeForReserve(reserve2.address); + // assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve2 is not set correctly"); + // + // // remove reserve3 + // await network.addReserve(reserve3.address, false, {from: admin}); + // + // // fee should be 0 as we have set it to 0 + // recordedFeePercent = await network.feeForReserve(reserve3.address); + // assert.equal(recordedFeePercent, 0, "1. Fee percent for reserve3 is not set correctly"); + // + // try { + // await network.setFeePercent(reserve3.address, feePercent, {from: admin}); + // assert.equal(false, "expect to throw error the line above"); + // } catch (e) { + // assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); + // } + // + // recordedFeePercent = await network.feeForReserve(reserve3.address); + // assert.equal(0, (new BigNumber(recordedFeePercent)).valueOf(), "2. Fee percent for reserve3 is not set correctly"); + // + // await network.setFeePercent(reserve1.address, feePercent, {from: admin}); + // }); + // + // it("Should test default fee holder is network and receive fee as expected", async function() { + // let tokenInd = 0; + // let token = tokens[tokenInd]; //choose some token + // let amountTwei = 4000; + // let maxDestAmount = 100000000; + // + // let feeHolder = await network.feeSharing(); + // assert.equal(feeHolder, zeroAddress, "Fee holder should be zero address"); + // + // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + // let minRate = rates[0].valueOf(); + // + // // transfer funds to user and approve funds to network + // await token.transfer(network.address, amountTwei); + // // await token.approve(network.address, amountTwei, {from:user1}) + // + // let networkBalBefore = await Helper.getBalancePromise(network.address); + // //perform full amount trade. see token balance on user 1 zero + // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, + // minRate, walletId, {from:networkProxy}); + // + // let feePercent = 25; + // let tokenDecimal = await token.decimals(); + // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + // + // let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); + // + // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + // + // let networkBalAfter = await Helper.getBalancePromise(network.address); + // + // const expectedDiffInPct = new BigNumber(0.03); // diff 3% + // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + // }); + // + // it("Should test receive fee as expected when network is the dest address", async function() { + // let tokenInd = 0; + // let token = tokens[tokenInd]; //choose some token + // let amountTwei = 4000; + // let maxDestAmount = 100000000; + // + // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + // let minRate = rates[0].valueOf(); + // + // // transfer funds to user and approve funds to network + // await token.transfer(network.address, amountTwei); + // // await token.approve(network.address, amountTwei, {from:user1}) + // + // let networkBalBefore = await Helper.getBalancePromise(network.address); + // //perform full amount trade. see token balance on user 1 zero + // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, network.address, maxDestAmount, + // minRate, walletId, {from:networkProxy}); + // + // let feePercent = 25; + // let tokenDecimal = await token.decimals(); + // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + // + // let expectedWeiAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); + // let expectedFee = new BigNumber(expectedWeiAmount).mul(feePercent).div(10000).round(); + // + // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedWeiAmount); + // + // let networkBalAfter = await Helper.getBalancePromise(network.address); + // + // const expectedDiffInPct = new BigNumber(0.03); // diff 3% + // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + // }); + // + // it("Should test receive fee as expected for buy transaction", async function() { + // let tokenInd = 0; + // let token = tokens[tokenInd]; //choose some token + // let amountTwei = 4000; + // let maxDestAmount = 100000000; + // + // let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); + // let minRate = rates[0].valueOf(); + // + // let networkBalBefore = await Helper.getBalancePromise(network.address); + // //perform full amount trade. see token balance on user 1 zero + // let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, + // minRate, walletId, {from:networkProxy, value: 4000}); + // + // let feePercent = 25; + // + // let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); + // + // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + // + // let networkBalAfter = await Helper.getBalancePromise(network.address); + // + // const expectedDiffInPct = new BigNumber(0.03); // diff 3% + // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + // }); + // + // it("Should test can init fee holder, set fee holder to network and receive fee as expected", async function() { + // let feeSharing = await FeeSharing.new(admin, network); + // + // try { + // await network.setFeeSharing(zeroAddress, {from: admin}); + // assert(false, "throw was expected in line above.") + // } catch(e){ + // assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); + // } + // + // await network.setFeeSharing(feeSharing, {from: admin}); + // console.log("Successfully set fee holder address"); + // let feeHolder = await network.feeSharing(); + // assert.equal(feeHolder.toLowerCase(), feeSharing.toLowerCase(), "Fee holder is not correct"); + // + // let tokenInd = 0; + // let token = tokens[tokenInd]; //choose some token + // let amountTwei = 4000; + // let maxDestAmount = 100000000; + // + // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + // let minRate = rates[0].valueOf(); + // + // // transfer funds to user and approve funds to network + // await token.transfer(network.address, amountTwei); + // // await token.approve(network.address, amountTwei, {from:user1}) + // + // let networkBalBefore = await Helper.getBalancePromise(feeHolder); + // //perform full amount trade. see token balance on user 1 zero + // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, + // minRate, walletId, {from:networkProxy}); + // + // let feePercent = 25; + // let tokenDecimal = await token.decimals(); + // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + // + // let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); + // + // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + // + // let networkBalAfter = await Helper.getBalancePromise(feeHolder); + // + // const expectedDiffInPct = new BigNumber(0.03); // diff 3% + // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + // }); + // + // it("should TOMO balance changes correctly for fee holder if it is receiver of swap tx", async function() { + // let tokenInd = 0; + // let token = tokens[tokenInd]; //choose some token + // let amountTwei = 4000; + // let maxDestAmount = 100000000; + // + // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + // let minRate = rates[0].valueOf(); + // + // // transfer funds to user and approve funds to network + // await token.transfer(network.address, amountTwei); + // // await token.approve(network.address, amountTwei, {from:user1}) + // + // let networkBalBefore = await Helper.getBalancePromise(fixedFeeHolder); + // //perform full amount trade. see token balance on user 1 zero + // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, fixedFeeHolder, maxDestAmount, + // minRate, walletId, {from:networkProxy}); + // + // let feePercent = 25; + // let tokenDecimal = await token.decimals(); + // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + // + // let expectedDestAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); + // let expectedFee = new BigNumber(expectedDestAmount).mul(feePercent).div(10000).round(); + // + // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedDestAmount); + // + // let networkBalAfter = await Helper.getBalancePromise(fixedFeeHolder); + // + // const expectedDiffInPct = new BigNumber(0.03); // diff 3% + // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + // }); + // + // it("Should test receive fee in holder wallet as expected for buy transaction", async function() { + // let tokenInd = 0; + // let token = tokens[tokenInd]; //choose some token + // let amountTwei = 4000; + // let maxDestAmount = 100000000; + // + // let feeHolder = await network.feeSharing(); + // + // let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); + // let minRate = rates[0].valueOf(); + // + // let networkBalBefore = await Helper.getBalancePromise(feeHolder); + // //perform full amount trade. see token balance on user 1 zero + // let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, + // minRate, walletId, {from:networkProxy, value: 4000}); + // + // let feePercent = 25; + // + // let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); + // + // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + // + // let networkBalAfter = await Helper.getBalancePromise(feeHolder); + // + // const expectedDiffInPct = new BigNumber(0.03); // diff 3% + // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + // }); + // }); }); function convertRateToConversionRatesRate (baseRate) { From eebffa73141b5b22c4eefbc0c4cdf0c61ffbfdc1 Mon Sep 17 00:00:00 2001 From: manhlx Date: Wed, 3 Apr 2019 22:31:22 +0700 Subject: [PATCH 4/6] Add sending fee to fee sharing --- contracts/Network.sol | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/contracts/Network.sol b/contracts/Network.sol index ec71b9f..6123ac9 100644 --- a/contracts/Network.sol +++ b/contracts/Network.sol @@ -536,25 +536,25 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { rateResult.rateTomoToDest, true)); - // uint totalFeeInWei = 0; - // - // if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - // //not a "fake" trade tomo to tomo - // totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; - // } - // - // if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - // //not a "fake" trade tomo to tomo - // totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; - // } - // - // if (totalFeeInWei > 0) { - // uint balanceBefore = address(this).balance; - // require(balanceBefore >= totalFeeInWei); - // require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); - // uint balanceAfter = address(this).balance; - // require(balanceAfter == balanceBefore - totalFeeInWei); - // } + uint totalFeeInWei = 0; + + if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { + //not a "fake" trade tomo to tomo + totalFeeInWei += (weiAmount * feeForReserve[rateResult.reserve1] / 10000); + } + + if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { + //not a "fake" trade tomo to tomo + totalFeeInWei += (weiAmount * feeForReserve[rateResult.reserve2] / 10000); + } + + if (totalFeeInWei > 0 && feeSharing != address(0)) { + uint balanceBefore = address(this).balance; + require(balanceBefore >= totalFeeInWei); + require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); + uint balanceAfter = address(this).balance; + require(balanceAfter == balanceBefore - totalFeeInWei); + } emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); From e0249871f612c933f459f1b2590760a77d451d11 Mon Sep 17 00:00:00 2001 From: manhlx Date: Thu, 4 Apr 2019 19:25:30 +0700 Subject: [PATCH 5/6] Add fee sharing tests --- test/feeSharing.js | 173 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 test/feeSharing.js diff --git a/test/feeSharing.js b/test/feeSharing.js new file mode 100644 index 0000000..9150a97 --- /dev/null +++ b/test/feeSharing.js @@ -0,0 +1,173 @@ +const FeeSharing = artifacts.require("FeeSharing.sol"); + +const Helper = require("./helper.js"); +const BigNumber = require('bignumber.js'); +const truffleAssert = require('truffle-assertions'); + +//global variables +////////////////// +const precisionUnits = (new BigNumber(10).pow(18)); +const max_rate = (precisionUnits.mul(10 ** 6)).valueOf(); //internal parameter in Utils.sol +const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; +const zeroAddress = '0x0000000000000000000000000000000000000000'; +const gasPrice = (new BigNumber(10).pow(9).mul(50)); +let negligibleRateDiff = 11; + +//permission groups +let admin; +let operator; +let alerter; +let commission1; +let commission2; + +let commission1Percent = 30; +let commission2Percent = 50; + +let network; +let feeSharing; + + +contract('FeeSharing', function(accounts) { + it("should init globals. init FeeSharing Inst", async function () { + // set account addresses + admin = accounts[0]; + network = accounts[1]; + commission1 = accounts[2]; + commission2 = accounts[3]; + + feeSharing = await FeeSharing.new(admin, network); + }); + it("should test can not init or set with zero address", async function () { + let tempFeeSharing; + try { + tempFeeSharing = await FeeSharing.new(admin, zeroAddress); + assert.equal(false, "Throw was expected in line above"); + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + } + + try { + tempFeeSharing = await FeeSharing.new(zeroAddress, network); + assert.equal(false, "Throw was expected in line above"); + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + } + + try { + tempFeeSharing = await FeeSharing.new(zeroAddress, zeroAddress); + assert.equal(false, "Throw was expected in line above"); + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + } + + tempFeeSharing = await FeeSharing.new(admin, network); + + // try { + // tempFeeSharing.setNetworkContract(zeroAddress); + // assert.equal(false, "Throw was expected in line above"); + // } catch (e) { + // assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + // } + + tempFeeSharing.setNetworkContract(network); + + const networkRecorded = await tempFeeSharing.network(); + assert.equal(networkRecorded, network, "Network is not set properly"); + }); + + it("Should test default fee is 0 and can set new fee for commission wallet", async function() { + console.log("Get fee sharing fee in bps"); + var feeInBps = await feeSharing.walletFeesInBps(commission1); + // console.log("Fee: " + feeInBps.valueOf()); + assert.equal(feeInBps.valueOf(0), 0, "Fee is not set default to zero"); + feeInBps = await feeSharing.walletFeesInBps(commission2); + assert.equal(feeInBps.valueOf(0), 0, "Fee is not set default to zero"); + await feeSharing.setWalletFees(commission1, commission1Percent); + await feeSharing.setWalletFees(commission2, commission2Percent); + + feeInBps = await feeSharing.walletFeesInBps(commission1); + assert.equal(feeInBps.valueOf(), commission1Percent, "Fee is not set correctly"); + feeInBps = await feeSharing.walletFeesInBps(commission2); + assert.equal(feeInBps.valueOf(), commission2Percent, "Fee is not set correctly"); + }); + + it("Should test can not set wallet with fee more than 100%", async function() { + try { + await feeSharing.setWalletFees(commission1, 10000); + assert.equal(false, "Throw was expected in line above"); + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + } + }); + + it("Should test correct record and balance for not set wallet", async function() { + console.log("Getting balance before"); + const balanceBefore = await Helper.getBalancePromise(feeSharing.address); + // console.log("Balance before: " + balanceBefore); + await feeSharing.handleFees(network, {from: network, value: 3000}); + const balanceAfter = await Helper.getBalancePromise(feeSharing.address); + // console.log("Balance before: " + balanceBefore.valueOf()); + // console.log("Balance after: " + balanceAfter.valueOf()); + assert.equal((new BigNumber(balanceBefore).plus(3000)).valueOf(), (new BigNumber(balanceAfter)).valueOf(), "Balance is not changed correctly"); + + const feeToPay = await feeSharing.walletFeesToPay(network); + // console.log("Fee to pay: " + feeToPay); + assert.equal(feeToPay.valueOf(0), 0, "wallet fee to pay is not set correct"); + }); + + it("Should test correct record and balance for commission wallet 1", async function() { + const balanceBefore = await Helper.getBalancePromise(feeSharing.address); + await feeSharing.handleFees(commission1, {from: network, value: 30000}); + const balanceAfter = await Helper.getBalancePromise(feeSharing.address); + // console.log("Balance before: " + balanceBefore.valueOf()); + // console.log("Balance after: " + balanceAfter.valueOf()); + assert.equal((new BigNumber(balanceBefore).plus(30000)).valueOf(), (new BigNumber(balanceAfter)).valueOf(), "Balance is not changed correctly"); + + const feeToPay = await feeSharing.walletFeesToPay(commission1); + const expectedFeeToPay = 30000 * commission1Percent / 10000; + // console.log("Fee to pay: " + feeToPay); + assert.equal(feeToPay.valueOf(), expectedFeeToPay, "wallet fee to pay is not set correct"); + }); + + it("Should test correct record and balance for commission wallet 2", async function() { + const balanceBefore = await Helper.getBalancePromise(feeSharing.address); + await feeSharing.handleFees(commission2, {from: network, value: 30000}); + const balanceAfter = await Helper.getBalancePromise(feeSharing.address); + // console.log("Balance before: " + balanceBefore.valueOf()); + // console.log("Balance after: " + balanceAfter.valueOf()); + assert.equal((new BigNumber(balanceBefore).plus(30000)).valueOf(), (new BigNumber(balanceAfter)).valueOf(), "Balance is not changed correctly"); + + const feeToPay = await feeSharing.walletFeesToPay(commission2); + // console.log("Fee to pay: " + feeToPay); + const expectedFeeToPay = 30000 * commission2Percent / 10000; + // console.log("Expected fee to pay: " + expectedFeeToPay); + assert.equal(feeToPay.valueOf(), expectedFeeToPay, "wallet fee to pay is not set correct"); + }); + + it("Should test correct send fee to commission wallet and reset fee to pay", async function() { + const balanceBefore = await Helper.getBalancePromise(commission1); + var feeToPay = await feeSharing.walletFeesToPay(commission1); + await feeSharing.sendFeeToWallet(commission1); + const balanceAfter = await Helper.getBalancePromise(commission1); + // console.log("Balance before: " + balanceBefore.valueOf()); + // console.log("Balance after: " + balanceAfter.valueOf()); + // console.log("Fee to pay: " + feeToPay.valueOf()); + assert.equal((new BigNumber(balanceBefore).plus(feeToPay)).valueOf(), (new BigNumber(balanceAfter)).valueOf(), "Fee is not sent to wallet"); + + feeToPay = await feeSharing.walletFeesToPay(commission1); + assert.equal(feeToPay.valueOf(0), 0, "Fee to pay is not reset correctly"); + }); + + it("Should test can not send fee to commission wallet if it doest not have any fee", async function() { + const feeToPay = await feeSharing.walletFeesToPay(commission1); + // console.log("Fee to pay: " + feeToPay.valueOf()); + assert.equal(feeToPay.valueOf(0), 0, "Fee for commission wallet 1 should be 0"); + try { + await feeSharing.sendFeeToWallet(commission1); + assert.equal(false, "Throw was expected in line above"); + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected throw but got: " + e); + } + await feeSharing.sendFeeToWallet(commission2); + }); +}); From 2aa1446a10682d98b48396010be852473bba19b7 Mon Sep 17 00:00:00 2001 From: manhlx Date: Fri, 5 Apr 2019 18:04:33 +0700 Subject: [PATCH 6/6] Update new network with fee sharing --- contracts/Network.sol | 87 ++-- contracts/WrapContracts/WrapExpectedRate.sol | 84 ++-- contracts/WrapContracts/WrapNetwork.sol | 83 ++-- contracts/mockContracts/GenerousNetwork.sol | 27 +- contracts/mockContracts/MaliciousNetwork.sol | 35 +- contracts/mockContracts/MaliciousNetwork2.sol | 9 +- test/network.js | 470 +++++++++--------- token_ropsten_configs.json | 6 +- 8 files changed, 362 insertions(+), 439 deletions(-) diff --git a/contracts/Network.sol b/contracts/Network.sol index 6123ac9..965fc12 100644 --- a/contracts/Network.sol +++ b/contracts/Network.sol @@ -217,7 +217,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (add) { require(token.approve(reserve, 2**255)); // approve infinity } else { - require(token.approve(reserve, 0)); + require(token.approve(reserve, 0)); } emit ListReservePairs(reserve, token, TOMO_TOKEN_ADDRESS, add); @@ -226,6 +226,13 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { setDecimals(token); } + event FeeSharingSet(address fee, address sender); + function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { + require(_feeSharing != address(0)); + feeSharing = _feeSharing; + emit FeeSharingSet(_feeSharing, msg.sender); + } + function setWhiteList(WhiteListInterface whiteList) public onlyAdmin { whiteListContract = whiteList; } @@ -268,13 +275,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { emit NetworkProxySet(networkProxy, msg.sender); } - event FeeSharingSet(address fee, address sender); - function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { - require(_feeSharing != address(0)); - feeSharing = _feeSharing; - emit FeeSharingSet(_feeSharing, msg.sender); - } - /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { @@ -515,6 +515,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { //do the trade //src to ETH + require(doReserveTrade( tradeInput.src, actualSrcAmount, @@ -523,7 +524,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { weiAmount, ReserveInterface(rateResult.reserve1), rateResult.rateSrcToTomo, - true)); + true, + tradeInput.walletId)); //Eth to dest require(doReserveTrade( @@ -534,27 +536,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { actualDestAmount, ReserveInterface(rateResult.reserve2), rateResult.rateTomoToDest, - true)); - - uint totalFeeInWei = 0; - - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += (weiAmount * feeForReserve[rateResult.reserve1] / 10000); - } - - if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += (weiAmount * feeForReserve[rateResult.reserve2] / 10000); - } - - if (totalFeeInWei > 0 && feeSharing != address(0)) { - uint balanceBefore = address(this).balance; - require(balanceBefore >= totalFeeInWei); - require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - totalFeeInWei); - } + true, + tradeInput.walletId)); emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -604,7 +587,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (actualSrcAmount < tradeInput.srcAmount) { // if there is "change" send back to trader - require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); + tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); } // verify trade size is smaller than user cap, dest is always TOMO @@ -619,17 +602,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { actualDestAmount, ReserveInterface(reserve), expectedRate, - true)); - - uint feeInWei = actualDestAmount * feeForReserve[reserve] / 10000; - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeInWei > 0) { - //not a "fake" trade tomo to tomo - uint balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); - } + true, + tradeInput.walletId)); emit TradeFee(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -683,10 +657,10 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint expectedDestAmount, ReserveInterface reserve, uint conversionRate, - bool validate + bool validate, + address walletId ) internal - nonReentrant returns(bool) { uint callValue = 0; @@ -703,22 +677,22 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // calculate expected fee for this transaction based on amount of Tomo - // feePercent = 25 -> fee = 25/10000 = 0.25% - uint feeInWei = (src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount) * feeForReserve[reserve] / 10000; + uint feeInWei = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; + feeInWei = feeInWei * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% - uint expectedNetworkBalance = address(this).balance; + uint expectedTomoBal = address(this).balance; if (src == TOMO_TOKEN_ADDRESS) { // callValue amount of Tomo will be transfered to reserve - require(expectedNetworkBalance >= callValue); - expectedNetworkBalance -= callValue; + require(expectedTomoBal >= callValue); + expectedTomoBal -= callValue; } if (address(this) == destAddress && dest == TOMO_TOKEN_ADDRESS) { - // expectedDestAmount of Tomo will be transfered to destAdress (which is also network) - expectedNetworkBalance += expectedDestAmount; + // expectedDestAmount of Tomo will be transfered to destAdress + expectedTomoBal += expectedDestAmount; } // receive feeInWei amount of Tomo as fee - expectedNetworkBalance += feeInWei; + expectedTomoBal += feeInWei; // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, feeInWei, validate), "doReserveTrade: reserve trade failed"); @@ -733,7 +707,14 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // Expected to receive exact amount fee in TOMO - require(address(this).balance == expectedNetworkBalance); + require(address(this).balance == expectedTomoBal); + + if (feeSharing != address(0)) { + require(address(this).balance >= feeInWei); + // transfer fee to feeSharing + require(feeSharing.handleFees.value(feeInWei)(walletId)); + } + return true; } diff --git a/contracts/WrapContracts/WrapExpectedRate.sol b/contracts/WrapContracts/WrapExpectedRate.sol index 93418e4..098fc56 100644 --- a/contracts/WrapContracts/WrapExpectedRate.sol +++ b/contracts/WrapContracts/WrapExpectedRate.sol @@ -530,7 +530,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (add) { require(token.approve(reserve, 2**255)); // approve infinity } else { - require(token.approve(reserve, 0)); + require(token.approve(reserve, 0)); } emit ListReservePairs(reserve, token, TOMO_TOKEN_ADDRESS, add); @@ -539,6 +539,13 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { setDecimals(token); } + event FeeSharingSet(address fee, address sender); + function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { + require(_feeSharing != address(0)); + feeSharing = _feeSharing; + emit FeeSharingSet(_feeSharing, msg.sender); + } + function setWhiteList(WhiteListInterface whiteList) public onlyAdmin { whiteListContract = whiteList; } @@ -581,13 +588,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { emit NetworkProxySet(networkProxy, msg.sender); } - event FeeSharingSet(address fee, address sender); - function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { - require(_feeSharing != address(0)); - feeSharing = _feeSharing; - emit FeeSharingSet(_feeSharing, msg.sender); - } - /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { @@ -828,6 +828,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { //do the trade //src to ETH + require(doReserveTrade( tradeInput.src, actualSrcAmount, @@ -836,7 +837,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { weiAmount, ReserveInterface(rateResult.reserve1), rateResult.rateSrcToTomo, - true)); + true, + tradeInput.walletId)); //Eth to dest require(doReserveTrade( @@ -847,27 +849,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { actualDestAmount, ReserveInterface(rateResult.reserve2), rateResult.rateTomoToDest, - true)); - - uint totalFeeInWei = 0; - - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; - } - - if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; - } - - if (totalFeeInWei > 0) { - uint balanceBefore = address(this).balance; - require(balanceBefore >= totalFeeInWei); - require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - totalFeeInWei); - } + true, + tradeInput.walletId)); emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -917,7 +900,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (actualSrcAmount < tradeInput.srcAmount) { // if there is "change" send back to trader - require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); + tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); } // verify trade size is smaller than user cap, dest is always TOMO @@ -932,17 +915,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { actualDestAmount, ReserveInterface(reserve), expectedRate, - true)); - - uint feeInWei = actualDestAmount * feeForReserve[reserve] / 10000; - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeInWei > 0) { - //not a "fake" trade tomo to tomo - uint balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); - } + true, + tradeInput.walletId)); emit TradeFee(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -996,10 +970,10 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint expectedDestAmount, ReserveInterface reserve, uint conversionRate, - bool validate + bool validate, + address walletId ) internal - nonReentrant returns(bool) { uint callValue = 0; @@ -1019,19 +993,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint feeInWei = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; feeInWei = feeInWei * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% - uint expectedNetworkBalance = address(this).balance; + uint expectedTomoBal = address(this).balance; if (src == TOMO_TOKEN_ADDRESS) { // callValue amount of Tomo will be transfered to reserve - require(expectedNetworkBalance >= callValue); - expectedNetworkBalance -= callValue; + require(expectedTomoBal >= callValue); + expectedTomoBal -= callValue; } if (address(this) == destAddress && dest == TOMO_TOKEN_ADDRESS) { - // expectedDestAmount of Tomo will be transfered to destAdress (which is also network) - expectedNetworkBalance += expectedDestAmount; + // expectedDestAmount of Tomo will be transfered to destAdress + expectedTomoBal += expectedDestAmount; } // receive feeInWei amount of Tomo as fee - expectedNetworkBalance += feeInWei; + expectedTomoBal += feeInWei; // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, feeInWei, validate), "doReserveTrade: reserve trade failed"); @@ -1046,7 +1020,14 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // Expected to receive exact amount fee in TOMO - require(address(this).balance == expectedNetworkBalance); + require(address(this).balance == expectedTomoBal); + + if (feeSharing != address(0)) { + require(address(this).balance >= feeInWei); + // transfer fee to feeSharing + require(feeSharing.handleFees.value(feeInWei)(walletId)); + } + return true; } @@ -1080,6 +1061,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } } + contract ExpectedRate is Withdrawable, ExpectedRateInterface, Utils2 { Network public network; diff --git a/contracts/WrapContracts/WrapNetwork.sol b/contracts/WrapContracts/WrapNetwork.sol index d10bedb..2382270 100644 --- a/contracts/WrapContracts/WrapNetwork.sol +++ b/contracts/WrapContracts/WrapNetwork.sol @@ -530,7 +530,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (add) { require(token.approve(reserve, 2**255)); // approve infinity } else { - require(token.approve(reserve, 0)); + require(token.approve(reserve, 0)); } emit ListReservePairs(reserve, token, TOMO_TOKEN_ADDRESS, add); @@ -539,6 +539,13 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { setDecimals(token); } + event FeeSharingSet(address fee, address sender); + function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { + require(_feeSharing != address(0)); + feeSharing = _feeSharing; + emit FeeSharingSet(_feeSharing, msg.sender); + } + function setWhiteList(WhiteListInterface whiteList) public onlyAdmin { whiteListContract = whiteList; } @@ -581,13 +588,6 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { emit NetworkProxySet(networkProxy, msg.sender); } - event FeeSharingSet(address fee, address sender); - function setFeeSharing(FeeSharingInterface _feeSharing) public onlyAdmin { - require(_feeSharing != address(0)); - feeSharing = _feeSharing; - emit FeeSharingSet(_feeSharing, msg.sender); - } - /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { @@ -828,6 +828,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { //do the trade //src to ETH + require(doReserveTrade( tradeInput.src, actualSrcAmount, @@ -836,7 +837,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { weiAmount, ReserveInterface(rateResult.reserve1), rateResult.rateSrcToTomo, - true)); + true, + tradeInput.walletId)); //Eth to dest require(doReserveTrade( @@ -847,27 +849,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { actualDestAmount, ReserveInterface(rateResult.reserve2), rateResult.rateTomoToDest, - true)); - - uint totalFeeInWei = 0; - - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; - } - - if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; - } - - if (totalFeeInWei > 0) { - uint balanceBefore = address(this).balance; - require(balanceBefore >= totalFeeInWei); - require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - totalFeeInWei); - } + true, + tradeInput.walletId)); emit Trade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -917,7 +900,7 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { if (actualSrcAmount < tradeInput.srcAmount) { // if there is "change" send back to trader - require(tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount))); + tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount)); } // verify trade size is smaller than user cap, dest is always TOMO @@ -932,17 +915,8 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { actualDestAmount, ReserveInterface(reserve), expectedRate, - true)); - - uint feeInWei = actualDestAmount * feeForReserve[reserve] / 10000; - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeInWei > 0) { - //not a "fake" trade tomo to tomo - uint balanceBefore = address(this).balance; - require(balanceBefore >= feeInWei); - require(feeSharing.handleFees.value(feeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - feeInWei); - } + true, + tradeInput.walletId)); emit TradeFee(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest, actualDestAmount); @@ -996,10 +970,10 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint expectedDestAmount, ReserveInterface reserve, uint conversionRate, - bool validate + bool validate, + address walletId ) internal - nonReentrant returns(bool) { uint callValue = 0; @@ -1019,19 +993,19 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { uint feeInWei = src == TOMO_TOKEN_ADDRESS ? callValue : expectedDestAmount; feeInWei = feeInWei * feeForReserve[reserve] / 10000; // feePercent = 25 -> fee = 25/10000 = 0.25% - uint expectedNetworkBalance = address(this).balance; + uint expectedTomoBal = address(this).balance; if (src == TOMO_TOKEN_ADDRESS) { // callValue amount of Tomo will be transfered to reserve - require(expectedNetworkBalance >= callValue); - expectedNetworkBalance -= callValue; + require(expectedTomoBal >= callValue); + expectedTomoBal -= callValue; } if (address(this) == destAddress && dest == TOMO_TOKEN_ADDRESS) { - // expectedDestAmount of Tomo will be transfered to destAdress (which is also network) - expectedNetworkBalance += expectedDestAmount; + // expectedDestAmount of Tomo will be transfered to destAdress + expectedTomoBal += expectedDestAmount; } // receive feeInWei amount of Tomo as fee - expectedNetworkBalance += feeInWei; + expectedTomoBal += feeInWei; // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, feeInWei, validate), "doReserveTrade: reserve trade failed"); @@ -1046,7 +1020,14 @@ contract Network is Withdrawable, Utils2, NetworkInterface, ReentrancyGuard { } // Expected to receive exact amount fee in TOMO - require(address(this).balance == expectedNetworkBalance); + require(address(this).balance == expectedTomoBal); + + if (feeSharing != address(0)) { + require(address(this).balance >= feeInWei); + // transfer fee to feeSharing + require(feeSharing.handleFees.value(feeInWei)(walletId)); + } + return true; } diff --git a/contracts/mockContracts/GenerousNetwork.sol b/contracts/mockContracts/GenerousNetwork.sol index 5fb1d15..059afae 100644 --- a/contracts/mockContracts/GenerousNetwork.sol +++ b/contracts/mockContracts/GenerousNetwork.sol @@ -63,7 +63,8 @@ contract GenerousNetwork is Network { weiAmount, ReserveInterface(rateResult.reserve1), rateResult.rateSrcToTomo, - true)); + true, + tradeInput.walletId)); //Eth to dest require(doReserveTrade( @@ -74,28 +75,8 @@ contract GenerousNetwork is Network { actualDestAmount, ReserveInterface(rateResult.reserve2), rateResult.rateTomoToDest, - true)); - - - uint totalFeeInWei = 0; - - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; - } - - if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; - } - - if (totalFeeInWei > 0) { - uint balanceBefore = address(this).balance; - require(balanceBefore >= totalFeeInWei); - require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - totalFeeInWei); - } + true, + tradeInput.walletId)); return (actualDestAmount); } diff --git a/contracts/mockContracts/MaliciousNetwork.sol b/contracts/mockContracts/MaliciousNetwork.sol index d399f1a..e2cfdd1 100644 --- a/contracts/mockContracts/MaliciousNetwork.sol +++ b/contracts/mockContracts/MaliciousNetwork.sol @@ -59,7 +59,8 @@ contract MaliciousNetwork is Network { weiAmount, ReserveInterface(rateResult.reserve1), rateResult.rateSrcToTomo, - true)); + true, + tradeInput.walletId)); //Eth to dest require(doReserveTrade( @@ -70,27 +71,8 @@ contract MaliciousNetwork is Network { actualDestAmount, ReserveInterface(rateResult.reserve2), rateResult.rateTomoToDest, - true)); - - uint totalFeeInWei = 0; - - if (tradeInput.src != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve1] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve1] / 10000; - } - - if (tradeInput.dest != TOMO_TOKEN_ADDRESS && feeSharing != address(0) && feeForReserve[rateResult.reserve2] > 0) { - //not a "fake" trade tomo to tomo - totalFeeInWei += weiAmount * feeForReserve[rateResult.reserve2] / 10000; - } - - if (totalFeeInWei > 0) { - uint balanceBefore = address(this).balance; - require(balanceBefore >= totalFeeInWei); - require(feeSharing.handleFees.value(totalFeeInWei)(tradeInput.walletId)); - uint balanceAfter = address(this).balance; - require(balanceAfter == balanceBefore - totalFeeInWei); - } + true, + tradeInput.walletId)); return (actualDestAmount - myFeeWei); } @@ -117,7 +99,8 @@ contract MaliciousNetwork is Network { uint expectedDestAmount, ReserveInterface reserve, uint conversionRate, - bool validate + bool validate, + address walletId ) internal returns(bool) @@ -155,6 +138,12 @@ contract MaliciousNetwork is Network { } } + if (feeSharing != address(0)) { + require(address(this).balance >= feeInWei); + // transfer fee to feeSharing + require(feeSharing.handleFees.value(feeInWei)(walletId)); + } + return true; } } diff --git a/contracts/mockContracts/MaliciousNetwork2.sol b/contracts/mockContracts/MaliciousNetwork2.sol index 773fd54..2e1472d 100644 --- a/contracts/mockContracts/MaliciousNetwork2.sol +++ b/contracts/mockContracts/MaliciousNetwork2.sol @@ -34,7 +34,8 @@ contract MaliciousNetwork2 is Network { uint expectedDestAmount, ReserveInterface reserve, uint conversionRate, - bool validate + bool validate, + address walletId ) internal returns(bool) @@ -73,6 +74,12 @@ contract MaliciousNetwork2 is Network { } } + if (feeSharing != address(0)) { + require(address(this).balance >= feeInWei); + // transfer fee to feeSharing + require(feeSharing.handleFees.value(feeInWei)(walletId)); + } + return true; } } diff --git a/test/network.js b/test/network.js index 65134ab..caeb773 100644 --- a/test/network.js +++ b/test/network.js @@ -2714,240 +2714,242 @@ contract('Network', function(accounts) { log("average gas usage " + numTrades + " buys. token to token: " + avgGas.floor().valueOf()); }); }); - // describe("transaction fee", function() { - // it("should test set fee percent for added reserve and not for not added reserve", async function () { - // let feePercent = 25; - // await network.setFeePercent(reserve1.address, feePercent, {from: admin}); - // // console.log("Set fee success"); - // let recordedFeePercent = await network.feeForReserve(reserve1.address); - // // console.log("Get fee percent: " + recordedFeePercent); - // assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve1 is not set correctly"); - // - // await network.setFeePercent(reserve2.address, feePercent, {from: admin}); - // recordedFeePercent = await network.feeForReserve(reserve2.address); - // assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve2 is not set correctly"); - // - // // remove reserve3 - // await network.addReserve(reserve3.address, false, {from: admin}); - // - // // fee should be 0 as we have set it to 0 - // recordedFeePercent = await network.feeForReserve(reserve3.address); - // assert.equal(recordedFeePercent, 0, "1. Fee percent for reserve3 is not set correctly"); - // - // try { - // await network.setFeePercent(reserve3.address, feePercent, {from: admin}); - // assert.equal(false, "expect to throw error the line above"); - // } catch (e) { - // assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); - // } - // - // recordedFeePercent = await network.feeForReserve(reserve3.address); - // assert.equal(0, (new BigNumber(recordedFeePercent)).valueOf(), "2. Fee percent for reserve3 is not set correctly"); - // - // await network.setFeePercent(reserve1.address, feePercent, {from: admin}); - // }); - // - // it("Should test default fee holder is network and receive fee as expected", async function() { - // let tokenInd = 0; - // let token = tokens[tokenInd]; //choose some token - // let amountTwei = 4000; - // let maxDestAmount = 100000000; - // - // let feeHolder = await network.feeSharing(); - // assert.equal(feeHolder, zeroAddress, "Fee holder should be zero address"); - // - // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - // let minRate = rates[0].valueOf(); - // - // // transfer funds to user and approve funds to network - // await token.transfer(network.address, amountTwei); - // // await token.approve(network.address, amountTwei, {from:user1}) - // - // let networkBalBefore = await Helper.getBalancePromise(network.address); - // //perform full amount trade. see token balance on user 1 zero - // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, - // minRate, walletId, {from:networkProxy}); - // - // let feePercent = 25; - // let tokenDecimal = await token.decimals(); - // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - // - // let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); - // - // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - // - // let networkBalAfter = await Helper.getBalancePromise(network.address); - // - // const expectedDiffInPct = new BigNumber(0.03); // diff 3% - // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - // }); - // - // it("Should test receive fee as expected when network is the dest address", async function() { - // let tokenInd = 0; - // let token = tokens[tokenInd]; //choose some token - // let amountTwei = 4000; - // let maxDestAmount = 100000000; - // - // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - // let minRate = rates[0].valueOf(); - // - // // transfer funds to user and approve funds to network - // await token.transfer(network.address, amountTwei); - // // await token.approve(network.address, amountTwei, {from:user1}) - // - // let networkBalBefore = await Helper.getBalancePromise(network.address); - // //perform full amount trade. see token balance on user 1 zero - // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, network.address, maxDestAmount, - // minRate, walletId, {from:networkProxy}); - // - // let feePercent = 25; - // let tokenDecimal = await token.decimals(); - // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - // - // let expectedWeiAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); - // let expectedFee = new BigNumber(expectedWeiAmount).mul(feePercent).div(10000).round(); - // - // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedWeiAmount); - // - // let networkBalAfter = await Helper.getBalancePromise(network.address); - // - // const expectedDiffInPct = new BigNumber(0.03); // diff 3% - // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - // }); - // - // it("Should test receive fee as expected for buy transaction", async function() { - // let tokenInd = 0; - // let token = tokens[tokenInd]; //choose some token - // let amountTwei = 4000; - // let maxDestAmount = 100000000; - // - // let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); - // let minRate = rates[0].valueOf(); - // - // let networkBalBefore = await Helper.getBalancePromise(network.address); - // //perform full amount trade. see token balance on user 1 zero - // let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, - // minRate, walletId, {from:networkProxy, value: 4000}); - // - // let feePercent = 25; - // - // let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); - // - // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - // - // let networkBalAfter = await Helper.getBalancePromise(network.address); - // - // const expectedDiffInPct = new BigNumber(0.03); // diff 3% - // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - // }); - // - // it("Should test can init fee holder, set fee holder to network and receive fee as expected", async function() { - // let feeSharing = await FeeSharing.new(admin, network); - // - // try { - // await network.setFeeSharing(zeroAddress, {from: admin}); - // assert(false, "throw was expected in line above.") - // } catch(e){ - // assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); - // } - // - // await network.setFeeSharing(feeSharing, {from: admin}); - // console.log("Successfully set fee holder address"); - // let feeHolder = await network.feeSharing(); - // assert.equal(feeHolder.toLowerCase(), feeSharing.toLowerCase(), "Fee holder is not correct"); - // - // let tokenInd = 0; - // let token = tokens[tokenInd]; //choose some token - // let amountTwei = 4000; - // let maxDestAmount = 100000000; - // - // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - // let minRate = rates[0].valueOf(); - // - // // transfer funds to user and approve funds to network - // await token.transfer(network.address, amountTwei); - // // await token.approve(network.address, amountTwei, {from:user1}) - // - // let networkBalBefore = await Helper.getBalancePromise(feeHolder); - // //perform full amount trade. see token balance on user 1 zero - // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, - // minRate, walletId, {from:networkProxy}); - // - // let feePercent = 25; - // let tokenDecimal = await token.decimals(); - // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - // - // let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); - // - // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - // - // let networkBalAfter = await Helper.getBalancePromise(feeHolder); - // - // const expectedDiffInPct = new BigNumber(0.03); // diff 3% - // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - // }); - // - // it("should TOMO balance changes correctly for fee holder if it is receiver of swap tx", async function() { - // let tokenInd = 0; - // let token = tokens[tokenInd]; //choose some token - // let amountTwei = 4000; - // let maxDestAmount = 100000000; - // - // let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); - // let minRate = rates[0].valueOf(); - // - // // transfer funds to user and approve funds to network - // await token.transfer(network.address, amountTwei); - // // await token.approve(network.address, amountTwei, {from:user1}) - // - // let networkBalBefore = await Helper.getBalancePromise(fixedFeeHolder); - // //perform full amount trade. see token balance on user 1 zero - // let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, fixedFeeHolder, maxDestAmount, - // minRate, walletId, {from:networkProxy}); - // - // let feePercent = 25; - // let tokenDecimal = await token.decimals(); - // let changeInDecimals = new BigNumber(10).pow(tokenDecimal); - // - // let expectedDestAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); - // let expectedFee = new BigNumber(expectedDestAmount).mul(feePercent).div(10000).round(); - // - // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedDestAmount); - // - // let networkBalAfter = await Helper.getBalancePromise(fixedFeeHolder); - // - // const expectedDiffInPct = new BigNumber(0.03); // diff 3% - // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - // }); - // - // it("Should test receive fee in holder wallet as expected for buy transaction", async function() { - // let tokenInd = 0; - // let token = tokens[tokenInd]; //choose some token - // let amountTwei = 4000; - // let maxDestAmount = 100000000; - // - // let feeHolder = await network.feeSharing(); - // - // let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); - // let minRate = rates[0].valueOf(); - // - // let networkBalBefore = await Helper.getBalancePromise(feeHolder); - // //perform full amount trade. see token balance on user 1 zero - // let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, - // minRate, walletId, {from:networkProxy, value: 4000}); - // - // let feePercent = 25; - // - // let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); - // - // let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); - // - // let networkBalAfter = await Helper.getBalancePromise(feeHolder); - // - // const expectedDiffInPct = new BigNumber(0.03); // diff 3% - // Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); - // }); - // }); + describe("transaction fee", function() { + it("should test set fee percent for added reserve and not for not added reserve", async function () { + let feePercent = 25; + await network.setFeePercent(reserve1.address, feePercent, {from: admin}); + // console.log("Set fee success"); + let recordedFeePercent = await network.feeForReserve(reserve1.address); + // console.log("Get fee percent: " + recordedFeePercent); + assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve1 is not set correctly"); + + await network.setFeePercent(reserve2.address, feePercent, {from: admin}); + recordedFeePercent = await network.feeForReserve(reserve2.address); + assert.equal(recordedFeePercent, feePercent, "Fee percent for reserve2 is not set correctly"); + + // remove reserve3 + await network.addReserve(reserve3.address, false, {from: admin}); + + // fee should be 0 as we have set it to 0 + recordedFeePercent = await network.feeForReserve(reserve3.address); + assert.equal(recordedFeePercent, 0, "1. Fee percent for reserve3 is not set correctly"); + + try { + await network.setFeePercent(reserve3.address, feePercent, {from: admin}); + assert.equal(false, "expect to throw error the line above"); + } catch (e) { + assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); + } + + recordedFeePercent = await network.feeForReserve(reserve3.address); + assert.equal(0, (new BigNumber(recordedFeePercent)).valueOf(), "2. Fee percent for reserve3 is not set correctly"); + + await network.setFeePercent(reserve1.address, feePercent, {from: admin}); + }); + + it("Should test default fee holder is network and receive fee as expected", async function() { + let tokenInd = 0; + let token = tokens[tokenInd]; //choose some token + let amountTwei = 4000; + let maxDestAmount = 100000000; + + let feeHolder = await network.feeSharing(); + assert.equal(feeHolder, zeroAddress, "Fee holder should be zero address"); + + let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + let minRate = rates[0].valueOf(); + + // transfer funds to user and approve funds to network + await token.transfer(network.address, amountTwei); + // await token.approve(network.address, amountTwei, {from:user1}) + + let networkBalBefore = await Helper.getBalancePromise(network.address); + //perform full amount trade. see token balance on user 1 zero + let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, + minRate, walletId, {from:networkProxy}); + + let feePercent = 25; + let tokenDecimal = await token.decimals(); + let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + + let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); + + let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + + let networkBalAfter = await Helper.getBalancePromise(network.address); + + const expectedDiffInPct = new BigNumber(0.03); // diff 3% + Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + }); + + it("Should test receive fee as expected when network is the dest address", async function() { + let tokenInd = 0; + let token = tokens[tokenInd]; //choose some token + let amountTwei = 4000; + let maxDestAmount = 100000000; + + let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + let minRate = rates[0].valueOf(); + + // transfer funds to user and approve funds to network + await token.transfer(network.address, amountTwei); + // await token.approve(network.address, amountTwei, {from:user1}) + + let networkBalBefore = await Helper.getBalancePromise(network.address); + //perform full amount trade. see token balance on user 1 zero + let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, network.address, maxDestAmount, + minRate, walletId, {from:networkProxy}); + + let feePercent = 25; + let tokenDecimal = await token.decimals(); + let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + + let expectedWeiAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); + let expectedFee = new BigNumber(expectedWeiAmount).mul(feePercent).div(10000).round(); + + let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedWeiAmount); + + let networkBalAfter = await Helper.getBalancePromise(network.address); + + const expectedDiffInPct = new BigNumber(0.03); // diff 3% + Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + }); + + it("Should test receive fee as expected for buy transaction", async function() { + let tokenInd = 0; + let token = tokens[tokenInd]; //choose some token + let amountTwei = 4000; + let maxDestAmount = 100000000; + + let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); + let minRate = rates[0].valueOf(); + + let networkBalBefore = await Helper.getBalancePromise(network.address); + //perform full amount trade. see token balance on user 1 zero + let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, + minRate, walletId, {from:networkProxy, value: 4000}); + + let feePercent = 25; + + let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); + + let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + + let networkBalAfter = await Helper.getBalancePromise(network.address); + + const expectedDiffInPct = new BigNumber(0.03); // diff 3% + Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + }); + + it("Should test can init fee holder, set fee holder to network and receive fee as expected", async function() { + console.log("Init fee sharing"); + let feeSharing = await FeeSharing.new(admin, network); + console.log("Set fee sharing"); + try { + await network.setFeeSharing(zeroAddress, {from: admin}); + assert(false, "throw was expected in line above.") + } catch(e){ + assert(Helper.isRevertErrorMessage(e), "expected revert but got: " + e); + } + console.log("Done settings fee sharing"); + + await network.setFeeSharing(feeSharing, {from: admin}); + console.log("Successfully set fee holder address"); + let feeHolder = await network.feeSharing(); + assert.equal(feeHolder.toLowerCase(), feeSharing.toLowerCase(), "Fee holder is not correct"); + + let tokenInd = 0; + let token = tokens[tokenInd]; //choose some token + let amountTwei = 4000; + let maxDestAmount = 100000000; + + let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + let minRate = rates[0].valueOf(); + + // transfer funds to user and approve funds to network + await token.transfer(network.address, amountTwei); + // await token.approve(network.address, amountTwei, {from:user1}) + + let networkBalBefore = await Helper.getBalancePromise(feeHolder); + //perform full amount trade. see token balance on user 1 zero + let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, user2, maxDestAmount, + minRate, walletId, {from:networkProxy}); + + let feePercent = 25; + let tokenDecimal = await token.decimals(); + let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + + let expectedFee = new BigNumber(feePercent * amountTwei).mul(rates[0]).div(10000).div(changeInDecimals).round(); + + let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + + let networkBalAfter = await Helper.getBalancePromise(feeHolder); + + const expectedDiffInPct = new BigNumber(0.03); // diff 3% + Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + }); + + it("should TOMO balance changes correctly for fee holder if it is receiver of swap tx", async function() { + let tokenInd = 0; + let token = tokens[tokenInd]; //choose some token + let amountTwei = 4000; + let maxDestAmount = 100000000; + + let rates = await network.getExpectedRate(tokenAdd[tokenInd], ethAddress, amountTwei); + let minRate = rates[0].valueOf(); + + // transfer funds to user and approve funds to network + await token.transfer(network.address, amountTwei); + // await token.approve(network.address, amountTwei, {from:user1}) + + let networkBalBefore = await Helper.getBalancePromise(fixedFeeHolder); + //perform full amount trade. see token balance on user 1 zero + let txData = await network.swap(user1, tokenAdd[tokenInd], amountTwei, ethAddress, fixedFeeHolder, maxDestAmount, + minRate, walletId, {from:networkProxy}); + + let feePercent = 25; + let tokenDecimal = await token.decimals(); + let changeInDecimals = new BigNumber(10).pow(tokenDecimal); + + let expectedDestAmount = new BigNumber(amountTwei).mul(rates[0]).div(changeInDecimals).round(); + let expectedFee = new BigNumber(expectedDestAmount).mul(feePercent).div(10000).round(); + + let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee).plus(expectedDestAmount); + + let networkBalAfter = await Helper.getBalancePromise(fixedFeeHolder); + + const expectedDiffInPct = new BigNumber(0.03); // diff 3% + Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + }); + + it("Should test receive fee in holder wallet as expected for buy transaction", async function() { + let tokenInd = 0; + let token = tokens[tokenInd]; //choose some token + let amountTwei = 4000; + let maxDestAmount = 100000000; + + let feeHolder = await network.feeSharing(); + + let rates = await network.getExpectedRate(ethAddress, tokenAdd[tokenInd], amountTwei); + let minRate = rates[0].valueOf(); + + let networkBalBefore = await Helper.getBalancePromise(feeHolder); + //perform full amount trade. see token balance on user 1 zero + let txData = await network.swap(user1, ethAddress, amountTwei, tokenAdd[tokenInd], user2, maxDestAmount, + minRate, walletId, {from:networkProxy, value: 4000}); + + let feePercent = 25; + + let expectedFee = new BigNumber(feePercent * amountTwei).div(10000).round(); + + let expectedBalanceAfter = new BigNumber(networkBalBefore).plus(expectedFee); + + let networkBalAfter = await Helper.getBalancePromise(feeHolder); + + const expectedDiffInPct = new BigNumber(0.03); // diff 3% + Helper.assertAbsDiff(networkBalAfter.valueOf(), expectedBalanceAfter.valueOf(), expectedDiffInPct); + }); + }); }); function convertRateToConversionRatesRate (baseRate) { diff --git a/token_ropsten_configs.json b/token_ropsten_configs.json index e7b27c0..9e31760 100644 --- a/token_ropsten_configs.json +++ b/token_ropsten_configs.json @@ -1,5 +1,5 @@ { - "networkProxy": "0xd9a5478ccf6b74a9c8b992c6a634cc1bfbc2f1bc", + "networkProxy": "0xbbe372fe629ea83b36c2272af2ea104ea74810d0", "networkID": 3, "chainName": "Ropsten", "ethScanUrl": "https://ropsten.etherscan.io/", @@ -18,7 +18,7 @@ "name": "Mike Token", "icon": "mtk", "symbol": "MTK", - "reserve": "0xfb75fcca6e6570df906ea5a55c5c791bb98b3dd6" + "reserve": "0x73f232b1026e18e837dc218ad03da085f05d1337" }, "CTC": { "address": "0x18098c678ed6277d4922ddc069e4ddc39f99ecf7", @@ -26,7 +26,7 @@ "name": "ChainTEX Coin", "icon": "ctc", "symbol": "CTC", - "reserve": "0x3eec5a25118a0059c438b8b1c8bb93fda96aff46" + "reserve": "0xf258f7d2621a22f374849e0ae8cc5a06574a83d4" } }, "connections": {