Skip to content

Commit

Permalink
feat: Factor in speedup in relays, add more general entry point and a…
Browse files Browse the repository at this point in the history
…dd docker file (#12)

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* feat: Implement HubPoolEventClient.validateFill

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

* nit

Signed-off-by: chrismaree <[email protected]>

Co-authored-by: ubuntu <>
  • Loading branch information
chrismaree authored Mar 29, 2022
1 parent 4f09ba8 commit 7d66c00
Show file tree
Hide file tree
Showing 40 changed files with 492 additions and 222 deletions.
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.github
node_modules
package-lock.json
env
dist
.DS_Store
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:14

WORKDIR /relayer-v2

COPY . ./

RUN apt-get update
RUN apt-get install -y libudev-dev libusb-1.0-0-dev jq yarn rsync
RUN yarn

RUN yarn build

ENTRYPOINT ["/bin/bash", "scripts/runCommand.sh"]
20 changes: 20 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Logger } from "@uma/financial-templates-lib";

import { runRelayer } from "./src/relayer";

export async function run(): Promise<void> {
if (process.argv.includes("--relayer")) await runRelayer(Logger);
else if (process.argv.includes("--dataworker")) console.log("NOT YET IMPLEMENTED");
else console.log("Select either relayer OR dataworker");
}

if (require.main === module) {
run()
.then(() => {
process.exit(0);
})
.catch((error) => {
console.error("Process exited with", error);
process.exit(1);
});
}
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "relayer-v2",
"version": "1.0.0",
"version": "0.0.1",
"description": "Across Protocol V2 Relayer Bot",
"repository": "[email protected]:across-protocol/relayer-v2.git",
"author": "UMA Team",
Expand All @@ -15,8 +15,6 @@
"hardhat": "^2.9.0"
},
"files": [
"/contracts/**/*.sol",
"/artifacts/**/*",
"/dist/**/*"
],
"types": "dist/index.d.ts",
Expand All @@ -25,7 +23,8 @@
"lint": "yarn prettier --list-different",
"lint-fix": "yarn prettier --write",
"prettier": "prettier .",
"test": "hardhat test"
"test": "hardhat test",
"build": "tsc && rsync -a --include '*/' --include '*.d.ts' --exclude '*' ./dist/"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.0",
Expand Down
30 changes: 0 additions & 30 deletions scripts/deploy.ts

This file was deleted.

5 changes: 5 additions & 0 deletions scripts/runCommand.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

#!/bin/bash

# Simple script that simply runs a command input as an environment variable.
$COMMAND
Empty file removed src/ProfitabilityCalculator.ts
Empty file.
21 changes: 0 additions & 21 deletions src/RelayerConfig.ts

This file was deleted.

75 changes: 75 additions & 0 deletions src/clients/ClientHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import winston from "winston";
import { getProvider, getSigner, contractAt, Contract } from "../utils";
import { SpokePoolClient, HubPoolClient, RateModelClient, MultiCallBundler } from "./";
import { RelayerConfig } from "../relayer/RelayerConfig";

export function constructClients(logger: winston.Logger, config: RelayerConfig) {
// Create signers for each chain. Each is connected to an associated provider for that chain.
const baseSigner = getSigner();

const hubSigner = baseSigner.connect(getProvider(config.hubPoolChainId));
const spokeSigners = config.spokePoolChains
.map((networkId) => getProvider(networkId))
.map((provider) => baseSigner.connect(provider));

// Create contract instances for each chain for each required contract.
const hubPool = contractAt("HubPool", getAddress("HubPool", config.hubPoolChainId), hubSigner);

const rateModelStore = contractAt("RateModelStore", getAddress("RateModelStore", config.hubPoolChainId), hubSigner);

const spokePools = config.spokePoolChains.map((networkId, index) => {
return { networkId, contract: contractAt("SpokePool", getAddress("SpokePool", networkId), spokeSigners[index]) };
});

// Create clients for each contract for each chain.

const hubPoolClient = new HubPoolClient(logger, hubPool);

const rateModelClient = new RateModelClient(logger, rateModelStore, hubPoolClient);

let spokePoolClients = {};
spokePools.forEach((obj: { networkId: number; contract: Contract }) => {
spokePoolClients[obj.networkId] = new SpokePoolClient(logger, obj.contract, rateModelClient, obj.networkId);
});

// const gasEstimator = new GasEstimator() // todo when this is implemented in the SDK.
const multiCallBundler = new MultiCallBundler(logger, null);

return { hubPoolClient, rateModelClient, spokePoolClients, multiCallBundler };
}

// If this is the first run then the hubPoolClient will have no whitelisted routes. If this is the case then first
// update the hubPoolClient and the rateModelClients followed by the spokePoolClients. Else, update all at once.
export async function updateClients(
logger: winston.Logger,
hubPoolClient: HubPoolClient,
rateModelClient: RateModelClient,
spokePoolClients: { [chainId: number]: SpokePoolClient }
) {
if (Object.keys(hubPoolClient.getL1TokensToDestinationTokens()).length === 0) {
logger.debug({ at: "ClientHelper", message: "Updating clients for first run" });
await Promise.all([hubPoolClient.update(), rateModelClient.update()]);
await updateSpokePoolClients(spokePoolClients);
} else {
logger.debug({ at: "ClientHelper", message: "Updating clients for standard run" });
await Promise.all([hubPoolClient.update(), rateModelClient.update(), updateSpokePoolClients(spokePoolClients)]);
}
}

async function updateSpokePoolClients(spokePoolClients: { [chainId: number]: SpokePoolClient }) {
await Promise.all(Object.values(spokePoolClients).map((client) => client.update()));
}

// TODO: this method is temp to enable this constructor to work. this should be replaced by a method from the contracts
// package that exports the relationship between contractName and the associated chain they are deployed on.
function getAddress(contractName: string, chainId: number) {
const mapping = {
RateModelStore: { 42: "0x5923929DF7A2D6E038bb005B167c1E8a86cd13C8" },
HubPool: { 42: "0xD449Af45a032Df413b497A709EeD3E8C112EbcE3" },
SpokePool: {
42: "0x73549B5639B04090033c1E77a22eE9Aa44C2eBa0",
69: "0x2b7b7bAE341089103dD22fa4e8D7E4FA63E11084",
},
};
return mapping[contractName][chainId];
}
24 changes: 13 additions & 11 deletions src/clients/HubPoolClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spreadEvent, assign, Contract, BaseContract, toBNWei, Block, BigNumber, toBN, utils } from "../utils";
import { Deposit, Fill, SpeedUp } from "../interfaces/SpokePool";
import { spreadEvent, assign, Contract, winston, BigNumber } from "../utils";
import { Deposit } from "../interfaces/SpokePool";

export class HubPoolClient {
// l1Token -> destinationChainId -> destinationToken
Expand All @@ -8,14 +8,18 @@ export class HubPoolClient {
public firstBlockToSearch: number;

constructor(
readonly logger: winston.Logger,
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);
const destinationToken = this.getDestinationTokenForL1TokenDestinationChainId(l1Token, deposit.destinationChainId);
if (!destinationToken)
this.logger.error({ at: "HubPoolClient", message: "No destination token found for deposit", deposit });
return destinationToken;
}

getL1TokensToDestinationTokens() {
Expand All @@ -31,7 +35,7 @@ export class HubPoolClient {
return l1Token;
}

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

Expand All @@ -45,8 +49,10 @@ export class HubPoolClient {
}

async update() {
const searchConfig = [this.firstBlockToSearch, this.endingBlock || (await this.getBlockNumber())];
const searchConfig = [this.firstBlockToSearch, this.endingBlock || (await this.hubPool.provider.getBlockNumber())];
this.logger.debug({ at: "HubPoolClient", message: "Updating client", searchConfig });
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),
]);
Expand All @@ -55,13 +61,9 @@ export class HubPoolClient {
const args = spreadEvent(event);
assign(this.l1TokensToDestinationTokens, [args.l1Token, args.destinationChainId], args.destinationToken);
}
}

async getBlockNumber(): Promise<number> {
return await this.hubPool.provider.getBlockNumber();
}
this.firstBlockToSearch = searchConfig[1] + 1; // Next iteration should start off from where this one ended.

getProvider() {
return this.hubPool.provider;
this.logger.debug({ at: "HubPoolClient", message: "Client updated!" });
}
}
11 changes: 6 additions & 5 deletions src/MulticallBundler.ts → src/clients/MultiCallBundler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { winston, Transaction } from "./utils";
import { winston, Transaction } from "../utils";

export class MulticallBundler {
export class MultiCallBundler {
private transactions: Transaction[] = [];
constructor(readonly logger: winston.Logger, readonly gasEstimator: any) {}

// Adds defined transaction to the transaction queue.
addTransaction(transaction: Transaction) {
this.transactions.push(transaction);
if (transaction) this.transactions.push(transaction);
}

transactionCount() {
Expand All @@ -18,13 +19,13 @@ export class MulticallBundler {

async executeTransactionQueue() {
// TODO: this should include grouping logic for multicall
this.logger.debug({ at: "MulticallBundler", message: "Executing tx bundle", number: this.transactions.length });
this.logger.debug({ at: "MultiCallBundler", message: "Executing tx bundle", number: this.transactions.length });
const transactionResults = await Promise.all(this.transactions);
const transactionReceipts = await Promise.all(transactionResults.map((transaction: any) => transaction.wait()));

// TODO: add additional logging on error processing.
this.logger.debug({
at: "MulticallBundler",
at: "MultiCallBundler",
message: "All transactions executed",
hashes: transactionReceipts.map((receipt) => receipt.transactionHash),
});
Expand Down
40 changes: 27 additions & 13 deletions src/clients/RateModelClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { spreadEvent, Contract, toBN } from "../utils";
import { spreadEvent, winston, Contract, toBN } from "../utils";
import { Deposit, Fill } from "../interfaces/SpokePool";
import { lpFeeCalculator } from "@across-protocol/sdk-v2";
import { BlockFinder, across } from "@uma/sdk";
Expand All @@ -12,29 +12,38 @@ export class RateModelClient {

public firstBlockToSearch: number;

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

async computeRealizedLpFeePct(deposit: Deposit, l1Token: string) {
this.logger.debug({ at: "RateModelClient", message: "Computing realizedLPFeePct", deposit, l1Token });
const quoteBlockNumber = (await this.blockFinder.getBlockForTimestamp(deposit.quoteTimestamp)).number;
// Set to this temporarily until we re-deploy. The RateModelStore was deployed after the spokePool's deposits.
// const quoteBlockNumber = 30626071;

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

const blockOffset = { blockTag: quoteBlockNumber };
const [liquidityUtilizationCurrent, liquidityUtilizationPostRelay] = await Promise.all([
const [utilizationCurrent, utilizationPost] = 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
);
const realizedLpFeePct = lpFeeCalculator.calculateRealizedLpFeePct(rateModel, utilizationCurrent, utilizationPost);

this.logger.debug({
at: "RateModelClient",
message: "Computed realizedLPFeePct",
quoteBlockNumber,
rateModel,
realizedLpFeePct: realizedLpFeePct.toString(),
});

return toBN(realizedLpFeePct);
}
Expand All @@ -49,7 +58,8 @@ export class RateModelClient {
}

async update() {
const searchConfig = [this.firstBlockToSearch, await this.hubPoolClient.getBlockNumber()];
const searchConfig = [this.firstBlockToSearch, await this.rateModelStore.provider.getBlockNumber()];
this.logger.debug({ at: "RateModelClient", message: "Updating client", searchConfig });
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(),
Expand All @@ -66,5 +76,9 @@ export class RateModelClient {
this.cumulativeRateModelEvents = [...this.cumulativeRateModelEvents, args];
}
this.rateModelDictionary.updateWithEvents(this.cumulativeRateModelEvents);

this.firstBlockToSearch = searchConfig[1] + 1; // Next iteration should start off from where this one ended.

this.logger.debug({ at: "RateModelClient", message: "Client updated!" });
}
}
Loading

0 comments on commit 7d66c00

Please sign in to comment.