Merge pull request #326 from status-im/feat/send-fees

Send fees to the arbiter and /or the Staking pool
This commit is contained in:
Jonathan Rainville 2019-06-27 10:18:07 -04:00 committed by GitHub
commit 512cb7f5ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 63 deletions

View File

@ -50,7 +50,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient {
TradeType tradeType; TradeType tradeType;
EscrowStatus status; EscrowStatus status;
address payable buyer; address payable buyer;
address arbitrator; address payable arbitrator;
} }
enum TradeType {FIAT, CRYPTO} enum TradeType {FIAT, CRYPTO}
@ -180,7 +180,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient {
if (token != address(0)) { if (token != address(0)) {
require(msg.value == 0, "Cannot send ETH with token address different from 0"); require(msg.value == 0, "Cannot send ETH with token address different from 0");
ERC20Token erc20token = ERC20Token(token); 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); payFee(_from, _escrowId, _tokenAmount, token);
@ -220,7 +220,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient {
uint _assetPrice uint _assetPrice
) internal returns(uint escrowId) { ) internal returns(uint escrowId) {
address seller; address seller;
address arbitrator; address payable arbitrator;
bool deleted; bool deleted;
(,,,,seller, arbitrator, deleted) = metadataStore.offer(_offerId); (,,,,seller, arbitrator, deleted) = metadataStore.offer(_offerId);
@ -257,7 +257,7 @@ contract Escrow is Pausable, MessageSigned, Fees, Arbitrable, RelayRecipient {
EscrowStatus mStatus = transactions[_escrowId].status; EscrowStatus mStatus = transactions[_escrowId].status;
require(metadataStore.getOfferOwner(transactions[_escrowId].offerId) == get_sender(), "Only the seller can release the escrow"); 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"); 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 _escrowId Id of the escrow
* @param trx EscrowTransaction with data of transaction to be released * @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; trx.status = EscrowStatus.RELEASED;
address token = metadataStore.getAsset(trx.offerId); address token = metadataStore.getAsset(trx.offerId);
if(token == address(0)){ if(token == address(0)){
trx.buyer.transfer(trx.tokenAmount); // TODO: transfer fee to Status? trx.buyer.transfer(trx.tokenAmount);
} else { } else {
ERC20Token erc20token = ERC20Token(token); require(ERC20Token(token).transfer(trx.buyer, trx.tokenAmount), "Couldn't transfer funds");
require(erc20token.transfer(trx.buyer, trx.tokenAmount), "Couldn't transfer funds");
} }
releaseFee(trx.arbitrator, trx.tokenAmount, token, isDispute);
emit Released(_escrowId, block.timestamp); 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 _escrowId Id of the escrow
* @param trx EscrowTransaction with details of transaction to be marked as canceled * @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){ if(trx.status == EscrowStatus.FUNDED){
address token = metadataStore.getAsset(trx.offerId); 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)){ if(token == address(0)){
_seller.transfer(amount); _seller.transfer(amount);
} else { } 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"); 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); 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"); require(trx.buyer != _arbitrator && seller != _arbitrator, "Arbitrator cannot be part of transaction");
if(_releaseFunds){ if(_releaseFunds){
_release(_escrowId, trx); _release(_escrowId, trx, true);
} else { } else {
_cancel(_escrowId, seller, trx); _cancel(_escrowId, seller, trx, true);
releaseFee(trx.arbitrator, trx.tokenAmount, metadataStore.getAsset(trx.offerId), true);
} }
} }

View File

@ -16,7 +16,6 @@ contract Fees is Ownable {
event FeeDestinationChanged(address payable); event FeeDestinationChanged(address payable);
event FeeMilliPercentChanged(uint amount); event FeeMilliPercentChanged(uint amount);
event FeesWithdrawn(uint amount, address token); 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 * @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 * @notice Release fee to fee destination and arbitrator
*/ * @param _arbitrator Arbitrator address to transfer fee to
function withdrawFees(address _tokenAddress) public { * @param _value Value sold in the escrow
uint fees = feeTokenBalances[_tokenAddress]; * @param _isDispute Boolean telling if it was from a dispute. With a dispute, the arbitrator gets more
feeTokenBalances[_tokenAddress] = 0; */
if (_tokenAddress == address(0)) { function releaseFee(address payable _arbitrator, uint _value, address _tokenAddress, bool _isDispute) internal {
require(address(this).balance >= fees, "Not enough balance"); uint _milliPercentToArbitrator;
feeDestination.transfer(fees); if (_isDispute) {
emit MyEvent(fees, feeDestination, address(this).balance); _milliPercentToArbitrator = 100000; // 100%
} else { } else {
ERC20Token tokenToWithdraw = ERC20Token(_tokenAddress); _milliPercentToArbitrator = 10000; // 10%
require(tokenToWithdraw.transfer(feeDestination, fees), "Error transferring fees"); }
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 // 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 // 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; if (feePaid[_id]) return;
feePaid[_id] = true; feePaid[_id] = true;
uint feeAmount = getFeeFromAmount(_value); uint feeAmount = getValueOffMillipercent(_value, feeMilliPercent);
feeTokenBalances[_tokenAddress] += feeAmount; feeTokenBalances[_tokenAddress] += feeAmount;
if (_tokenAddress != address(0)) { if (_tokenAddress != address(0)) {
ERC20Token tokenToPay = ERC20Token(_tokenAddress); 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 { } else {
require(msg.value == (_value + feeAmount), "ETH amount is required"); require(msg.value == (_value + feeAmount), "ETH amount is required");
} }

View File

@ -23,7 +23,7 @@ contract MetadataStore is MessageSigned {
address asset; address asset;
string currency; string currency;
address payable owner; address payable owner;
address arbitrator; address payable arbitrator;
bool deleted; bool deleted;
} }
@ -161,12 +161,12 @@ contract MetadataStore is MessageSigned {
uint _nonce uint _nonce
) public returns(address payable _user) { ) public returns(address payable _user) {
_user = address(uint160(getSigner(_username, _statusContactCode, _nonce, _signature))); _user = address(uint160(getSigner(_username, _statusContactCode, _nonce, _signature)));
require(_nonce == user_nonce[_user], "Invalid nonce"); require(_nonce == user_nonce[_user], "Invalid nonce");
user_nonce[_user]++; user_nonce[_user]++;
_addOrUpdateUser(_user, _statusContactCode, _location, _username); _addOrUpdateUser(_user, _statusContactCode, _location, _username);
return _user; return _user;
} }
@ -204,7 +204,7 @@ contract MetadataStore is MessageSigned {
string memory _username, string memory _username,
PaymentMethods[] memory _paymentMethods, PaymentMethods[] memory _paymentMethods,
int8 _margin, int8 _margin,
address _arbitrator address payable _arbitrator
) public { ) public {
require(sellingLicenses.isLicenseOwner(msg.sender), "Not a license owner"); require(sellingLicenses.isLicenseOwner(msg.sender), "Not a license owner");
require(arbitrationLicenses.isLicenseOwner(_arbitrator), "Not an arbitrator"); require(arbitrationLicenses.isLicenseOwner(_arbitrator), "Not an arbitrator");
@ -267,7 +267,7 @@ contract MetadataStore is MessageSigned {
int8 margin, int8 margin,
PaymentMethods[] memory paymentMethods, PaymentMethods[] memory paymentMethods,
address payable owner, address payable owner,
address arbitrator, address payable arbitrator,
bool deleted bool deleted
) { ) {
return ( return (
@ -307,7 +307,7 @@ contract MetadataStore is MessageSigned {
* @param _id Offer id * @param _id Offer id
* @return Arbitrator address * @return Arbitrator address
*/ */
function getArbitrator(uint256 _id) public view returns (address) { function getArbitrator(uint256 _id) public view returns (address payable) {
return (offers[_id].arbitrator); return (offers[_id].arbitrator);
} }

View File

@ -164,6 +164,22 @@
"0x243b1fc854f17cfe515881f5d2fa88d13e335761be7ff309a3bcbda85c5c0657": { "0x243b1fc854f17cfe515881f5d2fa88d13e335761be7ff309a3bcbda85c5c0657": {
"name": "Escrow", "name": "Escrow",
"address": "0x6b0Fe7fD0195617d611D4d7C48Fc9B084c66Ab0F" "address": "0x6b0Fe7fD0195617d611D4d7C48Fc9B084c66Ab0F"
},
"0x7dcace6b87f6fdb9bbc007f97935903616540920c6e2ad29d775da0ad92f5ba6": {
"name": "SellerLicense",
"address": "0x587c8B39e9b93e157aA6868FBF026eadcD092806"
},
"0x875d12c948438b63284354c3f5b5bb3868124e3adbb7b30a2d999ffda8fd59ed": {
"name": "ArbitrationLicense",
"address": "0x7DeECdA728F9291fEee06a8141e84b26B5bb0496"
},
"0x46e306e5174128c9c45ae57df1f61547469fcc4601fd5d7051658427ca1d74a9": {
"name": "MetadataStore",
"address": "0xaA4Bcf44B00dA7Bb43522b21b54dAE9CB82634E4"
},
"0xf60e5d23e992bee7f624b9d45b5aa8649119c0175948eef360e5759b33fac38c": {
"name": "Escrow",
"address": "0xb15C531dc6284E566e0010B458CEb4039bfdf175"
} }
} }
} }

View File

@ -584,7 +584,7 @@ export const Tokens = {
{ {
symbol: 'SNT', symbol: 'SNT',
name: "Status Network Token", name: "Status Network Token",
address: SNT.address.toLowerCase(), address: SNT.address && SNT.address.toLowerCase(),
decimals: 18 decimals: 18
}, },
{ {
@ -596,13 +596,13 @@ export const Tokens = {
{ {
symbol: 'DAI', symbol: 'DAI',
name: 'DAI', name: 'DAI',
address: DAI.address.toLowerCase(), address: DAI.address && DAI.address.toLowerCase(),
decimals: 18 decimals: 18
}, },
{ {
symbol: 'MKR', symbol: 'MKR',
name: "MKR", name: "MKR",
address: MKR.address.toLowerCase(), address: MKR.address && MKR.address.toLowerCase(),
decimals: 18 decimals: 18
} }
], ],

View File

@ -87,7 +87,7 @@ contract("Escrow", function() {
const {toBN} = web3.utils; const {toBN} = web3.utils;
const tradeAmount = 100; const tradeAmount = 1000000;
const feeAmount = Math.round(tradeAmount * (feePercent / 100)); const feeAmount = Math.round(tradeAmount * (feePercent / 100));
// util // util
@ -250,8 +250,7 @@ contract("Escrow", function() {
} }
}); });
it("A seller can cancel their ETH escrows", async () => { it("A seller can cancel their expired ETH escrows", async () => {
await StandardToken.methods.approve(Escrow.options.address, feeAmount).send({from: accounts[0]});
receipt = await Escrow.methods.create_and_fund(accounts[1], ethOfferId, tradeAmount, expirationTime, FIAT, 140).send({from: accounts[0], value: tradeAmount + feeAmount}); 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; created = receipt.events.Created;
escrowId = created.returnValues.escrowId; escrowId = created.returnValues.escrowId;
@ -267,7 +266,7 @@ contract("Escrow", function() {
assert.equal(escrow.status, ESCROW_CANCELED, "Should have been canceled"); 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.mint(accounts[0], tradeAmount + feeAmount).send();
await StandardToken.methods.approve(Escrow.options.address, tradeAmount + feeAmount).send({from: accounts[0]}); 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(); const contractBalanceAfterEscrow = await StandardToken.methods.balanceOf(Escrow.options.address).call();
assert.equal(toBN(escrow.tokenAmount).add(toBN(buyerBalanceBeforeEscrow)), buyerBalanceAfterEscrow, "Invalid buyer balance"); 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() => { it("Released escrow cannot be released again", async() => {
@ -743,13 +743,17 @@ contract("Escrow", function() {
assert.strictEqual(arbitrationCanceled.returnValues.escrowId, escrowId, "Invalid escrowId"); 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.pay(escrowId).send({from: accounts[1]});
await Escrow.methods.openCase(escrowId, 'Motive').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}); receipt = await Escrow.methods.setArbitrationResult(escrowId, ARBITRATION_SOLVED_BUYER).send({from: arbitrator});
const released = receipt.events.Released; const released = receipt.events.Released;
assert(!!released, "Released() not triggered"); 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() => { 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(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"); 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 () => { describe("Other operations", async () => {