test buyer and seller payout tx fee and amount

This commit is contained in:
woodser 2023-12-07 18:05:15 -05:00
parent 6725b44792
commit 29af0f4d14
7 changed files with 1031 additions and 767 deletions

View File

@ -2650,12 +2650,6 @@ export class TradeInfo extends jspb.Message {
getTakerFee(): string; getTakerFee(): string;
setTakerFee(value: string): TradeInfo; setTakerFee(value: string): TradeInfo;
getTakerFeeTxId(): string;
setTakerFeeTxId(value: string): TradeInfo;
getPayoutTxId(): string;
setPayoutTxId(value: string): TradeInfo;
getAmount(): string; getAmount(): string;
setAmount(value: string): TradeInfo; setAmount(value: string): TradeInfo;
@ -2665,6 +2659,24 @@ export class TradeInfo extends jspb.Message {
getSellerSecurityDeposit(): string; getSellerSecurityDeposit(): string;
setSellerSecurityDeposit(value: string): TradeInfo; setSellerSecurityDeposit(value: string): TradeInfo;
getBuyerDepositTxFee(): string;
setBuyerDepositTxFee(value: string): TradeInfo;
getSellerDepositTxFee(): string;
setSellerDepositTxFee(value: string): TradeInfo;
getBuyerPayoutTxFee(): string;
setBuyerPayoutTxFee(value: string): TradeInfo;
getSellerPayoutTxFee(): string;
setSellerPayoutTxFee(value: string): TradeInfo;
getBuyerPayoutAmount(): string;
setBuyerPayoutAmount(value: string): TradeInfo;
getSellerPayoutAmount(): string;
setSellerPayoutAmount(value: string): TradeInfo;
getPrice(): string; getPrice(): string;
setPrice(value: string): TradeInfo; setPrice(value: string): TradeInfo;
@ -2733,6 +2745,9 @@ export class TradeInfo extends jspb.Message {
getTakerDepositTxId(): string; getTakerDepositTxId(): string;
setTakerDepositTxId(value: string): TradeInfo; setTakerDepositTxId(value: string): TradeInfo;
getPayoutTxId(): string;
setPayoutTxId(value: string): TradeInfo;
serializeBinary(): Uint8Array; serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): TradeInfo.AsObject; toObject(includeInstance?: boolean): TradeInfo.AsObject;
static toObject(includeInstance: boolean, msg: TradeInfo): TradeInfo.AsObject; static toObject(includeInstance: boolean, msg: TradeInfo): TradeInfo.AsObject;
@ -2749,11 +2764,15 @@ export namespace TradeInfo {
date: number, date: number,
role: string, role: string,
takerFee: string, takerFee: string,
takerFeeTxId: string,
payoutTxId: string,
amount: string, amount: string,
buyerSecurityDeposit: string, buyerSecurityDeposit: string,
sellerSecurityDeposit: string, sellerSecurityDeposit: string,
buyerDepositTxFee: string,
sellerDepositTxFee: string,
buyerPayoutTxFee: string,
sellerPayoutTxFee: string,
buyerPayoutAmount: string,
sellerPayoutAmount: string,
price: string, price: string,
arbitratorNodeAddress: string, arbitratorNodeAddress: string,
tradePeerNodeAddress: string, tradePeerNodeAddress: string,
@ -2776,6 +2795,7 @@ export namespace TradeInfo {
tradeVolume: string, tradeVolume: string,
makerDepositTxId: string, makerDepositTxId: string,
takerDepositTxId: string, takerDepositTxId: string,
payoutTxId: string,
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -3180,11 +3180,11 @@ export class DisputeResult extends jspb.Message {
getArbitratorSignature_asB64(): string; getArbitratorSignature_asB64(): string;
setArbitratorSignature(value: Uint8Array | string): DisputeResult; setArbitratorSignature(value: Uint8Array | string): DisputeResult;
getBuyerPayoutAmount(): number; getBuyerPayoutAmountBeforeCost(): number;
setBuyerPayoutAmount(value: number): DisputeResult; setBuyerPayoutAmountBeforeCost(value: number): DisputeResult;
getSellerPayoutAmount(): number; getSellerPayoutAmountBeforeCost(): number;
setSellerPayoutAmount(value: number): DisputeResult; setSellerPayoutAmountBeforeCost(value: number): DisputeResult;
getSubtractFeeFrom(): DisputeResult.SubtractFeeFrom; getSubtractFeeFrom(): DisputeResult.SubtractFeeFrom;
setSubtractFeeFrom(value: DisputeResult.SubtractFeeFrom): DisputeResult; setSubtractFeeFrom(value: DisputeResult.SubtractFeeFrom): DisputeResult;
@ -3220,8 +3220,8 @@ export namespace DisputeResult {
summaryNotes: string, summaryNotes: string,
chatMessage?: ChatMessage.AsObject, chatMessage?: ChatMessage.AsObject,
arbitratorSignature: Uint8Array | string, arbitratorSignature: Uint8Array | string,
buyerPayoutAmount: number, buyerPayoutAmountBeforeCost: number,
sellerPayoutAmount: number, sellerPayoutAmountBeforeCost: number,
subtractFeeFrom: DisputeResult.SubtractFeeFrom, subtractFeeFrom: DisputeResult.SubtractFeeFrom,
arbitratorPubKey: Uint8Array | string, arbitratorPubKey: Uint8Array | string,
closeDate: number, closeDate: number,
@ -6030,9 +6030,6 @@ export class Trade extends jspb.Message {
getTakerFee(): number; getTakerFee(): number;
setTakerFee(value: number): Trade; setTakerFee(value: number): Trade;
getTotalTxFee(): number;
setTotalTxFee(value: number): Trade;
getTakeOfferDate(): number; getTakeOfferDate(): number;
setTakeOfferDate(value: number): Trade; setTakeOfferDate(value: number): Trade;
@ -6125,7 +6122,6 @@ export namespace Trade {
payoutTxKey: string, payoutTxKey: string,
amount: number, amount: number,
takerFee: number, takerFee: number,
totalTxFee: number,
takeOfferDate: number, takeOfferDate: number,
price: number, price: number,
state: Trade.State, state: Trade.State,
@ -6531,6 +6527,12 @@ export class TradePeer extends jspb.Message {
getExchangedMultisigHex(): string; getExchangedMultisigHex(): string;
setExchangedMultisigHex(value: string): TradePeer; setExchangedMultisigHex(value: string): TradePeer;
getUpdatedMultisigHex(): string;
setUpdatedMultisigHex(value: string): TradePeer;
getDepositsConfirmedMessageAcked(): boolean;
setDepositsConfirmedMessageAcked(value: boolean): TradePeer;
getDepositTxHash(): string; getDepositTxHash(): string;
setDepositTxHash(value: string): TradePeer; setDepositTxHash(value: string): TradePeer;
@ -6546,11 +6548,14 @@ export class TradePeer extends jspb.Message {
getSecurityDeposit(): number; getSecurityDeposit(): number;
setSecurityDeposit(value: number): TradePeer; setSecurityDeposit(value: number): TradePeer;
getUpdatedMultisigHex(): string; getUnsignedPayoutTxHex(): string;
setUpdatedMultisigHex(value: string): TradePeer; setUnsignedPayoutTxHex(value: string): TradePeer;
getDepositsConfirmedMessageAcked(): boolean; getPayoutTxFee(): number;
setDepositsConfirmedMessageAcked(value: boolean): TradePeer; setPayoutTxFee(value: number): TradePeer;
getPayoutAmount(): number;
setPayoutAmount(value: number): TradePeer;
serializeBinary(): Uint8Array; serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): TradePeer.AsObject; toObject(includeInstance?: boolean): TradePeer.AsObject;
@ -6589,13 +6594,16 @@ export namespace TradePeer {
preparedMultisigHex: string, preparedMultisigHex: string,
madeMultisigHex: string, madeMultisigHex: string,
exchangedMultisigHex: string, exchangedMultisigHex: string,
updatedMultisigHex: string,
depositsConfirmedMessageAcked: boolean,
depositTxHash: string, depositTxHash: string,
depositTxHex: string, depositTxHex: string,
depositTxKey: string, depositTxKey: string,
depositTxFee: number, depositTxFee: number,
securityDeposit: number, securityDeposit: number,
updatedMultisigHex: string, unsignedPayoutTxHex: string,
depositsConfirmedMessageAcked: boolean, payoutTxFee: number,
payoutAmount: number,
} }
} }

812
dist/protobuf/pb_pb.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -89,7 +89,8 @@ class PeerContext {
depositTx: moneroTs.MoneroTx; depositTx: moneroTs.MoneroTx;
depositTxFee: bigint; depositTxFee: bigint;
securityDepositActual: bigint; securityDepositActual: bigint;
payoutAmount: bigint payoutTxFee: bigint;
payoutAmount: bigint;
constructor(ctx?: Partial<PeerContext>) { constructor(ctx?: Partial<PeerContext>) {
Object.assign(this, ctx); Object.assign(this, ctx);
@ -259,6 +260,10 @@ class TradeContext {
return this.disputeWinner === DisputeResult.Winner.BUYER ? this.getSeller() : this.getBuyer(); return this.disputeWinner === DisputeResult.Winner.BUYER ? this.getSeller() : this.getBuyer();
} }
isOfflineFlow() {
return this.buyerOfflineAfterDisputeOpened || this.sellerOfflineAfterDisputeOpened || this.buyerOfflineAfterPaymentSent || this.buyerOfflineAfterTake || this.sellerOfflineAfterTake;
}
static init(ctxP: Partial<TradeContext> | undefined): TradeContext { static init(ctxP: Partial<TradeContext> | undefined): TradeContext {
let ctx = ctxP instanceof TradeContext ? ctxP : new TradeContext(ctxP); let ctx = ctxP instanceof TradeContext ? ctxP : new TradeContext(ctxP);
if (!ctx.offerAmount && ctx.tradeAmount) ctx.offerAmount = ctx.tradeAmount; if (!ctx.offerAmount && ctx.tradeAmount) ctx.offerAmount = ctx.tradeAmount;
@ -1755,7 +1760,7 @@ test("Can resolve disputes (CI)", async () => {
await executeTrades(ctxs.slice(configIdx, configIdx === undefined ? undefined : configIdx + 1), {concurrentTrades: !testBalancesSequentially}); await executeTrades(ctxs.slice(configIdx, configIdx === undefined ? undefined : configIdx + 1), {concurrentTrades: !testBalancesSequentially});
}); });
test("Can go offline while resolving disputes (CI)", async () => { test("Can go offline while resolving a dispute (CI)", async () => {
let traders: HavenoClient[] = []; let traders: HavenoClient[] = [];
let ctx: Partial<TradeContext> = {}; let ctx: Partial<TradeContext> = {};
let err: any; let err: any;
@ -2455,10 +2460,7 @@ async function executeTrade(ctxP: Partial<TradeContext>): Promise<string> {
} }
// test balances after payout tx unless other trades can interfere // test balances after payout tx unless other trades can interfere
if (!ctx.concurrentTrades) { if (!ctx.concurrentTrades) await testAmountsAfterComplete(ctx);
await testBalancesAfterComplete(ctx, ctx.getMaker());
await testBalancesAfterComplete(ctx, ctx.getTaker());
}
// test payout unlock // test payout unlock
await testTradePayoutUnlock(ctx); await testTradePayoutUnlock(ctx);
@ -2787,7 +2789,7 @@ async function testOpenDispute(ctxP: Partial<TradeContext>) {
await arbitrator.addNotificationListener(notification => { HavenoUtils.log(3, "Arbitrator received notification " + notification.getType() + " " + (notification.getChatMessage() ? notification.getChatMessage()?.getMessage() : "")); arbitratorNotifications.push(notification); }); await arbitrator.addNotificationListener(notification => { HavenoUtils.log(3, "Arbitrator received notification " + notification.getType() + " " + (notification.getChatMessage() ? notification.getChatMessage()?.getMessage() : "")); arbitratorNotifications.push(notification); });
// arbitrator sends chat messages to traders // arbitrator sends chat messages to traders
HavenoUtils.log(1, "Testing chat messages"); HavenoUtils.log(1, "Arbitrator sending chat messages to traders. tradeId=" + ctx.offerId + ", disputeId=" + openerDispute.getId());
await ctx.arbitrator.havenod!.sendDisputeChatMessage(arbDisputeOpener!.getId(), "Arbitrator chat message to dispute opener", []); await ctx.arbitrator.havenod!.sendDisputeChatMessage(arbDisputeOpener!.getId(), "Arbitrator chat message to dispute opener", []);
await ctx.arbitrator.havenod!.sendDisputeChatMessage(arbDisputePeer!.getId(), "Arbitrator chat message to dispute peer", []); await ctx.arbitrator.havenod!.sendDisputeChatMessage(arbDisputePeer!.getId(), "Arbitrator chat message to dispute peer", []);
@ -2887,8 +2889,6 @@ async function resolveDispute(ctxP: Partial<TradeContext>) {
} }
// resolve dispute according to configuration // resolve dispute according to configuration
const winnerd = ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.getBuyer().havenod : ctx.getSeller().havenod;
const loserd = ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.getSeller().havenod : ctx.getBuyer().havenod;
HavenoUtils.log(1, "Resolving dispute for trade " + ctx.offerId); HavenoUtils.log(1, "Resolving dispute for trade " + ctx.offerId);
const startTime = Date.now(); const startTime = Date.now();
await arbitrator.resolveDispute(ctx.offerId!, ctx.disputeWinner!, ctx.disputeReason!, ctx.disputeSummary!, ctx.disputeWinnerAmount); await arbitrator.resolveDispute(ctx.offerId!, ctx.disputeWinner!, ctx.disputeReason!, ctx.disputeSummary!, ctx.disputeWinnerAmount);
@ -2926,7 +2926,10 @@ async function resolveDispute(ctxP: Partial<TradeContext>) {
await testTradeState(await ctx.arbitrator.havenod!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true}); await testTradeState(await ctx.arbitrator.havenod!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
// signing peer has payout tx id on 0 conf (peers must wait for confirmation to see outgoing tx) // signing peer has payout tx id on 0 conf (peers must wait for confirmation to see outgoing tx)
if (winnerd) ctx.payoutTxId = (await winnerd!.getTrade(ctx.offerId!)).getPayoutTxId() const winnerd = ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.getBuyer().havenod : ctx.getSeller().havenod;
const loserd = ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.getSeller().havenod : ctx.getBuyer().havenod;
const signerd = winnerd ? winnerd : loserd;
ctx.payoutTxId = (await signerd!.getTrade(ctx.offerId!)).getPayoutTxId();
// record balances on completion // record balances on completion
if (!ctx.maker.balancesAfterPayout) { if (!ctx.maker.balancesAfterPayout) {
@ -2935,45 +2938,68 @@ async function resolveDispute(ctxP: Partial<TradeContext>) {
} }
// test balances after payout tx unless concurrent trades // test balances after payout tx unless concurrent trades
if (!ctx.concurrentTrades) { if (!ctx.concurrentTrades) await testAmountsAfterComplete(ctx);
if (winnerd) await testBalancesAfterComplete(ctx, ctx.getDisputeWinner()!);
if (loserd) await testBalancesAfterComplete(ctx, ctx.getDisputeLoser()!);
}
// test payout unlock // test payout unlock
await testTradePayoutUnlock(ctx); await testTradePayoutUnlock(ctx);
} }
async function testBalancesAfterComplete(tradeCtx: TradeContext, peerCtx: PeerContext) { async function testAmountsAfterComplete(tradeCtx: TradeContext) {
// update context // get payout tx
const trade = await peerCtx.havenod?.getTrade(tradeCtx.offerId!)!;
peerCtx.trade = trade;
if (!tradeCtx.payoutTxId) throw new Error("Missing payout tx id"); if (!tradeCtx.payoutTxId) throw new Error("Missing payout tx id");
const payoutTx = await monerod.getTx(tradeCtx.payoutTxId); const payoutTx = await monerod.getTx(tradeCtx.payoutTxId);
const payoutTxFee = BigInt(payoutTx!.getFee()); const payoutTxFee = BigInt(payoutTx!.getFee());
// calculate expected payout amount for normal trade // get expected payouts for normal trade
if (tradeCtx.getDisputeOpener() === undefined) { const isDisputedTrade = tradeCtx.getDisputeOpener() !== undefined;
let receiveAmount: bigint = tradeCtx.getBuyer() == peerCtx ? BigInt(trade.getAmount()) : 0n; if (!isDisputedTrade) {
peerCtx.payoutAmount = peerCtx.securityDepositActual + receiveAmount - (payoutTxFee / 2n); tradeCtx.getBuyer().payoutTxFee = payoutTxFee / 2n;
} tradeCtx.getBuyer().payoutAmount = tradeCtx.getBuyer().securityDepositActual + tradeCtx.tradeAmount! - tradeCtx.getBuyer().payoutTxFee;
tradeCtx.getSeller().payoutTxFee = payoutTxFee / 2n;
tradeCtx.getSeller().payoutAmount = tradeCtx.getSeller().securityDepositActual - tradeCtx.getSeller().payoutTxFee;
} else {
// calculate expected payout amount for dispute trade // get expected payouts for disputed trade
else {
const winnerGetsAll = tradeCtx.disputeWinnerAmount === tradeCtx.maker.securityDepositActual! + tradeCtx.taker.securityDepositActual! + tradeCtx.tradeAmount!; const winnerGetsAll = tradeCtx.disputeWinnerAmount === tradeCtx.maker.securityDepositActual! + tradeCtx.taker.securityDepositActual! + tradeCtx.tradeAmount!;
if (tradeCtx.getDisputeWinner() === peerCtx) { if (tradeCtx.disputeWinnerAmount) {
if (tradeCtx.disputeWinnerAmount) peerCtx.payoutAmount = tradeCtx.disputeWinnerAmount - (winnerGetsAll ? payoutTxFee : 0n); tradeCtx.getDisputeWinner()!.payoutTxFee = winnerGetsAll ? payoutTxFee : 0n;
else peerCtx.payoutAmount = tradeCtx.tradeAmount! + peerCtx.securityDepositActual - (payoutTxFee / 2n); tradeCtx.getDisputeWinner()!.payoutAmount = tradeCtx.disputeWinnerAmount - tradeCtx.getDisputeWinner()!.payoutTxFee;
tradeCtx.getDisputeLoser()!.payoutTxFee = winnerGetsAll ? 0n : payoutTxFee;
tradeCtx.getDisputeLoser()!.payoutAmount = tradeCtx.maker.securityDepositActual! + tradeCtx.taker.securityDepositActual! + tradeCtx.tradeAmount! - tradeCtx.disputeWinnerAmount - tradeCtx.getDisputeLoser()!.payoutTxFee;
} else { } else {
if (tradeCtx.disputeWinnerAmount) { tradeCtx.getDisputeWinner()!.payoutTxFee = payoutTxFee / 2n;
const multisigBalance = tradeCtx.maker.securityDepositActual! + tradeCtx.taker.securityDepositActual! + tradeCtx.tradeAmount!; tradeCtx.getDisputeWinner()!.payoutAmount = tradeCtx.tradeAmount! + tradeCtx.getDisputeWinner()!.securityDepositActual - tradeCtx.getDisputeWinner()!.payoutTxFee;
peerCtx.payoutAmount = multisigBalance - tradeCtx.disputeWinnerAmount - (winnerGetsAll ? 0n : payoutTxFee); tradeCtx.getDisputeLoser()!.payoutTxFee = payoutTxFee / 2n;
} tradeCtx.getDisputeLoser()!.payoutAmount = tradeCtx.getDisputeLoser()!.securityDepositActual - tradeCtx.getDisputeLoser()!.payoutTxFee;
else peerCtx.payoutAmount = peerCtx.securityDepositActual - (payoutTxFee / 2n);
} }
} }
// TODO: payout tx is unknown to offline non-signer until confirmed
if (isDisputedTrade || tradeCtx.isOfflineFlow()) {
await mineToHeight(await monerod.getHeight() + 1);
await wait(TestConfig.maxWalletStartupMs + tradeCtx.walletSyncPeriodMs * 2);
}
// test trade payouts
if (tradeCtx.maker.havenod) await testPeerAmountsAfterComplete(tradeCtx, tradeCtx.getMaker());
if (tradeCtx.taker.havenod) await testPeerAmountsAfterComplete(tradeCtx, tradeCtx.getTaker());
}
async function testPeerAmountsAfterComplete(tradeCtx: TradeContext, peerCtx: PeerContext) {
// get trade
const trade = await peerCtx.havenod.getTrade(tradeCtx.offerId!);
// test trade amounts
const isBuyer = tradeCtx.getBuyer() === peerCtx;
if (isBuyer) expect(BigInt(trade.getBuyerDepositTxFee())).toEqual(tradeCtx.getBuyer().depositTxFee); // TODO: get and test peer's security deposit tx fee?
else expect(BigInt(trade.getSellerDepositTxFee())).toEqual(tradeCtx.getSeller().depositTxFee);
expect(BigInt(trade.getBuyerPayoutTxFee())).toEqual(tradeCtx.getBuyer().payoutTxFee);
expect(BigInt(trade.getSellerPayoutTxFee())).toEqual(tradeCtx.getSeller().payoutTxFee);
expect(BigInt(trade.getBuyerPayoutAmount())).toEqual(tradeCtx.getBuyer().payoutAmount);
expect(BigInt(trade.getSellerPayoutAmount())).toEqual(tradeCtx.getSeller().payoutAmount);
// test balance change after payout tx // test balance change after payout tx
const differenceAfterPayout = BigInt(peerCtx.balancesAfterPayout?.getBalance()!) - BigInt(peerCtx.balancesBeforePayout?.getBalance()!); const differenceAfterPayout = BigInt(peerCtx.balancesAfterPayout?.getBalance()!) - BigInt(peerCtx.balancesBeforePayout?.getBalance()!);
expect(differenceAfterPayout).toEqual(peerCtx.payoutAmount); expect(differenceAfterPayout).toEqual(peerCtx.payoutAmount);