fix incorrect deposit amount for range trades

improve display of reserved and pending balances by adjusting
support subtracting fee from buyer and/or seller on dispute resolution
validate trade amount is within offer amount
expose maker's split output tx fee
expose security deposit received from buyer and seller
This commit is contained in:
woodser 2023-10-27 17:11:46 -04:00
parent 0294062312
commit 05e2d925f0
25 changed files with 267 additions and 91 deletions

View file

@ -69,6 +69,7 @@ import java.math.BigInteger;
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;
@ -869,12 +870,29 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// add any loss of precision to winner payout
winnerPayoutAmount = winnerPayoutAmount.add(trade.getWallet().getUnlockedBalance().subtract(winnerPayoutAmount.add(loserPayoutAmount)));
// create dispute payout tx
// create dispute payout tx config
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0);
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);
txConfig.setSubtractFeeFrom(loserPayoutAmount.equals(BigInteger.ZERO) ? 0 : txConfig.getDestinations().size() - 1); // winner only pays fee if loser gets 0
txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY);
// configure who pays mining fee
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));
break;
case BUYER_ONLY:
txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.BUYER ? 0 : 1);
break;
case SELLER_ONLY:
txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.SELLER ? 0 : 1);
break;
}
}
// create dispute payout tx
MoneroTxWallet payoutTx = null;
try {
payoutTx = trade.getWallet().createTx(txConfig);

View file

@ -61,12 +61,21 @@ public final class DisputeResult implements NetworkPayload {
PEER_WAS_LATE
}
public enum SubtractFeeFrom {
BUYER_ONLY,
SELLER_ONLY,
BUYER_AND_SELLER
}
private final String tradeId;
private final int traderId;
@Setter
@Nullable
private Winner winner;
private int reasonOrdinal = Reason.OTHER.ordinal();
@Setter
@Nullable
private SubtractFeeFrom subtractFeeFrom;
private final BooleanProperty tamperProofEvidenceProperty = new SimpleBooleanProperty();
private final BooleanProperty idVerificationProperty = new SimpleBooleanProperty();
private final BooleanProperty screenCastProperty = new SimpleBooleanProperty();
@ -93,6 +102,7 @@ public final class DisputeResult implements NetworkPayload {
int traderId,
@Nullable Winner winner,
int reasonOrdinal,
@Nullable SubtractFeeFrom subtractFeeFrom,
boolean tamperProofEvidence,
boolean idVerification,
boolean screenCast,
@ -107,6 +117,7 @@ public final class DisputeResult implements NetworkPayload {
this.traderId = traderId;
this.winner = winner;
this.reasonOrdinal = reasonOrdinal;
this.subtractFeeFrom = subtractFeeFrom;
this.tamperProofEvidenceProperty.set(tamperProofEvidence);
this.idVerificationProperty.set(idVerification);
this.screenCastProperty.set(screenCast);
@ -129,6 +140,7 @@ public final class DisputeResult implements NetworkPayload {
proto.getTraderId(),
ProtoUtil.enumFromProto(DisputeResult.Winner.class, proto.getWinner().name()),
proto.getReasonOrdinal(),
ProtoUtil.enumFromProto(DisputeResult.SubtractFeeFrom.class, proto.getSubtractFeeFrom().name()),
proto.getTamperProofEvidence(),
proto.getIdVerification(),
proto.getScreenCast(),
@ -158,6 +170,7 @@ public final class DisputeResult implements NetworkPayload {
Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey)));
Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name())));
Optional.ofNullable(subtractFeeFrom).ifPresent(result -> builder.setSubtractFeeFrom(protobuf.DisputeResult.SubtractFeeFrom.valueOf(subtractFeeFrom.name())));
Optional.ofNullable(chatMessage).ifPresent(chatMessage ->
builder.setChatMessage(chatMessage.toProtoNetworkEnvelope().getChatMessage()));
@ -201,6 +214,7 @@ public final class DisputeResult implements NetworkPayload {
}
public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) {
if (buyerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmount cannot be negative");
this.buyerPayoutAmount = buyerPayoutAmount.longValueExact();
}
@ -209,6 +223,7 @@ public final class DisputeResult implements NetworkPayload {
}
public void setSellerPayoutAmount(BigInteger sellerPayoutAmount) {
if (sellerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmount cannot be negative");
this.sellerPayoutAmount = sellerPayoutAmount.longValueExact();
}
@ -231,6 +246,7 @@ public final class DisputeResult implements NetworkPayload {
",\n traderId=" + traderId +
",\n winner=" + winner +
",\n reasonOrdinal=" + reasonOrdinal +
",\n subtractFeeFrom=" + subtractFeeFrom +
",\n tamperProofEvidenceProperty=" + tamperProofEvidenceProperty +
",\n idVerificationProperty=" + idVerificationProperty +
",\n screenCastProperty=" + screenCastProperty +

View file

@ -390,9 +390,25 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
BigInteger expectedWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount();
BigInteger expectedLoserAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount();
// winner pays cost if loser gets nothing, otherwise loser pays cost
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost);
else expectedLoserAmount = expectedLoserAmount.subtract(txCost);
// subtract mining fee from expected payouts
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost); // winner pays fee if loser gets 0
else {
switch (disputeResult.getSubtractFeeFrom()) {
case BUYER_AND_SELLER:
BigInteger txCostSplit = txCost.divide(BigInteger.valueOf(2));
expectedWinnerAmount = expectedWinnerAmount.subtract(txCostSplit);
expectedLoserAmount = expectedLoserAmount.subtract(txCostSplit);
break;
case BUYER_ONLY:
expectedWinnerAmount = expectedWinnerAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? txCost : BigInteger.ZERO);
expectedLoserAmount = expectedLoserAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? BigInteger.ZERO : txCost);
break;
case SELLER_ONLY:
expectedWinnerAmount = expectedWinnerAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? BigInteger.ZERO : txCost);
expectedLoserAmount = expectedLoserAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? txCost : BigInteger.ZERO);
break;
}
}
// verify winner and loser payout amounts
if (!expectedWinnerAmount.equals(actualWinnerAmount)) throw new RuntimeException("Unexpected winner payout: " + expectedWinnerAmount + " vs " + actualWinnerAmount);