diff --git a/contracts/teller-network/Escrow.sol b/contracts/teller-network/Escrow.sol index 1e134fa1..bb7d76dd 100644 --- a/contracts/teller-network/Escrow.sol +++ b/contracts/teller-network/Escrow.sol @@ -50,7 +50,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { TradeType tradeType; EscrowStatus status; address payable buyer; - address arbitrator; + address payable arbitrator; } enum TradeType {FIAT, CRYPTO} @@ -180,7 +180,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { if (token != address(0)) { require(msg.value == 0, "Cannot send ETH with token address different from 0"); ERC20Token erc20token = ERC20Token(token); - require(erc20token.transferFrom(_from, address(this), _tokenAmount), "Unsuccessful token transfer"); + require(erc20token.transferFrom(_from, address(this), _tokenAmount), "Unsuccessful token transfer fund"); } payFee(_from, _escrowId, _tokenAmount, token); @@ -220,7 +220,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { uint _assetPrice ) internal returns(uint escrowId) { address seller; - address arbitrator; + address payable arbitrator; bool deleted; (,,,,seller, arbitrator, deleted) = metadataStore.offer(_offerId); @@ -257,7 +257,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { EscrowStatus mStatus = transactions[_escrowId].status; require(metadataStore.getOfferOwner(transactions[_escrowId].offerId) == get_sender(), "Only the seller can release the escrow"); require(mStatus == EscrowStatus.PAID || mStatus == EscrowStatus.FUNDED, "Invalid transaction status"); - _release(_escrowId, transactions[_escrowId]); + _release(_escrowId, transactions[_escrowId], false); } /** @@ -265,16 +265,16 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { * @param _escrowId Id of the escrow * @param trx EscrowTransaction with data of transaction to be released */ - function _release(uint _escrowId, EscrowTransaction storage trx) internal { + function _release(uint _escrowId, EscrowTransaction storage trx, bool isDispute) internal { trx.status = EscrowStatus.RELEASED; address token = metadataStore.getAsset(trx.offerId); if(token == address(0)){ - trx.buyer.transfer(trx.tokenAmount); // TODO: transfer fee to Status? + trx.buyer.transfer(trx.tokenAmount); } else { - ERC20Token erc20token = ERC20Token(token); - require(erc20token.transfer(trx.buyer, trx.tokenAmount), "Couldn't transfer funds"); + require(ERC20Token(token).transfer(trx.buyer, trx.tokenAmount), "Couldn't transfer funds"); } + releaseFee(trx.arbitrator, trx.tokenAmount, token, isDispute); emit Released(_escrowId, block.timestamp); } @@ -362,7 +362,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { } } - _cancel(_escrowId, seller, trx); + _cancel(_escrowId, seller, trx, false); } /** @@ -370,10 +370,15 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { * @param _escrowId Id of the escrow * @param trx EscrowTransaction with details of transaction to be marked as canceled */ - function _cancel(uint _escrowId, address payable _seller, EscrowTransaction storage trx) internal { + function _cancel(uint _escrowId, address payable _seller, EscrowTransaction storage trx, bool isDispute) internal { if(trx.status == EscrowStatus.FUNDED){ address token = metadataStore.getAsset(trx.offerId); - uint amount = trx.tokenAmount + getFeeFromAmount(trx.tokenAmount); + uint amount; + if (isDispute) { + amount = trx.tokenAmount; + } else { + amount = trx.tokenAmount + getValueOffMillipercent(trx.tokenAmount, feeMilliPercent); + } if(token == address(0)){ _seller.transfer(amount); } else { @@ -398,7 +403,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { require(trx.status == EscrowStatus.FUNDED, "Cannot withdraw from escrow in a stage different from FUNDED. Open a case"); address payable seller = metadataStore.getOfferOwner(trx.offerId); - _cancel(_escrowId, seller, trx); + _cancel(_escrowId, seller, trx, false); } /** @@ -481,9 +486,10 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient { require(trx.buyer != _arbitrator && seller != _arbitrator, "Arbitrator cannot be part of transaction"); if(_releaseFunds){ - _release(_escrowId, trx); + _release(_escrowId, trx, true); } else { - _cancel(_escrowId, seller, trx); + _cancel(_escrowId, seller, trx, true); + releaseFee(trx.arbitrator, trx.tokenAmount, metadataStore.getAsset(trx.offerId), true); } } diff --git a/contracts/teller-network/Fees.sol b/contracts/teller-network/Fees.sol index ddb2a4e1..0191dcc9 100644 --- a/contracts/teller-network/Fees.sol +++ b/contracts/teller-network/Fees.sol @@ -16,7 +16,6 @@ contract Fees is Ownable { event FeeDestinationChanged(address payable); event FeeMilliPercentChanged(uint amount); event FeesWithdrawn(uint amount, address token); - event MyEvent(uint fees, address destination, uint balance); /** * @param _feeDestination Address to send the fees once withdraw is called @@ -51,26 +50,42 @@ contract Fees is Ownable { } /** - * @notice Withdraw fees by sending them to the fee destination address - */ - function withdrawFees(address _tokenAddress) public { - uint fees = feeTokenBalances[_tokenAddress]; - feeTokenBalances[_tokenAddress] = 0; - if (_tokenAddress == address(0)) { - require(address(this).balance >= fees, "Not enough balance"); - feeDestination.transfer(fees); - emit MyEvent(fees, feeDestination, address(this).balance); + * @notice Release fee to fee destination and arbitrator + * @param _arbitrator Arbitrator address to transfer fee to + * @param _value Value sold in the escrow + * @param _isDispute Boolean telling if it was from a dispute. With a dispute, the arbitrator gets more + */ + function releaseFee(address payable _arbitrator, uint _value, address _tokenAddress, bool _isDispute) internal { + uint _milliPercentToArbitrator; + if (_isDispute) { + _milliPercentToArbitrator = 100000; // 100% } else { - ERC20Token tokenToWithdraw = ERC20Token(_tokenAddress); - require(tokenToWithdraw.transfer(feeDestination, fees), "Error transferring fees"); + _milliPercentToArbitrator = 10000; // 10% + } + + uint feeAmount = getValueOffMillipercent(_value, feeMilliPercent); + uint arbitratorValue = getValueOffMillipercent(feeAmount, _milliPercentToArbitrator); + uint destinationValue = feeAmount - arbitratorValue; + + if (_tokenAddress != address(0)) { + ERC20Token tokenToPay = ERC20Token(_tokenAddress); + // Arbitrator transfer + require(tokenToPay.transfer(_arbitrator, arbitratorValue), "Unsuccessful token transfer abri"); + if (destinationValue > 0) { + require(tokenToPay.transfer(feeDestination, destinationValue), "Unsuccessful token transfer destination"); + } + } else { + _arbitrator.transfer(arbitratorValue); + if (destinationValue > 0) { + feeDestination.transfer(destinationValue); + } } - emit FeesWithdrawn(fees, _tokenAddress); } - function getFeeFromAmount(uint _value) internal returns(uint) { + function getValueOffMillipercent(uint _value, uint _milliPercent) internal returns(uint) { // To get the factor, we divide like 100 like a normal percent, but we multiply that by 1000 because it's a milliPercent // Eg: 1 % = 1000 millipercent => Factor is 0.01, so 1000 divided by 100 * 1000 - return (_value * feeMilliPercent) / (100 * 1000); + return (_value * _milliPercent) / (100 * 1000); } /** @@ -85,12 +100,12 @@ contract Fees is Ownable { if (feePaid[_id]) return; feePaid[_id] = true; - uint feeAmount = getFeeFromAmount(_value); + uint feeAmount = getValueOffMillipercent(_value, feeMilliPercent); feeTokenBalances[_tokenAddress] += feeAmount; if (_tokenAddress != address(0)) { ERC20Token tokenToPay = ERC20Token(_tokenAddress); - require(tokenToPay.transferFrom(_from, address(this), feeAmount), "Unsuccessful token transfer"); + require(tokenToPay.transferFrom(_from, address(this), feeAmount), "Unsuccessful token transfer pay fee"); } else { require(msg.value == (_value + feeAmount), "ETH amount is required"); } diff --git a/contracts/teller-network/MetadataStore.sol b/contracts/teller-network/MetadataStore.sol index 0518087b..5a36301f 100644 --- a/contracts/teller-network/MetadataStore.sol +++ b/contracts/teller-network/MetadataStore.sol @@ -23,7 +23,7 @@ contract MetadataStore is MessageSigned { address asset; string currency; address payable owner; - address arbitrator; + address payable arbitrator; bool deleted; } @@ -161,12 +161,12 @@ contract MetadataStore is MessageSigned { uint _nonce ) public returns(address payable _user) { _user = address(uint160(getSigner(_username, _statusContactCode, _nonce, _signature))); - + require(_nonce == user_nonce[_user], "Invalid nonce"); - + user_nonce[_user]++; _addOrUpdateUser(_user, _statusContactCode, _location, _username); - + return _user; } @@ -204,7 +204,7 @@ contract MetadataStore is MessageSigned { string memory _username, PaymentMethods[] memory _paymentMethods, int8 _margin, - address _arbitrator + address payable _arbitrator ) public { require(sellingLicenses.isLicenseOwner(msg.sender), "Not a license owner"); require(arbitrationLicenses.isLicenseOwner(_arbitrator), "Not an arbitrator"); @@ -267,7 +267,7 @@ contract MetadataStore is MessageSigned { int8 margin, PaymentMethods[] memory paymentMethods, address payable owner, - address arbitrator, + address payable arbitrator, bool deleted ) { return ( @@ -307,7 +307,7 @@ contract MetadataStore is MessageSigned { * @param _id Offer id * @return Arbitrator address */ - function getArbitrator(uint256 _id) public view returns (address) { + function getArbitrator(uint256 _id) public view returns (address payable) { return (offers[_id].arbitrator); } diff --git a/shared.chains.json b/shared.chains.json index 3a79e714..caa75611 100644 --- a/shared.chains.json +++ b/shared.chains.json @@ -164,6 +164,22 @@ "0x243b1fc854f17cfe515881f5d2fa88d13e335761be7ff309a3bcbda85c5c0657": { "name": "Escrow", "address": "0x6b0Fe7fD0195617d611D4d7C48Fc9B084c66Ab0F" + }, + "0x7dcace6b87f6fdb9bbc007f97935903616540920c6e2ad29d775da0ad92f5ba6": { + "name": "SellerLicense", + "address": "0x587c8B39e9b93e157aA6868FBF026eadcD092806" + }, + "0x875d12c948438b63284354c3f5b5bb3868124e3adbb7b30a2d999ffda8fd59ed": { + "name": "ArbitrationLicense", + "address": "0x7DeECdA728F9291fEee06a8141e84b26B5bb0496" + }, + "0x46e306e5174128c9c45ae57df1f61547469fcc4601fd5d7051658427ca1d74a9": { + "name": "MetadataStore", + "address": "0xaA4Bcf44B00dA7Bb43522b21b54dAE9CB82634E4" + }, + "0xf60e5d23e992bee7f624b9d45b5aa8649119c0175948eef360e5759b33fac38c": { + "name": "Escrow", + "address": "0xb15C531dc6284E566e0010B458CEb4039bfdf175" } } } diff --git a/src/js/utils/networks.js b/src/js/utils/networks.js index 0a6655c9..042e0f84 100644 --- a/src/js/utils/networks.js +++ b/src/js/utils/networks.js @@ -584,7 +584,7 @@ export const Tokens = { { symbol: 'SNT', name: "Status Network Token", - address: SNT.address.toLowerCase(), + address: SNT.address && SNT.address.toLowerCase(), decimals: 18 }, { @@ -596,13 +596,13 @@ export const Tokens = { { symbol: 'DAI', name: 'DAI', - address: DAI.address.toLowerCase(), + address: DAI.address && DAI.address.toLowerCase(), decimals: 18 }, { symbol: 'MKR', name: "MKR", - address: MKR.address.toLowerCase(), + address: MKR.address && MKR.address.toLowerCase(), decimals: 18 } ], diff --git a/test/escrow_spec.js b/test/escrow_spec.js index 56ff5b24..4acf714f 100644 --- a/test/escrow_spec.js +++ b/test/escrow_spec.js @@ -87,7 +87,7 @@ contract("Escrow", function() { const {toBN} = web3.utils; - const tradeAmount = 100; + const tradeAmount = 1000000; const feeAmount = Math.round(tradeAmount * (feePercent / 100)); // util @@ -250,8 +250,7 @@ contract("Escrow", function() { } }); - it("A seller can cancel their ETH escrows", async () => { - await StandardToken.methods.approve(Escrow.options.address, feeAmount).send({from: accounts[0]}); + it("A seller can cancel their expired ETH escrows", async () => { receipt = await Escrow.methods.create_and_fund(accounts[1], ethOfferId, tradeAmount, expirationTime, FIAT, 140).send({from: accounts[0], value: tradeAmount + feeAmount}); created = receipt.events.Created; escrowId = created.returnValues.escrowId; @@ -267,7 +266,7 @@ contract("Escrow", function() { assert.equal(escrow.status, ESCROW_CANCELED, "Should have been canceled"); }); - it("A seller can cancel their token escrows", async () => { + it("A seller can cancel their expired token escrows and gets back the fee", async () => { await StandardToken.methods.mint(accounts[0], tradeAmount + feeAmount).send(); await StandardToken.methods.approve(Escrow.options.address, tradeAmount + feeAmount).send({from: accounts[0]}); @@ -409,7 +408,8 @@ contract("Escrow", function() { const contractBalanceAfterEscrow = await StandardToken.methods.balanceOf(Escrow.options.address).call(); assert.equal(toBN(escrow.tokenAmount).add(toBN(buyerBalanceBeforeEscrow)), buyerBalanceAfterEscrow, "Invalid buyer balance"); - assert.equal(contractBalanceAfterEscrow, toBN(contractBalanceBeforeEscrow).sub(toBN(tradeAmount)), "Invalid contract balance"); + const after = toBN(contractBalanceBeforeEscrow).sub(toBN(tradeAmount).add(toBN(feeAmount))); + assert.equal(contractBalanceAfterEscrow, after, "Invalid contract balance"); }); it("Released escrow cannot be released again", async() => { @@ -743,13 +743,17 @@ contract("Escrow", function() { assert.strictEqual(arbitrationCanceled.returnValues.escrowId, escrowId, "Invalid escrowId"); }); - it("should transfer to buyer if case is solved in their favor", async() => { + it("should transfer to buyer if case is solved in their favor and the fee goes to arbitrator", async() => { + const arbitratorBalanceBefore = await web3.eth.getBalance(arbitrator); await Escrow.methods.pay(escrowId).send({from: accounts[1]}); await Escrow.methods.openCase(escrowId, 'Motive').send({from: accounts[1]}); receipt = await Escrow.methods.setArbitrationResult(escrowId, ARBITRATION_SOLVED_BUYER).send({from: arbitrator}); const released = receipt.events.Released; assert(!!released, "Released() not triggered"); + + const arbitratorBalanceAfter = await web3.eth.getBalance(arbitrator); + assert.strictEqual((toBN(arbitratorBalanceAfter).add(toBN(receipt.gasUsed))).toString(), (toBN(arbitratorBalanceBefore).add(toBN(feeAmount))).toString(), 'Arbitrator balance should have the fee'); }); it("should cancel escrow if case is solved in favor of the seller", async() => { @@ -799,22 +803,6 @@ contract("Escrow", function() { assert.strictEqual(parseInt(ethFeeBalance, 10), parseInt(ethFeeBalanceBefore, 10) + feeAmount, "Fee balance did not increase"); assert.strictEqual(parseInt(totalEthAfter, 10), parseInt(totalEthBefore, 10) + feeAmount + tradeAmount, "Total balance did not increase"); }); - - it("fees can be withdrawn to burn address", async() => { - const ethFeeBalanceBefore = await Escrow.methods.feeTokenBalances(TestUtils.zeroAddress).call(); - const totalEthBefore = await web3.eth.getBalance(Escrow.options.address); - const destAddressBalanceBefore = await web3.eth.getBalance(await Escrow.methods.feeDestination().call()); - - receipt = await Escrow.methods.withdrawFees(TestUtils.zeroAddress).send({from: accounts[0]}); - - const ethFeeBalanceAfter = await Escrow.methods.feeTokenBalances(TestUtils.zeroAddress).call(); - const totalEthAfter = await web3.eth.getBalance(Escrow.options.address); - const destAddressBalanceAfter = await web3.eth.getBalance(await Escrow.methods.feeDestination().call()); - - assert.strictEqual(toBN(totalEthAfter).toString(), (toBN(totalEthBefore).sub(toBN(ethFeeBalanceBefore)).toString()), "Invalid contract balance"); - assert.strictEqual(parseInt(ethFeeBalanceAfter, 10), 0, "Invalid fee balance"); - assert.strictEqual(toBN(destAddressBalanceAfter).toString(), (toBN(destAddressBalanceBefore).add(toBN(ethFeeBalanceBefore)).toString()), "Invalid address balance"); - }); }); describe("Other operations", async () => {