Skip to content

Commit 2705af7

Browse files
authored
fix: [codex] Only count completed Binance withdrawals in callers (#3176)
* filter callers to completed Binance withdrawals * fix: [codex] Handle failed Binance withdrawals (#3175) * handle failed Binance withdrawals * preserve failed Binance withdrawal tags * match only terminal Binance withdrawals * reuse Binance withdrawal status enum
1 parent 87ee488 commit 2705af7

File tree

7 files changed

+121
-7
lines changed

7 files changed

+121
-7
lines changed

src/adapter/bridges/BinanceCEXBridge.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
getBinanceDepositType,
2525
BinanceTransactionType,
2626
getBinanceWithdrawalType,
27+
isCompletedBinanceWithdrawal,
2728
toAddressType,
2829
} from "../../utils";
2930
import { BaseBridgeAdapter, BridgeTransactionDetails, BridgeEvents, BridgeEvent } from "./BaseBridgeAdapter";
@@ -155,6 +156,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter {
155156
const withdrawalHistory = await filterAsync(_withdrawalHistory, async (withdrawal) => {
156157
const withdrawalType = await getBinanceWithdrawalType(withdrawal);
157158
return (
159+
isCompletedBinanceWithdrawal(withdrawal.status) &&
158160
withdrawal.network === BINANCE_NETWORKS[CHAIN_IDs.BSC] &&
159161
compareAddressesSimple(withdrawal.recipient, toAddress.toNative()) &&
160162
withdrawalType !== BinanceTransactionType.SWAP

src/adapter/l2Bridges/BinanceCEXBridge.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getBinanceDepositType,
2222
BinanceTransactionType,
2323
getBinanceWithdrawalType,
24+
isCompletedBinanceWithdrawal,
2425
getOutstandingBinanceDeposits,
2526
} from "../../utils";
2627
import { L1Token } from "../../interfaces";
@@ -110,6 +111,7 @@ export class BinanceCEXBridge extends BaseL2BridgeAdapter {
110111
const withdrawHistory = await filterAsync(_withdrawHistory, async (withdrawal) => {
111112
const withdrawalType = await getBinanceWithdrawalType(withdrawal);
112113
return (
114+
isCompletedBinanceWithdrawal(withdrawal.status) &&
113115
withdrawal.network === BINANCE_NETWORKS[CHAIN_IDs.MAINNET] &&
114116
compareAddressesSimple(withdrawal.recipient, fromAddress.toNative()) &&
115117
withdrawalType !== BinanceTransactionType.SWAP

src/finalizer/utils/binance.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
getBinanceDepositType,
2424
BinanceTransactionType,
2525
getBinanceWithdrawalType,
26+
isCompletedBinanceWithdrawal,
2627
truncate,
2728
} from "../../utils";
2829
import { HubPoolClient, SpokePoolClient } from "../../clients";
@@ -130,7 +131,7 @@ export async function binanceFinalizer(
130131
// as the existing inventory client logic does not yet tag withdrawals with this BRIDGE type.
131132
const withdrawals = await filterAsync(_withdrawals, async (withdrawal) => {
132133
const withdrawalType = await getBinanceWithdrawalType(withdrawal);
133-
return withdrawalType !== BinanceTransactionType.SWAP;
134+
return isCompletedBinanceWithdrawal(withdrawal.status) && withdrawalType !== BinanceTransactionType.SWAP;
134135
});
135136

136137
// @dev Since we cannot determine the address of the binance depositor without querying the transaction receipt, we need to assume that all tokens

src/rebalancer/adapters/binance.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
assert,
44
BigNumber,
55
BINANCE_NETWORKS,
6+
BINANCE_WITHDRAWAL_STATUS,
67
BinanceTransactionType,
78
BinanceWithdrawal,
89
bnZero,
@@ -33,6 +34,7 @@ import { AugmentedTransaction } from "../../clients";
3334
import { RebalancerConfig } from "../RebalancerConfig";
3435
import { CctpAdapter } from "./cctpAdapter";
3536
import { OftAdapter } from "./oftAdapter";
37+
3638
interface SPOT_MARKET_META {
3739
symbol: string;
3840
baseAssetName: string;
@@ -43,6 +45,29 @@ interface SPOT_MARKET_META {
4345
isBuy: boolean;
4446
}
4547

48+
export function isFailedBinanceWithdrawal(status?: number): boolean {
49+
switch (status) {
50+
case BINANCE_WITHDRAWAL_STATUS.CANCELLED:
51+
case BINANCE_WITHDRAWAL_STATUS.REJECTED:
52+
case BINANCE_WITHDRAWAL_STATUS.FAILURE:
53+
return true;
54+
default:
55+
return false;
56+
}
57+
}
58+
59+
export function isTerminalBinanceWithdrawal(status?: number): boolean {
60+
switch (status) {
61+
case BINANCE_WITHDRAWAL_STATUS.CANCELLED:
62+
case BINANCE_WITHDRAWAL_STATUS.REJECTED:
63+
case BINANCE_WITHDRAWAL_STATUS.FAILURE:
64+
case BINANCE_WITHDRAWAL_STATUS.COMPLETED:
65+
return true;
66+
default:
67+
return false;
68+
}
69+
}
70+
4671
export class BinanceStablecoinSwapAdapter extends BaseAdapter {
4772
private binanceApiClient: Binance;
4873

@@ -307,12 +332,25 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
307332
continue;
308333
} // Only proceed to update the order status if it has finalized:
309334
// @todo: Can we cache this result to avoid making the same query for orders with the same destination token and withdrawal network?
310-
const { unfinalizedWithdrawals, finalizedWithdrawals } = await this._getBinanceWithdrawals(
335+
const { unfinalizedWithdrawals, finalizedWithdrawals, failedWithdrawals } = await this._getBinanceWithdrawals(
311336
orderDetails.destinationToken,
312337
binanceWithdrawalNetwork,
313338
Math.floor(matchingFill.time / 1000) - 5 * 60 // Floor this so we can grab the initiated withdrawal data whose
314339
// ID we've already saved into Redis
315340
);
341+
const failedWithdrawal = failedWithdrawals.find((withdrawal) => withdrawal.id === initiatedWithdrawalId);
342+
if (failedWithdrawal) {
343+
this.logger.warn({
344+
at: "BinanceStablecoinSwapAdapter.updateRebalanceStatuses",
345+
message: `Withdrawal for order ${cloid} failed on Binance, resetting it to retry withdrawal`,
346+
cloid,
347+
initiatedWithdrawalId,
348+
failedWithdrawal,
349+
});
350+
await this._redisDeleteInitiatedWithdrawalId(cloid);
351+
await this._redisUpdateOrderStatus(cloid, STATUS.PENDING_WITHDRAWAL, STATUS.PENDING_SWAP);
352+
continue;
353+
}
316354
const initiatedWithdrawalIsUnfinalized = unfinalizedWithdrawals.find(
317355
(withdrawal) => withdrawal.id === initiatedWithdrawalId
318356
);
@@ -976,16 +1014,19 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
9761014
withdrawal.coin === token &&
9771015
withdrawal.network === BINANCE_NETWORKS[chain] &&
9781016
withdrawal.recipient === this.baseSignerAddress.toNative() &&
979-
withdrawal.status > 4
980-
// @dev (0: Email Sent, 1: Cancelled 2: Awaiting Approval, 3: Rejected, 4: Processing, 5: Failure, 6: Completed)
1017+
isTerminalBinanceWithdrawal(withdrawal.status)
9811018
);
9821019
}
9831020

9841021
private async _getBinanceWithdrawals(
9851022
destinationToken: string,
9861023
destinationChain: number,
9871024
startTimeSeconds: number
988-
): Promise<{ unfinalizedWithdrawals: BinanceWithdrawal[]; finalizedWithdrawals: BinanceWithdrawal[] }> {
1025+
): Promise<{
1026+
unfinalizedWithdrawals: BinanceWithdrawal[];
1027+
finalizedWithdrawals: BinanceWithdrawal[];
1028+
failedWithdrawals: BinanceWithdrawal[];
1029+
}> {
9891030
assert(isDefined(BINANCE_NETWORKS[destinationChain]), "Destination chain should be a Binance network");
9901031
const provider = await getProvider(destinationChain);
9911032
// @dev Binance withdrawals are fast, so setting a lookback of 6 hours should capture any unfinalized withdrawals.
@@ -1013,7 +1054,12 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
10131054

10141055
const finalizedWithdrawals: BinanceWithdrawal[] = [];
10151056
const unfinalizedWithdrawals: BinanceWithdrawal[] = [];
1057+
const failedWithdrawals: BinanceWithdrawal[] = [];
10161058
for (const initiated of initiatedWithdrawals) {
1059+
if (isFailedBinanceWithdrawal(initiated.status)) {
1060+
failedWithdrawals.push(initiated);
1061+
continue;
1062+
}
10171063
const withdrawalAmount = toBNWei(
10181064
initiated.amount, // @dev This should be the post-withdrawal fee amount so it should match perfectly
10191065
// with the finalized amount.
@@ -1030,7 +1076,7 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
10301076
unfinalizedWithdrawals.push(initiated);
10311077
}
10321078
}
1033-
return { unfinalizedWithdrawals, finalizedWithdrawals };
1079+
return { unfinalizedWithdrawals, finalizedWithdrawals, failedWithdrawals };
10341080
}
10351081

10361082
private _redisGetInitiatedWithdrawalKey(cloid: string): string {
@@ -1043,6 +1089,11 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
10431089
return initiatedWithdrawal;
10441090
}
10451091

1092+
private async _redisDeleteInitiatedWithdrawalId(cloid: string): Promise<void> {
1093+
const initiatedWithdrawalKey = this._redisGetInitiatedWithdrawalKey(cloid);
1094+
await this.redisCache.del(initiatedWithdrawalKey);
1095+
}
1096+
10461097
protected async _bridgeToChain(
10471098
token: string,
10481099
originChain: number,

src/utils/BinanceUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ export type BinanceWithdrawal = Omit<BinanceDeposit, "insertTime"> & {
7878
applyTime: string;
7979
};
8080

81+
export enum BINANCE_WITHDRAWAL_STATUS {
82+
EMAIL_SENT = 0,
83+
CANCELLED = 1,
84+
AWAITING_APPROVAL = 2,
85+
REJECTED = 3,
86+
PROCESSING = 4,
87+
FAILURE = 5,
88+
COMPLETED = 6,
89+
}
90+
8191
// ParsedAccountCoins represents a simplified return type of the Binance `accountCoins` endpoint.
8292
type ParsedAccountCoins = Coin[];
8393

@@ -229,6 +239,10 @@ export async function getBinanceWithdrawalType(
229239
}
230240
}
231241

242+
export function isCompletedBinanceWithdrawal(status?: number): boolean {
243+
return status === BINANCE_WITHDRAWAL_STATUS.COMPLETED;
244+
}
245+
232246
/**
233247
* Gets all binance deposits for the Binance account starting from `startTime`-present.
234248
* @returns An array of parsed binance deposits.

test/BinanceAdapter.withdrawals.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { expect } from "./utils";
2+
import { isFailedBinanceWithdrawal, isTerminalBinanceWithdrawal } from "../src/rebalancer/adapters/binance";
3+
4+
describe("Binance adapter withdrawal state", function () {
5+
it("treats terminal Binance withdrawal failures as failed", function () {
6+
expect(isFailedBinanceWithdrawal(1)).to.equal(true);
7+
expect(isFailedBinanceWithdrawal(3)).to.equal(true);
8+
expect(isFailedBinanceWithdrawal(5)).to.equal(true);
9+
});
10+
11+
it("does not treat in-flight or completed Binance withdrawals as failed", function () {
12+
expect(isFailedBinanceWithdrawal(0)).to.equal(false);
13+
expect(isFailedBinanceWithdrawal(2)).to.equal(false);
14+
expect(isFailedBinanceWithdrawal(4)).to.equal(false);
15+
expect(isFailedBinanceWithdrawal(6)).to.equal(false);
16+
expect(isFailedBinanceWithdrawal(undefined)).to.equal(false);
17+
});
18+
19+
it("only treats terminal Binance withdrawals as terminal", function () {
20+
expect(isTerminalBinanceWithdrawal(1)).to.equal(true);
21+
expect(isTerminalBinanceWithdrawal(3)).to.equal(true);
22+
expect(isTerminalBinanceWithdrawal(5)).to.equal(true);
23+
expect(isTerminalBinanceWithdrawal(6)).to.equal(true);
24+
expect(isTerminalBinanceWithdrawal(0)).to.equal(false);
25+
expect(isTerminalBinanceWithdrawal(2)).to.equal(false);
26+
expect(isTerminalBinanceWithdrawal(4)).to.equal(false);
27+
expect(isTerminalBinanceWithdrawal(undefined)).to.equal(false);
28+
});
29+
});

test/BinanceUtils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { expect } from "./utils";
2-
import { BinanceDeposit, BinanceWithdrawal, getOutstandingBinanceDeposits } from "../src/utils";
2+
import {
3+
BinanceDeposit,
4+
BinanceWithdrawal,
5+
getOutstandingBinanceDeposits,
6+
isCompletedBinanceWithdrawal,
7+
} from "../src/utils";
38

49
function makeDeposit(network: string, amount: number, insertTime: number): BinanceDeposit {
510
return { network, amount, coin: "USDT", txId: `0x${insertTime}`, insertTime };
@@ -99,3 +104,13 @@ describe("BinanceUtils: getOutstandingBinanceDeposits", function () {
99104
expect(deposits.length).to.equal(1);
100105
});
101106
});
107+
describe("BinanceUtils withdrawal helpers", function () {
108+
it("only treats completed Binance withdrawals as completed", function () {
109+
expect(isCompletedBinanceWithdrawal(6)).to.equal(true);
110+
expect(isCompletedBinanceWithdrawal(0)).to.equal(false);
111+
expect(isCompletedBinanceWithdrawal(2)).to.equal(false);
112+
expect(isCompletedBinanceWithdrawal(4)).to.equal(false);
113+
expect(isCompletedBinanceWithdrawal(5)).to.equal(false);
114+
expect(isCompletedBinanceWithdrawal(undefined)).to.equal(false);
115+
});
116+
});

0 commit comments

Comments
 (0)