Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion helpers/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { isHexString } from "ethers/lib/utils";
import { define } from "superstruct";

export function hexString() {
return define<`0x${string}`>("hexString", (value) => {
return define<`0x${string}`>("hex string", (value) => {
return isHexString(value);
});
}

export function positiveIntStr() {
return define<string>("positive integer", (value) => {
return Number.isInteger(Number(value)) && Number(value) >= 0;
});
}
213 changes: 52 additions & 161 deletions helpers/voting/resolveAncillaryData.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,6 @@
import { RequestAddedEvent } from "@uma/contracts-frontend/dist/typechain/core/ethers/VotingV2";
import { Contract } from "ethers";
import {
BytesLike,
defaultAbiCoder,
Interface,
keccak256,
} from "ethers/lib/utils";
import { getProvider } from "helpers/config";
import { decodeHexString, encodeHexString } from "helpers/web3/decodeHexString";

// bytes, bytes32, address do NOT have "0x" prefix
// uint are represented as decimal string

const abi = [
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "requester",
type: "address",
},
{
indexed: false,
internalType: "bytes32",
name: "identifier",
type: "bytes32",
},
{
indexed: false,
internalType: "uint256",
name: "time",
type: "uint256",
},
{
indexed: false,
internalType: "bytes",
name: "ancillaryData",
type: "bytes",
},
{
indexed: true,
internalType: "bytes32",
name: "childRequestId",
type: "bytes32",
},
{
indexed: true,
internalType: "bytes32",
name: "parentRequestId",
type: "bytes32",
},
],
name: "PriceRequestBridged",
type: "event",
},
];
import { resolveAncillaryData as resolveAncillaryDataShared } from "lib/l2-ancillary-data";
import { buildSearchParams } from "helpers/util/buildSearchParams";

export async function resolveAncillaryDataForRequests<
T extends Parameters<typeof resolveAncillaryData>[0]
Expand All @@ -73,117 +17,64 @@ export async function resolveAncillaryDataForRequests<
export async function resolveAncillaryData(
args: Pick<RequestAddedEvent["args"], "ancillaryData" | "time" | "identifier">
): Promise<string> {
const decodedAncillaryData = decodeHexString(args.ancillaryData);
const { ancillaryDataHash, childOracle, childChainId, childBlockNumber } =
extractMaybeAncillaryDataFields(decodedAncillaryData);

if (ancillaryDataHash && childOracle && childChainId && childBlockNumber) {
try {
// if decoded ancillary data contains these fields, then we must extract from spoke
const _childOracle = `0x${childOracle}`;
const _childChainId = Number(childChainId);
const _childBlockNumber = Number(childBlockNumber);

const parentRequestId = keccak256(
defaultAbiCoder.encode(
["bytes32", "uint256", "bytes"],
[args.identifier, args.time, args.ancillaryData]
)
);

const resolved = await fetchAncillaryDataFromSpoke({
parentRequestId,
childOracle: _childOracle,
childChainId: _childChainId,
childBlockNumber: _childBlockNumber,
});
try {
// Use the serverless API endpoint for L2 ancillary data resolution
const response = await fetch(
`/api/resolve-l2-ancillary-data?${buildSearchParams({
identifier: args.identifier,
time: args.time.toString(),
ancillaryData: args.ancillaryData,
})}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);

return mergeAncillaryData(
decodedAncillaryData,
decodeHexString(resolved)
if (!response.ok) {
throw new Error(
`API request failed: ${response.status} ${response.statusText}`
);
} catch (error) {
console.error("Unable to resolve original ancillary data", {
at: "resolveAncillaryData()",
data: args,
cause: error,
});

return args.ancillaryData;
}
}

return args.ancillaryData;
}
const result = (await response.json()) as
| {
resolvedAncillaryData: string;
}
| {
error: string;
};

function mergeAncillaryData(
decodedL1Data: string,
decodedL2Data: string
): string {
const pattern =
/^ancillaryDataHash:\w+,childBlockNumber:\d+,childOracle:\w+,/;
const merged = decodedL1Data.replace(pattern, `${decodedL2Data},`);
return encodeHexString(merged);
}

function extractMaybeAncillaryDataFields(decodedAncillaryData: string) {
const pattern = new RegExp(
"^" +
"ancillaryDataHash:(\\w+),\\s*" +
"childBlockNumber:(\\d+),\\s*" +
"childOracle:(\\w+),\\s*" +
"childRequester:(\\w+),\\s*" +
"childChainId:(\\d+)" +
"$"
);
if ("error" in result) {
throw new Error(result.error);
}

const match = decodedAncillaryData.match(pattern);
if (!match) return {};
if (!result.resolvedAncillaryData) {
throw new Error("No resolved ancillary data returned from API");
}

return {
ancillaryDataHash: match[1],
childBlockNumber: match[2],
childOracle: match[3],
childRequester: match[4],
childChainId: match[5],
};
}
return result.resolvedAncillaryData;
} catch (error) {
console.warn("Unable to resolve original ancillary data via API", {
at: "resolveAncillaryData()",
data: args,
cause: error,
});

async function fetchAncillaryDataFromSpoke(args: {
parentRequestId: BytesLike;
childOracle: string;
childChainId: number;
childBlockNumber: number;
}): Promise<string> {
const provider = getProvider(args.childChainId);
// TODO: install from upgraded @uma/contracts-frontend pkg
const OracleSpoke = new Contract(
args.childOracle,
new Interface(abi),
provider
);
const filter = OracleSpoke.filters.PriceRequestBridged(
null,
null,
null,
null,
null,
args.parentRequestId
);

const events = await OracleSpoke.queryFilter(
filter,
args.childBlockNumber - 1,
args.childBlockNumber
);
// Fallback to local
try {
const result = await resolveAncillaryDataShared(args);
return result.resolvedAncillaryData;
} catch (fallbackError) {
console.error("Fallback resolution also failed", {
at: "resolveAncillaryData()",
data: args,
cause: fallbackError,
});

if (!events.length) {
throw new Error(
`Unable to find event with request Id: ${String(
args.parentRequestId
)} on chain ${args.childChainId}`
);
throw fallbackError;
}
}

return events[0]?.args?.ancillaryData as string;
}
Loading