mirror of
https://github.com/haveno-dex/haveno-ts.git
synced 2024-10-01 01:35:42 -04:00
test resolving dispute while traders go offline
remove duplicate state from TradeContext
This commit is contained in:
parent
3465eb5ce6
commit
83d2a9123d
@ -204,6 +204,8 @@ interface TradeContext {
|
|||||||
buyerOfflineAfterTake?: boolean,
|
buyerOfflineAfterTake?: boolean,
|
||||||
sellerOfflineAfterTake?: boolean,
|
sellerOfflineAfterTake?: boolean,
|
||||||
buyerOfflineAfterPaymentSent?: boolean
|
buyerOfflineAfterPaymentSent?: boolean
|
||||||
|
buyerOfflineAfterDisputeOpened?: boolean,
|
||||||
|
sellerOfflineAfterDisputeOpened?: boolean,
|
||||||
sellerDisputeContext?: DisputeContext,
|
sellerDisputeContext?: DisputeContext,
|
||||||
buyerDisputeContext?: DisputeContext,
|
buyerDisputeContext?: DisputeContext,
|
||||||
buyerSendsPayment?: boolean,
|
buyerSendsPayment?: boolean,
|
||||||
@ -232,16 +234,14 @@ interface TradeContext {
|
|||||||
// resolve dispute
|
// resolve dispute
|
||||||
resolveDispute?: boolean
|
resolveDispute?: boolean
|
||||||
arbitrator?: HavenoClient,
|
arbitrator?: HavenoClient,
|
||||||
disputeOpener?: HavenoClient
|
disputeOpener?: SaleRole,
|
||||||
disputePeer?: HavenoClient
|
|
||||||
disputeWinner?: DisputeResult.Winner,
|
disputeWinner?: DisputeResult.Winner,
|
||||||
disputeReason?: DisputeResult.Reason,
|
disputeReason?: DisputeResult.Reason,
|
||||||
disputeSummary?: string,
|
disputeSummary?: string,
|
||||||
disputeWinnerAmount?: bigint
|
disputeWinnerAmount?: bigint
|
||||||
|
|
||||||
// other context
|
// other context
|
||||||
buyer?: HavenoClient,
|
offer?: OfferInfo,
|
||||||
seller?: HavenoClient,
|
|
||||||
index?: number,
|
index?: number,
|
||||||
isOfferTaken?: boolean,
|
isOfferTaken?: boolean,
|
||||||
isPaymentSent?: boolean,
|
isPaymentSent?: boolean,
|
||||||
@ -258,7 +258,10 @@ interface TradeContext {
|
|||||||
maxConcurrency?: number,
|
maxConcurrency?: number,
|
||||||
walletSyncPeriodMs?: number,
|
walletSyncPeriodMs?: number,
|
||||||
maxTimePeerNoticeMs?: number,
|
maxTimePeerNoticeMs?: number,
|
||||||
stopOnFailure?: boolean
|
stopOnFailure?: boolean,
|
||||||
|
buyerAppName?: string,
|
||||||
|
sellerAppName?: string,
|
||||||
|
usedPorts?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TradeRole {
|
enum TradeRole {
|
||||||
@ -266,6 +269,11 @@ enum TradeRole {
|
|||||||
TAKER = "TAKER",
|
TAKER = "TAKER",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SaleRole {
|
||||||
|
BUYER = "BUYER",
|
||||||
|
SELLER = "SELLER"
|
||||||
|
}
|
||||||
|
|
||||||
enum DisputeContext {
|
enum DisputeContext {
|
||||||
NONE = "NONE",
|
NONE = "NONE",
|
||||||
OPEN_AFTER_DEPOSITS_UNLOCK = "OPEN_AFTER_DEPOSITS_UNLOCK",
|
OPEN_AFTER_DEPOSITS_UNLOCK = "OPEN_AFTER_DEPOSITS_UNLOCK",
|
||||||
@ -1320,7 +1328,6 @@ test("Can go offline while completing a trade (CI, sanity check)", async () => {
|
|||||||
let traders: HavenoClient[] = [];
|
let traders: HavenoClient[] = [];
|
||||||
let ctx: TradeContext = {};
|
let ctx: TradeContext = {};
|
||||||
let err: any;
|
let err: any;
|
||||||
let miningStarted = false;
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// start and fund 2 trader processes
|
// start and fund 2 trader processes
|
||||||
@ -1339,19 +1346,55 @@ test("Can go offline while completing a trade (CI, sanity check)", async () => {
|
|||||||
ctx.buyerOfflineAfterPaymentSent = true;
|
ctx.buyerOfflineAfterPaymentSent = true;
|
||||||
|
|
||||||
// execute trade
|
// execute trade
|
||||||
miningStarted = await startMining();
|
|
||||||
await executeTrade(ctx);
|
await executeTrade(ctx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop traders
|
// stop traders
|
||||||
if (miningStarted) await stopMining();
|
|
||||||
if (ctx.maker) await releaseHavenoProcess(ctx.maker, true);
|
if (ctx.maker) await releaseHavenoProcess(ctx.maker, true);
|
||||||
if (ctx.taker) await releaseHavenoProcess(ctx.taker, true);
|
if (ctx.taker) await releaseHavenoProcess(ctx.taker, true);
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Can go offline while resolving disputes", async () => {
|
||||||
|
let traders: HavenoClient[] = [];
|
||||||
|
let ctx: TradeContext = {};
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
|
||||||
|
// start and fund 2 trader processes
|
||||||
|
HavenoUtils.log(1, "Starting trader processes");
|
||||||
|
traders = await initHavenos(2);
|
||||||
|
HavenoUtils.log(1, "Funding traders");
|
||||||
|
const tradeAmount = BigInt("250000000000");
|
||||||
|
await waitForAvailableBalance(tradeAmount * BigInt("2"), ...traders);
|
||||||
|
|
||||||
|
// create trade config
|
||||||
|
ctx = Object.assign(getTradeContexts(1)[0], {
|
||||||
|
maker: traders[0],
|
||||||
|
taker: traders[1],
|
||||||
|
buyerOfflineAfterTake: true,
|
||||||
|
sellerOfflineAfterDisputeOpened: true,
|
||||||
|
buyerOfflineAfterDisputeOpened: false,
|
||||||
|
sellerDisputeContext: DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK,
|
||||||
|
disputeWinner: DisputeResult.Winner.SELLER,
|
||||||
|
disputeReason: DisputeResult.Reason.NO_REPLY,
|
||||||
|
disputeSummary: "Seller wins dispute because buyer has not replied",
|
||||||
|
});
|
||||||
|
|
||||||
|
// execute trade
|
||||||
|
await executeTrade(ctx);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop and delete traders
|
||||||
|
await releaseHavenoProcess(ctx.maker!, true);
|
||||||
|
deleteHavenoInstanceByAppName(ctx.sellerAppName!); // seller is offline
|
||||||
|
if (err) throw err;
|
||||||
|
});
|
||||||
|
|
||||||
test("Can resolve disputes (CI)", async () => {
|
test("Can resolve disputes (CI)", async () => {
|
||||||
|
|
||||||
// take trades but stop before sending payment
|
// take trades but stop before sending payment
|
||||||
@ -1720,6 +1763,28 @@ test("Selects arbitrators which are online, registered, and least used", async (
|
|||||||
|
|
||||||
// ----------------------------- TEST HELPERS ---------------------------------
|
// ----------------------------- TEST HELPERS ---------------------------------
|
||||||
|
|
||||||
|
function getBuyer(ctx: TradeContext) {
|
||||||
|
return ctx.direction?.toUpperCase() === "BUY" ? ctx.maker : ctx.taker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSeller(ctx: TradeContext) {
|
||||||
|
return ctx.direction?.toUpperCase() === "SELL" ? ctx.maker : ctx.taker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBuyerMaker(ctx: TradeContext) {
|
||||||
|
return ctx.direction?.toUpperCase() === "BUY";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisputeOpener(ctx: TradeContext) {
|
||||||
|
if (!ctx.disputeOpener) return undefined;
|
||||||
|
return ctx.disputeOpener === SaleRole.BUYER ? getBuyer(ctx) : getSeller(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisputePeer(ctx: TradeContext) {
|
||||||
|
if (!ctx.disputeOpener) return undefined;
|
||||||
|
return ctx.disputeOpener === SaleRole.BUYER ? getSeller(ctx) : getBuyer(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
function getTradeContexts(numConfigs: number): TradeContext[] {
|
function getTradeContexts(numConfigs: number): TradeContext[] {
|
||||||
const configs: TradeContext[] = [];
|
const configs: TradeContext[] = [];
|
||||||
for (let i = 0; i < numConfigs; i++) configs.push({});
|
for (let i = 0; i < numConfigs; i++) configs.push({});
|
||||||
@ -1734,10 +1799,10 @@ function tradeContextToString(ctx: TradeContext) {
|
|||||||
arbitrator: ctx.arbitrator ? ctx.arbitrator.getUrl() : undefined,
|
arbitrator: ctx.arbitrator ? ctx.arbitrator.getUrl() : undefined,
|
||||||
maker: ctx.maker ? ctx.maker.getUrl() : undefined,
|
maker: ctx.maker ? ctx.maker.getUrl() : undefined,
|
||||||
taker: ctx.taker ? ctx.taker.getUrl() : undefined,
|
taker: ctx.taker ? ctx.taker.getUrl() : undefined,
|
||||||
buyer: ctx.buyer ? ctx.buyer.getUrl() : undefined,
|
buyer: getBuyer(ctx) ? getBuyer(ctx)?.getUrl() : undefined,
|
||||||
seller: ctx.seller ? ctx.seller.getUrl() : undefined,
|
seller: getSeller(ctx) ? getSeller(ctx)?.getUrl() : undefined,
|
||||||
disputeOpener: ctx.disputeOpener ? ctx.disputeOpener.getUrl() : undefined,
|
disputeOpener: ctx.maker ? getDisputeOpener(ctx)?.getUrl() : undefined,
|
||||||
disputePeer: ctx.disputePeer ? ctx.disputePeer.getUrl() : undefined,
|
disputePeer: ctx.maker ? getDisputePeer(ctx)?.getUrl() : undefined,
|
||||||
disputeWinner: ctx.disputeWinner === DisputeResult.Winner.BUYER ? "buyer" : "seller"
|
disputeWinner: ctx.disputeWinner === DisputeResult.Winner.BUYER ? "buyer" : "seller"
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -1833,32 +1898,22 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
|
|||||||
await waitForAvailableBalance(ctx.amount! * BigInt("2"), ...clientsToFund);
|
await waitForAvailableBalance(ctx.amount! * BigInt("2"), ...clientsToFund);
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine buyer and seller
|
|
||||||
let offer: OfferInfo|undefined = undefined;
|
|
||||||
let isBuyerMaker = false;
|
|
||||||
if (makingOffer) {
|
|
||||||
isBuyerMaker = "buy" === ctx.direction!.toLowerCase();
|
|
||||||
} else {
|
|
||||||
offer = getOffer(await ctx.maker!.getMyOffers(ctx.assetCode!, ctx.direction), ctx.offerId!);
|
|
||||||
if (!offer) {
|
|
||||||
const trade = await ctx.maker!.getTrade(ctx.offerId!);
|
|
||||||
offer = trade.getOffer();
|
|
||||||
}
|
|
||||||
isBuyerMaker = "buy" === offer!.getDirection().toLowerCase();
|
|
||||||
}
|
|
||||||
ctx.buyer = isBuyerMaker ? ctx.maker : ctx.taker;
|
|
||||||
ctx.seller = isBuyerMaker ? ctx.taker : ctx.maker;
|
|
||||||
|
|
||||||
// get info before trade
|
// get info before trade
|
||||||
const buyerBalancesBefore = await ctx.buyer!.getBalances();
|
const buyerBalancesBefore = await getBuyer(ctx)!.getBalances();
|
||||||
const sellerBalancesBefore = await ctx.seller!.getBalances();
|
const sellerBalancesBefore = await getSeller(ctx)!.getBalances();
|
||||||
|
|
||||||
// make offer if configured
|
// make offer if configured
|
||||||
if (makingOffer) {
|
if (makingOffer) {
|
||||||
offer = await makeOffer(ctx);
|
ctx.offer = await makeOffer(ctx);
|
||||||
expect(offer.getState()).toEqual("AVAILABLE");
|
expect(ctx.offer.getState()).toEqual("AVAILABLE");
|
||||||
ctx.offerId = offer.getId();
|
ctx.offerId = ctx.offer.getId();
|
||||||
await wait(ctx.maxTimePeerNoticeMs! + ctx.walletSyncPeriodMs! * 2);
|
await wait(ctx.maxTimePeerNoticeMs! + ctx.walletSyncPeriodMs! * 2);
|
||||||
|
} else {
|
||||||
|
ctx.offer = getOffer(await ctx.maker!.getMyOffers(ctx.assetCode!, ctx.direction), ctx.offerId!);
|
||||||
|
if (!ctx.offer) {
|
||||||
|
const trade = await ctx.maker!.getTrade(ctx.offerId!);
|
||||||
|
ctx.offer = trade.getOffer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): test error message taking offer before posted
|
// TODO (woodser): test error message taking offer before posted
|
||||||
@ -1876,31 +1931,29 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
|
|||||||
if (ctx.testTraderChat) await testTradeChat(ctx);
|
if (ctx.testTraderChat) await testTradeChat(ctx);
|
||||||
|
|
||||||
// get expected payment account payloads
|
// get expected payment account payloads
|
||||||
let expectedBuyerPaymentAccountPayload = (await ctx.buyer?.getPaymentAccount(ctx.maker == ctx.buyer ? ctx.makerPaymentAccountId! : ctx.takerPaymentAccountId!))?.getPaymentAccountPayload();
|
let expectedBuyerPaymentAccountPayload = (await getBuyer(ctx)?.getPaymentAccount(ctx.maker == getBuyer(ctx) ? ctx.makerPaymentAccountId! : ctx.takerPaymentAccountId!))?.getPaymentAccountPayload();
|
||||||
let expectedSellerPaymentAccountPayload = (await ctx.seller?.getPaymentAccount(ctx.maker == ctx.buyer ? ctx.takerPaymentAccountId! : ctx.makerPaymentAccountId!))?.getPaymentAccountPayload();
|
let expectedSellerPaymentAccountPayload = (await getSeller(ctx)?.getPaymentAccount(ctx.maker == getBuyer(ctx) ? ctx.takerPaymentAccountId! : ctx.makerPaymentAccountId!))?.getPaymentAccountPayload();
|
||||||
|
|
||||||
// seller does not have buyer's payment account payload until payment sent
|
// seller does not have buyer's payment account payload until payment sent
|
||||||
let fetchedTrade = await ctx.seller!.getTrade(ctx.offerId!);
|
let fetchedTrade = await getSeller(ctx)!.getTrade(ctx.offerId!);
|
||||||
let contract = fetchedTrade.getContract()!;
|
let contract = fetchedTrade.getContract()!;
|
||||||
let buyerPaymentAccountPayload = contract.getIsBuyerMakerAndSellerTaker() ? contract.getMakerPaymentAccountPayload() : contract.getTakerPaymentAccountPayload();
|
let buyerPaymentAccountPayload = contract.getIsBuyerMakerAndSellerTaker() ? contract.getMakerPaymentAccountPayload() : contract.getTakerPaymentAccountPayload();
|
||||||
if (ctx.isPaymentSent) expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
if (ctx.isPaymentSent) expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
||||||
else expect(buyerPaymentAccountPayload).toBeUndefined();
|
else expect(buyerPaymentAccountPayload).toBeUndefined();
|
||||||
|
|
||||||
// shut down buyer and seller if configured
|
// shut down buyer and seller if configured
|
||||||
const usedPorts = [getPort(ctx.buyer!.getUrl()), getPort(ctx.seller!.getUrl())];
|
ctx.usedPorts = [getPort(getBuyer(ctx)!.getUrl()), getPort(getSeller(ctx)!.getUrl())];
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
const buyerAppName = ctx.buyer!.getAppName();
|
ctx.buyerAppName = getBuyer(ctx)!.getAppName();
|
||||||
if (ctx.buyerOfflineAfterTake) {
|
if (ctx.buyerOfflineAfterTake) {
|
||||||
promises.push(releaseHavenoProcess(ctx.buyer!));
|
promises.push(releaseHavenoProcess(getBuyer(ctx)!));
|
||||||
ctx.buyer = undefined; // TODO: don't track them separately?
|
if (isBuyerMaker(ctx)) ctx.maker = undefined;
|
||||||
if (isBuyerMaker) ctx.maker = undefined;
|
|
||||||
else ctx.taker = undefined;
|
else ctx.taker = undefined;
|
||||||
}
|
}
|
||||||
const sellerAppName = ctx.seller!.getAppName();
|
ctx.sellerAppName = getSeller(ctx)!.getAppName();
|
||||||
if (ctx.sellerOfflineAfterTake) {
|
if (ctx.sellerOfflineAfterTake) {
|
||||||
promises.push(releaseHavenoProcess(ctx.seller!));
|
promises.push(releaseHavenoProcess(getSeller(ctx)!));
|
||||||
ctx.seller = undefined;
|
if (isBuyerMaker(ctx)) ctx.taker = undefined;
|
||||||
if (isBuyerMaker) ctx.taker = undefined;
|
|
||||||
else ctx.maker = undefined;
|
else ctx.maker = undefined;
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
@ -1908,115 +1961,127 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
|
|||||||
// wait for deposit txs to unlock
|
// wait for deposit txs to unlock
|
||||||
await waitForUnlockedTxs(trade.getMakerDepositTxId(), trade.getTakerDepositTxId());
|
await waitForUnlockedTxs(trade.getMakerDepositTxId(), trade.getTakerDepositTxId());
|
||||||
|
|
||||||
// buyer comes online if offline
|
// buyer comes online if offline and used
|
||||||
if (ctx.buyerOfflineAfterTake) {
|
if (ctx.buyerOfflineAfterTake && ((ctx.buyerSendsPayment && !ctx.isPaymentSent && ctx.sellerDisputeContext !== DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK) || (ctx.buyerDisputeContext === DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK && !ctx.buyerOpenedDispute))) {
|
||||||
ctx.buyer = await initHaveno({appName: buyerAppName, excludePorts: usedPorts});
|
const buyer = await initHaveno({appName: ctx.buyerAppName, excludePorts: ctx.usedPorts});
|
||||||
if (isBuyerMaker) ctx.maker = ctx.buyer;
|
if (isBuyerMaker(ctx)) ctx.maker = buyer;
|
||||||
else ctx.taker = ctx.buyer;
|
else ctx.taker = buyer;
|
||||||
usedPorts.push(getPort(ctx.buyer!.getUrl()));
|
ctx.usedPorts.push(getPort(buyer.getUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// test trade states
|
// wait for traders to observe
|
||||||
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
||||||
|
|
||||||
|
// test buyer trade state if online
|
||||||
const expectedState = ctx.isPaymentSent ? "PAYMENT_SENT" : "DEPOSITS_UNLOCKED" // TODO: test COMPLETED, PAYMENT_RECEIVED states?
|
const expectedState = ctx.isPaymentSent ? "PAYMENT_SENT" : "DEPOSITS_UNLOCKED" // TODO: test COMPLETED, PAYMENT_RECEIVED states?
|
||||||
expect((await ctx.buyer!.getTrade(offer!.getId())).getPhase()).toEqual(expectedState);
|
if (getBuyer(ctx)) {
|
||||||
fetchedTrade = await ctx.buyer!.getTrade(ctx.offerId!);
|
expect((await getBuyer(ctx)!.getTrade(ctx.offer!.getId())).getPhase()).toEqual(expectedState);
|
||||||
|
fetchedTrade = await getBuyer(ctx)!.getTrade(ctx.offerId!);
|
||||||
expect(fetchedTrade.getIsDepositUnlocked()).toBe(true);
|
expect(fetchedTrade.getIsDepositUnlocked()).toBe(true);
|
||||||
expect(fetchedTrade.getPhase()).toEqual(expectedState);
|
expect(fetchedTrade.getPhase()).toEqual(expectedState);
|
||||||
if (!ctx.sellerOfflineAfterTake) {
|
}
|
||||||
fetchedTrade = await ctx.seller!.getTrade(trade.getTradeId());
|
|
||||||
|
// test seller trade state if online
|
||||||
|
if (getSeller(ctx)) {
|
||||||
|
fetchedTrade = await getSeller(ctx)!.getTrade(trade.getTradeId());
|
||||||
expect(fetchedTrade.getIsDepositUnlocked()).toBe(true);
|
expect(fetchedTrade.getIsDepositUnlocked()).toBe(true);
|
||||||
expect(fetchedTrade.getPhase()).toEqual(expectedState);
|
expect(fetchedTrade.getPhase()).toEqual(expectedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// buyer has seller's payment account payload after first confirmation
|
// buyer has seller's payment account payload after first confirmation
|
||||||
fetchedTrade = await ctx.buyer!.getTrade(ctx.offerId!);
|
let sellerPaymentAccountPayload;
|
||||||
|
let form;
|
||||||
|
let expectedForm;
|
||||||
|
if (getBuyer(ctx)) {
|
||||||
|
fetchedTrade = await getBuyer(ctx)!.getTrade(ctx.offerId!);
|
||||||
contract = fetchedTrade.getContract()!;
|
contract = fetchedTrade.getContract()!;
|
||||||
let sellerPaymentAccountPayload = contract.getIsBuyerMakerAndSellerTaker() ? contract.getTakerPaymentAccountPayload() : contract.getMakerPaymentAccountPayload();
|
sellerPaymentAccountPayload = contract.getIsBuyerMakerAndSellerTaker() ? contract.getTakerPaymentAccountPayload() : contract.getMakerPaymentAccountPayload();
|
||||||
expect(sellerPaymentAccountPayload).toEqual(expectedSellerPaymentAccountPayload);
|
expect(sellerPaymentAccountPayload).toEqual(expectedSellerPaymentAccountPayload);
|
||||||
let form = await ctx.buyer!.getPaymentAccountPayloadForm(sellerPaymentAccountPayload!);
|
form = await getBuyer(ctx)!.getPaymentAccountPayloadForm(sellerPaymentAccountPayload!);
|
||||||
let expectedForm = await ctx.buyer!.getPaymentAccountPayloadForm(expectedSellerPaymentAccountPayload!);
|
expectedForm = await getBuyer(ctx)!.getPaymentAccountPayloadForm(expectedSellerPaymentAccountPayload!);
|
||||||
expect(HavenoUtils.formToString(form)).toEqual(HavenoUtils.formToString(expectedForm));
|
expect(HavenoUtils.formToString(form)).toEqual(HavenoUtils.formToString(expectedForm));
|
||||||
|
}
|
||||||
|
|
||||||
// buyer notified to send payment TODO
|
// buyer notified to send payment TODO
|
||||||
|
|
||||||
// open dispute(s) if configured
|
// open dispute(s) if configured
|
||||||
if (ctx.buyerDisputeContext === DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK && !ctx.buyerOpenedDispute) {
|
if (ctx.buyerDisputeContext === DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK && !ctx.buyerOpenedDispute) {
|
||||||
await ctx.buyer!.openDispute(ctx.offerId!);
|
await getBuyer(ctx)!.openDispute(ctx.offerId!);
|
||||||
ctx.buyerOpenedDispute = true;
|
ctx.buyerOpenedDispute = true;
|
||||||
ctx.disputeOpener = ctx.buyer;
|
ctx.disputeOpener = SaleRole.BUYER;
|
||||||
}
|
}
|
||||||
if (ctx.sellerDisputeContext === DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK && !ctx.sellerOpenedDispute) {
|
if (ctx.sellerDisputeContext === DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK && !ctx.sellerOpenedDispute) {
|
||||||
await ctx.seller!.openDispute(ctx.offerId!);
|
await getSeller(ctx)!.openDispute(ctx.offerId!);
|
||||||
ctx.sellerOpenedDispute = true;
|
ctx.sellerOpenedDispute = true;
|
||||||
if (!ctx.disputeOpener) ctx.disputeOpener = ctx.seller;
|
if (!ctx.disputeOpener) ctx.disputeOpener = SaleRole.SELLER;
|
||||||
}
|
|
||||||
if (ctx.disputeOpener) {
|
|
||||||
ctx.disputePeer = ctx.disputeOpener === ctx.buyer ? ctx.seller : ctx.buyer;
|
|
||||||
await testOpenDispute(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if dispute opened, resolve dispute if configured and return
|
// handle opened dispute
|
||||||
if (ctx.disputeOpener) {
|
if (ctx.disputeOpener) {
|
||||||
|
|
||||||
|
// test open dispute
|
||||||
|
await testOpenDispute(ctx);
|
||||||
|
|
||||||
|
// resolve dispute if configured
|
||||||
if (ctx.resolveDispute) await resolveDispute(ctx);
|
if (ctx.resolveDispute) await resolveDispute(ctx);
|
||||||
|
|
||||||
|
// return offer id
|
||||||
return ctx.offerId!;
|
return ctx.offerId!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// buyer confirms payment is sent
|
// buyer confirms payment is sent
|
||||||
if (!ctx.buyerSendsPayment) return offer!.getId();
|
if (!ctx.buyerSendsPayment) return ctx.offer!.getId();
|
||||||
else if (!ctx.isPaymentSent) {
|
else if (!ctx.isPaymentSent) {
|
||||||
HavenoUtils.log(1, "Buyer confirming payment sent");
|
HavenoUtils.log(1, "Buyer confirming payment sent");
|
||||||
await ctx.buyer!.confirmPaymentStarted(trade.getTradeId());
|
await getBuyer(ctx)!.confirmPaymentStarted(trade.getTradeId());
|
||||||
ctx.isPaymentSent = true;
|
ctx.isPaymentSent = true;
|
||||||
fetchedTrade = await ctx.buyer!.getTrade(trade.getTradeId());
|
fetchedTrade = await getBuyer(ctx)!.getTrade(trade.getTradeId());
|
||||||
expect(fetchedTrade.getPhase()).toEqual("PAYMENT_SENT");
|
expect(fetchedTrade.getPhase()).toEqual("PAYMENT_SENT");
|
||||||
}
|
}
|
||||||
|
|
||||||
// buyer goes offline if configured
|
// buyer goes offline if configured
|
||||||
if (ctx.buyerOfflineAfterPaymentSent) {
|
if (ctx.buyerOfflineAfterPaymentSent) {
|
||||||
await releaseHavenoProcess(ctx.buyer!);
|
await releaseHavenoProcess(getBuyer(ctx)!);
|
||||||
ctx.buyer = undefined;
|
if (isBuyerMaker(ctx)) ctx.maker = undefined;
|
||||||
if (isBuyerMaker) ctx.maker = undefined;
|
|
||||||
else ctx.taker = undefined;
|
else ctx.taker = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// seller comes online if offline
|
// seller comes online if offline
|
||||||
if (!ctx.seller) {
|
if (!getSeller(ctx)) {
|
||||||
ctx.seller = await initHaveno({appName: sellerAppName, excludePorts: usedPorts});
|
const seller = await initHaveno({appName: ctx.sellerAppName, excludePorts: ctx.usedPorts});
|
||||||
if (isBuyerMaker) ctx.taker = ctx.seller;
|
if (isBuyerMaker(ctx)) ctx.taker = seller;
|
||||||
else ctx.maker = ctx.seller;
|
else ctx.maker = seller;
|
||||||
usedPorts.push(getPort(ctx.seller!.getUrl()))
|
ctx.usedPorts.push(getPort(getSeller(ctx)!.getUrl()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// seller notified payment is sent
|
// seller notified payment is sent
|
||||||
await wait(ctx.maxTimePeerNoticeMs! + TestConfig.maxWalletStartupMs); // TODO: test notification
|
await wait(ctx.maxTimePeerNoticeMs! + TestConfig.maxWalletStartupMs); // TODO: test notification
|
||||||
if (ctx.sellerOfflineAfterTake) await wait(ctx.walletSyncPeriodMs!); // wait to process mailbox messages
|
if (ctx.sellerOfflineAfterTake) await wait(ctx.walletSyncPeriodMs!); // wait to process mailbox messages
|
||||||
fetchedTrade = await ctx.seller.getTrade(trade.getTradeId());
|
fetchedTrade = await getSeller(ctx)!.getTrade(trade.getTradeId());
|
||||||
expect(fetchedTrade.getPhase()).toEqual("PAYMENT_SENT");
|
expect(fetchedTrade.getPhase()).toEqual("PAYMENT_SENT");
|
||||||
expect(fetchedTrade.getPayoutState()).toEqual("PAYOUT_UNPUBLISHED");
|
expect(fetchedTrade.getPayoutState()).toEqual("PAYOUT_UNPUBLISHED");
|
||||||
|
|
||||||
// seller has buyer's payment account payload after payment sent
|
// seller has buyer's payment account payload after payment sent
|
||||||
fetchedTrade = await ctx.seller!.getTrade(ctx.offerId!);
|
fetchedTrade = await getSeller(ctx)!.getTrade(ctx.offerId!);
|
||||||
contract = fetchedTrade.getContract()!;
|
contract = fetchedTrade.getContract()!;
|
||||||
buyerPaymentAccountPayload = contract.getIsBuyerMakerAndSellerTaker() ? contract.getMakerPaymentAccountPayload() : contract.getTakerPaymentAccountPayload();
|
buyerPaymentAccountPayload = contract.getIsBuyerMakerAndSellerTaker() ? contract.getMakerPaymentAccountPayload() : contract.getTakerPaymentAccountPayload();
|
||||||
expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
||||||
form = await ctx.seller!.getPaymentAccountPayloadForm(sellerPaymentAccountPayload!);
|
form = await getSeller(ctx)!.getPaymentAccountPayloadForm(sellerPaymentAccountPayload!);
|
||||||
expectedForm = await ctx.seller!.getPaymentAccountPayloadForm(expectedSellerPaymentAccountPayload!);
|
expectedForm = await getSeller(ctx)!.getPaymentAccountPayloadForm(expectedSellerPaymentAccountPayload!);
|
||||||
expect(HavenoUtils.formToString(form)).toEqual(HavenoUtils.formToString(expectedForm));
|
expect(HavenoUtils.formToString(form)).toEqual(HavenoUtils.formToString(expectedForm));
|
||||||
|
|
||||||
// open dispute(s) if configured
|
// open dispute(s) if configured
|
||||||
if (ctx.buyerDisputeContext === DisputeContext.OPEN_AFTER_PAYMENT_SENT && !ctx.buyerOpenedDispute) {
|
if (ctx.buyerDisputeContext === DisputeContext.OPEN_AFTER_PAYMENT_SENT && !ctx.buyerOpenedDispute) {
|
||||||
await ctx.buyer!.openDispute(ctx.offerId!);
|
await getBuyer(ctx)!.openDispute(ctx.offerId!);
|
||||||
ctx.buyerOpenedDispute = true;
|
ctx.buyerOpenedDispute = true;
|
||||||
if (!ctx.disputeOpener) ctx.disputeOpener = ctx.buyer;
|
if (!ctx.disputeOpener) ctx.disputeOpener = SaleRole.BUYER;
|
||||||
}
|
}
|
||||||
if (ctx.sellerDisputeContext === DisputeContext.OPEN_AFTER_PAYMENT_SENT && !ctx.sellerOpenedDispute) {
|
if (ctx.sellerDisputeContext === DisputeContext.OPEN_AFTER_PAYMENT_SENT && !ctx.sellerOpenedDispute) {
|
||||||
await ctx.seller!.openDispute(ctx.offerId!);
|
await getSeller(ctx)!.openDispute(ctx.offerId!);
|
||||||
ctx.sellerOpenedDispute = true;
|
ctx.sellerOpenedDispute = true;
|
||||||
if (!ctx.disputeOpener) ctx.disputeOpener = ctx.seller;
|
if (!ctx.disputeOpener) ctx.disputeOpener = SaleRole.SELLER;
|
||||||
}
|
}
|
||||||
if (ctx.disputeOpener) {
|
if (ctx.disputeOpener) {
|
||||||
ctx.disputePeer = ctx.disputeOpener === ctx.buyer ? ctx.seller : ctx.buyer;
|
|
||||||
await testOpenDispute(ctx);
|
await testOpenDispute(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2027,45 +2092,45 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// seller confirms payment is received
|
// seller confirms payment is received
|
||||||
if (!ctx.sellerReceivesPayment) return offer!.getId();
|
if (!ctx.sellerReceivesPayment) return ctx.offer!.getId();
|
||||||
else if (!ctx.isPaymentReceived) {
|
else if (!ctx.isPaymentReceived) {
|
||||||
HavenoUtils.log(1, "Seller confirming payment received");
|
HavenoUtils.log(1, "Seller confirming payment received");
|
||||||
await ctx.seller.confirmPaymentReceived(trade.getTradeId());
|
await getSeller(ctx)!.confirmPaymentReceived(trade.getTradeId());
|
||||||
ctx.isPaymentReceived = true;
|
ctx.isPaymentReceived = true;
|
||||||
fetchedTrade = await ctx.seller.getTrade(trade.getTradeId());
|
fetchedTrade = await getSeller(ctx)!.getTrade(trade.getTradeId());
|
||||||
expect(fetchedTrade.getPhase()).toEqual("PAYMENT_RECEIVED");
|
expect(fetchedTrade.getPhase()).toEqual("PAYMENT_RECEIVED");
|
||||||
await wait(ctx.walletSyncPeriodMs!); // buyer or arbitrator will sign and publish payout tx
|
await wait(ctx.walletSyncPeriodMs!); // buyer or arbitrator will sign and publish payout tx
|
||||||
await testTradeState(await ctx.seller!.getTrade(trade.getTradeId()), {phase: "PAYMENT_RECEIVED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: false, isPayoutPublished: true});
|
await testTradeState(await getSeller(ctx)!.getTrade(trade.getTradeId()), {phase: "PAYMENT_RECEIVED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: false, isPayoutPublished: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
// payout tx is published by buyer (priority) or arbitrator
|
// payout tx is published by buyer (priority) or arbitrator
|
||||||
await wait(ctx.walletSyncPeriodMs!);
|
await wait(ctx.walletSyncPeriodMs!);
|
||||||
await testTradeState(await ctx.seller!.getTrade(trade.getTradeId()), {phase: "PAYMENT_RECEIVED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: false, isPayoutPublished: true});
|
await testTradeState(await getSeller(ctx)!.getTrade(trade.getTradeId()), {phase: "PAYMENT_RECEIVED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: false, isPayoutPublished: true});
|
||||||
await testTradeState(await ctx.arbitrator!.getTrade(trade.getTradeId()), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: true, isPayoutPublished: true}); // arbitrator trade auto completes
|
await testTradeState(await ctx.arbitrator!.getTrade(trade.getTradeId()), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: true, isPayoutPublished: true}); // arbitrator trade auto completes
|
||||||
|
|
||||||
// buyer comes online if offline
|
// buyer comes online if offline
|
||||||
if (ctx.buyerOfflineAfterPaymentSent) {
|
if (ctx.buyerOfflineAfterPaymentSent) {
|
||||||
ctx.buyer = await initHaveno({appName: buyerAppName, excludePorts: usedPorts});
|
const buyer = await initHaveno({appName: ctx.buyerAppName, excludePorts: ctx.usedPorts});
|
||||||
if (isBuyerMaker) ctx.maker = ctx.buyer;
|
if (isBuyerMaker(ctx)) ctx.maker = buyer;
|
||||||
else ctx.taker = ctx.buyer;
|
else ctx.taker = buyer;
|
||||||
usedPorts.push(getPort(ctx.buyer!.getUrl()));
|
ctx.usedPorts.push(getPort(buyer.getUrl()));
|
||||||
HavenoUtils.log(1, "Done starting buyer");
|
HavenoUtils.log(1, "Done starting buyer");
|
||||||
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs!);
|
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs!);
|
||||||
}
|
}
|
||||||
await testTradeState(await ctx.buyer!.getTrade(trade.getTradeId()), {phase: "PAYMENT_RECEIVED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: false, isPayoutPublished: true});
|
await testTradeState(await getBuyer(ctx)!.getTrade(trade.getTradeId()), {phase: "PAYMENT_RECEIVED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: false, isPayoutPublished: true});
|
||||||
|
|
||||||
// test trade completion
|
// test trade completion
|
||||||
await ctx.buyer!.completeTrade(trade.getTradeId());
|
await getBuyer(ctx)!.completeTrade(trade.getTradeId());
|
||||||
await testTradeState(await ctx.buyer!.getTrade(trade.getTradeId()), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: true, isPayoutPublished: true});
|
await testTradeState(await getBuyer(ctx)!.getTrade(trade.getTradeId()), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: true, isPayoutPublished: true});
|
||||||
await ctx.seller!.completeTrade(trade.getTradeId());
|
await getSeller(ctx)!.completeTrade(trade.getTradeId());
|
||||||
await testTradeState(await ctx.seller!.getTrade(trade.getTradeId()), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: true, isPayoutPublished: true});
|
await testTradeState(await getSeller(ctx)!.getTrade(trade.getTradeId()), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], isCompleted: true, isPayoutPublished: true});
|
||||||
|
|
||||||
// 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) {
|
||||||
const buyerBalancesAfter = await ctx.buyer!.getBalances();
|
const buyerBalancesAfter = await getBuyer(ctx)!.getBalances();
|
||||||
const sellerBalancesAfter = await ctx.seller.getBalances();
|
const sellerBalancesAfter = await getSeller(ctx)!.getBalances();
|
||||||
const buyerFee = BigInt(buyerBalancesBefore.getBalance()) + BigInt(buyerBalancesBefore.getReservedOfferBalance()) + BigInt(offer!.getAmount()) - (BigInt(buyerBalancesAfter.getBalance()) + BigInt(buyerBalancesAfter.getReservedOfferBalance())); // buyer fee = total balance before + offer amount - total balance after
|
const buyerFee = BigInt(buyerBalancesBefore.getBalance()) + BigInt(buyerBalancesBefore.getReservedOfferBalance()) + BigInt(ctx.offer!.getAmount()) - (BigInt(buyerBalancesAfter.getBalance()) + BigInt(buyerBalancesAfter.getReservedOfferBalance())); // buyer fee = total balance before + offer amount - total balance after
|
||||||
const sellerFee = BigInt(sellerBalancesBefore.getBalance()) + BigInt(sellerBalancesBefore.getReservedOfferBalance()) - BigInt(offer!.getAmount()) - (BigInt(sellerBalancesAfter.getBalance()) + BigInt(sellerBalancesAfter.getReservedOfferBalance())); // seller fee = total balance before - offer amount - total balance after
|
const sellerFee = BigInt(sellerBalancesBefore.getBalance()) + BigInt(sellerBalancesBefore.getReservedOfferBalance()) - BigInt(ctx.offer!.getAmount()) - (BigInt(sellerBalancesAfter.getBalance()) + BigInt(sellerBalancesAfter.getReservedOfferBalance())); // seller fee = total balance before - offer amount - total balance after
|
||||||
expect(buyerFee).toBeLessThanOrEqual(TestConfig.maxFee);
|
expect(buyerFee).toBeLessThanOrEqual(TestConfig.maxFee);
|
||||||
expect(buyerFee).toBeGreaterThan(BigInt("0"));
|
expect(buyerFee).toBeGreaterThan(BigInt("0"));
|
||||||
expect(sellerFee).toBeLessThanOrEqual(TestConfig.maxFee);
|
expect(sellerFee).toBeLessThanOrEqual(TestConfig.maxFee);
|
||||||
@ -2074,7 +2139,8 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
|
|||||||
|
|
||||||
// test payout unlock
|
// test payout unlock
|
||||||
await testTradePayoutUnlock(ctx);
|
await testTradePayoutUnlock(ctx);
|
||||||
return offer!.getId();
|
if (ctx.offer!.getId() !== ctx.offerId) throw new Error("Expected offer ids to match");
|
||||||
|
return ctx.offer!.getId();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
HavenoUtils.log(0, "Error executing trade " + ctx!.offerId + (ctx!.index === undefined ? "" : " at index " + ctx!.index) + ": " + err.message);
|
HavenoUtils.log(0, "Error executing trade " + ctx!.offerId + (ctx!.index === undefined ? "" : " at index " + ctx!.index) + ": " + err.message);
|
||||||
HavenoUtils.log(0, tradeContextToString(ctx!));
|
HavenoUtils.log(0, tradeContextToString(ctx!));
|
||||||
@ -2086,24 +2152,24 @@ async function testTradePayoutUnlock(ctx: TradeContext) {
|
|||||||
const height = await monerod.getHeight();
|
const height = await monerod.getHeight();
|
||||||
|
|
||||||
// test after payout confirmed
|
// test after payout confirmed
|
||||||
const payoutTxId = (await ctx.buyer!.getTrade(ctx.offerId!)).getPayoutTxId();
|
const payoutTxId = (await ctx.arbitrator!.getTrade(ctx.offerId!)).getPayoutTxId();
|
||||||
let trade = await ctx.buyer!.getTrade(ctx.offerId!);
|
let trade = await ctx.arbitrator!.getTrade(ctx.offerId!);
|
||||||
if (trade.getPayoutState() !== "PAYOUT_CONFIRMED") await mineToHeight(height + 1);
|
if (trade.getPayoutState() !== "PAYOUT_CONFIRMED") await mineToHeight(height + 1);
|
||||||
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
||||||
await testTradeState(await ctx.buyer!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]});
|
if (getBuyer(ctx)) await testTradeState(await getBuyer(ctx)!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]});
|
||||||
await testTradeState(await ctx.seller!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]});
|
if (getSeller(ctx)) await testTradeState(await getSeller(ctx)!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]});
|
||||||
await testTradeState(await ctx.arbitrator!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]}); // arbitrator idles wallet
|
await testTradeState(await ctx.arbitrator!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]}); // arbitrator idles wallet
|
||||||
let payoutTx = await ctx.buyer?.getXmrTx(payoutTxId);
|
let payoutTx = getBuyer(ctx) ? await getBuyer(ctx)?.getXmrTx(payoutTxId) : await getSeller(ctx)?.getXmrTx(payoutTxId);
|
||||||
expect(payoutTx?.getIsConfirmed());
|
expect(payoutTx?.getIsConfirmed());
|
||||||
|
|
||||||
// test after payout unlocked
|
// test after payout unlocked
|
||||||
trade = await ctx.buyer!.getTrade(ctx.offerId!);
|
trade = await ctx.arbitrator!.getTrade(ctx.offerId!);
|
||||||
if (trade.getPayoutState() !== "PAYOUT_UNLOCKED") await mineToHeight(height + 10);
|
if (trade.getPayoutState() !== "PAYOUT_UNLOCKED") await mineToHeight(height + 10);
|
||||||
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
||||||
await testTradeState(await ctx.buyer!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_UNLOCKED"]});
|
if (await getBuyer(ctx)) await testTradeState(await getBuyer(ctx)!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_UNLOCKED"]});
|
||||||
await testTradeState(await ctx.seller!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_UNLOCKED"]});
|
if (await getSeller(ctx)) await testTradeState(await getSeller(ctx)!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_UNLOCKED"]});
|
||||||
await testTradeState(await ctx.arbitrator!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]}); // arbitrator idles wallet
|
await testTradeState(await ctx.arbitrator!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"]}); // arbitrator idles wallet
|
||||||
payoutTx = await ctx.buyer?.getXmrTx(payoutTxId);
|
payoutTx = getBuyer(ctx) ? await getBuyer(ctx)?.getXmrTx(payoutTxId) : await getSeller(ctx)?.getXmrTx(payoutTxId);
|
||||||
expect(!payoutTx?.getIsLocked());
|
expect(!payoutTx?.getIsLocked());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2235,15 +2301,21 @@ async function takeOffer(ctx: TradeContext): Promise<TradeInfo> {
|
|||||||
|
|
||||||
async function testOpenDispute(ctx: TradeContext) {
|
async function testOpenDispute(ctx: TradeContext) {
|
||||||
|
|
||||||
|
// TODO: test open dispute when buyer or seller offline
|
||||||
|
if (!getBuyer(ctx) || !getSeller(ctx)) {
|
||||||
|
HavenoUtils.log(0, "WARNING: skipping test open dispute tests because a trader is offline"); // TODO: update tests for offline trader
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// test dispute state
|
// test dispute state
|
||||||
const openerDispute = await ctx.disputeOpener!.getDispute(ctx.offerId!);
|
const openerDispute = await getDisputeOpener(ctx)!.getDispute(ctx.offerId!);
|
||||||
expect(openerDispute.getTradeId()).toEqual(ctx.offerId);
|
expect(openerDispute.getTradeId()).toEqual(ctx.offerId);
|
||||||
expect(openerDispute.getIsOpener()).toBe(true);
|
expect(openerDispute.getIsOpener()).toBe(true);
|
||||||
expect(openerDispute.getDisputeOpenerIsBuyer()).toBe(ctx.disputeOpener === ctx.buyer);
|
expect(openerDispute.getDisputeOpenerIsBuyer()).toBe(getDisputeOpener(ctx) === getBuyer(ctx));
|
||||||
|
|
||||||
// get non-existing dispute should fail
|
// get non-existing dispute should fail
|
||||||
try {
|
try {
|
||||||
await ctx.disputeOpener!.getDispute("invalid");
|
await getDisputeOpener(ctx)!.getDispute("invalid");
|
||||||
throw new Error("get dispute with invalid id should fail");
|
throw new Error("get dispute with invalid id should fail");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
assert.equal(err.message, "dispute for trade id 'invalid' not found");
|
assert.equal(err.message, "dispute for trade id 'invalid' not found");
|
||||||
@ -2251,7 +2323,7 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
|
|
||||||
// peer sees the dispute
|
// peer sees the dispute
|
||||||
await wait(ctx.maxTimePeerNoticeMs! + TestConfig.maxWalletStartupMs);
|
await wait(ctx.maxTimePeerNoticeMs! + TestConfig.maxWalletStartupMs);
|
||||||
const peerDispute = await ctx.disputePeer!.getDispute(ctx.offerId!);
|
const peerDispute = await getDisputePeer(ctx)!.getDispute(ctx.offerId!);
|
||||||
expect(peerDispute.getTradeId()).toEqual(ctx.offerId);
|
expect(peerDispute.getTradeId()).toEqual(ctx.offerId);
|
||||||
expect(peerDispute.getIsOpener()).toBe(false || ctx.buyerDisputeContext === ctx.sellerDisputeContext); // TODO: both peers think they're the opener if disputes opened at same time since not waiting for ack
|
expect(peerDispute.getIsOpener()).toBe(false || ctx.buyerDisputeContext === ctx.sellerDisputeContext); // TODO: both peers think they're the opener if disputes opened at same time since not waiting for ack
|
||||||
|
|
||||||
@ -2265,7 +2337,7 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
|
|
||||||
// arbitrator has seller's payment account info
|
// arbitrator has seller's payment account info
|
||||||
let sellerPaymentAccountPayload = arbDisputeOpener.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputeOpener.getTakerPaymentAccountPayload() : arbDisputeOpener.getMakerPaymentAccountPayload();
|
let sellerPaymentAccountPayload = arbDisputeOpener.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputeOpener.getTakerPaymentAccountPayload() : arbDisputeOpener.getMakerPaymentAccountPayload();
|
||||||
let expectedSellerPaymentAccountPayload = (await ctx.seller?.getPaymentAccount(sellerPaymentAccountPayload?.getId()!))?.getPaymentAccountPayload();
|
let expectedSellerPaymentAccountPayload = (await getSeller(ctx)?.getPaymentAccount(sellerPaymentAccountPayload?.getId()!))?.getPaymentAccountPayload();
|
||||||
expect(sellerPaymentAccountPayload).toEqual(expectedSellerPaymentAccountPayload);
|
expect(sellerPaymentAccountPayload).toEqual(expectedSellerPaymentAccountPayload);
|
||||||
expect(await ctx.arbitrator?.getPaymentAccountPayloadForm(sellerPaymentAccountPayload!)).toEqual(await ctx.arbitrator?.getPaymentAccountPayloadForm(expectedSellerPaymentAccountPayload!));
|
expect(await ctx.arbitrator?.getPaymentAccountPayloadForm(sellerPaymentAccountPayload!)).toEqual(await ctx.arbitrator?.getPaymentAccountPayloadForm(expectedSellerPaymentAccountPayload!));
|
||||||
sellerPaymentAccountPayload = arbDisputePeer.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputePeer.getTakerPaymentAccountPayload() : arbDisputeOpener.getMakerPaymentAccountPayload();
|
sellerPaymentAccountPayload = arbDisputePeer.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputePeer.getTakerPaymentAccountPayload() : arbDisputeOpener.getMakerPaymentAccountPayload();
|
||||||
@ -2274,16 +2346,16 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
|
|
||||||
// arbitrator has buyer's payment account info unless opener is seller and payment not sent
|
// arbitrator has buyer's payment account info unless opener is seller and payment not sent
|
||||||
let buyerPaymentAccountPayload = arbDisputeOpener.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputeOpener.getMakerPaymentAccountPayload() : arbDisputeOpener.getTakerPaymentAccountPayload();
|
let buyerPaymentAccountPayload = arbDisputeOpener.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputeOpener.getMakerPaymentAccountPayload() : arbDisputeOpener.getTakerPaymentAccountPayload();
|
||||||
if (ctx.disputeOpener === ctx.seller && !ctx.isPaymentSent) expect(buyerPaymentAccountPayload).toBeUndefined();
|
if (getDisputeOpener(ctx) === getSeller(ctx) && !ctx.isPaymentSent) expect(buyerPaymentAccountPayload).toBeUndefined();
|
||||||
else {
|
else {
|
||||||
let expectedBuyerPaymentAccountPayload = (await ctx.buyer?.getPaymentAccount(buyerPaymentAccountPayload?.getId()!))?.getPaymentAccountPayload();
|
let expectedBuyerPaymentAccountPayload = (await getBuyer(ctx)?.getPaymentAccount(buyerPaymentAccountPayload?.getId()!))?.getPaymentAccountPayload();
|
||||||
expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
||||||
expect(await ctx.arbitrator?.getPaymentAccountPayloadForm(buyerPaymentAccountPayload!)).toEqual(await ctx.arbitrator?.getPaymentAccountPayloadForm(expectedBuyerPaymentAccountPayload!));
|
expect(await ctx.arbitrator?.getPaymentAccountPayloadForm(buyerPaymentAccountPayload!)).toEqual(await ctx.arbitrator?.getPaymentAccountPayloadForm(expectedBuyerPaymentAccountPayload!));
|
||||||
}
|
}
|
||||||
buyerPaymentAccountPayload = arbDisputePeer.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputePeer.getMakerPaymentAccountPayload() : arbDisputePeer.getTakerPaymentAccountPayload();
|
buyerPaymentAccountPayload = arbDisputePeer.getContract()!.getIsBuyerMakerAndSellerTaker() ? arbDisputePeer.getMakerPaymentAccountPayload() : arbDisputePeer.getTakerPaymentAccountPayload();
|
||||||
if (ctx.disputeOpener === ctx.seller && !ctx.isPaymentSent) expect(buyerPaymentAccountPayload).toBeUndefined();
|
if (getDisputeOpener(ctx) === getSeller(ctx) && !ctx.isPaymentSent) expect(buyerPaymentAccountPayload).toBeUndefined();
|
||||||
else {
|
else {
|
||||||
let expectedBuyerPaymentAccountPayload = (await ctx.buyer?.getPaymentAccount(buyerPaymentAccountPayload?.getId()!))?.getPaymentAccountPayload();
|
let expectedBuyerPaymentAccountPayload = (await getBuyer(ctx)?.getPaymentAccount(buyerPaymentAccountPayload?.getId()!))?.getPaymentAccountPayload();
|
||||||
expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
expect(buyerPaymentAccountPayload).toEqual(expectedBuyerPaymentAccountPayload);
|
||||||
expect(await ctx.arbitrator?.getPaymentAccountPayloadForm(buyerPaymentAccountPayload!)).toEqual(await ctx.arbitrator?.getPaymentAccountPayloadForm(expectedBuyerPaymentAccountPayload!));
|
expect(await ctx.arbitrator?.getPaymentAccountPayloadForm(buyerPaymentAccountPayload!)).toEqual(await ctx.arbitrator?.getPaymentAccountPayloadForm(expectedBuyerPaymentAccountPayload!));
|
||||||
}
|
}
|
||||||
@ -2292,8 +2364,8 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
const disputeOpenerNotifications: NotificationMessage[] = [];
|
const disputeOpenerNotifications: NotificationMessage[] = [];
|
||||||
const disputePeerNotifications: NotificationMessage[] = [];
|
const disputePeerNotifications: NotificationMessage[] = [];
|
||||||
const arbitratorNotifications: NotificationMessage[] = [];
|
const arbitratorNotifications: NotificationMessage[] = [];
|
||||||
await ctx.disputeOpener!.addNotificationListener(notification => { HavenoUtils.log(3, "Dispute opener received notification " + notification.getType() + " " + (notification.getChatMessage() ? notification.getChatMessage()?.getMessage() : "")); disputeOpenerNotifications.push(notification); });
|
await getDisputeOpener(ctx)!.addNotificationListener(notification => { HavenoUtils.log(3, "Dispute opener received notification " + notification.getType() + " " + (notification.getChatMessage() ? notification.getChatMessage()?.getMessage() : "")); disputeOpenerNotifications.push(notification); });
|
||||||
await ctx.disputePeer!.addNotificationListener(notification => { HavenoUtils.log(3, "Dispute peer received notification " + notification.getType() + " " + (notification.getChatMessage() ? notification.getChatMessage()?.getMessage() : "")); disputePeerNotifications.push(notification); });
|
await getDisputePeer(ctx)!.addNotificationListener(notification => { HavenoUtils.log(3, "Dispute peer received notification " + notification.getType() + " " + (notification.getChatMessage() ? notification.getChatMessage()?.getMessage() : "")); disputePeerNotifications.push(notification); });
|
||||||
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
|
||||||
@ -2312,14 +2384,14 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
attachment2.setBytes(bytes2);
|
attachment2.setBytes(bytes2);
|
||||||
attachment2.setFileName("proof.png");
|
attachment2.setFileName("proof.png");
|
||||||
HavenoUtils.log(2, "Dispute opener sending chat message to arbitrator. tradeId=" + ctx.offerId + ", disputeId=" + openerDispute.getId());
|
HavenoUtils.log(2, "Dispute opener sending chat message to arbitrator. tradeId=" + ctx.offerId + ", disputeId=" + openerDispute.getId());
|
||||||
await ctx.disputeOpener!.sendDisputeChatMessage(openerDispute.getId(), "Dispute opener chat message", [attachment, attachment2]);
|
await getDisputeOpener(ctx)!.sendDisputeChatMessage(openerDispute.getId(), "Dispute opener chat message", [attachment, attachment2]);
|
||||||
await wait(ctx.maxTimePeerNoticeMs!); // wait for user2's message to arrive
|
await wait(ctx.maxTimePeerNoticeMs!); // wait for user2's message to arrive
|
||||||
HavenoUtils.log(2, "Dispute peer sending chat message to arbitrator. tradeId=" + ctx.offerId + ", disputeId=" + peerDispute.getId());
|
HavenoUtils.log(2, "Dispute peer sending chat message to arbitrator. tradeId=" + ctx.offerId + ", disputeId=" + peerDispute.getId());
|
||||||
await ctx.disputePeer!.sendDisputeChatMessage(peerDispute.getId(), "Dispute peer chat message", []);
|
await getDisputePeer(ctx)!.sendDisputeChatMessage(peerDispute.getId(), "Dispute peer chat message", []);
|
||||||
|
|
||||||
// test trader chat messages
|
// test trader chat messages
|
||||||
await wait(ctx.maxTimePeerNoticeMs!);
|
await wait(ctx.maxTimePeerNoticeMs!);
|
||||||
let dispute = await ctx.disputeOpener!.getDispute(ctx.offerId!);
|
let dispute = await getDisputeOpener(ctx)!.getDispute(ctx.offerId!);
|
||||||
let messages = dispute.getChatMessageList();
|
let messages = dispute.getChatMessageList();
|
||||||
expect(messages.length).toBeGreaterThanOrEqual(3); // last messages are chat, first messages are system message and possibly DisputeOpenedMessage acks
|
expect(messages.length).toBeGreaterThanOrEqual(3); // last messages are chat, first messages are system message and possibly DisputeOpenedMessage acks
|
||||||
expect(messages[messages.length - 2].getMessage()).toEqual("Arbitrator chat message to dispute opener");
|
expect(messages[messages.length - 2].getMessage()).toEqual("Arbitrator chat message to dispute opener");
|
||||||
@ -2330,7 +2402,7 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
expect(attachments[0].getBytes()).toEqual(bytes);
|
expect(attachments[0].getBytes()).toEqual(bytes);
|
||||||
expect(attachments[1].getFileName()).toEqual("proof.png");
|
expect(attachments[1].getFileName()).toEqual("proof.png");
|
||||||
expect(attachments[1].getBytes()).toEqual(bytes2);
|
expect(attachments[1].getBytes()).toEqual(bytes2);
|
||||||
dispute = await ctx.disputePeer!.getDispute(ctx.offerId!);
|
dispute = await getDisputePeer(ctx)!.getDispute(ctx.offerId!);
|
||||||
messages = dispute.getChatMessageList();
|
messages = dispute.getChatMessageList();
|
||||||
expect(messages.length).toBeGreaterThanOrEqual(3);
|
expect(messages.length).toBeGreaterThanOrEqual(3);
|
||||||
expect(messages[messages.length - 2].getMessage()).toEqual("Arbitrator chat message to dispute peer");
|
expect(messages[messages.length - 2].getMessage()).toEqual("Arbitrator chat message to dispute peer");
|
||||||
@ -2359,10 +2431,24 @@ async function testOpenDispute(ctx: TradeContext) {
|
|||||||
|
|
||||||
async function resolveDispute(ctx: TradeContext) {
|
async function resolveDispute(ctx: TradeContext) {
|
||||||
|
|
||||||
|
// stop buyer or seller depending on configuration
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
if (getBuyer(ctx) && ctx.buyerOfflineAfterDisputeOpened) {
|
||||||
|
promises.push(releaseHavenoProcess(getBuyer(ctx)!)); // stop buyer
|
||||||
|
if (isBuyerMaker(ctx)) ctx.maker = undefined;
|
||||||
|
else ctx.taker = undefined;
|
||||||
|
}
|
||||||
|
if (getSeller(ctx) && ctx.sellerOfflineAfterDisputeOpened) {
|
||||||
|
promises.push(releaseHavenoProcess(getSeller(ctx)!)); // stop seller
|
||||||
|
if (isBuyerMaker(ctx)) ctx.taker = undefined;
|
||||||
|
else ctx.maker = undefined;
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
|
||||||
// award too little to loser
|
// award too little to loser
|
||||||
const offer = (await ctx.maker!.getTrade(ctx.offerId!)).getOffer();
|
const tradeAmount: bigint = BigInt(ctx.offer!.getAmount());
|
||||||
const tradeAmount: bigint = BigInt(offer!.getAmount());
|
const customWinnerAmount = tradeAmount + BigInt(ctx.offer!.getBuyerSecurityDeposit() + ctx.offer!.getSellerSecurityDeposit()) - BigInt("10000");
|
||||||
const customWinnerAmount = tradeAmount + BigInt(offer!.getBuyerSecurityDeposit() + offer!.getSellerSecurityDeposit()) - BigInt("10000");
|
|
||||||
try {
|
try {
|
||||||
await arbitrator.resolveDispute(ctx.offerId!, ctx.disputeWinner!, ctx.disputeReason!, "Loser gets too little", customWinnerAmount);
|
await arbitrator.resolveDispute(ctx.offerId!, ctx.disputeWinner!, ctx.disputeReason!, "Loser gets too little", customWinnerAmount);
|
||||||
throw new Error("Should have failed resolving dispute with insufficient loser payout");
|
throw new Error("Should have failed resolving dispute with insufficient loser payout");
|
||||||
@ -2371,42 +2457,62 @@ async function resolveDispute(ctx: TradeContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolve dispute according to configuration
|
// resolve dispute according to configuration
|
||||||
const winner = ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.buyer : ctx.seller;
|
const winner = ctx.disputeWinner === DisputeResult.Winner.BUYER ? getBuyer(ctx) : getSeller(ctx);
|
||||||
const loser = ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.seller : ctx.buyer;
|
const loser = ctx.disputeWinner === DisputeResult.Winner.BUYER ? getSeller(ctx) : getBuyer(ctx);
|
||||||
const winnerBalancesBefore = await winner!.getBalances();
|
const winnerBalancesBefore = winner ? await winner!.getBalances() : undefined;
|
||||||
const loserBalancesBefore = await loser!.getBalances();
|
const loserBalancesBefore = loser ? await loser!.getBalances() : undefined;
|
||||||
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);
|
||||||
HavenoUtils.log(1, "Done resolving dispute (" + (Date.now() - startTime) + ")");
|
HavenoUtils.log(1, "Done resolving dispute (" + (Date.now() - startTime) + ")");
|
||||||
|
|
||||||
|
// start buyer or seller depending on configuration
|
||||||
|
if (!getBuyer(ctx) && ctx.buyerOfflineAfterDisputeOpened === false) {
|
||||||
|
const buyer = await initHaveno({appName: ctx.buyerAppName, excludePorts: ctx.usedPorts}); // start buyer
|
||||||
|
if (isBuyerMaker(ctx)) ctx.maker = buyer;
|
||||||
|
else ctx.taker = buyer;
|
||||||
|
ctx.usedPorts!.push(getPort(buyer.getUrl()));
|
||||||
|
}
|
||||||
|
if (!getSeller(ctx) && ctx.sellerOfflineAfterDisputeOpened === false) {
|
||||||
|
const seller = await initHaveno({appName: ctx.sellerAppName, excludePorts: ctx.usedPorts}); // start seller
|
||||||
|
if (isBuyerMaker(ctx)) ctx.taker = seller;
|
||||||
|
else ctx.maker = seller;
|
||||||
|
ctx.usedPorts!.push(getPort(getSeller(ctx)!.getUrl()))
|
||||||
|
}
|
||||||
|
|
||||||
// test resolved dispute
|
// test resolved dispute
|
||||||
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
await wait(TestConfig.maxWalletStartupMs + ctx.walletSyncPeriodMs! * 2);
|
||||||
let dispute = await ctx.disputeOpener!.getDispute(ctx.offerId!);
|
if (getDisputeOpener(ctx)) {
|
||||||
|
const dispute = await getDisputeOpener(ctx)!.getDispute(ctx.offerId!);
|
||||||
assert(dispute.getIsClosed(), "Dispute is not closed for opener, trade " + ctx.offerId);
|
assert(dispute.getIsClosed(), "Dispute is not closed for opener, trade " + ctx.offerId);
|
||||||
dispute = await ctx.disputePeer!.getDispute(ctx.offerId!);
|
}
|
||||||
assert(dispute.getIsClosed(), "Dispute is not closed for opener's peer, trade " + ctx.offerId);
|
if (getDisputePeer(ctx)) {
|
||||||
|
const dispute = await getDisputePeer(ctx)!.getDispute(ctx.offerId!);
|
||||||
|
assert(dispute.getIsClosed(), "Dispute is not closed for opener, trade " + ctx.offerId);
|
||||||
|
}
|
||||||
|
|
||||||
// test trade state
|
// test trade state
|
||||||
await testTradeState(await ctx.buyer!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
|
if (getBuyer(ctx)) await testTradeState(await getBuyer(ctx)!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
|
||||||
await testTradeState(await ctx.seller!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
|
if (getSeller(ctx)) await testTradeState(await getSeller(ctx)!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
|
||||||
await testTradeState(await ctx.arbitrator!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
|
await testTradeState(await ctx.arbitrator!.getTrade(ctx.offerId!), {phase: "COMPLETED", payoutState: ["PAYOUT_PUBLISHED", "PAYOUT_CONFIRMED", "PAYOUT_UNLOCKED"], disputeState: "DISPUTE_CLOSED", isCompleted: true, isPayoutPublished: true});
|
||||||
|
|
||||||
// TODO: test trade state after txs confirm and unlock
|
|
||||||
|
|
||||||
// test balances after payout tx unless concurrent trades
|
// test balances after payout tx unless concurrent trades
|
||||||
if (!ctx.concurrentTrades) {
|
if (!ctx.concurrentTrades) {
|
||||||
|
if (winner) {
|
||||||
const winnerBalancesAfter = await winner!.getBalances();
|
const winnerBalancesAfter = await winner!.getBalances();
|
||||||
const loserBalancesAfter = await loser!.getBalances();
|
const winnerDifference = BigInt(winnerBalancesAfter.getBalance()) - BigInt(winnerBalancesBefore!.getBalance());
|
||||||
const winnerDifference = BigInt(winnerBalancesAfter.getBalance()) - BigInt(winnerBalancesBefore.getBalance());
|
const winnerSecurityDeposit = BigInt(ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.offer!.getBuyerSecurityDeposit() : ctx.offer!.getSellerSecurityDeposit())
|
||||||
const loserDifference = BigInt(loserBalancesAfter.getBalance()) - BigInt(loserBalancesBefore.getBalance());
|
|
||||||
const winnerSecurityDeposit = BigInt(ctx.disputeWinner === DisputeResult.Winner.BUYER ? offer!.getBuyerSecurityDeposit() : offer!.getSellerSecurityDeposit())
|
|
||||||
const loserSecurityDeposit = BigInt(ctx.disputeWinner === DisputeResult.Winner.BUYER ? offer!.getSellerSecurityDeposit() : offer!.getBuyerSecurityDeposit());
|
|
||||||
const winnerPayout = ctx.disputeWinnerAmount ? ctx.disputeWinnerAmount : tradeAmount + winnerSecurityDeposit; // TODO: this assumes security deposit is returned to winner, but won't be the case if payment sent
|
const winnerPayout = ctx.disputeWinnerAmount ? ctx.disputeWinnerAmount : tradeAmount + winnerSecurityDeposit; // TODO: this assumes security deposit is returned to winner, but won't be the case if payment sent
|
||||||
const loserPayout = loserSecurityDeposit;
|
|
||||||
expect(winnerDifference).toEqual(winnerPayout);
|
expect(winnerDifference).toEqual(winnerPayout);
|
||||||
|
}
|
||||||
|
if (loser) {
|
||||||
|
const loserBalancesAfter = await loser!.getBalances();
|
||||||
|
const loserDifference = BigInt(loserBalancesAfter.getBalance()) - BigInt(loserBalancesBefore!.getBalance());
|
||||||
|
const loserSecurityDeposit = BigInt(ctx.disputeWinner === DisputeResult.Winner.BUYER ? ctx.offer!.getSellerSecurityDeposit() : ctx.offer!.getBuyerSecurityDeposit());
|
||||||
|
const loserPayout = loserSecurityDeposit;
|
||||||
expect(loserPayout - loserDifference).toBeLessThan(TestConfig.maxFee);
|
expect(loserPayout - loserDifference).toBeLessThan(TestConfig.maxFee);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// test payout unlock
|
// test payout unlock
|
||||||
await testTradePayoutUnlock(ctx);
|
await testTradePayoutUnlock(ctx);
|
||||||
@ -2615,8 +2721,12 @@ async function releaseHavenoProcess(havenod: HavenoClient, deleteAppDir?: boolea
|
|||||||
*/
|
*/
|
||||||
function deleteHavenoInstance(havenod: HavenoClient) {
|
function deleteHavenoInstance(havenod: HavenoClient) {
|
||||||
if (!havenod.getAppName()) throw new Error("Cannot delete Haveno instance owned by different process")
|
if (!havenod.getAppName()) throw new Error("Cannot delete Haveno instance owned by different process")
|
||||||
|
deleteHavenoInstanceByAppName(havenod.getAppName()!);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteHavenoInstanceByAppName(appName: string) {
|
||||||
const userDataDir = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.local/share");
|
const userDataDir = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.local/share");
|
||||||
const appPath = path.normalize(userDataDir + "/" + havenod.getAppName()!);
|
const appPath = path.normalize(userDataDir + "/" + appName);
|
||||||
fs.rmSync(appPath, { recursive: true, force: true });
|
fs.rmSync(appPath, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user