Skip to content

Commit

Permalink
Finish v3 fill events
Browse files Browse the repository at this point in the history
  • Loading branch information
amateima committed Feb 17, 2024
1 parent ddba1fc commit dfc9b08
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 68 deletions.
4 changes: 2 additions & 2 deletions src/modules/deposit/model/deposit.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type DepositFillTxV3 = {
fillType: number;
hash: string;
date?: string;
}
};
export type RequestedSpeedUpDepositTx = {
hash: string;
blockNumber: number;
Expand Down Expand Up @@ -175,7 +175,7 @@ export class Deposit {
depositTxHash: string;

@Column({ type: "jsonb", default: [] })
fillTxs: (DepositFillTx | DepositFillTx2 | ])[];
fillTxs: (DepositFillTx | DepositFillTx2 | DepositFillTxV3)[];

@Column({ type: "jsonb", default: [] })
speedUps: RequestedSpeedUpDepositTx[];
Expand Down
4 changes: 4 additions & 0 deletions src/modules/rewards/services/op-rebate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export class OpRebateService {

this.assertDepositKeys(deposit, ["price", "token", "depositDate", "feeBreakdown"]);

if (deposit.outputTokenAddress) {
this.assertDepositKeys(deposit, ["outputTokenPrice", "outputToken"]);
}

if (Object.keys(deposit.feeBreakdown).length === 0) {
throw new Error(`Deposit with id ${depositPrimaryKey} is missing fee breakdown`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Repository } from "typeorm";
import { DateTime } from "luxon";
import { EthProvidersService } from "../../../web3/services/EthProvidersService";
import { DepositFilledDateQueueMessage, ScraperQueue, TrackFillEventQueueMessage } from ".";
import { Deposit, DepositFillTx, DepositFillTx2 } from "../../../deposit/model/deposit.entity";
import { Deposit, DepositFillTx, DepositFillTx2, DepositFillTxV3 } from "../../../deposit/model/deposit.entity";
import { ScraperQueuesService } from "../../service/ScraperQueuesService";

@Processor(ScraperQueue.DepositFilledDate)
Expand All @@ -27,9 +27,7 @@ export class DepositFilledDateConsumer {
if (!deposit) return;
if (deposit.status !== "filled") return;

if (!deposit.depositDate) {
throw new Error("Wait for deposit date");
}
if (!deposit.depositDate) throw new Error("Wait for deposit date");

const fillTxsWithoutDate = deposit.fillTxs.filter((fillTx) => !fillTx.date).length;

Expand Down Expand Up @@ -67,7 +65,7 @@ export class DepositFilledDateConsumer {
});
}

private async fillDateForFillTx(chainId: number, fillTx: DepositFillTx | DepositFillTx2) {
private async fillDateForFillTx(chainId: number, fillTx: DepositFillTx | DepositFillTx2 | DepositFillTxV3) {
if (fillTx.date) return fillTx;

const tx = await this.providers.getCachedTransaction(chainId, fillTx.hash);
Expand Down
101 changes: 81 additions & 20 deletions src/modules/scraper/adapter/messaging/FeeBreakdownConsumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import BigNumber from "bignumber.js";
import { GasFeesService } from "../gas-fees/gas-fees-service";
import { ScraperQueuesService } from "../../service/ScraperQueuesService";
import { FeeBreakdownQueueMessage, OpRebateRewardMessage, ScraperQueue } from ".";
import { Deposit, DepositFillTx, DepositFillTx2 } from "../../../deposit/model/deposit.entity";
import { Deposit, DepositFillTx, DepositFillTx2, DepositFillTxV3 } from "../../../deposit/model/deposit.entity";
import { deriveRelayerFeeComponents, makePctValuesCalculator, toWeiPct } from "../../utils";
import { AcrossContractsVersion } from "src/modules/web3/model/across-version";

@Processor(ScraperQueue.FeeBreakdown)
export class FeeBreakdownConsumer {
Expand All @@ -24,32 +25,35 @@ export class FeeBreakdownConsumer {
@Process()
private async process(job: Job<FeeBreakdownQueueMessage>) {
const { depositId } = job.data;
const deposit = await this.depositRepository.findOne({ where: { id: depositId }, relations: ["token", "price"] });
const deposit = await this.depositRepository.findOne({
where: { id: depositId },
relations: ["token", "price", "outputToken", "outputTokenPrice"],
});

if (!deposit) {
this.logger.verbose("Deposit not found in db");
return;
}
if (!deposit) return;
if (deposit.status !== "filled") return;
if (deposit.fillTxs.length === 0) return;

if (!deposit.token) {
throw new Error("Token not populated");
}
if (!deposit.token) throw new Error("Token not populated");
if (!deposit.price) throw new Error("Price not populated");
if (deposit.outputTokenAddress && !deposit.outputToken) throw new Error("Output token not populated");
if (deposit.outputTokenAddress && !deposit.outputTokenPrice) throw new Error("Output token price not populated");

if (!deposit.price) {
throw new Error("Price not populated");
}
const fillEventsVersion = this.getFillEventsVersion(deposit);
if (!fillEventsVersion) throw new Error("Fill events version not found");

if (deposit.status !== "filled") {
throw new Error("Deposit is not filled");
if (fillEventsVersion === AcrossContractsVersion.V2_5) {
await this.computeFeeBreakdownForV2FillEvents(deposit);
} else if (fillEventsVersion === AcrossContractsVersion.V3) {
await this.computeFeeBreakdownForV3FillEvents(deposit);
}
}

const fillTx = deposit.fillTxs.find((fillTx) => fillTx.totalFilledAmount === deposit.amount);
private async computeFeeBreakdownForV2FillEvents(deposit: Deposit) {
const typedFillTx = deposit.fillTxs as DepositFillTx2[];
const fillTx = typedFillTx.find((fillTx) => fillTx.totalFilledAmount === deposit.amount);

if (!fillTx) {
// Only consider fill txs that have been fully filled
this.logger.verbose("Skip partial fill tx");
return;
}
if (!fillTx) return;

const feeBreakdown = await this.getFeeBreakdownForFillTx(
fillTx,
Expand All @@ -64,6 +68,63 @@ export class FeeBreakdownConsumer {
});
}

private async computeFeeBreakdownForV3FillEvents(deposit: Deposit) {
const typedFillTx = deposit.fillTxs as DepositFillTxV3[];
const fillTx = typedFillTx[0];

const { feeUsd, fee } = await this.gasFeesService.getFillTxNetworkFee(deposit.destinationChainId, fillTx.hash);
const relayGasFeeUsd = feeUsd;
const relayGasFeeAmount = fee;

// Bridge fee computation
const wei = new BigNumber(10).pow(18);
const outputWeiPct = toWeiPct(new BigNumber(fillTx.updatedOutputAmount).dividedBy(deposit.amount).toString());
const bridgeFeePct = wei.minus(outputWeiPct);
const inputAmountUsd = new BigNumber(deposit.amount).multipliedBy(deposit.price.usd);
const outputAmountUsd = new BigNumber(deposit.outputAmount).multipliedBy(deposit.outputTokenPrice.usd);
const bridgeFeeUsd = inputAmountUsd.minus(outputAmountUsd);
const bridgeFeeAmount = bridgeFeeUsd.dividedBy(deposit.price.usd);
const feeBreakdown = {
lpFeeUsd: undefined,
lpFeePct: undefined,
lpFeeAmount: undefined,
relayCapitalFeeUsd: undefined,
relayCapitalFeePct: undefined,
relayCapitalFeeAmount: undefined,
relayGasFeeUsd,
// TODO: undefined
relayGasFeePct: undefined,
relayGasFeeAmount,
totalBridgeFeeUsd: bridgeFeeUsd.toString(),
totalBridgeFeePct: bridgeFeePct.toString(),
totalBridgeFeeAmount: bridgeFeeAmount.toFixed(0),
};
await this.depositRepository.update({ id: deposit.id }, { feeBreakdown });

this.scraperQueuesService.publishMessage<OpRebateRewardMessage>(ScraperQueue.OpRebateReward, {
depositPrimaryKey: deposit.id,
});
}

private getFillEventsVersion(deposit: Deposit) {
const fillsCount = deposit.fillTxs.length;
const fillsWithTotalFilledAmount = deposit.fillTxs.filter((fillTx) => !!(fillTx as any).totalFilledAmount).length;

if (fillsCount === fillsWithTotalFilledAmount) {
return AcrossContractsVersion.V2_5;
}

const fillsWithUpdatedOutputAmount = deposit.fillTxs.filter(
(fillTx) => !!(fillTx as any).updatedOutputAmount,
).length;

if (fillsCount === fillsWithUpdatedOutputAmount) {
return AcrossContractsVersion.V3;
}

return undefined;
}

private async getFeeBreakdownForFillTx(
fillTx: DepositFillTx | DepositFillTx2,
priceUsd: string,
Expand Down
51 changes: 21 additions & 30 deletions src/modules/scraper/adapter/messaging/FillEventsV3Consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,55 +30,46 @@ export class FillEventsV3Consumer {

if (!deposit) return;
if (this.fillTxAlreadyProcessed(deposit, job.data)) return;

await this.processFillEventQueueMessage(deposit, job.data);

// this.scraperQueuesService.publishMessage<DepositFilledDateQueueMessage>(ScraperQueue.DepositFilledDate, {
// depositId: deposit.id,
// });
// this.scraperQueuesService.publishMessage<FeeBreakdownQueueMessage>(ScraperQueue.FeeBreakdown, {
// depositId: deposit.id,
// });
this.scraperQueuesService.publishMessage<DepositFilledDateQueueMessage>(ScraperQueue.DepositFilledDate, {
depositId: deposit.id,
});
this.scraperQueuesService.publishMessage<FeeBreakdownQueueMessage>(ScraperQueue.FeeBreakdown, {
depositId: deposit.id,
});
}

public async processFillEventQueueMessage(deposit: Deposit, data: FillEventsV3QueueMessage) {
const { transactionHash, fillType, updatedMessage, updatedOutputAmount, updatedRecipient } = data;
deposit.fillTxs = [
const fillTxs = [
...deposit.fillTxs,
{ hash: transactionHash, fillType, updatedMessage, updatedOutputAmount, updatedRecipient },
];
const wei = new BigNumber(10).pow(18);
const outputPercentage = new BigNumber(updatedOutputAmount).multipliedBy(wei).dividedBy(deposit.amount);
const bridgeFeePct = wei.minus(outputPercentage);
deposit.status = "filled";
deposit.bridgeFeePct = bridgeFeePct.toString();
deposit.outputAmount = updatedOutputAmount;
deposit.recipientAddr = updatedRecipient;

return this.depositRepository.save(deposit);
}

private computeBridgeFee(deposit: Deposit, fill: FillEventsQueueMessage2) {
if (new BigNumber(deposit.amount).eq(0)) {
return new BigNumber(0);
}
const bridgeFeePct = wei.minus(outputPercentage).toString();
const maxBridgeFeePct = new BigNumber(10).pow(18).times(0.0012);
const validFills = (deposit.fillTxs as DepositFillTx2[]).filter((fill) => fill.relayerFeePct !== "0"); // all fills associated with a deposit that are NOT slow fills
const relayerFeeChargedToUser = validFills.reduce((cumulativeFee, fill) => {
const relayerFee = new BigNumber(fill.fillAmount).multipliedBy(fill.relayerFeePct);
return relayerFee.plus(cumulativeFee);
}, new BigNumber(0));
const blendedRelayerFeePct = relayerFeeChargedToUser.dividedBy(deposit.amount).decimalPlaces(0, 1);
const bridgeFeePct = blendedRelayerFeePct.plus(fill.realizedLpFeePct);
const bridgeFeePctCapped = BigNumber.min(bridgeFeePct, maxBridgeFeePct);

return bridgeFeePctCapped;
await this.depositRepository.update(
{ id: deposit.id },
{
status: "filled",
bridgeFeePct: bridgeFeePctCapped.toFixed(0),
outputAmount: updatedOutputAmount,
recipientAddr: updatedRecipient,
fillTxs,
},
);

return this.depositRepository.findOne({ where: { id: deposit.id } });
}

public fillTxAlreadyProcessed(deposit: Deposit, fill: FillEventsV3QueueMessage) {
const { transactionHash } = fill;
const fillTxIndex = (deposit.fillTxs as DepositFillTxV3[]).findIndex((fillTx) => fillTx.hash === transactionHash);

// replace if with one line code
return fillTxIndex !== -1;
}

Expand Down
13 changes: 2 additions & 11 deletions src/modules/scraper/adapter/messaging/OpRebateRewardConsumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,8 @@ export class OpRebateRewardConsumer {

const deposit = await this.depositRepository.findOne({ where: { id: depositPrimaryKey } });

if (!deposit) {
this.logger.verbose(`${ScraperQueue.OpRebateReward} Deposit with id ${depositPrimaryKey} not found`);
return;
}

if (deposit.destinationChainId !== ChainIds.optimism) {
this.logger.verbose(
`${ScraperQueue.OpRebateReward} Deposit with id ${depositPrimaryKey} is not going to Optimism`,
);
return;
}
if (!deposit) return;
if (deposit.destinationChainId !== ChainIds.optimism) return;

await this.opRebateService.createOpRebatesForDeposit(deposit.id);
}
Expand Down
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export const chainIdToInfo = {
chainId: ChainIds.zkSyncMainnet,
nativeSymbol: "eth",
},
[ChainIds.sepolia]: {
name: "Sepolia",
chainId: ChainIds.sepolia,
nativeSymbol: "eth",
},
};

export const wait = (seconds = 1) =>
Expand Down

0 comments on commit dfc9b08

Please sign in to comment.