Skip to content

Commit

Permalink
feat: refactor clients and spokePool Validate fill (#6)
Browse files Browse the repository at this point in the history
Co-authored-by: chrismaree <[email protected]>
Co-authored-by: ubuntu <>
  • Loading branch information
nicholaspai and chrismaree authored Mar 25, 2022
1 parent 043e711 commit b9a939a
Show file tree
Hide file tree
Showing 19 changed files with 499 additions and 310 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@defi-wonderland/smock": "^2.0.7",
"@uma/contracts-node": "^0.3.1",
"@uma/financial-templates-lib": "2.25.0",
"@across-protocol/contracts-v2": "^0.0.29",
"@across-protocol/contracts-v2": "^0.0.32",
"@across-protocol/sdk-v2": "^0.0.1",
"hardhat": "^2.9.0"
},
Expand Down
109 changes: 0 additions & 109 deletions src/HubPoolEventClient.ts

This file was deleted.

42 changes: 17 additions & 25 deletions src/Relayer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { BigNumber, winston, buildFillRelayProps } from "./utils";
import { SpokePoolEventClient } from "./SpokePoolEventClient";
import { HubPoolEventClient } from "./HubPoolEventClient";
import { SpokePoolClient } from "./clients/SpokePoolClient";
import { HubPoolClient } from "./clients/HubPoolClient";
import { RateModelClient } from "./clients/RateModelClient";
import { MulticallBundler } from "./MulticallBundler";
import { Deposit } from "./interfaces/SpokePool";

export class Relayer {
private repaymentChainId = 1; // Set to 1 for now. In future can be dynamically set to adjust bots capital allocation.
constructor(
readonly logger: winston.Logger,
readonly spokePoolEventClients: { [chainId: number]: SpokePoolEventClient },
readonly hubPoolClient: HubPoolEventClient,
readonly spokePoolClients: { [chainId: number]: SpokePoolClient },
readonly hubPoolClient: HubPoolClient,
readonly multicallBundler: MulticallBundler | any
) {}
async checkForUnfilledDepositsAndFill() {
Expand All @@ -23,50 +24,41 @@ export class Relayer {
this.logger.debug({ at: "Relayer", message: "Filling deposits", number: unfilledDeposits.length });
else this.logger.debug({ at: "Relayer", message: "No unfilled deposits" });

// Fetch the realizedLpFeePct for each unfilled deposit. Execute this in parallel as each requires an async call.
const realizedLpFeePcts = await Promise.all(
unfilledDeposits.map((deposit) => this.hubPoolClient.computeRealizedLpFeePctForDeposit(deposit.deposit))
);

// Iterate over all unfilled deposits. For each unfilled deposit add a fillRelay tx to the multicallBundler.
for (const [index, unfilledDeposit] of unfilledDeposits.entries()) {
for (const unfilledDeposit of unfilledDeposits) {
const destinationToken = this.hubPoolClient.getDestinationTokenForDeposit(unfilledDeposit.deposit);
this.multicallBundler.addTransaction(this.fillRelay(unfilledDeposit, destinationToken, realizedLpFeePcts[index]));
this.multicallBundler.addTransaction(this.fillRelay(unfilledDeposit, destinationToken));
}
}

// TODO: right now this method will fill the whole amount of the relay. Next iteration should consider the wallet balance.
fillRelay(
depositInfo: { unfilledAmount: BigNumber; deposit: Deposit },
destinationToken: string,
realizedLpFeePct: BigNumber
) {
this.logger.debug({ at: "Relayer", message: "Filling deposit", depositInfo, destinationToken, realizedLpFeePct });
fillRelay(depositInfo: { unfilledAmount: BigNumber; deposit: Deposit }, destinationToken: string) {
this.logger.debug({ at: "Relayer", message: "Filling deposit", depositInfo, destinationToken });
return this.getDestinationSpokePoolForDeposit(depositInfo.deposit).fillRelay(
...buildFillRelayProps(depositInfo, destinationToken, this.repaymentChainId, realizedLpFeePct)
...buildFillRelayProps(depositInfo, this.repaymentChainId)
);
}

getDestinationSpokePoolForDeposit(deposit: Deposit) {
return this.spokePoolEventClients[deposit.destinationChainId].spokePool;
return this.spokePoolClients[deposit.destinationChainId].spokePool;
}

// Returns all unfilled deposits over all spokePoolClients. Return values include the amount of the unfilled deposit.
getUnfilledDeposits() {
let unfilledDeposits: { unfilledAmount: BigNumber; deposit: Deposit }[] = [];
// Iterate over each chainId and check for unfilled deposits.
const chainIds = Object.keys(this.spokePoolEventClients);
const chainIds = Object.keys(this.spokePoolClients);
for (const originChain of chainIds) {
const originClient = this.spokePoolEventClients[originChain];
const originClient = this.spokePoolClients[originChain];
for (const destinationChain of chainIds) {
if (originChain === destinationChain) continue;
// Find all unfilled deposits for the current loops originChain -> destinationChain. Note that this also
// validates that the deposit is filled "correctly" for the given deposit information. Additional validation is
// needed later to verify realizedLpFeePct and the destinationToken that the SpokePoolClient can't validate.
const destinationClient = this.spokePoolEventClients[destinationChain];
// validates that the deposit is filled "correctly" for the given deposit information. This includes validation
// of the all deposit -> relay props, the realizedLpFeePct and the origin->destination token mapping.
const destinationClient = this.spokePoolClients[destinationChain];
const depositsForDestinationChain = originClient.getDepositsForDestinationChain(destinationChain);
const unfilledDepositsForDestinationChain = depositsForDestinationChain.map((deposit) => {
return { unfilledAmount: destinationClient.getUnfilledAmountForDeposit(deposit), deposit };
return { unfilledAmount: destinationClient.getValidUnfilledAmountForDeposit(deposit), deposit };
});
// Remove any deposits that have no unfilled amount (i.e that have an unfilled amount of 0) and append the
// remaining deposits to the unfilledDeposits array.
Expand Down
67 changes: 67 additions & 0 deletions src/clients/HubPoolClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { spreadEvent, assign, Contract, BaseContract, toBNWei, Block, BigNumber, toBN, utils } from "../utils";
import { Deposit, Fill, SpeedUp } from "../interfaces/SpokePool";

export class HubPoolClient {
// l1Token -> destinationChainId -> destinationToken
private l1TokensToDestinationTokens: { [l1Token: string]: { [destinationChainId: number]: string } } = {};

public firstBlockToSearch: number;

constructor(
readonly hubPool: Contract,
readonly startingBlock: number = 0,
readonly endingBlock: number | null = null
) {}

getDestinationTokenForDeposit(deposit: Deposit) {
const l1Token = this.getL1TokenForDeposit(deposit);
return this.getDestinationTokenForL1TokenAndDestinationChainId(l1Token, deposit.destinationChainId);
}

getL1TokensToDestinationTokens() {
return this.l1TokensToDestinationTokens;
}

getL1TokenForDeposit(deposit: Deposit) {
let l1Token = null;
Object.keys(this.l1TokensToDestinationTokens).forEach((_l1Token) => {
if (this.l1TokensToDestinationTokens[_l1Token][deposit.originChainId.toString()] === deposit.originToken)
l1Token = _l1Token;
});
return l1Token;
}

getDestinationTokenForL1TokenAndDestinationChainId(l1Token: string, destinationChainId: number) {
return this.l1TokensToDestinationTokens[l1Token][destinationChainId];
}

async getPoolUtilization(quoteBlockNumber: number, l1Token: string, amount: BigNumber) {
const blockOffset = { blockTag: quoteBlockNumber };
const [liquidityUtilizationCurrent, liquidityUtilizationPostRelay] = await Promise.all([
this.hubPool.callStatic.liquidityUtilizationCurrent(l1Token, blockOffset),
this.hubPool.callStatic.liquidityUtilizationPostRelay(l1Token, amount.toString(), blockOffset),
]);
return { liquidityUtilizationCurrent, liquidityUtilizationPostRelay };
}

async update() {
const searchConfig = [this.firstBlockToSearch, this.endingBlock || (await this.getBlockNumber())];
if (searchConfig[0] > searchConfig[1]) return; // If the starting block is greater than the ending block return.
const [poolRebalanceRouteEvents] = await Promise.all([
this.hubPool.queryFilter(this.hubPool.filters.SetPoolRebalanceRoute(), ...searchConfig),
]);

for (const event of poolRebalanceRouteEvents) {
const args = spreadEvent(event);
assign(this.l1TokensToDestinationTokens, [args.l1Token, args.destinationChainId], args.destinationToken);
}
}

async getBlockNumber(): Promise<number> {
return await this.hubPool.provider.getBlockNumber();
}

getProvider() {
return this.hubPool.provider;
}
}
70 changes: 70 additions & 0 deletions src/clients/RateModelClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { spreadEvent, assign, Contract, toBNWei, Block, BigNumber, toBN, utils } from "../utils";
import { Deposit, Fill } from "../interfaces/SpokePool";
import { lpFeeCalculator } from "@across-protocol/sdk-v2";
import { BlockFinder, across } from "@uma/sdk";
import { HubPoolClient } from "./HubPoolClient";

export class RateModelClient {
private readonly blockFinder;

private cumulativeRateModelEvents: across.rateModel.RateModelEvent[] = [];
private rateModelDictionary: across.rateModel.RateModelDictionary;

public firstBlockToSearch: number;

constructor(readonly rateModelStore: Contract, readonly hubPoolClient: HubPoolClient) {
this.blockFinder = new BlockFinder(
this.hubPoolClient.getProvider().getBlock.bind(this.hubPoolClient.getProvider())
);
this.rateModelDictionary = new across.rateModel.RateModelDictionary();
}

async computeRealizedLpFeePct(deposit: Deposit, l1Token: string) {
const quoteBlockNumber = (await this.blockFinder.getBlockForTimestamp(deposit.quoteTimestamp)).number;

const rateModelForBlockNumber = this.getRateModelForBlockNumber(l1Token, quoteBlockNumber);

const blockOffset = { blockTag: quoteBlockNumber };
const [liquidityUtilizationCurrent, liquidityUtilizationPostRelay] = await Promise.all([
this.hubPoolClient.hubPool.callStatic.liquidityUtilizationCurrent(l1Token, blockOffset),
this.hubPoolClient.hubPool.callStatic.liquidityUtilizationPostRelay(l1Token, deposit.amount, blockOffset),
]);

const realizedLpFeePct = lpFeeCalculator.calculateRealizedLpFeePct(
rateModelForBlockNumber,
liquidityUtilizationCurrent,
liquidityUtilizationPostRelay
);

return toBN(realizedLpFeePct);
}

getRateModelForBlockNumber(l1Token: string, blockNumber: number | undefined = undefined): across.constants.RateModel {
return this.rateModelDictionary.getRateModelForBlockNumber(l1Token, blockNumber);
}

async validateRealizedLpFeePctForFill(fill: Fill, deposit: Deposit) {
const expectedFee = await this.computeRealizedLpFeePct(deposit, this.hubPoolClient.getL1TokenForDeposit(deposit));
if (!expectedFee.eq(fill.realizedLpFeePct)) return false;
}

async update() {
const searchConfig = [this.firstBlockToSearch, await this.hubPoolClient.getBlockNumber()];
if (searchConfig[0] > searchConfig[1]) return; // If the starting block is greater than the ending block return.
const rateModelStoreEvents = await this.rateModelStore.queryFilter(
this.rateModelStore.filters.UpdatedRateModel(),
...searchConfig
);

for (const event of rateModelStoreEvents) {
const args = {
blockNumber: event.blockNumber,
transactionIndex: event.transactionIndex,
logIndex: event.logIndex,
...spreadEvent(event),
};
this.cumulativeRateModelEvents = [...this.cumulativeRateModelEvents, args];
}
this.rateModelDictionary.updateWithEvents(this.cumulativeRateModelEvents);
}
}
Loading

0 comments on commit b9a939a

Please sign in to comment.