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:
commit
512cb7f5ec
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
Loading…
Reference in New Issue