Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholaspai committed Feb 11, 2024
2 parents d09b7ad + ecb433c commit a7adcbf
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 200 deletions.
96 changes: 94 additions & 2 deletions src/utils/SpokeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import assert from "assert";
import { Contract, utils as ethersUtils } from "ethers";
import { RelayData, SlowFillRequest, V2RelayData, V3Deposit, V3Fill, V3RelayData } from "../interfaces";
import { CHAIN_IDs } from "../constants";
import { FillStatus, RelayData, SlowFillRequest, V2RelayData, V3Deposit, V3Fill, V3RelayData } from "../interfaces";
import { SpokePoolClient } from "../clients";
import { isDefined } from "./TypeGuards";
import { isV2RelayData } from "./V3Utils";
import { getNetworkName } from "./NetworkUtils";

/**
* Find the block range that contains the deposit ID. This is a binary search that searches for the block range
Expand Down Expand Up @@ -254,4 +256,94 @@ export function getV3RelayHashFromEvent(e: V3Deposit | V3Fill | SlowFillRequest)
message: e.message,
};
return getV3RelayHash(relayData, e.destinationChainId);
}
}
/**
* Find the amount filled for a deposit at a particular block.
* @param spokePool SpokePool contract instance.
* @param relayData Deposit information that is used to complete a fill.
* @param blockTag Block tag (numeric or "latest") to query at.
* @returns The amount filled for the specified deposit at the requested block (or latest).
*/
export async function relayFillStatus(
spokePool: Contract,
relayData: V3RelayData,
blockTag?: number | "latest",
destinationChainId?: number
): Promise<FillStatus> {
destinationChainId ??= await spokePool.chainId();
const hash = getRelayDataHash(relayData, destinationChainId);
const _fillStatus = await spokePool.fillStatuses(hash, { blockTag });
const fillStatus = Number(_fillStatus);

if (![FillStatus.Unfilled, FillStatus.RequestedSlowFill, FillStatus.Filled].includes(fillStatus)) {
const { originChainId, depositId } = relayData;
throw new Error(`relayFillStatus: Unexpected fillStatus for ${originChainId} deposit ${depositId}`);
}

return fillStatus;
}

/**
* Find the block at which a fill was completed.
* @todo After SpokePool upgrade, this function can be simplified to use the FillStatus enum.
* @param spokePool SpokePool contract instance.
* @param relayData Deposit information that is used to complete a fill.
* @param lowBlockNumber The lower bound of the search. Must be bounded by SpokePool deployment.
* @param highBlocknumber Optional upper bound for the search.
* @returns The block number at which the relay was completed, or undefined.
*/
export async function findFillBlock(
spokePool: Contract,
relayData: V3RelayData,
lowBlockNumber: number,
highBlockNumber?: number
): Promise<number | undefined> {
const { provider } = spokePool;
highBlockNumber ??= await provider.getBlockNumber();
assert(highBlockNumber > lowBlockNumber, `Block numbers out of range (${lowBlockNumber} > ${highBlockNumber})`);

// In production the chainId returned from the provider matches 1:1 with the actual chainId. Querying the provider
// object saves an RPC query becasue the chainId is cached by StaticJsonRpcProvider instances. In hre, the SpokePool
// may be configured with a different chainId than what is returned by the provider.
// @todo Sub out actual chain IDs w/ CHAIN_IDs constants
const destinationChainId = Object.values(CHAIN_IDs).includes(relayData.originChainId)
? (await provider.getNetwork()).chainId
: Number(await spokePool.chainId());
assert(
relayData.originChainId !== destinationChainId,
`Origin & destination chain IDs must not be equal (${destinationChainId})`
);

// Make sure the relay war completed within the block range supplied by the caller.
const [initialFillStatus, finalFillStatus] = (
await Promise.all([
relayFillStatus(spokePool, relayData, lowBlockNumber, destinationChainId),
relayFillStatus(spokePool, relayData, highBlockNumber, destinationChainId),
])
).map(Number);

if (finalFillStatus !== FillStatus.Filled) {
return undefined; // Wasn't filled within the specified block range.
}

// Was filled earlier than the specified lowBlock. This is an error by the caller.
if (initialFillStatus === FillStatus.Filled) {
const { depositId, originChainId } = relayData;
const [srcChain, dstChain] = [getNetworkName(originChainId), getNetworkName(destinationChainId)];
throw new Error(`${srcChain} deposit ${depositId} filled on ${dstChain} before block ${lowBlockNumber}`);
}

// Find the leftmost block where filledAmount equals the deposit amount.
do {
const midBlockNumber = Math.floor((highBlockNumber + lowBlockNumber) / 2);
const fillStatus = await relayFillStatus(spokePool, relayData, midBlockNumber, destinationChainId);

if (fillStatus === FillStatus.Filled) {
highBlockNumber = midBlockNumber;
} else {
lowBlockNumber = midBlockNumber + 1;
}
} while (lowBlockNumber < highBlockNumber);

return lowBlockNumber;
}
62 changes: 36 additions & 26 deletions test/SpokePoolClient.ValidateFill.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import assert from "assert";
import { RelayData } from "../src/interfaces";
import { FillStatus } from "../src/interfaces";
import { SpokePoolClient } from "../src/clients";
import { bnZero, bnOne, InvalidFill, validateFillForDeposit, queryHistoricalDepositForFill } from "../src/utils";
import {
bnZero,
bnOne,
InvalidFill,
relayFillStatus,
validateFillForDeposit,
queryHistoricalDepositForFill,
} from "../src/utils";
import {
assert,
expect,
toBNWei,
ethers,
SignerWithAddress,
depositV2,
depositV3,
fillV3Relay,
setupTokensForWallet,
toBN,
buildFill,
Expand All @@ -28,7 +37,6 @@ import {
mineRandomBlocks,
winston,
lastSpyLogIncludes,
relayFilledAmount,
} from "./utils";
import { CHAIN_ID_TEST_LIST, repaymentChainId } from "./constants";
import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks";
Expand Down Expand Up @@ -105,25 +113,27 @@ describe("SpokePoolClient: Fill Validation", function () {
await spokePool_1.setCurrentTime(await getLastBlockTime(spokePool_1.provider));
});

it("Tracks v2 fill status", async function () {
const deposit = await buildDeposit(hubPoolClient, spokePool_1, erc20_1, depositor, destinationChainId);

let filled = await relayFilledAmount(spokePool_2, deposit as RelayData);
expect(filled.eq(0)).is.true;

await buildFill(spokePool_2, erc20_2, depositor, relayer, deposit, 1);
filled = await relayFilledAmount(spokePool_2, deposit as RelayData);
expect(filled.eq(deposit.amount)).is.true;
});

it.skip("Tracks v3 fill status", async function () {
const deposit = await buildDeposit(hubPoolClient, spokePool_1, erc20_1, depositor, destinationChainId);
it("Tracks v3 fill status", async function () {
const inputToken = erc20_1.address;
const inputAmount = toBNWei(1);
const outputToken = erc20_2.address;
const outputAmount = inputAmount.sub(bnOne);
const deposit = await depositV3(
spokePool_1,
destinationChainId,
depositor,
inputToken,
inputAmount,
outputToken,
outputAmount
);

const filled = await relayFilledAmount(spokePool_2, deposit as RelayData);
expect(filled.eq(0)).is.true;
let filled = await relayFillStatus(spokePool_2, deposit);
expect(filled).to.equal(FillStatus.Unfilled);

await buildFill(spokePool_2, erc20_2, depositor, relayer, deposit, 1);
// @todo: Verify fill
await fillV3Relay(spokePool_2, deposit, relayer);
filled = await relayFillStatus(spokePool_2, deposit);
expect(filled).to.equal(FillStatus.Filled);
});

it("Accepts valid fills", async function () {
Expand Down Expand Up @@ -425,7 +435,7 @@ describe("SpokePoolClient: Fill Validation", function () {
expect(spokePoolClient1.getDeposits().length).to.equal(0);

const historicalDeposit = await queryHistoricalDepositForFill(spokePoolClient1, fill);
assert(historicalDeposit.found === true, "Test is broken"); // Help tsc to narrow the discriminated union.
assert.equal(historicalDeposit.found, true, "Test is broken"); // Help tsc to narrow the discriminated union.
expect(historicalDeposit.deposit.depositId).to.deep.equal(deposit.depositId);
});

Expand All @@ -450,7 +460,7 @@ describe("SpokePoolClient: Fill Validation", function () {
expect(spokePoolClient1.getDeposits().length).to.equal(0);

const historicalDeposit = await queryHistoricalDepositForFill(spokePoolClient1, fill);
assert(historicalDeposit.found === true, "Test is broken"); // Help tsc to narrow the discriminated union.
assert.equal(historicalDeposit.found, true, "Test is broken"); // Help tsc to narrow the discriminated union.
expect(historicalDeposit.deposit.depositId).to.deep.equal(deposit.depositId);
});

Expand Down Expand Up @@ -500,7 +510,7 @@ describe("SpokePoolClient: Fill Validation", function () {
expect(fill.depositId < spokePoolClient1.firstDepositIdForSpokePool).is.true;
const search = await queryHistoricalDepositForFill(spokePoolClient1, fill);

assert(search.found === false, "Test is broken"); // Help tsc to narrow the discriminated union.
assert.equal(search.found, false, "Test is broken"); // Help tsc to narrow the discriminated union.
expect(search.code).to.equal(InvalidFill.DepositIdInvalid);
expect(lastSpyLogIncludes(spy, "Queried RPC for deposit")).is.not.true;
});
Expand All @@ -526,7 +536,7 @@ describe("SpokePoolClient: Fill Validation", function () {
expect(fill.depositId > spokePoolClient1.lastDepositIdForSpokePool).is.true;
const search = await queryHistoricalDepositForFill(spokePoolClient1, fill);

assert(search.found === false, "Test is broken"); // Help tsc to narrow the discriminated union.
assert.equal(search.found, false, "Test is broken"); // Help tsc to narrow the discriminated union.
expect(search.code).to.equal(InvalidFill.DepositIdInvalid);
expect(lastSpyLogIncludes(spy, "Queried RPC for deposit")).is.not.true;
});
Expand All @@ -540,7 +550,7 @@ describe("SpokePoolClient: Fill Validation", function () {
await Promise.all([spokePoolClient1.update(), spokePoolClient2.update()]);

const search = await queryHistoricalDepositForFill(spokePoolClient1, fill);
assert(search.found === false, "Test is broken"); // Help tsc to narrow the discriminated union.
assert.equal(search.found, false, "Test is broken"); // Help tsc to narrow the discriminated union.
expect(search.code).to.equal(InvalidFill.FillMismatch);
});

Expand Down
Loading

0 comments on commit a7adcbf

Please sign in to comment.