Skip to content

Commit

Permalink
set buyer and seller payout tx fee and amount, fix csv export #720
Browse files Browse the repository at this point in the history
  • Loading branch information
woodser committed Dec 7, 2023
1 parent 846a863 commit 4c1b496
Show file tree
Hide file tree
Showing 19 changed files with 434 additions and 192 deletions.
36 changes: 16 additions & 20 deletions core/src/main/java/haveno/core/api/CoreDisputesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ public void resolveDispute(String tradeId, DisputeResult.Winner winner, DisputeR
if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
var loserDispute = loserDisputeOptional.get();
var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
loserDisputeResult.setBuyerPayoutAmountBeforeCost(winnerDisputeResult.getBuyerPayoutAmountBeforeCost());
loserDisputeResult.setSellerPayoutAmountBeforeCost(winnerDisputeResult.getSellerPayoutAmountBeforeCost());
loserDisputeResult.setBuyerPayoutTxFee(winnerDisputeResult.getBuyerPayoutTxFee());
loserDisputeResult.setSellerPayoutTxFee(winnerDisputeResult.getSellerPayoutTxFee());
loserDisputeResult.setBuyerPayoutAmount(winnerDisputeResult.getBuyerPayoutAmount());
loserDisputeResult.setSellerPayoutAmount(winnerDisputeResult.getSellerPayoutAmount());
loserDisputeResult.setSubtractFeeFrom(winnerDisputeResult.getSubtractFeeFrom());
Expand Down Expand Up @@ -217,31 +221,23 @@ public void applyPayoutAmountsToDisputeResult(DisputePayout payout, Dispute disp
BigInteger tradeAmount = contract.getTradeAmount();
disputeResult.setSubtractFeeFrom(DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER);
if (payout == DisputePayout.BUYER_GETS_TRADE_AMOUNT) {
disputeResult.setBuyerPayoutAmount(tradeAmount.add(buyerSecurityDeposit));
disputeResult.setSellerPayoutAmount(sellerSecurityDeposit);
disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit));
disputeResult.setSellerPayoutAmountBeforeCost(sellerSecurityDeposit);
} else if (payout == DisputePayout.BUYER_GETS_ALL) {
disputeResult.setBuyerPayoutAmount(tradeAmount
.add(buyerSecurityDeposit)
.add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser? (see post v1.1.7)
disputeResult.setSellerPayoutAmount(BigInteger.valueOf(0));
disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser? (see post v1.1.7)
disputeResult.setSellerPayoutAmountBeforeCost(BigInteger.valueOf(0));
} else if (payout == DisputePayout.SELLER_GETS_TRADE_AMOUNT) {
disputeResult.setBuyerPayoutAmount(buyerSecurityDeposit);
disputeResult.setSellerPayoutAmount(tradeAmount.add(sellerSecurityDeposit));
disputeResult.setBuyerPayoutAmountBeforeCost(buyerSecurityDeposit);
disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit));
} else if (payout == DisputePayout.SELLER_GETS_ALL) {
disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(0));
disputeResult.setSellerPayoutAmount(tradeAmount
.add(sellerSecurityDeposit)
.add(buyerSecurityDeposit));
disputeResult.setBuyerPayoutAmountBeforeCost(BigInteger.valueOf(0));
disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit).add(buyerSecurityDeposit));
} else if (payout == DisputePayout.CUSTOM) {
if (customWinnerAmount > trade.getWallet().getBalance().longValueExact()) {
throw new RuntimeException("Winner payout is more than the trade wallet's balance");
}
if (customWinnerAmount > trade.getWallet().getBalance().longValueExact()) throw new RuntimeException("Winner payout is more than the trade wallet's balance");
long loserAmount = tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit).subtract(BigInteger.valueOf(customWinnerAmount)).longValueExact();
if (loserAmount < 0) {
throw new RuntimeException("Loser payout cannot be negative");
}
disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount));
disputeResult.setSellerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount));
if (loserAmount < 0) throw new RuntimeException("Loser payout cannot be negative");
disputeResult.setBuyerPayoutAmountBeforeCost(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount));
disputeResult.setSellerPayoutAmountBeforeCost(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount));
disputeResult.setSubtractFeeFrom(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? SubtractFeeFrom.SELLER_ONLY : SubtractFeeFrom.BUYER_ONLY); // winner gets exact amount, loser pays mining fee
}
}
Expand Down
25 changes: 25 additions & 0 deletions core/src/main/java/haveno/core/api/model/TradeInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public class TradeInfo implements Payload {
private final long amount;
private final long buyerSecurityDeposit;
private final long sellerSecurityDeposit;
private final long buyerDepositTxFee;
private final long sellerDepositTxFee;
private final long buyerPayoutTxFee;
private final long sellerPayoutTxFee;
private final long buyerPayoutAmount;
private final long sellerPayoutAmount;
private final String price;
private final String volume;
private final String arbitratorNodeAddress;
Expand Down Expand Up @@ -105,6 +111,12 @@ public TradeInfo(TradeInfoV1Builder builder) {
this.amount = builder.getAmount();
this.buyerSecurityDeposit = builder.getBuyerSecurityDeposit();
this.sellerSecurityDeposit = builder.getSellerSecurityDeposit();
this.buyerDepositTxFee = builder.getBuyerDepositTxFee();
this.sellerDepositTxFee = builder.getSellerDepositTxFee();
this.buyerPayoutTxFee = builder.getBuyerPayoutTxFee();
this.sellerPayoutTxFee = builder.getSellerPayoutTxFee();
this.buyerPayoutAmount = builder.getBuyerPayoutAmount();
this.sellerPayoutAmount = builder.getSellerPayoutAmount();
this.price = builder.getPrice();
this.volume = builder.getVolume();
this.arbitratorNodeAddress = builder.getArbitratorNodeAddress();
Expand Down Expand Up @@ -161,6 +173,13 @@ public static TradeInfo toTradeInfo(Trade trade, String role) {
.withAmount(trade.getAmount().longValueExact())
.withBuyerSecurityDeposit(trade.getBuyer().getSecurityDeposit() == null ? -1 : trade.getBuyer().getSecurityDeposit().longValueExact())
.withSellerSecurityDeposit(trade.getSeller().getSecurityDeposit() == null ? -1 : trade.getSeller().getSecurityDeposit().longValueExact())
.withBuyerDepositTxFee(trade.getBuyer().getDepositTxFee() == null ? -1 : trade.getBuyer().getDepositTxFee().longValueExact())
.withSellerDepositTxFee(trade.getSeller().getDepositTxFee() == null ? -1 : trade.getSeller().getDepositTxFee().longValueExact())
.withBuyerPayoutTxFee(trade.getBuyer().getPayoutTxFee() == null ? -1 : trade.getBuyer().getPayoutTxFee().longValueExact())
.withSellerPayoutTxFee(trade.getSeller().getPayoutTxFee() == null ? -1 : trade.getSeller().getPayoutTxFee().longValueExact())
.withBuyerPayoutAmount(trade.getBuyer().getPayoutAmount() == null ? -1 : trade.getBuyer().getPayoutAmount().longValueExact())
.withSellerPayoutAmount(trade.getSeller().getPayoutAmount() == null ? -1 : trade.getSeller().getPayoutAmount().longValueExact())
.withTotalTxFee(trade.getTotalTxFee().longValueExact())
.withPrice(toPreciseTradePrice.apply(trade))
.withVolume(toRoundedVolume.apply(trade))
.withArbitratorNodeAddress(toArbitratorNodeAddress.apply(trade))
Expand Down Expand Up @@ -204,6 +223,12 @@ public haveno.proto.grpc.TradeInfo toProtoMessage() {
.setAmount(amount)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
.setSellerSecurityDeposit(sellerSecurityDeposit)
.setBuyerDepositTxFee(buyerDepositTxFee)
.setSellerDepositTxFee(sellerDepositTxFee)
.setBuyerPayoutTxFee(buyerPayoutTxFee)
.setSellerPayoutTxFee(sellerPayoutTxFee)
.setBuyerPayoutAmount(buyerPayoutAmount)
.setSellerPayoutAmount(sellerPayoutAmount)
.setPrice(price)
.setTradeVolume(volume)
.setArbitratorNodeAddress(arbitratorNodeAddress)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public final class TradeInfoV1Builder {
private long takerFee;
private long buyerSecurityDeposit;
private long sellerSecurityDeposit;
private long buyerDepositTxFee;
private long sellerDepositTxFee;
private long buyerPayoutTxFee;
private long sellerPayoutTxFee;
private long buyerPayoutAmount;
private long sellerPayoutAmount;
private String makerDepositTxId;
private String takerDepositTxId;
private String payoutTxId;
Expand Down Expand Up @@ -117,6 +123,36 @@ public TradeInfoV1Builder withSellerSecurityDeposit(long sellerSecurityDeposit)
return this;
}

public TradeInfoV1Builder withBuyerDepositTxFee(long buyerDepositTxFee) {
this.buyerDepositTxFee = buyerDepositTxFee;
return this;
}

public TradeInfoV1Builder withSellerDepositTxFee(long sellerDepositTxFee) {
this.sellerDepositTxFee = sellerDepositTxFee;
return this;
}

public TradeInfoV1Builder withBuyerPayoutTxFee(long buyerPayoutTxFee) {
this.buyerPayoutTxFee = buyerPayoutTxFee;
return this;
}

public TradeInfoV1Builder withSellerPayoutTxFee(long sellerPayoutTxFee) {
this.sellerPayoutTxFee = sellerPayoutTxFee;
return this;
}

public TradeInfoV1Builder withBuyerPayoutAmount(long buyerPayoutAmount) {
this.buyerPayoutAmount = buyerPayoutAmount;
return this;
}

public TradeInfoV1Builder withSellerPayoutAmount(long sellerPayoutAmount) {
this.sellerPayoutAmount = sellerPayoutAmount;
return this;
}

public TradeInfoV1Builder withMakerDepositTxId(String makerDepositTxId) {
this.makerDepositTxId = makerDepositTxId;
return this;
Expand Down
70 changes: 47 additions & 23 deletions core/src/main/java/haveno/core/support/dispute/DisputeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
import java.security.KeyPair;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -848,42 +847,34 @@ public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, Disp
// trade wallet must be synced
if (trade.getWallet().isMultisigImportNeeded()) throw new RuntimeException("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx for trade " + trade.getId());

// collect winner and loser payout address and amounts
String winnerPayoutAddress = disputeResult.getWinner() == Winner.BUYER ?
(contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString()) :
(contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString());
String loserPayoutAddress = winnerPayoutAddress.equals(contract.getMakerPayoutAddressString()) ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
BigInteger winnerPayoutAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount();
BigInteger loserPayoutAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount();

// check sufficient balance
if (winnerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Winner payout cannot be negative");
if (loserPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Loser payout cannot be negative");
if (winnerPayoutAmount.add(loserPayoutAmount).compareTo(trade.getWallet().getUnlockedBalance()) > 0) {
throw new RuntimeException("The payout amounts are more than the wallet's unlocked balance, unlocked balance=" + trade.getWallet().getUnlockedBalance() + " vs " + winnerPayoutAmount + " + " + loserPayoutAmount + " = " + (winnerPayoutAmount.add(loserPayoutAmount)));
// check amounts
if (disputeResult.getBuyerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Buyer payout cannot be negative");
if (disputeResult.getSellerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Seller payout cannot be negative");
if (disputeResult.getBuyerPayoutAmountBeforeCost().add(disputeResult.getSellerPayoutAmountBeforeCost()).compareTo(trade.getWallet().getUnlockedBalance()) > 0) {
throw new RuntimeException("The payout amounts are more than the wallet's unlocked balance, unlocked balance=" + trade.getWallet().getUnlockedBalance() + " vs " + disputeResult.getBuyerPayoutAmountBeforeCost() + " + " + disputeResult.getSellerPayoutAmountBeforeCost() + " = " + (disputeResult.getBuyerPayoutAmountBeforeCost().add(disputeResult.getSellerPayoutAmountBeforeCost())));
}

// add any loss of precision to winner payout
winnerPayoutAmount = winnerPayoutAmount.add(trade.getWallet().getUnlockedBalance().subtract(winnerPayoutAmount.add(loserPayoutAmount)));

// create dispute payout tx config
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0);
String buyerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString();
String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY);
if (winnerPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(winnerPayoutAddress, winnerPayoutAmount);
if (loserPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(loserPayoutAddress, loserPayoutAmount);
if (disputeResult.getBuyerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(buyerPayoutAddress, disputeResult.getBuyerPayoutAmountBeforeCost());
if (disputeResult.getSellerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(sellerPayoutAddress, disputeResult.getSellerPayoutAmountBeforeCost());

// configure who pays mining fee
BigInteger loserPayoutAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmountBeforeCost() : disputeResult.getBuyerPayoutAmountBeforeCost();
if (loserPayoutAmount.equals(BigInteger.ZERO)) txConfig.setSubtractFeeFrom(0); // winner pays fee if loser gets 0
else {
switch (disputeResult.getSubtractFeeFrom()) {
case BUYER_AND_SELLER:
txConfig.setSubtractFeeFrom(Arrays.asList(0, 1));
txConfig.setSubtractFeeFrom(0, 1);
break;
case BUYER_ONLY:
txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.BUYER ? 0 : 1);
txConfig.setSubtractFeeFrom(0);
break;
case SELLER_ONLY:
txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.SELLER ? 0 : 1);
txConfig.setSubtractFeeFrom(1);
break;
}
}
Expand All @@ -897,8 +888,19 @@ public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, Disp
throw new RuntimeException("Loser payout is too small to cover the mining fee");
}

// save updated multisig hex
// update trade state
BigInteger[] buyerSellerPayoutTxCosts = getBuyerSellerPayoutTxCost(disputeResult, payoutTx.getFee());
trade.getBuyer().setPayoutTxFee(buyerSellerPayoutTxCosts[0]);
trade.getSeller().setPayoutTxFee(buyerSellerPayoutTxCosts[1]);
if (disputeResult.getBuyerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) trade.getBuyer().setPayoutAmount(HavenoUtils.getDestination(buyerPayoutAddress, payoutTx).getAmount());
if (disputeResult.getSellerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) trade.getSeller().setPayoutAmount(HavenoUtils.getDestination(sellerPayoutAddress, payoutTx).getAmount());
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());

// update dispute result
disputeResult.setBuyerPayoutTxFee(trade.getBuyer().getPayoutTxFee());
disputeResult.setSellerPayoutTxFee(trade.getSeller().getPayoutTxFee());
disputeResult.setBuyerPayoutAmount(trade.getBuyer().getPayoutAmount());
disputeResult.setSellerPayoutAmount(trade.getSeller().getPayoutAmount());
return payoutTx;
} catch (Exception e) {
trade.syncAndPollWallet();
Expand All @@ -908,6 +910,28 @@ public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, Disp
return null; // can be null if already published or we don't have receiver's multisig hex
}

public static BigInteger[] getBuyerSellerPayoutTxCost(DisputeResult disputeResult, BigInteger payoutTxFee) {
boolean isBuyerWinner = disputeResult.getWinner() == Winner.BUYER;
BigInteger loserAmount = isBuyerWinner ? disputeResult.getSellerPayoutAmountBeforeCost() : disputeResult.getBuyerPayoutAmountBeforeCost();
if (loserAmount.equals(BigInteger.valueOf(0))) {
BigInteger buyerPayoutTxFee = isBuyerWinner ? payoutTxFee : BigInteger.ZERO;
BigInteger sellerPayoutTxFee = isBuyerWinner ? BigInteger.ZERO : payoutTxFee;
return new BigInteger[] { buyerPayoutTxFee, sellerPayoutTxFee };
} else {
switch (disputeResult.getSubtractFeeFrom()) {
case BUYER_AND_SELLER:
BigInteger payoutTxFeeSplit = payoutTxFee.divide(BigInteger.valueOf(2));
return new BigInteger[] { payoutTxFeeSplit, payoutTxFeeSplit };
case BUYER_ONLY:
return new BigInteger[] { payoutTxFee, BigInteger.ZERO };
case SELLER_ONLY:
return new BigInteger[] { BigInteger.ZERO, payoutTxFee };
default:
throw new RuntimeException("Unrecognized subtract fee from: " + disputeResult.getSubtractFeeFrom());
}
}
}

private Tuple2<NodeAddress, PubKeyRing> getNodeAddressPubKeyRingTuple(Dispute dispute) {
PubKeyRing receiverPubKeyRing = null;
NodeAddress peerNodeAddress = null;
Expand Down
Loading

0 comments on commit 4c1b496

Please sign in to comment.