Skip to content

Commit

Permalink
chore: Use property based testing; Adjust the types; Add serializatio…
Browse files Browse the repository at this point in the history
…n and all schemas
  • Loading branch information
janjakubnanista committed Nov 23, 2023
1 parent 9f0477c commit de833f4
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 102 deletions.
1 change: 1 addition & 0 deletions packages/ua-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@layerzerolabs/lz-definitions": "~1.5.58",
"@types/jest": "^29.5.10",
"chai": "^4.3.10",
"fast-check": "^3.14.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
Expand Down
45 changes: 41 additions & 4 deletions packages/ua-utils/src/coordinates.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import { OmnichainGraphCoordinate } from "./types"
import { OmnichainEdgeCoordinates, OmnichainNodeCoordinate } from "./types"

export const areSameCoordinates = (a: OmnichainGraphCoordinate, b: OmnichainGraphCoordinate): boolean =>
a.address === b.address && a.eid === b.eid
/**
* Compares two coordinates by value
*
* @param a `OmnichainNodeCoordinate`
* @param b `OmnichainNodeCoordinate`
*
* @returns `true` if the coordinates point to the same point in omniverse
*/
export const isCoordinateEqual = (a: OmnichainNodeCoordinate, b: OmnichainNodeCoordinate): boolean => a.address === b.address && a.eid === b.eid

export const serializeCoordinate = ({ address, eid }: OmnichainGraphCoordinate): string => `${eid}|${address}`
/**
* Compares two coordinate vectors
*
* @param a `OmnichainEdgeCoordinates`
* @param b `OmnichainEdgeCoordinates`
*
* @returns `true` if the coordinates point from and to the same point in omniverse
*/
export const areCoordinatesEqual = (a: OmnichainEdgeCoordinates, b: OmnichainEdgeCoordinates): boolean =>
isCoordinateEqual(a.from, b.from) && isCoordinateEqual(a.to, b.to)

/**
* Serializes a coordinate. Useful for when coordinates need to be used in Map
* where we cannot adjust the default behavior of using a reference equality
*
* @param coordinate `OmnichainNodeCoordinate`
*
* @returns `string`
*/
export const serializeCoordinate = ({ address, eid }: OmnichainNodeCoordinate): string => `${eid}|${address}`

/**
* Serializes coordinate vector. Useful for when coordinates need to be used in Map
* where we cannot adjust the default behavior of using a reference equality
*
* @param coordinate `OmnichainEdgeCoordinates`
*
* @returns `string`
*/
export const serializeCoordinates = ({ from, to }: OmnichainEdgeCoordinates): string =>
`${serializeCoordinate(from)}${serializeCoordinate(to)}`
41 changes: 34 additions & 7 deletions packages/ua-utils/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,47 @@
import { EndpointId } from "@layerzerolabs/lz-definitions"
import { z } from "zod"
import type { OmnichainGraphCoordinate, OmnichainGraphNode } from "./types"
import type { OmnichainNodeCoordinate, OmnichainNode, OmnichainEdgeCoordinates, OmnichainEdge } from "./types"

export const AddressSchema = z.string()

export const EndpointIdSchema = z.nativeEnum(EndpointId).pipe(z.number())
export const EndpointIdSchema: z.ZodSchema<EndpointId, z.ZodTypeDef, unknown> = z.nativeEnum(EndpointId).pipe(z.number())

export const OmnichainGraphCoordinateSchema: z.ZodSchema<OmnichainGraphCoordinate, z.ZodTypeDef, unknown> = z.object({
export const OmnichainNodeCoordinateSchema: z.ZodSchema<OmnichainNodeCoordinate, z.ZodTypeDef, unknown> = z.object({
address: AddressSchema,
eid: EndpointIdSchema,
})

export const createOmnichainGraphNodeSchema = <TConfig = unknown>(
export const OmnichainEdgeCoordinatesSchema: z.ZodSchema<OmnichainEdgeCoordinates, z.ZodTypeDef, unknown> = z.object({
from: OmnichainNodeCoordinateSchema,
to: OmnichainNodeCoordinateSchema,
})

/**
* Factory for OmnichainNode schemas
*
* @param configSchema Schema of the config contained in the node
*
* @returns `z.ZodSchema<OmnichainNode<TConfig>>` schema for a node with the particular config type
*/
export const createOmnichainNodeSchema = <TConfig = unknown>(
configSchema: z.ZodSchema<TConfig, z.ZodTypeDef, unknown>
): z.ZodSchema<OmnichainNode<TConfig>, z.ZodTypeDef, unknown> =>
z.object({
coordinate: OmnichainNodeCoordinateSchema,
config: configSchema,
}) as z.ZodSchema<OmnichainNode<TConfig>, z.ZodTypeDef, unknown>

/**
* Factory for OmnichainEdge schemas
*
* @param configSchema `z.ZodSchema<TConfig>` Schema of the config contained in the edge
*
* @returns `z.ZodSchema<OmnichainEdge<TConfig>>` schema for an edge with the particular config type
*/
export const createOmnichainEdgeSchema = <TConfig = unknown>(
configSchema: z.ZodSchema<TConfig, z.ZodTypeDef, unknown>
): z.ZodSchema<OmnichainGraphNode<TConfig>, z.ZodTypeDef, unknown> =>
): z.ZodSchema<OmnichainEdge<TConfig>, z.ZodTypeDef, unknown> =>
z.object({
coordinate: OmnichainGraphCoordinateSchema,
coordinates: OmnichainEdgeCoordinatesSchema,
config: configSchema,
}) as z.ZodSchema<OmnichainGraphNode<TConfig>, z.ZodTypeDef, unknown>
}) as z.ZodSchema<OmnichainEdge<TConfig>, z.ZodTypeDef, unknown>
36 changes: 20 additions & 16 deletions packages/ua-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,40 @@ import type { EndpointId } from "@layerzerolabs/lz-definitions"
export type Address = string

/**
* OmnichainGraphCoordinate identifies a point in omniverse, an omnichain universe.
* OmnichainNodeCoordinate identifies a point in omniverse, an omnichain universe.
*
* In layman terms this is a contract deployed on a particular network (represented by an endpoint).
*/
export interface OmnichainGraphCoordinate {
export interface OmnichainNodeCoordinate {
eid: EndpointId
address: Address
}

/**
* OmnichainGraphNode represents a point in omniverse
* OmnichainEdgeCoordinates identify a line in omniverse, an omnichain universe.
*
* In layman terms this is a directional connection between two contracts
*/
export interface OmnichainEdgeCoordinates {
from: OmnichainNodeCoordinate
to: OmnichainNodeCoordinate
}

/**
* OmnichainNode represents a point in omniverse
* with an additional piece of information attached
*/
export interface OmnichainGraphNode<TConfig = unknown> {
coordinate: OmnichainGraphCoordinate
export interface OmnichainNode<TConfig = unknown> {
coordinate: OmnichainNodeCoordinate
config: TConfig
}

/**
* OmnichainGraphEdge represents a connection between two points in omniverse
* OmnichainEdge represents a connection between two points in omniverse
* with an additional piece of information attached
*
* TODO There is a certain asymetry now between OmnichainGraphNode and OmnichainGraphEdge -
* OmnichainGraphNode contains a coordinate and config whereas OmnichainGraphEdge has no wrapper
* for the coordinate part - the coordinate (which in this case is a tuple [from, to]) is spread directly
* on its type. This asymetry looks fine - the sacrifice of verbosity is made in the name of smaller boilerplate
*/
export interface OmnichainGraphEdge<TConfig = unknown> {
from: OmnichainGraphCoordinate
to: OmnichainGraphCoordinate
export interface OmnichainEdge<TConfig = unknown> {
coordinates: OmnichainEdgeCoordinates
config: TConfig
}

Expand All @@ -44,6 +48,6 @@ export interface OmnichainGraphEdge<TConfig = unknown> {
* the names are set to be `contracts` rather than `nodes` and `connections` rather than `edges`
*/
export interface OmnichainGraph<TNodeConfig = unknown, TEdgeConfig = unknown> {
contracts: OmnichainGraphNode<TNodeConfig>[]
connections: OmnichainGraphEdge<TEdgeConfig>[]
contracts: OmnichainNode<TNodeConfig>[]
connections: OmnichainEdge<TEdgeConfig>[]
}
18 changes: 18 additions & 0 deletions packages/ua-utils/test/__utils__/arbitraries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import fc from "fast-check"
import { EndpointId } from "@layerzerolabs/lz-definitions"
import { ENDPOINT_IDS } from "./constants"
import { OmnichainNodeCoordinate, OmnichainEdgeCoordinates } from "@/types"

export const addressArbitrary = fc.string()

export const endpointArbitrary: fc.Arbitrary<EndpointId> = fc.constantFrom(...ENDPOINT_IDS)

export const coordinateArbitrary: fc.Arbitrary<OmnichainNodeCoordinate> = fc.record({
eid: endpointArbitrary,
address: addressArbitrary,
})

export const coordinatesArbitrary: fc.Arbitrary<OmnichainEdgeCoordinates> = fc.record({
from: coordinateArbitrary,
to: coordinateArbitrary,
})
4 changes: 4 additions & 0 deletions packages/ua-utils/test/__utils__/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { EndpointId } from "@layerzerolabs/lz-definitions"
import { EndpointIdSchema } from "../../src/schema"

export const ENDPOINT_IDS = Object.values(EndpointId).filter((value): value is EndpointId => EndpointIdSchema.safeParse(value).success)
156 changes: 118 additions & 38 deletions packages/ua-utils/test/coordinates.test.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,125 @@
import { areSameCoordinates } from "@/coordinates"
import { OmnichainGraphCoordinate } from "@/types"
import { EndpointId } from "@layerzerolabs/lz-definitions"
import fc from "fast-check"
import { areCoordinatesEqual, isCoordinateEqual, serializeCoordinate, serializeCoordinates } from "@/coordinates"
import { coordinateArbitrary, addressArbitrary, endpointArbitrary, coordinatesArbitrary } from "./__utils__/arbitraries"

describe("coordinates", () => {
describe("areSameCoordinates", () => {
type TestCase = [OmnichainGraphCoordinate, OmnichainGraphCoordinate]

const GOOD: TestCase[] = [
[
{ eid: EndpointId.BSC_TESTNET, address: "0x0" },
{ eid: EndpointId.BSC_TESTNET, address: "0x0" },
],
[
{ eid: EndpointId.SOLANA_MAINNET, address: "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" },
{ eid: EndpointId.SOLANA_MAINNET, address: "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" },
],
]

it.each(GOOD)(`should be true for %j and %j`, (a, b) => {
expect(areSameCoordinates(a, b)).toBeTruthy()
expect(areSameCoordinates(a, a)).toBeTruthy()
expect(areSameCoordinates(b, b)).toBeTruthy()
describe("assertions", () => {
describe("isCoordinateEqual", () => {
it("should be true for referentially equal coordinates", () => {
fc.assert(
fc.property(coordinateArbitrary, (coordinate) => {
expect(isCoordinateEqual(coordinate, coordinate)).toBeTruthy()
})
)
})

it("should be true for value equal coordinates", () => {
fc.assert(
fc.property(coordinateArbitrary, (coordinate) => {
expect(isCoordinateEqual(coordinate, { ...coordinate })).toBeTruthy()
})
)
})

it("should be false when addresses don't match", () => {
fc.assert(
fc.property(coordinateArbitrary, addressArbitrary, (coordinate, address) => {
fc.pre(coordinate.address !== address)

expect(isCoordinateEqual(coordinate, { ...coordinate, address })).toBeFalsy()
})
)
})

it("should be false when endpoint IDs don't match", () => {
fc.assert(
fc.property(coordinateArbitrary, endpointArbitrary, (coordinate, eid) => {
fc.pre(coordinate.eid !== eid)

expect(isCoordinateEqual(coordinate, { ...coordinate, eid })).toBeFalsy()
})
)
})
})

const BAD: TestCase[] = [
[
{ eid: EndpointId.ETHEREUM_MAINNET, address: "0x0" },
{ eid: EndpointId.BSC_TESTNET, address: "0x0" },
],
[
{ eid: EndpointId.ETHEREUM_MAINNET, address: "0x0" },
{ eid: EndpointId.ETHEREUM_MAINNET, address: "0x1" },
],
[
{ eid: EndpointId.ETHEREUM_MAINNET, address: "0xa" },
{ eid: EndpointId.ETHEREUM_MAINNET, address: "0xA" },
],
]

it.each(BAD)(`should be false for %j and %j`, (a, b) => {
expect(areSameCoordinates(a, b)).toBeFalsy()
describe("areCoordinatesEqual", () => {
it("should be true for referentially equal coordinates", () => {
fc.assert(
fc.property(coordinatesArbitrary, (coordinates) => {
expect(areCoordinatesEqual(coordinates, coordinates)).toBeTruthy()
})
)
})

it("should be true for value equal coordinates", () => {
fc.assert(
fc.property(coordinatesArbitrary, (coordinates) => {
expect(areCoordinatesEqual(coordinates, { ...coordinates })).toBeTruthy()
})
)
})

it("should be false when from coordinate doesn't match", () => {
fc.assert(
fc.property(coordinatesArbitrary, coordinateArbitrary, (coordinates, from) => {
fc.pre(!isCoordinateEqual(coordinates.from, from))

expect(areCoordinatesEqual(coordinates, { ...coordinates, from })).toBeFalsy()
})
)
})

it("should be false when to coordinate doesn't match", () => {
fc.assert(
fc.property(coordinatesArbitrary, coordinateArbitrary, (coordinates, to) => {
fc.pre(!isCoordinateEqual(coordinates.from, to))

expect(areCoordinatesEqual(coordinates, { ...coordinates, to })).toBeFalsy()
})
)
})
})
})

describe("serialization", () => {
describe("serializeCoordinate", () => {
it("should produce identical serialized values if the coordinates match", () => {
fc.assert(
fc.property(coordinateArbitrary, (coordinate) => {
expect(serializeCoordinate(coordinate)).toBe(serializeCoordinate({ ...coordinate }))
})
)
})

it("should produce different serialized values if the coordinates don't match", () => {
fc.assert(
fc.property(coordinateArbitrary, coordinateArbitrary, (coordinateA, coordinateB) => {
fc.pre(!isCoordinateEqual(coordinateA, coordinateB))

expect(serializeCoordinate(coordinateA)).not.toBe(serializeCoordinate(coordinateB))
})
)
})
})

describe("serializeCoordinates", () => {
it("should produce identical serialized values if the coordinates match", () => {
fc.assert(
fc.property(coordinatesArbitrary, (coordinates) => {
expect(serializeCoordinates(coordinates)).toBe(serializeCoordinates({ ...coordinates }))
})
)
})

it("should produce different serialized values if the coordinates don't match", () => {
fc.assert(
fc.property(coordinatesArbitrary, coordinatesArbitrary, (coordinatesA, coordinatesB) => {
fc.pre(!areCoordinatesEqual(coordinatesA, coordinatesB))

expect(serializeCoordinates(coordinatesA)).not.toBe(serializeCoordinates(coordinatesB))
})
)
})
})
})
})
Loading

0 comments on commit de833f4

Please sign in to comment.