Skip to content

Commit

Permalink
Typeguards WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholaspai committed Feb 8, 2024
1 parent 06ffbf5 commit 4de36c2
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 116 deletions.
143 changes: 76 additions & 67 deletions src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import {
isV2SpeedUp,
isV3SpeedUp,
toBN,
isValidType,
assert,
isPartialDepositFormedCorrectly,
isV3Deposit,
} from "../utils";
import {
paginatedEventQuery,
Expand Down Expand Up @@ -51,7 +51,6 @@ import {
V2SpeedUp,
V3DepositWithBlock,
V3FillWithBlock,
V3FundsDepositEventProps,
V3FundsDepositedEvent,
V3RelayData,
V3RelayerRefundExecutionWithBlock,
Expand Down Expand Up @@ -757,34 +756,33 @@ export class SpokePoolClient extends BaseAbstractClient {

const dataForQuoteTime = await this.batchComputeRealizedLpFeePct(depositEvents);
for (const [index, event] of Array.from(depositEvents.entries())) {
const rawDeposit = spreadEventWithBlockNumber(event);
let deposit: DepositWithBlock;

if (this.isV3DepositEvent(event)) {
deposit = { ...(rawDeposit as V3DepositWithBlock) };
if (deposit.outputToken === ZERO_ADDRESS) {
deposit.outputToken = this.getDestinationTokenForDeposit(deposit);
}
} else {
deposit = { ...(rawDeposit as V2DepositWithBlock) };
deposit.destinationToken = this.getDestinationTokenForDeposit(deposit);
}

// Derive and append the common properties that are not part of the onchain event.
const partialDeposit = spreadEventWithBlockNumber(event);
const { quoteBlock: quoteBlockNumber, realizedLpFeePct } = dataForQuoteTime[index];
deposit.realizedLpFeePct = realizedLpFeePct;
deposit.quoteBlockNumber = quoteBlockNumber;
if (isPartialDepositFormedCorrectly(partialDeposit)) {
let deposit: DepositWithBlock;

if (isV3Deposit(partialDeposit)) {
let modifiedOutputToken = partialDeposit.outputToken;
if (partialDeposit.outputToken === ZERO_ADDRESS) {
modifiedOutputToken = this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber });
}
deposit = { ...partialDeposit, quoteBlockNumber, realizedLpFeePct, outputToken: modifiedOutputToken };
} else {
const destinationToken = this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber });
deposit = { ...partialDeposit, quoteBlockNumber, realizedLpFeePct, destinationToken };
}

if (this.depositHashes[this.getDepositHash(deposit)] !== undefined) {
throw new Error(`SpokePoolClient: Duplicate deposit for relayDataHash: ${this.getDepositHash(deposit)}`);
}
assign(this.depositHashes, [this.getDepositHash(deposit)], deposit);
if (this.depositHashes[this.getDepositHash(deposit)] !== undefined) {
throw new Error(`SpokePoolClient: Duplicate deposit for relayDataHash: ${this.getDepositHash(deposit)}`);
}
assign(this.depositHashes, [this.getDepositHash(deposit)], deposit);

if (deposit.depositId < this.earliestDepositIdQueried) {
this.earliestDepositIdQueried = deposit.depositId;
}
if (deposit.depositId > this.latestDepositIdQueried) {
this.latestDepositIdQueried = deposit.depositId;
if (deposit.depositId < this.earliestDepositIdQueried) {
this.earliestDepositIdQueried = deposit.depositId;
}
if (deposit.depositId > this.latestDepositIdQueried) {
this.latestDepositIdQueried = deposit.depositId;
}
}
}
}
Expand Down Expand Up @@ -994,7 +992,11 @@ export class SpokePoolClient extends BaseAbstractClient {
* @param deposit The deposit to retrieve the destination token for.
* @returns The destination token.
*/
protected getDestinationTokenForDeposit(deposit: DepositWithBlock): string {
protected getDestinationTokenForDeposit(
deposit:
| Pick<V2DepositWithBlock, "originChainId" | "destinationChainId" | "originToken" | "quoteBlockNumber">
| Pick<V3DepositWithBlock, "originChainId" | "destinationChainId" | "inputToken" | "quoteBlockNumber">
): string {
// If there is no rate model client return address(0).
if (!this.hubPoolClient) {
return ZERO_ADDRESS;
Expand Down Expand Up @@ -1088,25 +1090,29 @@ export class SpokePoolClient extends BaseAbstractClient {
` between ${srcChain} blocks [${searchBounds.low}, ${searchBounds.high}]`
);
}
const partialDeposit = spreadEventWithBlockNumber(event) as V2DepositWithBlock;
const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct.

// Append destination token and realized lp fee to deposit.
const deposit: V2DepositWithBlock = {
...partialDeposit,
realizedLpFeePct,
destinationToken: this.getDestinationTokenForDeposit(partialDeposit),
quoteBlockNumber,
};
const partialDeposit = spreadEventWithBlockNumber(event);
if (isPartialDepositFormedCorrectly(partialDeposit) && isV2Deposit(partialDeposit)) {
const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct.

// Append destination token and realized lp fee to deposit.
const deposit: V2DepositWithBlock = {
...partialDeposit,
realizedLpFeePct,
destinationToken: this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber }),
quoteBlockNumber,
};

this.logger.debug({
at: "SpokePoolClient#findDeposit",
message: "Located deposit outside of SpokePoolClient's search range",
deposit,
elapsedMs: tStop - tStart,
});
this.logger.debug({
at: "SpokePoolClient#findDeposit",
message: "Located deposit outside of SpokePoolClient's search range",
deposit,
elapsedMs: tStop - tStart,
});

return deposit;
return deposit;
} else {
throw new Error("event format is unexpected");
}
}

async findDepositV3(depositId: number, destinationChainId: number, depositor: string): Promise<V3DepositWithBlock> {
Expand Down Expand Up @@ -1160,28 +1166,31 @@ export class SpokePoolClient extends BaseAbstractClient {
` between ${srcChain} blocks [${searchBounds.low}, ${searchBounds.high}]`
);
}
const partialDeposit = spreadEventWithBlockNumber(event) as V3DepositWithBlock;
assert(isValidType(partialDeposit, V3FundsDepositEventProps), "Invalid FundsDeposited event");
const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct.

// Append destination token and realized lp fee to deposit.
const deposit: V3DepositWithBlock = {
...partialDeposit,
realizedLpFeePct,
quoteBlockNumber,
outputToken:
partialDeposit.outputToken === ZERO_ADDRESS
? this.getDestinationTokenForDeposit(partialDeposit)
: partialDeposit.outputToken,
};
const partialDeposit = spreadEventWithBlockNumber(event);
if (isPartialDepositFormedCorrectly(partialDeposit) && isV3Deposit(partialDeposit)) {
const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct.

// Append destination token and realized lp fee to deposit.
const deposit: V3DepositWithBlock = {
...partialDeposit,
realizedLpFeePct,
quoteBlockNumber,
outputToken:
partialDeposit.outputToken === ZERO_ADDRESS
? this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber })
: partialDeposit.outputToken,
};

this.logger.debug({
at: "SpokePoolClient#findDepositV3",
message: "Located V3 deposit outside of SpokePoolClient's search range",
deposit,
elapsedMs: tStop - tStart,
});
this.logger.debug({
at: "SpokePoolClient#findDepositV3",
message: "Located V3 deposit outside of SpokePoolClient's search range",
deposit,
elapsedMs: tStop - tStart,
});

return deposit;
return deposit;
} else {
throw new Error("event format is unexpected");
}
}
}
37 changes: 11 additions & 26 deletions src/interfaces/SpokePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,51 +43,36 @@ export interface V2Deposit extends V2RelayData {
updatedMessage?: string;
}

export interface V2PartialDepositWithBlock extends V2RelayData, SortableEvent {
originToken: string;
quoteTimestamp: number;
}

export interface V2DepositWithBlock extends V2Deposit, SortableEvent {
quoteBlockNumber: number;
}

export interface V3PartialDepositWithBlock extends V3RelayData, SortableEvent {
destinationChainId: number;
quoteTimestamp: number;
}

export interface V3Deposit extends V3RelayData {
destinationChainId: number;
quoteTimestamp: number;
realizedLpFeePct?: BigNumber;
relayerFeePct?: BigNumber;
speedUpSignature?: string;
updatedRecipient?: string;
updatedOutputAmount?: BigNumber;
updatedMessage?: string;
}

export const SortableEventProps = [
"blockNumber",
"transactionIndex",
"logIndex",
"transactionHash"
]
export const V3RelayDataProps = [
"originChainId",
"depositor",
"recipient",
"depositId",
"message",
"inputToken",
"inputAmount",
"outputToken",
"outputAmount",
"fillDeadline",
"exclusiveRelayer",
"exclusivityDeadline",
]
export const V3FundsDepositEventProps = [
...SortableEventProps,
...V3RelayDataProps
]

export interface V3DepositWithBlock extends V3Deposit, SortableEvent {
quoteBlockNumber: number;
}

export type Deposit = V2Deposit | V3Deposit;
export type PartialDepositWithBlock = V2PartialDepositWithBlock | V3PartialDepositWithBlock;
export type DepositWithBlock = V2DepositWithBlock | V3DepositWithBlock;

export interface RelayExecutionInfoCommon {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/TypeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ export function isValidType(input: object, expectedTypeProps: string[]): boolean
}
}
return true;
}
}
84 changes: 62 additions & 22 deletions src/utils/ValidatorUtils.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,76 @@
import { BigNumber, ethers } from "ethers";
import { object, min as Min, define, optional, string, integer } from "superstruct";
import { DepositWithBlock } from "../interfaces";
import { object, min as Min, define, optional, string, integer, assign } from "superstruct";
import { DepositWithBlock, PartialDepositWithBlock } from "../interfaces";
import { isV2Deposit } from "./V3Utils";

const AddressValidator = define<string>("AddressValidator", (v) => ethers.utils.isAddress(String(v)));
const HexValidator = define<string>("HexValidator", (v) => ethers.utils.isHexString(String(v)));
const BigNumberValidator = define<BigNumber>("BigNumberValidator", (v) => ethers.BigNumber.isBigNumber(v));

const DepositSchema = object({
depositId: Min(integer(), 0),
depositor: AddressValidator,
recipient: AddressValidator,
originToken: AddressValidator,
amount: BigNumberValidator,
originChainId: Min(integer(), 0),
destinationChainId: Min(integer(), 0),
relayerFeePct: BigNumberValidator,
quoteTimestamp: Min(integer(), 0),
realizedLpFeePct: optional(BigNumberValidator),
destinationToken: AddressValidator,
message: string(),
speedUpSignature: optional(string()),
newRelayerFeePct: optional(BigNumberValidator),
updatedRecipient: optional(string()),
updatedMessage: optional(string()),
const CommonEventSchema = object({
blockNumber: Min(integer(), 0),
transactionIndex: Min(integer(), 0),
logIndex: Min(integer(), 0),
quoteBlockNumber: Min(integer(), 0),
transactionHash: HexValidator,
blockTimestamp: optional(Min(integer(), 0)),
});
const CommonPartialDepositSchema = assign(
CommonEventSchema,
object({
depositId: Min(integer(), 0),
depositor: AddressValidator,
recipient: AddressValidator,
originChainId: Min(integer(), 0),
destinationChainId: Min(integer(), 0),
quoteTimestamp: Min(integer(), 0),
message: string(),
})
);
const V2PartialDepositSchema = assign(
assign(CommonEventSchema, CommonPartialDepositSchema),
object({
originToken: AddressValidator,
amount: BigNumberValidator,
relayerFeePct: BigNumberValidator,
})
);
const V3PartialDepositSchema = assign(
assign(CommonEventSchema, CommonPartialDepositSchema),
object({
inputToken: AddressValidator,
inputAmount: BigNumberValidator,
outputToken: AddressValidator,
outputAmount: BigNumberValidator,
exclusivityDeadline: Min(integer(), 0),
exclusiveRelayer: AddressValidator,
fillDeadline: Min(integer(), 0),
})
);
const CommonDepositSchema = object({
realizedLpFeePct: BigNumberValidator,
speedUpSignature: optional(string()),
updatedRecipient: optional(string()),
updatedMessage: optional(string()),
});
const V2DepositSchema = assign(
assign(V2PartialDepositSchema, CommonDepositSchema),
object({
destinationToken: AddressValidator,
newRelayerFeePct: optional(BigNumberValidator),
})
);
const V3DepositSchema = assign(
assign(V3PartialDepositSchema, CommonDepositSchema),
object({
updatedOutputAmount: optional(BigNumberValidator),
})
);

export function isPartialDepositFormedCorrectly(deposit: unknown): deposit is PartialDepositWithBlock {
if (isV2Deposit(deposit as DepositWithBlock)) return V2PartialDepositSchema.is(deposit);
else return V3PartialDepositSchema.is(deposit);
}

export function isDepositFormedCorrectly(deposit: unknown): deposit is DepositWithBlock {
return DepositSchema.is(deposit);
if (isV2Deposit(deposit as DepositWithBlock)) return V2DepositSchema.is(deposit);
else return V3DepositSchema.is(deposit);
}

0 comments on commit 4de36c2

Please sign in to comment.