diff --git a/core/base/package.json b/core/base/package.json index fe6dfce41..4922781d8 100644 --- a/core/base/package.json +++ b/core/base/package.json @@ -116,7 +116,8 @@ "dist/cjs" ], "dependencies": { - "@scure/base": "^1.1.3" + "@scure/base": "^1.1.3", + "binary-layout": "^1.0.3" }, "sideEffects": false, "scripts": { @@ -132,4 +133,4 @@ "prettier": "prettier --write ./src" }, "type": "module" -} \ No newline at end of file +} diff --git a/core/base/src/utils/index.ts b/core/base/src/utils/index.ts index 09d29638a..5f96d0f63 100644 --- a/core/base/src/utils/index.ts +++ b/core/base/src/utils/index.ts @@ -2,9 +2,7 @@ export * from "./array.js"; export * from "./mapping.js"; export * from "./metaprogramming.js"; export * from "./misc.js"; - -export * from "./layout/index.js"; +export * from "./layout.js"; export * as amount from "./amount.js"; -export * as layout from "./layout/index.js"; export * as encoding from "./encoding.js"; diff --git a/core/base/src/utils/layout.ts b/core/base/src/utils/layout.ts new file mode 100644 index 000000000..2d9527807 --- /dev/null +++ b/core/base/src/utils/layout.ts @@ -0,0 +1,26 @@ +export { + type Layout, + type DeriveType as LayoutToType, + type FixedConversion, + type CustomConversion, + type Endianness, + type CustomizableBytes, + type CustomizableBytesReturn, + type Discriminator, + type FixedItemsOf as FixedItemsOfLayout, + type DynamicItemsOf as DynamicItemsOfLayout, + type Bitset, + type BitsetItem, + serialize as serializeLayout, + deserialize as deserializeLayout, + buildDiscriminator as layoutDiscriminator, + calcSize as calcLayoutSize, + calcStaticSize as calcStaticLayoutSize, + customizableBytes, + addFixedValues, + fixedItemsOf as fixedItemsOfLayout, + dynamicItemsOf as dynamicItemsOfLayout, + enumItem, + optionItem, + bitsetItem +} from "binary-layout"; diff --git a/core/base/src/utils/layout/deserialize.ts b/core/base/src/utils/layout/deserialize.ts deleted file mode 100644 index 3abe40303..000000000 --- a/core/base/src/utils/layout/deserialize.ts +++ /dev/null @@ -1,225 +0,0 @@ -import type { - Endianness, - Layout, - LayoutItem, - LayoutToType, - CustomConversion, - NumSizeToPrimitive, - NumType, - BytesType, -} from './layout.js'; -import { defaultEndianness, numberMaxSize } from './layout.js'; - -import { - isNumType, - isBytesType, - isFixedBytesConversion, - checkBytesTypeEqual, - checkNumEquals, -} from './utils.js'; -import { getCachedSerializedFrom } from './serialize.js'; - -export function deserializeLayout( - layout: L, - bytes: BytesType, - opts?: { - offset?: number, - end?: number, - consumeAll?: B, - }, -) { - const encoded = { - bytes, - offset: opts?.offset ?? 0, - end: opts?.end ?? bytes.length, - }; - const decoded = internalDeserializeLayout(layout, encoded); - - if ((opts?.consumeAll ?? true) && encoded.offset !== encoded.end) - throw new Error(`encoded data is longer than expected: ${encoded.end} > ${encoded.offset}`); - - return ( - (opts?.consumeAll ?? true) ? decoded : [decoded, encoded.offset] - ) as B extends true ? LayoutToType : readonly [LayoutToType, number]; -} - -type BytesChunk = { - bytes: BytesType, - offset: number, - end: number, -}; - -function updateOffset(encoded: BytesChunk, size: number) { - const newOffset = encoded.offset + size; - if (newOffset > encoded.end) - throw new Error(`chunk is shorter than expected: ${encoded.end} < ${newOffset}`); - - encoded.offset = newOffset; -} - -function internalDeserializeLayout(layout: Layout, encoded: BytesChunk): any { - if (!Array.isArray(layout)) - return deserializeLayoutItem(layout as LayoutItem, encoded); - - let decoded = {} as any; - for (const item of layout) - try { - ((item as any).omit ? {} : decoded)[item.name] = deserializeLayoutItem(item, encoded); - } - catch (e) { - (e as Error).message = `when deserializing item '${item.name}': ${(e as Error).message}`; - throw e; - } - - return decoded; -} - -function deserializeNum( - encoded: BytesChunk, - size: S, - endianness: Endianness = defaultEndianness, - signed: boolean = false, -) { - let val = 0n; - for (let i = 0; i < size; ++i) - val |= BigInt(encoded.bytes[encoded.offset + i]!) - << BigInt(8 * (endianness === "big" ? size - i - 1 : i)); - - //check sign bit if value is indeed signed and adjust accordingly - if (signed && (encoded.bytes[encoded.offset + (endianness === "big" ? 0 : size - 1)]! & 0x80)) - val -= 1n << BigInt(8 * size); - - updateOffset(encoded, size); - - return ((size > numberMaxSize) ? val : Number(val)) as NumSizeToPrimitive; -} - -function deserializeLayoutItem(item: LayoutItem, encoded: BytesChunk): any { - switch (item.binary) { - case "int": - case "uint": { - const value = deserializeNum(encoded, item.size, item.endianness, item.binary === "int"); - - const { custom } = item; - if (isNumType(custom)) { - checkNumEquals(custom, value); - return custom; - } - if (isNumType(custom?.from)) { - checkNumEquals(custom!.from, value); - return custom!.to; - } - - //narrowing to CustomConversion is a bit hacky here, since the true type - // would be CustomConversion | CustomConversion, but then we'd - // have to further tease that apart still for no real gain... - return custom !== undefined ? (custom as CustomConversion).to(value) : value; - } - case "bytes": { - const expectedSize = ("lengthSize" in item && item.lengthSize !== undefined) - ? deserializeNum(encoded, item.lengthSize, item.lengthEndianness) - : (item as {size?: number})?.size; - - if ("layout" in item) { //handle layout conversions - const { custom } = item; - const offset = encoded.offset; - let layoutData; - if (expectedSize === undefined) - layoutData = internalDeserializeLayout(item.layout, encoded); - else { - const subChunk = {...encoded, end: encoded.offset + expectedSize}; - updateOffset(encoded, expectedSize); - layoutData = internalDeserializeLayout(item.layout, subChunk); - if (subChunk.offset !== subChunk.end) - throw new Error( - `read less data than expected: ${subChunk.offset - encoded.offset} < ${expectedSize}` - ); - } - - if (custom !== undefined) { - if (typeof custom.from !== "function") { - checkBytesTypeEqual( - getCachedSerializedFrom(item as any), - encoded.bytes, - {dataSlize: [offset, encoded.offset]} - ); - return custom.to; - } - return custom.to(layoutData); - } - - return layoutData; - } - - const { custom } = item; - { //handle fixed conversions - let fixedFrom; - let fixedTo; - if (isBytesType(custom)) - fixedFrom = custom; - else if (isFixedBytesConversion(custom)) { - fixedFrom = custom.from; - fixedTo = custom.to; - } - if (fixedFrom !== undefined) { - const size = expectedSize ?? fixedFrom.length; - const value = encoded.bytes.slice(encoded.offset, encoded.offset + size); - checkBytesTypeEqual(fixedFrom, value); - updateOffset(encoded, size); - return fixedTo ?? fixedFrom; - } - } - - //handle no or custom conversions - const start = encoded.offset; - const end = (expectedSize !== undefined) ? encoded.offset + expectedSize : encoded.end; - updateOffset(encoded, end - start); - - const value = encoded.bytes.slice(start, end); - return custom !== undefined ? (custom as CustomConversion).to(value) : value; - } - case "array": { - let ret = [] as any[]; - const { layout } = item; - const deserializeArrayItem = () => { - const deserializedItem = internalDeserializeLayout(layout, encoded); - ret.push(deserializedItem); - } - - let length = null; - if ("length" in item && item.length !== undefined) - length = item.length; - else if ("lengthSize" in item && item.lengthSize !== undefined) - length = deserializeNum(encoded, item.lengthSize, item.lengthEndianness); - - if (length !== null) - for (let i = 0; i < length; ++i) - deserializeArrayItem(); - else - while (encoded.offset < encoded.end) - deserializeArrayItem(); - - return ret; - } - case "switch": { - const id = deserializeNum(encoded, item.idSize, item.idEndianness); - const {layouts} = item; - if (layouts.length === 0) - throw new Error(`switch item has no layouts`); - - const hasPlainIds = typeof layouts[0]![0] === "number"; - const pair = (layouts as readonly any[]).find(([idOrConversionId]) => - hasPlainIds ? idOrConversionId === id : (idOrConversionId)[0] === id); - - if (pair === undefined) - throw new Error(`unknown id value: ${id}`); - - const [idOrConversionId, idLayout] = pair; - const decoded = internalDeserializeLayout(idLayout, encoded); - return { - [item.idTag ?? "id"]: hasPlainIds ? id : (idOrConversionId as any)[1], - ...decoded - }; - } - } -} diff --git a/core/base/src/utils/layout/discriminate.ts b/core/base/src/utils/layout/discriminate.ts deleted file mode 100644 index 004fd36a2..000000000 --- a/core/base/src/utils/layout/discriminate.ts +++ /dev/null @@ -1,613 +0,0 @@ -import type { Layout, LayoutItem, LengthPrefixed, BytesType } from './layout.js'; -import { serializeNum, getCachedSerializedFrom } from './serialize.js'; -import { isNumType, isBytesType, isFixedBytesConversion } from './utils.js'; -import { calcStaticLayoutSize } from './size.js'; - -//defining a bunch of types for readability -type Uint = number; -type Bitset = bigint; -type Size = Uint; -type BytePos = Uint; -type ByteVal = Uint; //actually a uint8 -type LayoutIndex = Uint; -type Candidates = Bitset; -type FixedBytes = (readonly [BytePos, BytesType])[]; -//using a Bounds type (even though currently the upper bound can only either be equal to the lower -// bound or Infinity) in anticipation of a future switch layout item that might contain multiple -// sublayouts which, unlike arrays currently, could all be bounded but potentially with -// different sizes -type Bounds = [Size, Size]; - -function arrayToBitset(arr: readonly number[]): Bitset { - return arr.reduce((bit, i) => bit | BigInt(1) << BigInt(i), BigInt(0)); -} - -function bitsetToArray(bitset: Bitset): number[] { - const ret = []; - for (let i = 0n; bitset > 0n; bitset >>= 1n, ++i) - if (bitset & 1n) - ret.push(Number(i)); - - return ret; -} - -function count(candidates: Candidates) { - let count = 0; - for (; candidates > 0n; candidates >>= 1n) - count += Number(candidates & 1n); - return count; -} - -const lengthSizeMax = (lengthSize: number) => - lengthSize > 0 ? 2**(8 * lengthSize) - 1 : Infinity; - -function layoutItemMeta( - item: LayoutItem, - offset: BytePos | null, - fixedBytes: FixedBytes, -): Bounds { - switch (item.binary) { - case "int": - case "uint": { - const fixedVal = - isNumType(item.custom) - ? item.custom - : isNumType(item?.custom?.from) - ? item!.custom!.from - : null; - - if (fixedVal !== null && offset !== null) { - const cursor = {bytes: new Uint8Array(item.size), offset: 0}; - serializeNum(fixedVal, item.size, cursor, item.endianness, item.binary === "int"); - fixedBytes.push([offset, cursor.bytes]); - } - - return [item.size, item.size]; - } - case "bytes": { - const lengthSize = ("lengthSize" in item) ? item.lengthSize | 0 : 0; - - let fixed; - let fixedSize; - if ("layout" in item) { - const { custom } = item; - if (custom !== undefined && typeof custom.from !== "function") { - fixed = getCachedSerializedFrom(item as any); - fixedSize = fixed.length; - } - else { - const layoutSize = calcStaticLayoutSize(item.layout); - if (layoutSize !== null) - fixedSize = layoutSize; - } - } - else { - const { custom } = item; - if (isBytesType(custom)) { - fixed = custom; - fixedSize = custom.length; - } - else if (isFixedBytesConversion(custom)) { - fixed = custom.from; - fixedSize = custom.from.length; - } - } - - if (lengthSize > 0 && offset !== null) { - if (fixedSize !== undefined) { - const cursor = {bytes: new Uint8Array(lengthSize), offset: 0}; - const endianess = (item as LengthPrefixed).lengthEndianness; - serializeNum(fixedSize, lengthSize, cursor, endianess, false); - fixedBytes.push([offset, cursor.bytes]); - } - offset += lengthSize; - } - - if (fixed !== undefined) { - if (offset !== null) - fixedBytes.push([offset, fixed]); - - return [lengthSize + fixed.length, lengthSize + fixed.length]; - } - - //lengthSize must be 0 if size is defined - const ret = ("size" in item && item.size !== undefined) - ? [item.size, item.size] as Bounds - : undefined; - - if ("layout" in item) { - const lm = createLayoutMeta(item.layout, offset, fixedBytes) - return ret ?? [lengthSize + lm[0], lengthSize + lm[1]]; - } - - return ret ?? [lengthSize, lengthSizeMax(lengthSize)]; - } - case "array": { - if ("length" in item) { - let localFixedBytes = [] as FixedBytes; - const itemSize = createLayoutMeta(item.layout, 0, localFixedBytes); - if (offset !== null) { - if (itemSize[0] !== itemSize[1]) { - //if the size of an array item is not fixed we can only add the fixed bytes of the - // first item - if (item.length > 0) - for (const [o, s] of localFixedBytes) - fixedBytes.push([offset + o, s]); - } - else { - //otherwise we can add fixed know bytes for each array item - for (let i = 0; i < item.length; ++i) - for (const [o, s] of localFixedBytes) - fixedBytes.push([offset + o + i * itemSize[0], s]); - } - } - - return [item.length * itemSize[0], item.length * itemSize[1]]; - } - const lengthSize = (item as LengthPrefixed).lengthSize | 0; - return [lengthSize, lengthSizeMax(lengthSize)]; - } - case "switch": { - const caseFixedBytes = item.layouts.map(_ => []) as FixedBytes[]; - const {idSize, idEndianness} = item; - const caseBounds = item.layouts.map(([idOrConversionId, layout], caseIndex) => { - const idVal = Array.isArray(idOrConversionId) ? idOrConversionId[0] : idOrConversionId; - if (offset !== null) { - const cursor = {bytes: new Uint8Array(idSize), offset: 0}; - serializeNum(idVal, idSize, cursor, idEndianness); - caseFixedBytes[caseIndex]!.push([0, cursor.bytes]); - } - const ret = createLayoutMeta(layout, offset !== null ? idSize : null, caseFixedBytes[caseIndex]!); - return [ret[0] + idSize, ret[1] + idSize] as Bounds; - }); - - if (offset !== null && caseFixedBytes.every(fbs => fbs.length > 0)) - //find bytes that have the same value across all cases - // (it's a lambda to enable early return from inner loops) - (() => { - //constrain search to the minimum length of all cases - const minLen = Math.min( - ...caseFixedBytes.map(fbs => fbs.at(-1)![0] + fbs.at(-1)![1].length) - ); - //keep track of the current index in each case's fixed bytes array - const itIndexes = caseFixedBytes.map(_ => 0); - - for (let bytePos = 0; bytePos < minLen;) { - let byteVal = null; - let caseIndex = 0; - while (caseIndex < caseFixedBytes.length) { - let curItIndex = itIndexes[caseIndex]!; - const curFixedBytes = caseFixedBytes[caseIndex]!; - const [curOffset, curSerialized] = curFixedBytes[curItIndex]!; - if (curOffset + curSerialized.length <= bytePos) { - //no fixed byte at this position in this case - ++curItIndex; - - if (curItIndex === curFixedBytes.length) - return; //we have exhausted all fixed bytes in at least one case - - itIndexes[caseIndex] = curItIndex; - //jump to the next possible bytePos given the fixed bytes of the current case index - bytePos = curFixedBytes[curItIndex]![0]; - break; - } - - const curByteVal = curSerialized[bytePos - curOffset]; - if (byteVal === null) - byteVal = curByteVal; - - if (curByteVal !== byteVal) { - ++bytePos; - break; - } - - ++caseIndex; - } - - //only if we made it through all cases without breaking do we have a fixed byte - // and hence add it to the list of fixed bytes - if (caseIndex === caseFixedBytes.length) { - fixedBytes.push([offset + bytePos, new Uint8Array([byteVal!])]); - ++bytePos; - } - } - })(); - - return [ - Math.min(...caseBounds.map(([lower]) => lower)), - Math.max(...caseBounds.map(([_, upper]) => upper)) - ] as Bounds; - } - } -} - -function createLayoutMeta( - layout: Layout, - offset: BytePos | null, - fixedBytes: FixedBytes -): Bounds { - if (!Array.isArray(layout)) - return layoutItemMeta(layout as LayoutItem, offset, fixedBytes); - - let bounds = [0, 0] as Bounds; - for (const item of layout) { - const itemSize = layoutItemMeta(item, offset, fixedBytes); - bounds[0] += itemSize[0]; - bounds[1] += itemSize[1]; - //if the bounds don't agree then we can't reliably predict the offset of subsequent items - if (offset !== null) - offset = itemSize[0] === itemSize[1] ? offset + itemSize[0] : null; - } - return bounds; -} - -function buildAscendingBounds(sortedBounds: readonly (readonly [Bounds, LayoutIndex])[]) { - const ascendingBounds = new Map(); - //sortedCandidates tracks all layouts that have a size bound that contains the size that's - // currently under consideration, sorted in ascending order of their respective upper bounds - let sortedCandidates = [] as (readonly [Size, LayoutIndex])[]; - const closeCandidatesBefore = (before: number) => { - while (sortedCandidates.length > 0 && sortedCandidates[0]![0] < before) { - const end = sortedCandidates[0]![0] + 1; - //remove all candidates that end at the same position - const removeIndex = sortedCandidates.findIndex(([upper]) => end <= upper); - if (removeIndex === -1) - sortedCandidates = []; - else - sortedCandidates.splice(0, removeIndex); - //introduce a new bound that captures all candidates that can have a size of at least `end` - ascendingBounds.set(end, arrayToBitset(sortedCandidates.map(([, j]) => j))); - } - }; - - for (const [[lower, upper], i] of sortedBounds) { - closeCandidatesBefore(lower); - const insertIndex = sortedCandidates.findIndex(([u]) => u > upper); - if (insertIndex === -1) - sortedCandidates.push([upper, i]); - else - sortedCandidates.splice(insertIndex, 0, [upper, i]); - - ascendingBounds.set(lower, arrayToBitset(sortedCandidates.map(([, j]) => j))); - } - closeCandidatesBefore(Infinity); - - return ascendingBounds; -} - -//Generates a greedy divide-and-conquer strategy to determine the layout (or set of layouts) that -// a given serialized byte array might conform to. -//It leverages size bounds and known fixed bytes of layouts to quickly eliminate candidates, by -// (greedily) choosing the discriminator (byte or size) that eliminates the most candidates at -// each step. -//Power is a relative measure of the strength of a discriminator given a set of layout candidates. -// It's in [0, candidate.length - 1] and states how many layouts of that set can _at least_ be -// eliminated when applying that discriminator. -//Layout sizes are only tracked in terms of lower and upper bounds. This means that while a layout -// like an array of e.g. 2 byte uints can actually never have an odd size, the algorithm will -// simply treat it as having a size bound of [0, Infinity]. This means that the algorithm is -// "lossy" in the sense that it does not use all the information that it actually has available -// and will e.g. wrongly conclude that the aforementioned layout cannot be distinguished from a -// second layout that starts off with a one byte uint followed by an array of 2 byte uints (and -// would thus always have odd size). I.e. it would wrongly conclude that the power of the size -// discriminator is 0 when it should be 1. -//The alternative to accepting this limitation is tracking all possible combinations of offsets, -// multiples, and their arbitrary composition which would be massively more complicated and -// also pointless in the general case because we'd have to figure out whether a given size can be -// expressed as some combination of offsets and array size multiples in which case it's almost -// certainly computaionally cheaper to simply attempt to deserialize the given given data for the -// respective layout. -function generateLayoutDiscriminator( - layouts: readonly Layout[] -): [boolean, (encoded: BytesType) => readonly LayoutIndex[]] { - //for debug output: - // const candStr = (candidate: Bitset) => candidate.toString(2).padStart(layouts.length, '0'); - - if (layouts.length === 0) - throw new Error("Cannot discriminate empty set of layouts"); - - const emptySet = 0n; - const allLayouts = (1n << BigInt(layouts.length)) - 1n; - - const fixedKnown = layouts.map(() => [] as FixedBytes); - const sizeBounds = layouts.map((l, i) => createLayoutMeta(l, 0, fixedKnown[i]!)); - const sortedBounds = sizeBounds.map((b, i) => [b, i] as const).sort(([[l1]], [[l2]]) => l1 - l2); - - const mustHaveByteAt = (() => { - let remaining = allLayouts; - const ret = new Map(); - for (const [[lower], i] of sortedBounds) { - remaining ^= 1n << BigInt(i); //delete the i-th bit - ret.set(lower, remaining); - } - return ret; - })(); - const ascendingBounds = buildAscendingBounds(sortedBounds); - const sizePower = layouts.length - Math.max( - ...[...ascendingBounds.values()].map(candidates => count(candidates)) - ); - //we don't check sizePower here and bail early if it is perfect because we prefer perfect byte - // discriminators over perfect size discriminators due to their faster lookup times (hash map - // vs binary search (and actually currently everything is even implement using linear search)) - // and more predictable / lower complexity branching behavior. - const layoutsWithByteAt = (bytePos: BytePos) => { - let ret = allLayouts; - for (const [lower, candidates] of mustHaveByteAt) { - if (bytePos < lower) - break; - - ret = candidates; - } - return ret; - }; - - const layoutsWithSize = (size: Size) => { - let ret = emptySet; - for (const [lower, candidates] of ascendingBounds) { - if (size < lower) - break; - - ret = candidates; - } - return ret; - }; - - const fixedKnownBytes: readonly ((readonly [ByteVal, LayoutIndex])[])[] = Array.from({length: - Math.max(...fixedKnown.map(fkb => fkb.length > 0 ? fkb.at(-1)![0] + fkb.at(-1)![1].length : 0)) - }).map(() => []); - - for (let i = 0; i < fixedKnown.length; ++i) - for (const [offset, serialized] of fixedKnown[i]!) - for (let j = 0; j < serialized.length; ++j) - fixedKnownBytes[offset + j]!.push([serialized[j]!, i]); - - //debug output: - // console.log("fixedKnownBytes:", - // fixedKnownBytes.map((v, i) => v.length > 0 ? [i, v] : undefined).filter(v => v !== undefined) - // ); - - let bestBytes = []; - for (const [bytePos, fixedKnownByte] of fixedKnownBytes.entries()) { - //the number of layouts with a given size is an upper bound on the discriminatory power of - // a byte at a given position: If the encoded data is too short we can automatically - // exclude all layouts whose minimum size is larger than it, nevermind those who expect - // a known, fixed value at this position. - const lwba = layoutsWithByteAt(bytePos); - const anyValueLayouts = lwba ^ arrayToBitset(fixedKnownByte.map(([, layoutIdx]) => layoutIdx)); - const outOfBoundsLayouts = allLayouts ^ lwba; - const distinctValues = new Map(); - //the following equation holds (after applying .length to each component): - //layouts = outOfBoundsLayouts + anyValueLayouts + fixedKnownByte - for (const [byteVal, candidate] of fixedKnownByte) { - if (!distinctValues.has(byteVal)) - distinctValues.set(byteVal, emptySet); - - distinctValues.set(byteVal, distinctValues.get(byteVal)! | 1n << BigInt(candidate)); - } - - let power = layouts.length - Math.max(count(anyValueLayouts), count(outOfBoundsLayouts)); - for (const layoutsWithValue of distinctValues.values()) { - //if we find the byte value associated with this set of layouts, we can eliminate - // all other layouts that don't have this value at this position and all layouts - // that are too short to have a value in this position regardless - const curPower = fixedKnownByte.length - count(layoutsWithValue) + count(outOfBoundsLayouts); - power = Math.min(power, curPower); - } - - //debug output: - // console.log( - // "bytePos:", bytePos, - // "\npower:", power, - // "\nfixedKnownByte:", fixedKnownByte, - // "\nlwba:", candStr(lwba), - // "\nanyValueLayouts:", candStr(anyValueLayouts), - // "\noutOfBoundsLayouts:", candStr(outOfBoundsLayouts), - // "\ndistinctValues:", new Map([...distinctValues].map(([k, v]) => [k, candStr(v)])) - // ); - - if (power === 0) - continue; - - if (power === layouts.length - 1) - //we have a perfect byte discriminator -> bail early - return [ - true, - (encoded: BytesType) => - bitsetToArray( - encoded.length <= bytePos - ? outOfBoundsLayouts - : distinctValues.get(encoded[bytePos]!) ?? emptySet - ) - ]; - - bestBytes.push([power, bytePos, outOfBoundsLayouts, distinctValues, anyValueLayouts] as const); - } - - //if we get here, we know we don't have a perfect byte discriminator so we now check wether we - // we have a perfect size discriminator and bail early if so - if (sizePower === layouts.length - 1) - return [true, (encoded: BytesType) => bitsetToArray(layoutsWithSize(encoded.length))]; - - //sort in descending order of power - bestBytes.sort(([lhsPower], [rhsPower]) => rhsPower - lhsPower); - type BestBytes = typeof bestBytes; - type Strategy = [BytePos, Candidates, Map] | "size" | "indistinguishable"; - - let distinguishable = true; - const strategies = new Map(); - const candidatesBySize = new Map(); - const addStrategy = (candidates: Candidates, strategy: Strategy) => { - strategies.set(candidates, strategy); - if (!candidatesBySize.has(count(candidates))) - candidatesBySize.set(count(candidates), []); - candidatesBySize.get(count(candidates))!.push(candidates); - }; - - const recursivelyBuildStrategy = ( - candidates: Candidates, - bestBytes: BestBytes, - ) => { - if (count(candidates) <= 1 || strategies.has(candidates)) - return; - - let sizePower = 0; - const narrowedBounds = new Map(); - for (const candidate of bitsetToArray(candidates)) { - const lower = sizeBounds[candidate]![0]; - const overlap = ascendingBounds.get(lower)! & candidates; - narrowedBounds.set(lower, overlap) - sizePower = Math.max(sizePower, count(overlap)); - } - sizePower = count(candidates) - sizePower; - - const narrowedBestBytes = [] as BestBytes; - for (const [power, bytePos, outOfBoundsLayouts, distinctValues, anyValueLayouts] of bestBytes) { - const narrowedDistinctValues = new Map(); - let fixedKnownCount = 0; - for (const [byteVal, layoutsWithValue] of distinctValues) { - const lwv = layoutsWithValue & candidates; - if (count(lwv) > 0) { - narrowedDistinctValues.set(byteVal, lwv); - fixedKnownCount += count(lwv); - } - } - const narrowedOutOfBoundsLayouts = outOfBoundsLayouts & candidates; - - let narrowedPower = narrowedDistinctValues.size > 0 ? power : 0; - for (const layoutsWithValue of narrowedDistinctValues.values()) { - const curPower = - fixedKnownCount - count(layoutsWithValue) + count(narrowedOutOfBoundsLayouts); - narrowedPower = Math.min(narrowedPower, curPower); - } - - if (narrowedPower === 0) - continue; - - if (narrowedPower === count(candidates) - 1) { - //if we have a perfect byte discriminator, we can bail early - addStrategy(candidates, [bytePos, narrowedOutOfBoundsLayouts, narrowedDistinctValues]); - return; - } - - narrowedBestBytes.push([ - narrowedPower, - bytePos, - narrowedOutOfBoundsLayouts, - narrowedDistinctValues, - anyValueLayouts & candidates - ] as const); - } - - if (sizePower === count(candidates) - 1) { - //if we have a perfect size discriminator, we can bail early - addStrategy(candidates, "size"); - return; - } - - narrowedBestBytes.sort(([lhsPower], [rhsPower]) => rhsPower - lhsPower); - - //prefer byte discriminators over size discriminators - if (narrowedBestBytes.length > 0 && narrowedBestBytes[0]![0] >= sizePower) { - const [, bytePos, narrowedOutOfBoundsLayouts, narrowedDistinctValues, anyValueLayouts] = - narrowedBestBytes[0]!; - addStrategy(candidates, [bytePos, narrowedOutOfBoundsLayouts, narrowedDistinctValues]); - recursivelyBuildStrategy(narrowedOutOfBoundsLayouts, narrowedBestBytes); - for (const cand of narrowedDistinctValues.values()) - recursivelyBuildStrategy(cand | anyValueLayouts, narrowedBestBytes.slice(1)); - - return; - } - - if (sizePower > 0) { - addStrategy(candidates, "size"); - for (const cands of narrowedBounds.values()) - recursivelyBuildStrategy(cands, narrowedBestBytes); - - return; - } - - addStrategy(candidates, "indistinguishable"); - distinguishable = false; - } - - recursivelyBuildStrategy(allLayouts, bestBytes); - - const findSmallestSuperSetStrategy = (candidates: Candidates) => { - for (let size = count(candidates) + 1; size < layouts.length - 2; ++size) - for (const larger of candidatesBySize.get(size) ?? []) - if ((candidates & larger) == candidates) //is subset? - return strategies.get(larger)!; - - throw new Error("Implementation error in layout discrimination algorithm"); - }; - - //debug output: - // console.log("strategies:", JSON.stringify( - // new Map([...strategies].map(([cands, strat]) => [ - // candStr(cands), - // typeof strat === "string" - // ? strat - // : [ - // strat[0], //bytePos - // candStr(strat[1]), //outOfBoundsLayouts - // new Map([...strat[2]].map(([value, cands]) => [value, candStr(cands)])) - // ] - // ] - // )) - // )); - - return [distinguishable, (encoded: BytesType) => { - let candidates = allLayouts; - - let strategy = strategies.get(candidates)!; - while (strategy !== "indistinguishable") { - //debug output: - // console.log( - // "applying strategy", strategy, - // "\nfor remaining candidates:", candStr(candidates) - // ); - if (strategy === "size") - candidates &= layoutsWithSize(encoded.length); - else { - const [bytePos, outOfBoundsLayouts, distinctValues] = strategy; - if (encoded.length <= bytePos) - candidates &= outOfBoundsLayouts; - else { - const byteVal = encoded[bytePos]; - for (const [val, cands] of distinctValues) - if (val !== byteVal) - candidates ^= candidates & cands; //= candidates - cands (set minus) - - candidates ^= candidates & outOfBoundsLayouts; - } - } - - if (count(candidates) <= 1) - break; - - strategy = strategies.get(candidates) ?? findSmallestSuperSetStrategy(candidates) - } - - //debug output: - // console.log("final candidates", candStr(candidates)); - return bitsetToArray(candidates); - }]; -} - -export function layoutDiscriminator( - layouts: readonly Layout[], - allowAmbiguous?: B -) { - const [distinguishable, discriminator] = generateLayoutDiscriminator(layouts); - if (!distinguishable && !allowAmbiguous) - throw new Error("Cannot uniquely distinguished the given layouts"); - - return ( - !allowAmbiguous - ? (encoded: BytesType) => { - const layout = discriminator(encoded); - return layout.length === 0 ? null : layout[0]; - } - : discriminator - ) as (encoded: BytesType) => B extends false ? LayoutIndex | null : readonly LayoutIndex[]; -} diff --git a/core/base/src/utils/layout/fixedDynamic.ts b/core/base/src/utils/layout/fixedDynamic.ts deleted file mode 100644 index b59936656..000000000 --- a/core/base/src/utils/layout/fixedDynamic.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { - Layout, - ProperLayout, - LayoutItem, - NumLayoutItem, - BytesLayoutItem, - ArrayLayoutItem, - SwitchLayoutItem, - LayoutToType, - NumType, - BytesType, - LayoutObject, - FixedConversion, - CustomConversion, -} from './layout.js'; - -import { isPrimitiveType, isLayoutItem, isFixedPrimitiveConversion } from './utils.js'; - -type NonEmpty = readonly [unknown, ...unknown[]]; - -type IPLPair = readonly [any, ProperLayout]; - -type FilterItemsOfIPLPairs = - ILA extends infer V extends readonly IPLPair[] - ? V extends readonly [infer H extends IPLPair, ...infer T extends readonly IPLPair[]] - ? FilterItemsOfLayout extends infer P extends ProperLayout | void - ? P extends NonEmpty - ? [[H[0], P], ...FilterItemsOfIPLPairs] - : FilterItemsOfIPLPairs - : never - : [] - : never; - -type FilterLayoutOfItem = - FilterItemsOfLayout extends infer L extends LayoutItem | NonEmpty - ? { readonly [K in keyof Item]: K extends "layout" ? L : Item[K] } - : void; - -type FilterItem = - Item extends infer I extends LayoutItem - ? I extends NumLayoutItem - ? I["custom"] extends NumType | FixedConversion - ? Fixed extends true ? I : void - : Fixed extends true ? void : I - : I extends ArrayLayoutItem - ? FilterLayoutOfItem - : I extends BytesLayoutItem & { layout: Layout } - ? I["custom"] extends { custom: FixedConversion} - ? Fixed extends true ? I : void - : I extends { custom: CustomConversion} - ? Fixed extends true ? void : I - : FilterLayoutOfItem - : I extends BytesLayoutItem - ? I["custom"] extends BytesType | FixedConversion - ? Fixed extends true ? I : void - : Fixed extends true ? void : I - : I extends SwitchLayoutItem - ? { readonly [K in keyof I]: - K extends "layouts" ? FilterItemsOfIPLPairs : I[K] - } - : never - : never; - -type FilterItemsOfLayout = - L extends infer LI extends LayoutItem - ? FilterItem - : L extends infer P extends ProperLayout - ? P extends readonly [infer H extends LayoutItem, ...infer T extends ProperLayout] - ? FilterItem extends infer NI - ? NI extends LayoutItem - // @ts-ignore - ? [NI, ...FilterItemsOfLayout] - : FilterItemsOfLayout - : never - : [] - : never; - -type StartFilterItemsOfLayout = - FilterItemsOfLayout extends infer V extends Layout - ? V - : never; - -function filterItem(item: LayoutItem, fixed: boolean): LayoutItem | null { - switch (item.binary) { - // @ts-ignore - fallthrough is intentional - case "bytes": { - if ("layout" in item) { - const { custom } = item; - if (custom === undefined) { - const { layout } = item; - if (isLayoutItem(layout)) - return filterItem(layout, fixed); - - const filteredItems = internalFilterItemsOfProperLayout(layout, fixed); - return (filteredItems.length > 0) ? { ...item, layout: filteredItems } : null; - } - const isFixedItem = typeof custom.from !== "function"; - return (fixed && isFixedItem || !fixed && !isFixedItem) ? item : null; - } - } - case "int": - case "uint": { - const { custom } = item; - const isFixedItem = isPrimitiveType(custom) || isFixedPrimitiveConversion(custom); - return (fixed && isFixedItem || !fixed && !isFixedItem) ? item : null; - } - case "array": { - const filtered = internalFilterItemsOfLayout(item.layout, fixed); - return (filtered !== null) ? { ...item, layout: filtered } : null; - } - case "switch": { - const filteredIdLayoutPairs = (item.layouts as readonly any[]).reduce( - (acc: any, [idOrConversionId, idLayout]: any) => { - const filteredItems = internalFilterItemsOfProperLayout(idLayout, fixed); - return filteredItems.length > 0 - ? [...acc, [idOrConversionId, filteredItems]] - : acc; - }, - [] as any[] - ); - return { ...item, layouts: filteredIdLayoutPairs }; - } - } -} - -function internalFilterItemsOfProperLayout(proper: ProperLayout, fixed: boolean): ProperLayout { - return proper.reduce( - (acc, item) => { - const filtered = filterItem(item, fixed) as ProperLayout[number] | null; - return filtered !== null ? [...acc, filtered] : acc; - }, - [] as ProperLayout - ); -} - -function internalFilterItemsOfLayout(layout: Layout, fixed: boolean): any { - return (Array.isArray(layout) - ? internalFilterItemsOfProperLayout(layout, fixed) - : filterItem(layout as LayoutItem, fixed) - ); -} - -function filterItemsOfLayout( - layout: L, - fixed: Fixed -): FilterItemsOfLayout { - return internalFilterItemsOfLayout(layout, fixed); -} - -export type FixedItemsOfLayout = StartFilterItemsOfLayout; -export type DynamicItemsOfLayout = StartFilterItemsOfLayout; - -export const fixedItemsOfLayout = (layout: L) => - filterItemsOfLayout(layout, true); - -export const dynamicItemsOfLayout = (layout: L) => - filterItemsOfLayout(layout, false); - -function internalAddFixedValuesItem(item: LayoutItem, dynamicValue: any): any { - switch (item.binary) { - // @ts-ignore - fallthrough is intentional - case "bytes": { - if ("layout" in item) { - const { custom } = item; - if (custom === undefined || typeof custom.from !== "function") - return internalAddFixedValues(item.layout, custom ? custom.from : dynamicValue); - - return dynamicValue; - } - } - case "int": - case "uint": { - const { custom } = item; - return (item as {omit?: boolean})?.omit - ? undefined - : isPrimitiveType(custom) - ? custom - : isFixedPrimitiveConversion(custom) - ? custom.to - : dynamicValue; - } - case "array": - return Array.isArray(dynamicValue) - ? dynamicValue.map(element => internalAddFixedValues(item.layout, element)) - : undefined; - case "switch": { - const id = dynamicValue[item.idTag ?? "id"]; - const [_, idLayout] = (item.layouts as readonly IPLPair[]).find(([idOrConversionId]) => - (Array.isArray(idOrConversionId) ? idOrConversionId[1] : idOrConversionId) == id - )!; - return { - [item.idTag ?? "id"]: id, - ...internalAddFixedValues(idLayout, dynamicValue) - }; - } - } -} - -function internalAddFixedValues(layout: Layout, dynamicValues: any): any { - dynamicValues = dynamicValues ?? {}; - if (isLayoutItem(layout)) - return internalAddFixedValuesItem(layout as LayoutItem, dynamicValues); - - const ret = {} as any; - for (const item of layout) { - const fixedVals = internalAddFixedValuesItem( - item, - dynamicValues[item.name as keyof typeof dynamicValues] ?? {} - ); - if (fixedVals !== undefined) - ret[item.name] = fixedVals; - } - return ret; -} - -export function addFixedValues( - layout: L, - dynamicValues: LayoutToType>, -): LayoutToType { - return internalAddFixedValues(layout, dynamicValues) as LayoutToType; -} diff --git a/core/base/src/utils/layout/index.ts b/core/base/src/utils/layout/index.ts deleted file mode 100644 index 7e76269d7..000000000 --- a/core/base/src/utils/layout/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from "./layout.js"; -export * from "./serialize.js"; -export * from "./deserialize.js"; -export * from "./fixedDynamic.js"; -export * from "./discriminate.js"; -export * from "./utils.js"; -export * from "./size.js"; -export * from "./items.js"; diff --git a/core/base/src/utils/layout/items.ts b/core/base/src/utils/layout/items.ts deleted file mode 100644 index 95f201d10..000000000 --- a/core/base/src/utils/layout/items.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { - Endianness, - NumberSize, - NumSizeToPrimitive, - LayoutToType, - CustomConversion -} from './layout.js'; -import { numberMaxSize } from './layout.js'; -import type { CustomizableBytes, CustomizableBytesReturn } from './utils.js'; -import { customizableBytes } from './utils.js'; - -export function enumItem< - const E extends readonly (readonly [string, number])[] ->(entries: E, opts?: {size?: NumberSize, endianness?: Endianness}) { - const valueToName = Object.fromEntries(entries.map(([name, value]) => [value, name])); - const nameToValue = Object.fromEntries(entries); - return { - binary: "uint", - size: opts?.size ?? 1, - endianness: opts?.endianness ?? "big", - custom: { - to: (encoded: number): E[number][0] => { - const name = valueToName[encoded]; - if (name === undefined) - throw new Error(`Invalid enum value: ${encoded}`); - - return name; - }, - from: (name: E[number][0]) => nameToValue[name]!, - } - } as const; -} - -const baseOptionItem = (someType: T) => ({ - binary: "switch", - idSize: 1, - idTag: "isSome", - layouts: [ - [[0, false], []], - [[1, true ], [customizableBytes({ name: "value"}, someType)]], - ] -} as const); - -type BaseOptionItem = - LayoutToType>>; - -type BaseOptionValue = - LayoutToType> | undefined; - -export function optionItem(optVal: T) { - return { - binary: "bytes", - layout: baseOptionItem(optVal), - custom: { - to: (obj: BaseOptionItem): BaseOptionValue => - obj.isSome === true - //TODO I'm really not sure why we need to manually narrow the type here - ? (obj as Exclude)["value"] - : undefined, - from: (value: BaseOptionValue): BaseOptionItem => - value === undefined - ? { isSome: false } - //TODO and this is even more sketch - : ({ isSome: true, value } as unknown as Exclude, { isSome: false }>), - } satisfies CustomConversion, BaseOptionValue> - } as const -}; - -export type Bitset = - {[K in B[number] as K extends "" | undefined ? never : K]: boolean}; - -type ByteSize = [ - never, - 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, -]; - -type BitsizeToBytesize = N extends keyof ByteSize ? ByteSize[N] : number; - -export type BitsetItem< - B extends readonly (string | undefined)[], - S extends number = BitsizeToBytesize, -> = { - binary: "uint"; - size: S; - custom: { - to: (encoded: NumSizeToPrimitive) => Bitset; - from: (obj: Bitset) => NumSizeToPrimitive; - }; -}; - -export function bitsetItem< - const B extends readonly (string | undefined)[], - const S extends number = BitsizeToBytesize, ->(bitnames: B, size?: S): BitsetItem { - return { - binary: "uint", - size: (size ?? Math.ceil(bitnames.length / 8)) as S, - custom: { - to: (encoded: NumSizeToPrimitive): Bitset => { - const ret: Bitset = {} as Bitset; - for (let i = 0; i < bitnames.length; ++i) - if (bitnames[i]) //skip undefined and empty string - //always use bigint for simplicity - ret[bitnames[i] as keyof Bitset] = (BigInt(encoded) & (1n << BigInt(i))) !== 0n; - - return ret; - }, - from: (obj: Bitset): NumSizeToPrimitive => { - let val = 0n; - for (let i = 0; i < bitnames.length; ++i) - if (bitnames[i] && obj[bitnames[i] as keyof Bitset]) - val |= 1n << BigInt(i); - - return (bitnames.length > numberMaxSize ? val : Number(val)) as NumSizeToPrimitive; - }, - }, - } as const -} diff --git a/core/base/src/utils/layout/layout.ts b/core/base/src/utils/layout/layout.ts deleted file mode 100644 index 48783e053..000000000 --- a/core/base/src/utils/layout/layout.ts +++ /dev/null @@ -1,251 +0,0 @@ -export type NumType = number | bigint; -export type BytesType = Uint8Array; -export type PrimitiveType = NumType | BytesType; - -//used wherever an object is expected that sprung from the LayoutToType type defined below -export type LayoutObject = { readonly [key: string]: any }; - -export const binaryLiterals = ["int", "uint", "bytes", "array", "switch"] as const; -export type BinaryLiterals = typeof binaryLiterals[number]; -export type Endianness = "little" | "big"; -export const defaultEndianness = "big"; - -export const numberMaxSize = 6; //Math.log2(Number.MAX_SAFE_INTEGER) / 8 = 6.625; -export type NumberSize = 1 | 2 | 3 | 4 | 5 | 6; - -export type NumSizeToPrimitive = - Size extends NumberSize - ? number - : Size & NumberSize extends never - ? bigint - : number | bigint; - -//TODO introduce an optional size property that specifies the number of required bytes to -// allow for more efficient size calculation before serialization -export type FixedConversion = { - //TODO readonly size?: number, - readonly to: ToType, - readonly from: FromType, -}; - -export type CustomConversion = { - //TODO readonly size?: (val: ToType) => number, - readonly to: (val: FromType) => ToType, - readonly from: (val: ToType) => FromType, -}; - -export interface LayoutItemBase { - readonly binary: BL, -}; - -interface FixedOmittableCustom { - custom: T, - omit?: boolean -}; - -//length size: number of bytes used to encode the preceeding length field which in turn -// holds either the number of bytes (for bytes) or elements (for array) -export interface LengthPrefixed { - readonly lengthSize: NumberSize, - readonly lengthEndianness?: Endianness, //see defaultEndianness - // //restricts the datarange of lengthSize to a maximum value to prevent out of memory - // // attacks/issues - // readonly maxLength?: number, -} - -//size: number of bytes used to encode the item -interface NumLayoutItemBase - extends LayoutItemBase { - size: T extends bigint ? number : NumberSize, - endianness?: Endianness, //see defaultEndianness -}; - -export interface FixedPrimitiveNum< - T extends NumType, - Signed extends Boolean -> extends NumLayoutItemBase, FixedOmittableCustom {}; - -export interface OptionalToFromNum< - T extends NumType, - Signed extends Boolean -> extends NumLayoutItemBase { - custom?: FixedConversion | CustomConversion -}; - -export interface FixedPrimitiveBytes - extends LayoutItemBase<"bytes">, FixedOmittableCustom {}; - -//A word on the somewhat confusing size, lengthSize, and custom properties of BytesLayouts: -// It is a common pattern that layouts define a certain structure, while also wanting to allow -// customization of certain portions of that structure. E.g. a layout might define a known, -// fixed header, followed by a body, which might be the rest of the data, or have some prefixed -// or even known, fixed size. -// A natural way to enable this sort of functionality is to specify a layout that contains a body -// LayoutItem with the given length/size fields, but with an overrideable custom field, where -// the generic version simply leaves custom unspecified resulting in an raw Uint8Array. -// Allowing such overriding can give somewhat confusing results thought: -// For example, if layouts are only ever defined by a single party, it would never make sense to -// specify both a size and a FixedConversion, since the former could be derived from the latter. -// But if multiple parties are involved, one party might want to nail down the size of the data -// itself, or the size and endianess of the length field, while another then specifies what the -// actual content is (and this might even be done recursively, where one protocol builds on -// top of another). -// So to facilitate this usecase, BytesLayouts allow for this sort of redundant specification. -// -// One annoying downside of this approach is that it allows for inconsistent specifications. -// Following C++'s philosophy, we'll simple define the behavior as undefined in such cases. -// We could remedy the problem by providing a consistency check function, but this too is awkward -// because it would then perform what is effectively a verification of the code itself during -// every startup... -// Such are the perils of an interpreted language. -// -//Number of bytes written/read by a BytesLayout: -// If a size is specified manually, it must be consistent with the conversion or layout. -// If a lengthSize is specified, it will encode the size of the data on serialization, and -// must match the size of the conversion / layout on deserialization. -// If neither a size, nor a lengthSize is specified, and the size is not derivable from the custom -// property (i.e. it's undefined, or a CustomConversion, or a Layout whose size can't be -// statically determined), it will consume the rest of the data on deserialization. -export interface FlexPureBytes extends LayoutItemBase<"bytes"> { - readonly custom?: BytesType | FixedConversion | CustomConversion, -}; - -export interface FlexLayoutBytes extends LayoutItemBase<"bytes"> { - readonly custom?: FixedConversion | CustomConversion, - readonly layout: Layout, -} - -export interface ManualSizePureBytes extends FlexPureBytes { - readonly size: number, -}; - -export interface LengthPrefixedPureBytes extends FlexPureBytes, LengthPrefixed {}; - -export interface ManualSizeLayoutBytes extends FlexLayoutBytes { - readonly size: number, -}; - -export interface LengthPrefixedLayoutBytes extends FlexLayoutBytes, LengthPrefixed {}; - -interface ArrayLayoutItemBase extends LayoutItemBase<"array"> { - readonly layout: Layout, -}; - -export interface FixedLengthArray extends ArrayLayoutItemBase { - readonly length: number, -}; - -export interface LengthPrefixedArray extends ArrayLayoutItemBase, LengthPrefixed {}; - -//consumes the rest of the data on deserialization -export interface RemainderArray extends ArrayLayoutItemBase {}; - -type PlainId = number; -type ConversionId = readonly [number, unknown]; -type IdProperLayoutPair< - Id extends PlainId | ConversionId, - P extends ProperLayout = ProperLayout -> = readonly [Id, P]; -type IdProperLayoutPairs = - readonly IdProperLayoutPair[] | - readonly IdProperLayoutPair[]; -type DistributiveAtLeast1 = T extends any ? readonly [T, ...T[]] : never; -export interface SwitchLayoutItem extends LayoutItemBase<"switch"> { - readonly idSize: NumberSize, - readonly idEndianness?: Endianness, //see defaultEndianness - readonly idTag?: string, - readonly layouts: - DistributiveAtLeast1 | IdProperLayoutPair>, -} - -export type NumLayoutItem = - //force distribution over union - Signed extends infer S extends boolean - ? FixedPrimitiveNum | - OptionalToFromNum | - FixedPrimitiveNum | - OptionalToFromNum - : never; - -export type UintLayoutItem = NumLayoutItem; -export type IntLayoutItem = NumLayoutItem; -export type BytesLayoutItem = - FixedPrimitiveBytes | - FlexPureBytes | - ManualSizePureBytes | - LengthPrefixedPureBytes | - FlexLayoutBytes | - ManualSizeLayoutBytes | - LengthPrefixedLayoutBytes; -export type ArrayLayoutItem = FixedLengthArray | LengthPrefixedArray | RemainderArray; -export type LayoutItem = NumLayoutItem | BytesLayoutItem | ArrayLayoutItem | SwitchLayoutItem; -export type NamedLayoutItem = LayoutItem & { readonly name: string }; -export type ProperLayout = readonly NamedLayoutItem[]; -export type Layout = LayoutItem | ProperLayout; - -type NameOrOmitted = T extends {omit: true} ? never : T["name"]; - -export type LayoutToType = - Layout extends L - ? any - : L extends infer LI extends LayoutItem - ? LayoutItemToType
  • - : L extends infer P extends ProperLayout - ? { readonly [I in P[number] as NameOrOmitted]: LayoutItemToType } - : never; - -type MaybeConvert = - Id extends readonly [number, infer Converted] ? Converted : Id; - -type IdLayoutPairsToTypeUnion = - A extends infer V extends IdProperLayoutPairs - ? V extends readonly [infer Head,...infer Tail extends IdProperLayoutPairs] - ? Head extends IdProperLayoutPair - ? MaybeConvert extends infer Id - ? LayoutToType

    extends infer LT extends LayoutObject - ? { readonly [K in IdTag | keyof LT]: K extends keyof LT ? LT[K] : Id } - | IdLayoutPairsToTypeUnion - : never - : never - : never - : never - : never; - -type LayoutItemToType = - Item extends infer I extends LayoutItem - ? I extends NumLayoutItem - //we must infer FromType here to make sure we "hit" the correct type of the conversion - ? I["custom"] extends CustomConversion - ? To - : I["custom"] extends FixedConversion - ? To - : I["custom"] extends undefined - ? NumSizeToPrimitive - : I["custom"] extends NumType - ? I["custom"] - : NumSizeToPrimitive - : I extends BytesLayoutItem - ? I extends { layout: Layout } - ? I["custom"] extends CustomConversion - ? To - : I["custom"] extends FixedConversion - ? To - : LayoutToType - : I["custom"] extends CustomConversion - ? To - : I["custom"] extends FixedConversion - ? To - : BytesType - : I extends ArrayLayoutItem - ? readonly LayoutToType[] - : I extends SwitchLayoutItem - ? IdLayoutPairsToTypeUnion< - I["layouts"], - I["idTag"] extends undefined - ? "id" - : I["idTag"] extends string - ? I["idTag"] - : "id" - > - : never - : never; diff --git a/core/base/src/utils/layout/serialize.ts b/core/base/src/utils/layout/serialize.ts deleted file mode 100644 index 103b278d0..000000000 --- a/core/base/src/utils/layout/serialize.ts +++ /dev/null @@ -1,225 +0,0 @@ -import type { - Endianness, - Layout, - LayoutItem, - LayoutToType, - CustomConversion, - NumType, - BytesType, - FixedConversion, - LayoutObject, - LayoutItemBase -} from './layout.js'; -import { - defaultEndianness, - numberMaxSize, -} from './layout.js'; -import { calcLayoutSize } from './size.js'; -import { - checkItemSize, - checkBytesTypeEqual, - checkNumEquals, - findIdLayoutPair, - isFixedBytesConversion, - isLayoutItem, - isNumType, - isBytesType, -} from './utils.js'; - -type Cursor = { - bytes: BytesType; - offset: number; -}; - -const cursorWrite = (cursor: Cursor, bytes: BytesType) => { - cursor.bytes.set(bytes, cursor.offset); - cursor.offset += bytes.length; -} - -export function serializeLayout< - const L extends Layout, - E extends BytesType | undefined = undefined ->( - layout: L, - data: LayoutToType, - encoded?: E, - offset = 0, -) { - const cursor = {bytes: encoded ?? new Uint8Array(calcLayoutSize(layout, data)), offset}; - internalSerializeLayout(layout, data, cursor); - if (!encoded && cursor.offset !== cursor.bytes.length) - throw new Error( - `encoded data is shorter than expected: ${cursor.bytes.length} > ${cursor.offset}` - ); - - return (encoded ? cursor.offset : cursor.bytes) as E extends undefined ? Uint8Array : number; -} - -//see numberMaxSize comment in layout.ts -const maxAllowedNumberVal = 2 ** (numberMaxSize * 8); - -export function serializeNum( - val: NumType, - size: number, - cursor: Cursor, - endianness: Endianness = defaultEndianness, - signed: boolean = false, -) { - if (!signed && val < 0) - throw new Error(`Value ${val} is negative but unsigned`); - - if (typeof val === "number") { - if (!Number.isInteger(val)) - throw new Error(`Value ${val} is not an integer`); - - if (size > numberMaxSize) { - if (val >= maxAllowedNumberVal) - throw new Error(`Value ${val} is too large to be safely converted into an integer`); - - if (signed && val <= -maxAllowedNumberVal) - throw new Error(`Value ${val} is too small to be safely converted into an integer`); - } - } - - const bound = 2n ** BigInt(size * 8) - if (val >= bound) - throw new Error(`Value ${val} is too large for ${size} bytes`); - - if (signed && val < -bound) - throw new Error(`Value ${val} is too small for ${size} bytes`); - - //correctly handles both signed and unsigned values - for (let i = 0; i < size; ++i) - cursor.bytes[cursor.offset + i] = - Number((BigInt(val) >> BigInt(8 * (endianness === "big" ? size - i - 1 : i)) & 0xffn)); - - cursor.offset += size; -} - -function internalSerializeLayout( - layout: Layout, - data: any, - cursor: Cursor, -) { - if (isLayoutItem(layout)) - serializeLayoutItem(layout as LayoutItem, data, cursor); - else - for (const item of layout) - try { - serializeLayoutItem(item, data[item.name], cursor); - } - catch (e: any) { - e.message = `when serializing item '${item.name}': ${e.message}`; - throw e; - } -} - -function serializeLayoutItem(item: LayoutItem, data: any, cursor: Cursor) { - switch (item.binary) { - case "int": - case "uint": { - const value = (() => { - if (isNumType(item.custom)) { - if (!("omit" in item && item.omit)) - checkNumEquals(item.custom, data); - return item.custom; - } - - if (isNumType(item?.custom?.from)) - //no proper way to deeply check equality of item.custom.to and data in JS - return item!.custom!.from; - - type narrowedCustom = CustomConversion | CustomConversion; - return item.custom !== undefined ? (item.custom as narrowedCustom).from(data) : data; - })(); - - serializeNum(value, item.size, cursor, item.endianness, item.binary === "int"); - break; - } - case "bytes": { - const offset = cursor.offset; - if ("lengthSize" in item && item.lengthSize !== undefined) - cursor.offset += item.lengthSize; - - if ("layout" in item) { - const { custom } = item; - let layoutData; - if (custom === undefined) - layoutData = data; - else if (typeof custom.from !== "function") - layoutData = custom.from; - else - layoutData = custom.from(data); - - internalSerializeLayout(item.layout, layoutData, cursor); - } - else { - const { custom } = item; - if (isBytesType(custom)) { - if (!("omit" in item && item.omit)) - checkBytesTypeEqual(custom, data); - - cursorWrite(cursor, custom); - } - else if (isFixedBytesConversion(custom)) - //no proper way to deeply check equality of custom.to and data - cursorWrite(cursor, custom.from); - else - cursorWrite(cursor, custom !== undefined ? custom.from(data) : data); - } - - if ("lengthSize" in item && item.lengthSize !== undefined) { - const itemSize = cursor.offset - offset - item.lengthSize; - const curOffset = cursor.offset; - cursor.offset = offset; - serializeNum(itemSize, item.lengthSize, cursor, item.lengthEndianness); - cursor.offset = curOffset; - } - else - checkItemSize(item, cursor.offset - offset); - - break; - } - case "array": { - if ("length" in item && item.length !== data.length) - throw new Error( - `array length mismatch: layout length: ${item.length}, data length: ${data.length}` - ); - - if ("lengthSize" in item && item.lengthSize !== undefined) - serializeNum(data.length, item.lengthSize, cursor, item.lengthEndianness); - - for (let i = 0; i < data.length; ++i) - internalSerializeLayout(item.layout, data[i], cursor); - - break; - } - case "switch": { - const [idOrConversionId, layout] = findIdLayoutPair(item, data); - const idNum = (Array.isArray(idOrConversionId) ? idOrConversionId[0] : idOrConversionId); - serializeNum(idNum, item.idSize, cursor, item.idEndianness); - internalSerializeLayout(layout, data, cursor); - break; - } - } -}; - -//slightly hacky, but the only way to ensure that we are actually deserializing the -// right data without having to re-serialize the layout every time -export function getCachedSerializedFrom( - item: LayoutItemBase<"bytes"> & {layout: Layout; custom: FixedConversion} -) { - const custom = - item.custom as FixedConversion & {cachedSerializedFrom?: BytesType}; - if (!("cachedSerializedFrom" in custom)) { - custom.cachedSerializedFrom = serializeLayout(item.layout, custom.from); - if ("size" in item && - item.size !== undefined && - item.size !== custom.cachedSerializedFrom.length - ) - throw new Error( - `Layout specification error: custom.from does not serialize to specified size` - ); - } - return custom.cachedSerializedFrom!; -} diff --git a/core/base/src/utils/layout/size.ts b/core/base/src/utils/layout/size.ts deleted file mode 100644 index 5a499b149..000000000 --- a/core/base/src/utils/layout/size.ts +++ /dev/null @@ -1,165 +0,0 @@ -import type { - Layout, - LayoutItem, - LayoutToType, -} from './layout.js'; -import { - findIdLayoutPair, - isBytesType, - isLayoutItem, - isFixedBytesConversion, - checkItemSize, -} from './utils.js'; - -function calcItemSize(item: LayoutItem, data: any): number | null { - switch (item.binary) { - case "int": - case "uint": - return item.size; - case "bytes": { - //items only have a size or a lengthSize, never both - const lengthSize = ("lengthSize" in item) ? item.lengthSize | 0 : 0; - - if ("layout" in item) { - const { custom } = item; - const layoutSize = internalCalcLayoutSize( - item.layout, - custom === undefined - ? data - : typeof custom.from === "function" - ? custom.from(data) - : custom.from - ); - if (layoutSize === null) - return ("size" in item ) ? item.size ?? null : null; - - return lengthSize + checkItemSize(item, layoutSize); - } - - const { custom } = item; - if (isBytesType(custom)) - return lengthSize + custom.length; //assumed to equal item.size if it exists - - if (isFixedBytesConversion(custom)) - return lengthSize + custom.from.length; //assumed to equal item.size if it exists - - if (custom === undefined) - return data ? lengthSize + checkItemSize(item, data.length) : null; - - return data !== undefined ? lengthSize + checkItemSize(item, custom.from(data).length) : null; - } - case "array": { - const length = "length" in item ? item.length : undefined; - if (data === undefined) { - if (length !== undefined) { - const layoutSize = internalCalcLayoutSize(item.layout); - if (layoutSize === null) - return null; - - return length * layoutSize; - } - return null; - } - - let size = 0; - if (length !== undefined && length !== data.length) - throw new Error( - `array length mismatch: layout length: ${length}, data length: ${data.length}` - ); - else if ("lengthSize" in item && item.lengthSize !== undefined) - size += item.lengthSize; - - for (let i = 0; i < data.length; ++i) { - const entrySize = internalCalcLayoutSize(item.layout, data[i]); - if (entrySize === null) - return null; - - size += entrySize; - } - - return size; - } - case "switch": { - if (data !== undefined) { - const [_, layout] = findIdLayoutPair(item, data); - const layoutSize = internalCalcLayoutSize(layout, data); - return layoutSize !== null ? item.idSize + layoutSize : null; - } - - let size = null; - for (const [_, layout] of item.layouts) { - const layoutSize = internalCalcLayoutSize(layout); - if (size === null) - size = layoutSize; - else if (layoutSize !== size) - return null; - } - return item.idSize + size!; - } - } -} - -function internalCalcLayoutSize(layout: Layout, data?: any): number | null { - if (isLayoutItem(layout)) - return calcItemSize(layout as LayoutItem, data); - - let size = 0; - for (const item of layout) { - let itemData; - if (data) - if (!("omit" in item) || !item.omit) { - if (!(item.name in data)) - throw new Error(`missing data for layout item: ${item.name}`); - - itemData = data[item.name]; - } - - const itemSize = calcItemSize(item, itemData); - if (itemSize === null) { - if (data !== undefined) - throw new Error(`coding error: couldn't calculate size for layout item: ${item.name}`); - - return null; - } - size += itemSize; - } - return size; -} - -//no way to use overloading here: -// export function calcLayoutSize(layout: L): number | null; -// export function calcLayoutSize(layout: L, data: LayoutToType): number; -// export function calcLayoutSize( -// layout: L, -// data?: LayoutToType -// ): number | null; //impl -//results in "instantiation too deep" error. -// -//Trying to pack everything into a single function definition means we either can't narrow the -// return type correctly: -// export function calcLayoutSize( -// layout: L, -// data: LayoutToType, -// ): number | null; -//or we have to make data overly permissive via: -// export function calcLayoutSize< -// L extends Layout, -// const D extends LayoutToType | undefined, -// >( -// layout: L, -// data?: D, //data can now contain additional properties -// ): undefined extends D ? number | null : number; -//so we're stuck with having to use to separate names -export function calcStaticLayoutSize(layout: Layout): number | null { - return internalCalcLayoutSize(layout); -} - -export function calcLayoutSize(layout: L, data: LayoutToType): number { - const size = internalCalcLayoutSize(layout, data); - if (size === null) - throw new Error( - `coding error: couldn't calculate layout size for layout ${layout} with data ${data}` - ); - - return size; -} diff --git a/core/base/src/utils/layout/utils.ts b/core/base/src/utils/layout/utils.ts deleted file mode 100644 index 9c242a074..000000000 --- a/core/base/src/utils/layout/utils.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { - Layout, - LayoutItem, - BytesLayoutItem, - SwitchLayoutItem, - FixedConversion, - CustomConversion, - NumType, - BytesType, - PrimitiveType -} from "./layout.js"; -import { binaryLiterals } from "./layout.js"; - -export const isNumType = (x: any): x is NumType => - typeof x === "number" || typeof x === "bigint"; - -export const isBytesType = (x: any): x is BytesType => x instanceof Uint8Array; - -export const isPrimitiveType = (x: any): x is PrimitiveType => - isNumType(x) || isBytesType(x); - -export const isLayoutItem = (x: any): x is LayoutItem => binaryLiterals.includes(x?.binary); - -export const isLayout = (x: any): x is Layout => - isLayoutItem(x) || Array.isArray(x) && x.every(isLayoutItem); - -const isFixedNumberConversion = (custom: any): custom is FixedConversion => - typeof custom?.from === "number"; - -const isFixedBigintConversion = (custom: any): custom is FixedConversion => - typeof custom?.from === "bigint"; - -export const isFixedUintConversion = (custom: any): custom is - FixedConversion | FixedConversion => - isFixedNumberConversion(custom) || isFixedBigintConversion(custom); - -export const isFixedBytesConversion = (custom: any): custom is FixedConversion => - isBytesType(custom?.from); - -export const isFixedPrimitiveConversion = (custom: any): custom is - FixedConversion | FixedConversion | FixedConversion => - isFixedUintConversion(custom) || isFixedBytesConversion(custom); - -export type CustomizableBytes = - undefined | - Layout | - Uint8Array | - FixedConversion | - CustomConversion | - readonly [Layout, FixedConversion | CustomConversion]; - -type CombineObjects = { - [K in keyof T | keyof U]: K extends keyof T ? T[K] : K extends keyof U ? U[K] : never; -}; - -export type BytesBase = - ( {} | { readonly name: string } ) & Omit; - - -export type CustomizableBytesReturn = - CombineObjects< - B, - P extends undefined - ? { readonly binary: "bytes" } - : P extends Layout - ? { readonly binary: "bytes", readonly layout: P } - : P extends Uint8Array | FixedConversion | CustomConversion - ? { readonly binary: "bytes", readonly custom: P } - : P extends readonly [Layout, FixedConversion | CustomConversion] - ? { readonly binary: "bytes", readonly layout: P[0], readonly custom: P[1] } - : never - >; - -export const customizableBytes = < - const B extends BytesBase, - const C extends CustomizableBytes ->(base: B, spec?: C) => ({ - ...base, - binary: "bytes", - ...(() => { - if (spec === undefined) - return {}; - - if (isLayout(spec)) - return { layout: spec }; - - if (spec instanceof Uint8Array || isFixedBytesConversion(spec) || !Array.isArray(spec)) - return { custom: spec }; - - return { layout: spec[0], custom: spec[1] }; - })() -} as CustomizableBytesReturn); - -export const checkSize = (layoutSize: number, dataSize: number): number => { - if (layoutSize !== dataSize) - throw new Error(`size mismatch: layout size: ${layoutSize}, data size: ${dataSize}`); - - return dataSize; -} - -export const checkItemSize = (item: any, dataSize: number): number => - ("size" in item && item.size !== undefined) ? checkSize(item.size, dataSize) : dataSize; - -export const checkNumEquals = (custom: number | bigint, data: number | bigint): void => { - if (custom != data) - throw new Error(`value mismatch: (constant) layout value: ${custom}, data value: ${data}`); -} - -export const checkBytesTypeEqual = ( - custom: BytesType, - data: BytesType, - opts?: { - customSlice?: number | readonly [number, number]; - dataSlize?: number | readonly [number, number]; - }): void => { - const toSlice = (bytes: BytesType, slice?: number | readonly [number, number]) => - slice === undefined - ? [0, bytes.length] as const - : Array.isArray(slice) - ? slice - : [slice, bytes.length] as const; - - const [customStart, customEnd] = toSlice(custom, opts?.customSlice); - const [dataStart, dataEnd] = toSlice(data, opts?.dataSlize); - const length = customEnd - customStart; - checkSize(length, dataEnd - dataStart); - - for (let i = 0; i < custom.length; ++i) - if (custom[i + customStart] !== data[i + dataStart]) - throw new Error(`binary data mismatch: ` + - `layout value: ${custom}, offset: ${customStart}, data value: ${data}, offset: ${dataStart}` - ); -} - -export function findIdLayoutPair(item: SwitchLayoutItem, data: any) { - const id = data[item.idTag ?? "id"]; - return (item.layouts as readonly any[]).find(([idOrConversionId]) => - (Array.isArray(idOrConversionId) ? idOrConversionId[1] : idOrConversionId) == id - )!; -} diff --git a/core/definitions/src/layout-items/amount.ts b/core/definitions/src/layout-items/amount.ts index 42eeab7b5..d82fb48c7 100644 --- a/core/definitions/src/layout-items/amount.ts +++ b/core/definitions/src/layout-items/amount.ts @@ -1,6 +1,6 @@ -import type { UintLayoutItem } from "@wormhole-foundation/sdk-base"; +import type { Layout } from "@wormhole-foundation/sdk-base"; export const amountItem = { binary: "uint", size: 32, -} as const satisfies UintLayoutItem; +} as const satisfies Layout; diff --git a/core/definitions/src/layout-items/boolean.ts b/core/definitions/src/layout-items/boolean.ts index 8fa5a43c8..c359041a5 100644 --- a/core/definitions/src/layout-items/boolean.ts +++ b/core/definitions/src/layout-items/boolean.ts @@ -1,4 +1,4 @@ -import type { CustomConversion, UintLayoutItem } from "@wormhole-foundation/sdk-base"; +import type { Layout, CustomConversion } from "@wormhole-foundation/sdk-base"; export const boolItem = { binary: "uint", @@ -7,4 +7,4 @@ export const boolItem = { to: (encoded: number): boolean => encoded > 0, from: (val: boolean): number => (val ? 1 : 0), } satisfies CustomConversion, -} as const satisfies UintLayoutItem; +} as const satisfies Layout; diff --git a/core/definitions/src/layout-items/chain.ts b/core/definitions/src/layout-items/chain.ts index e1d6a474d..14044951a 100644 --- a/core/definitions/src/layout-items/chain.ts +++ b/core/definitions/src/layout-items/chain.ts @@ -2,7 +2,7 @@ import type { Chain, CustomConversion, FixedConversion, - UintLayoutItem, + Layout, } from "@wormhole-foundation/sdk-base"; import { chainToChainId, chains, toChain } from "@wormhole-foundation/sdk-base"; @@ -37,7 +37,7 @@ export const chainItem = < }, from: (val: AllowNull): number => (val == null ? 0 : chainToChainId(val)), } satisfies CustomConversion>, - }) as const satisfies UintLayoutItem; + }) as const satisfies Layout; export const fixedChainItem = (chain: C) => ({ @@ -46,4 +46,4 @@ export const fixedChainItem = (chain: C) => to: chain, from: chainToChainId(chain), } satisfies FixedConversion, - }) as const satisfies UintLayoutItem; + }) as const satisfies Layout; diff --git a/core/definitions/src/layout-items/circle.ts b/core/definitions/src/layout-items/circle.ts index 2f94a9084..f213bb50f 100644 --- a/core/definitions/src/layout-items/circle.ts +++ b/core/definitions/src/layout-items/circle.ts @@ -1,9 +1,9 @@ -import type { UintLayoutItem } from "@wormhole-foundation/sdk-base"; +import type { Layout } from "@wormhole-foundation/sdk-base"; export const circleDomainItem = { binary: "uint", size: 4, -} as const satisfies UintLayoutItem; +} as const satisfies Layout; export const circleNonceItem = { binary: "uint", diff --git a/core/definitions/src/layout-items/guardianSet.ts b/core/definitions/src/layout-items/guardianSet.ts index 4f0b7426d..328204be9 100644 --- a/core/definitions/src/layout-items/guardianSet.ts +++ b/core/definitions/src/layout-items/guardianSet.ts @@ -1,6 +1,6 @@ -import type { UintLayoutItem } from "@wormhole-foundation/sdk-base"; +import type { Layout } from "@wormhole-foundation/sdk-base"; export const guardianSetItem = { binary: "uint", size: 4, -} as const satisfies UintLayoutItem; +} as const satisfies Layout; diff --git a/core/definitions/src/layout-items/payloadId.ts b/core/definitions/src/layout-items/payloadId.ts index 24813b517..ecc0812e7 100644 --- a/core/definitions/src/layout-items/payloadId.ts +++ b/core/definitions/src/layout-items/payloadId.ts @@ -1,4 +1,4 @@ -import type { UintLayoutItem } from "@wormhole-foundation/sdk-base"; +import type { Layout } from "@wormhole-foundation/sdk-base"; export const payloadIdItem = (id: ID) => ({ @@ -7,4 +7,4 @@ export const payloadIdItem = (id: ID) => size: 1, custom: id, omit: true, - } as const satisfies UintLayoutItem & { readonly name: string }); + } as const satisfies Layout & { readonly name: string }); diff --git a/core/definitions/src/layout-items/sequence.ts b/core/definitions/src/layout-items/sequence.ts index 96f322fa7..104677ebd 100644 --- a/core/definitions/src/layout-items/sequence.ts +++ b/core/definitions/src/layout-items/sequence.ts @@ -1,6 +1,6 @@ -import type { UintLayoutItem } from "@wormhole-foundation/sdk-base"; +import type { Layout } from "@wormhole-foundation/sdk-base"; export const sequenceItem = { binary: "uint", size: 8, -} as const satisfies UintLayoutItem; +} as const satisfies Layout; diff --git a/core/definitions/src/layout-items/signature.ts b/core/definitions/src/layout-items/signature.ts index f2e7680c5..9366340e3 100644 --- a/core/definitions/src/layout-items/signature.ts +++ b/core/definitions/src/layout-items/signature.ts @@ -1,6 +1,5 @@ import type { Layout, - BytesLayoutItem, LayoutToType, CustomConversion, } from "@wormhole-foundation/sdk-base"; @@ -19,4 +18,4 @@ export const signatureItem = { to: (val: LayoutToType) => new Signature(val.r, val.s, val.v), from: (val: Signature) => ({ r: val.r, s: val.s, v: val.v }), } as const satisfies CustomConversion, Signature>, -} as const satisfies BytesLayoutItem; +} as const satisfies Layout; diff --git a/core/definitions/src/layout-items/string.ts b/core/definitions/src/layout-items/string.ts index 1768585f6..76d04876d 100644 --- a/core/definitions/src/layout-items/string.ts +++ b/core/definitions/src/layout-items/string.ts @@ -1,6 +1,6 @@ import type { + Layout, CustomConversion, - LayoutItem, } from "@wormhole-foundation/sdk-base"; import { encoding } from "@wormhole-foundation/sdk-base"; @@ -21,4 +21,4 @@ export const fixedLengthStringItem = (size: number) => ({ to: (val: Uint8Array): string => encoding.bytes.decode(trimZeros(val)), from: (val: string): Uint8Array => encoding.bytes.zpad(encoding.bytes.encode(val), size), } satisfies CustomConversion, -} as const satisfies LayoutItem); +} as const satisfies Layout); diff --git a/core/definitions/src/layout-items/universalAddress.ts b/core/definitions/src/layout-items/universalAddress.ts index 4499a762a..281082641 100644 --- a/core/definitions/src/layout-items/universalAddress.ts +++ b/core/definitions/src/layout-items/universalAddress.ts @@ -1,5 +1,5 @@ import type { - LayoutItem, + Layout, CustomConversion, } from "@wormhole-foundation/sdk-base"; import { UniversalAddress } from '../universalAddress.js'; @@ -11,4 +11,4 @@ export const universalAddressItem = { to: (val: Uint8Array): UniversalAddress => new UniversalAddress(val), from: (val: UniversalAddress): Uint8Array => val.toUint8Array(), } satisfies CustomConversion, -} as const satisfies LayoutItem; +} as const satisfies Layout; diff --git a/core/definitions/src/protocols/circleBridge/automaticCircleBridgeLayout.ts b/core/definitions/src/protocols/circleBridge/automaticCircleBridgeLayout.ts index ae4510bd1..30b29a2d2 100644 --- a/core/definitions/src/protocols/circleBridge/automaticCircleBridgeLayout.ts +++ b/core/definitions/src/protocols/circleBridge/automaticCircleBridgeLayout.ts @@ -1,5 +1,5 @@ import type { Layout, CustomizableBytes } from "@wormhole-foundation/sdk-base"; -import { layout } from "@wormhole-foundation/sdk-base"; +import { customizableBytes } from "@wormhole-foundation/sdk-base"; import { payloadIdItem, universalAddressItem, @@ -29,7 +29,7 @@ export const depositWithPayloadLayout = ( customPayload?: P, diff --git a/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts b/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts index 887cd6ebd..c138dfc36 100644 --- a/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts +++ b/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts @@ -1,8 +1,7 @@ import type { + Layout, CustomConversion, CustomizableBytes, - Layout, - LayoutItem, } from "@wormhole-foundation/sdk-base"; import { customizableBytes, range } from "@wormhole-foundation/sdk-base"; import { @@ -24,7 +23,7 @@ const fixedLengthStringItem = { .join(""), from: (str: string) => new Uint8Array(str.split("").map((c) => c.charCodeAt(0))), } satisfies CustomConversion, -} as const satisfies LayoutItem; +} as const satisfies Layout; const transferCommonLayout = [ { diff --git a/core/definitions/src/vaa/functions.ts b/core/definitions/src/vaa/functions.ts index 5ee48f7dd..c148971b1 100644 --- a/core/definitions/src/vaa/functions.ts +++ b/core/definitions/src/vaa/functions.ts @@ -165,7 +165,7 @@ export function deserialize( ): DistributiveVAA> { if (typeof data === "string") data = encoding.hex.decode(data); - const [header, envelopeOffset] = deserializeLayout(headerLayout, data, { consumeAll: false }); + const [header, headerSize] = deserializeLayout(headerLayout, data, false); //ensure that guardian signature indicies are unique and in ascending order - see: //https://github.com/wormhole-foundation/wormhole/blob/8e0cf4c31f39b5ba06b0f6cdb6e690d3adf3d6a3/ethereum/contracts/Messages.sol#L121 @@ -173,18 +173,18 @@ export function deserialize( if (header.signatures[i]!.guardianIndex <= header.signatures[i - 1]!.guardianIndex) throw new Error("Guardian signatures must be in ascending order of guardian set index"); - const [envelope, payloadOffset] = deserializeLayout(envelopeLayout, data, { - offset: envelopeOffset, - consumeAll: false, - }); + const envelopeOffset = headerSize; + const [envelope, envelopeSize] = + deserializeLayout(envelopeLayout, data.subarray(envelopeOffset), false); + const payloadOffset = envelopeOffset + envelopeSize; const [payloadLiteral, payload] = typeof payloadDet === "string" ? [ payloadDet as PayloadLiteral, - deserializePayload(payloadDet as PayloadLiteral, data, payloadOffset), + deserializePayload(payloadDet as PayloadLiteral, data.subarray(payloadOffset)), ] - : deserializePayload(payloadDet as PayloadDiscriminator, data, payloadOffset); + : deserializePayload(payloadDet as PayloadDiscriminator, data.subarray(payloadOffset)); const [protocolName, payloadName] = decomposeLiteral(payloadLiteral); const hash = keccak256(data.slice(envelopeOffset)); @@ -231,7 +231,7 @@ export function deserializePayload; } @@ -322,11 +325,8 @@ export const deserializeUnknownVaa = (data: Uint8Array) => { { name: "consistencyLevel", binary: "uint", size: 1 }, ] as const satisfies Layout; - const [header, offset] = deserializeLayout(headerLayout, data, { consumeAll: false }); - const [envelope, offset2] = deserializeLayout(envelopeLayout, data, { - offset: offset, - consumeAll: false, - }); + const [header, offset] = deserializeLayout(headerLayout, data, false); + const [envelope, offset2] = deserializeLayout(envelopeLayout, data.subarray(offset), false); return { ...header, diff --git a/package-lock.json b/package-lock.json index 1a36d212b..df80d41ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ts-sdk", - "version": "0.14.0", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ts-sdk", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "workspaces": [ "core/base", @@ -59,11 +59,11 @@ }, "connect": { "name": "@wormhole-foundation/sdk-connect", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-base": "0.14.0", - "@wormhole-foundation/sdk-definitions": "0.14.0", + "@wormhole-foundation/sdk-base": "1.0.3", + "@wormhole-foundation/sdk-definitions": "1.0.3", "axios": "^1.4.0" }, "engines": { @@ -72,19 +72,20 @@ }, "core/base": { "name": "@wormhole-foundation/sdk-base", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@scure/base": "^1.1.3" + "@scure/base": "^1.1.3", + "binary-layout": "^1.0.3" } }, "core/definitions": { "name": "@wormhole-foundation/sdk-definitions", - "version": "0.14.0", + "version": "1.0.3", "dependencies": { "@noble/curves": "^1.4.0", "@noble/hashes": "^1.3.1", - "@wormhole-foundation/sdk-base": "0.14.0" + "@wormhole-foundation/sdk-base": "1.0.3" } }, "core/definitions/node_modules/@noble/curves": { @@ -109,10 +110,10 @@ }, "core/icons": { "name": "@wormhole-foundation/sdk-icons", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-base": "0.14.0" + "@wormhole-foundation/sdk-base": "1.0.3" }, "devDependencies": { "tsx": "^4.7.0" @@ -120,10 +121,10 @@ }, "examples": { "name": "@wormhole-foundation/connect-sdk-examples", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk": "0.14.0" + "@wormhole-foundation/sdk": "1.0.3" }, "devDependencies": { "dotenv": "^16.3.1", @@ -3390,6 +3391,12 @@ "node": ">=8" } }, + "node_modules/binary-layout": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/binary-layout/-/binary-layout-1.0.3.tgz", + "integrity": "sha512-kpXCSOko4wbQaQswZk4IPcjVZwN77TKZgjMacdoX54EvUHAn/CzJclCt25SUmpXfzFrGovoq3LkPJkMy10bZxQ==", + "license": "Apache-2.0" + }, "node_modules/bindings": { "version": "1.5.0", "license": "MIT", @@ -8972,10 +8979,10 @@ }, "platforms/algorand": { "name": "@wormhole-foundation/sdk-algorand", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", "algosdk": "2.7.0" }, "engines": { @@ -8984,11 +8991,11 @@ }, "platforms/algorand/protocols/core": { "name": "@wormhole-foundation/sdk-algorand-core", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-algorand": "0.14.0", - "@wormhole-foundation/sdk-connect": "0.14.0" + "@wormhole-foundation/sdk-algorand": "1.0.3", + "@wormhole-foundation/sdk-connect": "1.0.3" }, "engines": { "node": ">=16" @@ -8996,12 +9003,12 @@ }, "platforms/algorand/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-algorand-tokenbridge", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-algorand": "0.14.0", - "@wormhole-foundation/sdk-algorand-core": "0.14.0", - "@wormhole-foundation/sdk-connect": "0.14.0" + "@wormhole-foundation/sdk-algorand": "1.0.3", + "@wormhole-foundation/sdk-algorand-core": "1.0.3", + "@wormhole-foundation/sdk-connect": "1.0.3" }, "engines": { "node": ">=16" @@ -9009,10 +9016,10 @@ }, "platforms/aptos": { "name": "@wormhole-foundation/sdk-aptos", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", "aptos": "1.21.0" }, "engines": { @@ -9021,11 +9028,11 @@ }, "platforms/aptos/protocols/core": { "name": "@wormhole-foundation/sdk-aptos-core", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-aptos": "0.14.0", - "@wormhole-foundation/sdk-connect": "0.14.0" + "@wormhole-foundation/sdk-aptos": "1.0.3", + "@wormhole-foundation/sdk-connect": "1.0.3" }, "engines": { "node": ">=16" @@ -9033,11 +9040,11 @@ }, "platforms/aptos/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-aptos-tokenbridge", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-aptos": "0.14.0", - "@wormhole-foundation/sdk-connect": "0.14.0" + "@wormhole-foundation/sdk-aptos": "1.0.3", + "@wormhole-foundation/sdk-connect": "1.0.3" }, "engines": { "node": ">=16" @@ -9045,14 +9052,14 @@ }, "platforms/cosmwasm": { "name": "@wormhole-foundation/sdk-cosmwasm", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.32.0", "@cosmjs/proto-signing": "^0.32.0", "@cosmjs/stargate": "^0.32.0", "@injectivelabs/sdk-ts": "^1.14.13-beta.2", - "@wormhole-foundation/sdk-connect": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", "cosmjs-types": "^0.9.0" }, "engines": { @@ -9061,14 +9068,14 @@ }, "platforms/cosmwasm/protocols/core": { "name": "@wormhole-foundation/sdk-cosmwasm-core", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.32.0", "@cosmjs/stargate": "^0.32.0", "@injectivelabs/sdk-ts": "^1.14.13-beta.2", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm": "1.0.3" }, "engines": { "node": ">=16" @@ -9076,15 +9083,15 @@ }, "platforms/cosmwasm/protocols/ibc": { "name": "@wormhole-foundation/sdk-cosmwasm-ibc", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.32.0", "@cosmjs/stargate": "^0.32.0", "@injectivelabs/sdk-ts": "^1.14.13-beta.2", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm-core": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm-core": "1.0.3", "cosmjs-types": "^0.9.0" }, "engines": { @@ -9093,13 +9100,13 @@ }, "platforms/cosmwasm/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-cosmwasm-tokenbridge", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.32.0", "@injectivelabs/sdk-ts": "^1.14.13-beta.2", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm": "1.0.3" }, "engines": { "node": ">=16" @@ -9107,10 +9114,10 @@ }, "platforms/evm": { "name": "@wormhole-foundation/sdk-evm", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", "ethers": "^6.5.1" }, "devDependencies": { @@ -9122,11 +9129,11 @@ }, "platforms/evm/protocols/cctp": { "name": "@wormhole-foundation/sdk-evm-cctp", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-evm": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-evm": "1.0.3", "ethers": "^6.5.1" }, "engines": { @@ -9135,11 +9142,11 @@ }, "platforms/evm/protocols/core": { "name": "@wormhole-foundation/sdk-evm-core", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-evm": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-evm": "1.0.3", "ethers": "^6.5.1" }, "engines": { @@ -9148,13 +9155,13 @@ }, "platforms/evm/protocols/portico": { "name": "@wormhole-foundation/sdk-evm-portico", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-evm": "0.14.0", - "@wormhole-foundation/sdk-evm-core": "0.14.0", - "@wormhole-foundation/sdk-evm-tokenbridge": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-evm": "1.0.3", + "@wormhole-foundation/sdk-evm-core": "1.0.3", + "@wormhole-foundation/sdk-evm-tokenbridge": "1.0.3", "ethers": "^6.5.1" }, "engines": { @@ -9163,12 +9170,12 @@ }, "platforms/evm/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-evm-tokenbridge", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-evm": "0.14.0", - "@wormhole-foundation/sdk-evm-core": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-evm": "1.0.3", + "@wormhole-foundation/sdk-evm-core": "1.0.3", "ethers": "^6.5.1" }, "engines": { @@ -9177,14 +9184,14 @@ }, "platforms/solana": { "name": "@wormhole-foundation/sdk-solana", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "0.29.0", "@coral-xyz/borsh": "0.29.0", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.2", - "@wormhole-foundation/sdk-connect": "0.14.0", + "@wormhole-foundation/sdk-connect": "1.0.3", "rpc-websockets": "^7.10.0" }, "devDependencies": { @@ -9196,14 +9203,14 @@ }, "platforms/solana/protocols/cctp": { "name": "@wormhole-foundation/sdk-solana-cctp", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "0.29.0", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.2", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-solana": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-solana": "1.0.3" }, "engines": { "node": ">=16" @@ -9211,14 +9218,14 @@ }, "platforms/solana/protocols/core": { "name": "@wormhole-foundation/sdk-solana-core", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "0.29.0", "@coral-xyz/borsh": "0.29.0", "@solana/web3.js": "^1.95.2", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-solana": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-solana": "1.0.3" }, "engines": { "node": ">=16" @@ -9226,15 +9233,15 @@ }, "platforms/solana/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-solana-tokenbridge", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "0.29.0", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.2", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-solana": "0.14.0", - "@wormhole-foundation/sdk-solana-core": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-solana": "1.0.3", + "@wormhole-foundation/sdk-solana-core": "1.0.3" }, "engines": { "node": ">=16" @@ -9242,11 +9249,11 @@ }, "platforms/sui": { "name": "@wormhole-foundation/sdk-sui", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@mysten/sui.js": "^0.50.1", - "@wormhole-foundation/sdk-connect": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3" }, "engines": { "node": ">=16" @@ -9254,12 +9261,12 @@ }, "platforms/sui/protocols/core": { "name": "@wormhole-foundation/sdk-sui-core", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@mysten/sui.js": "^0.50.1", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-sui": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-sui": "1.0.3" }, "engines": { "node": ">=16" @@ -9267,13 +9274,13 @@ }, "platforms/sui/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-sui-tokenbridge", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@mysten/sui.js": "^0.50.1", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-sui": "0.14.0", - "@wormhole-foundation/sdk-sui-core": "0.14.0" + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-sui": "1.0.3", + "@wormhole-foundation/sdk-sui-core": "1.0.3" }, "engines": { "node": ">=16" @@ -9281,34 +9288,34 @@ }, "sdk": { "name": "@wormhole-foundation/sdk", - "version": "0.14.0", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-algorand": "0.14.0", - "@wormhole-foundation/sdk-algorand-core": "0.14.0", - "@wormhole-foundation/sdk-algorand-tokenbridge": "0.14.0", - "@wormhole-foundation/sdk-aptos": "0.14.0", - "@wormhole-foundation/sdk-aptos-core": "0.14.0", - "@wormhole-foundation/sdk-aptos-tokenbridge": "0.14.0", - "@wormhole-foundation/sdk-base": "0.14.0", - "@wormhole-foundation/sdk-connect": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm-core": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm-ibc": "0.14.0", - "@wormhole-foundation/sdk-cosmwasm-tokenbridge": "0.14.0", - "@wormhole-foundation/sdk-definitions": "0.14.0", - "@wormhole-foundation/sdk-evm": "0.14.0", - "@wormhole-foundation/sdk-evm-cctp": "0.14.0", - "@wormhole-foundation/sdk-evm-core": "0.14.0", - "@wormhole-foundation/sdk-evm-portico": "0.14.0", - "@wormhole-foundation/sdk-evm-tokenbridge": "0.14.0", - "@wormhole-foundation/sdk-solana": "0.14.0", - "@wormhole-foundation/sdk-solana-cctp": "0.14.0", - "@wormhole-foundation/sdk-solana-core": "0.14.0", - "@wormhole-foundation/sdk-solana-tokenbridge": "0.14.0", - "@wormhole-foundation/sdk-sui": "0.14.0", - "@wormhole-foundation/sdk-sui-core": "0.14.0", - "@wormhole-foundation/sdk-sui-tokenbridge": "0.14.0" + "@wormhole-foundation/sdk-algorand": "1.0.3", + "@wormhole-foundation/sdk-algorand-core": "1.0.3", + "@wormhole-foundation/sdk-algorand-tokenbridge": "1.0.3", + "@wormhole-foundation/sdk-aptos": "1.0.3", + "@wormhole-foundation/sdk-aptos-core": "1.0.3", + "@wormhole-foundation/sdk-aptos-tokenbridge": "1.0.3", + "@wormhole-foundation/sdk-base": "1.0.3", + "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm-core": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm-ibc": "1.0.3", + "@wormhole-foundation/sdk-cosmwasm-tokenbridge": "1.0.3", + "@wormhole-foundation/sdk-definitions": "1.0.3", + "@wormhole-foundation/sdk-evm": "1.0.3", + "@wormhole-foundation/sdk-evm-cctp": "1.0.3", + "@wormhole-foundation/sdk-evm-core": "1.0.3", + "@wormhole-foundation/sdk-evm-portico": "1.0.3", + "@wormhole-foundation/sdk-evm-tokenbridge": "1.0.3", + "@wormhole-foundation/sdk-solana": "1.0.3", + "@wormhole-foundation/sdk-solana-cctp": "1.0.3", + "@wormhole-foundation/sdk-solana-core": "1.0.3", + "@wormhole-foundation/sdk-solana-tokenbridge": "1.0.3", + "@wormhole-foundation/sdk-sui": "1.0.3", + "@wormhole-foundation/sdk-sui-core": "1.0.3", + "@wormhole-foundation/sdk-sui-tokenbridge": "1.0.3" }, "engines": { "node": ">=16" diff --git a/platforms/aptos/protocols/tokenBridge/src/foreignAddress.ts b/platforms/aptos/protocols/tokenBridge/src/foreignAddress.ts index d1fde0019..fac168d6b 100644 --- a/platforms/aptos/protocols/tokenBridge/src/foreignAddress.ts +++ b/platforms/aptos/protocols/tokenBridge/src/foreignAddress.ts @@ -1,4 +1,6 @@ -import { encoding, layout, layoutItems } from "@wormhole-foundation/sdk-connect"; +import type { Layout, LayoutToType } from "@wormhole-foundation/sdk-base"; +import { encoding, serializeLayout } from "@wormhole-foundation/sdk-base"; +import { layoutItems } from "@wormhole-foundation/sdk-definitions"; import { APTOS_SEPARATOR } from "@wormhole-foundation/sdk-aptos"; const foreignAddressSeedLayout = [ @@ -13,8 +15,8 @@ const foreignAddressSeedLayout = [ { name: "tokenId", ...layoutItems.universalAddressItem }, // from https://github.com/aptos-labs/aptos-core/blob/25696fd266498d81d346fe86e01c330705a71465/aptos-move/framework/aptos-framework/sources/account.move#L90-L95 { name: "domainSeparator", binary: "bytes", custom: new Uint8Array([0xff]), omit: true }, -] as const satisfies layout.Layout; +] as const satisfies Layout; export const serializeForeignAddressSeeds = ( - data: layout.LayoutToType, -): Uint8Array => layout.serializeLayout(foreignAddressSeedLayout, data); + data: LayoutToType, +): Uint8Array => serializeLayout(foreignAddressSeedLayout, data);