From 69a6e0772bd423bc0f53c31eb0e7728d83f17e15 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 21 Nov 2023 11:09:13 -0800 Subject: [PATCH 1/5] feat: New version of ua-utils, aligned with below-zero's model of configurables --- packages/ua-utils/.eslintignore | 2 + packages/ua-utils/.mocharc.json | 5 + packages/ua-utils/.prettierignore | 2 + packages/ua-utils/README.md | 129 ++++++++++++++++++++++++ packages/ua-utils/package.json | 41 ++++++++ packages/ua-utils/src/index.ts | 1 + packages/ua-utils/src/property.ts | 110 ++++++++++++++++++++ packages/ua-utils/test/property.test.ts | 96 ++++++++++++++++++ packages/ua-utils/tsconfig.build.json | 4 + packages/ua-utils/tsconfig.json | 9 ++ packages/ua-utils/tsup.config.ts | 12 +++ yarn.lock | 14 ++- 12 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 packages/ua-utils/.eslintignore create mode 100644 packages/ua-utils/.mocharc.json create mode 100644 packages/ua-utils/.prettierignore create mode 100644 packages/ua-utils/README.md create mode 100644 packages/ua-utils/package.json create mode 100644 packages/ua-utils/src/index.ts create mode 100644 packages/ua-utils/src/property.ts create mode 100644 packages/ua-utils/test/property.test.ts create mode 100644 packages/ua-utils/tsconfig.build.json create mode 100644 packages/ua-utils/tsconfig.json create mode 100644 packages/ua-utils/tsup.config.ts diff --git a/packages/ua-utils/.eslintignore b/packages/ua-utils/.eslintignore new file mode 100644 index 000000000..db4c6d9b6 --- /dev/null +++ b/packages/ua-utils/.eslintignore @@ -0,0 +1,2 @@ +dist +node_modules \ No newline at end of file diff --git a/packages/ua-utils/.mocharc.json b/packages/ua-utils/.mocharc.json new file mode 100644 index 000000000..d4ee538e6 --- /dev/null +++ b/packages/ua-utils/.mocharc.json @@ -0,0 +1,5 @@ +{ + "extensions": ["ts"], + "spec": ["**/*.test.*"], + "loader": "ts-node/esm" +} diff --git a/packages/ua-utils/.prettierignore b/packages/ua-utils/.prettierignore new file mode 100644 index 000000000..763301fc0 --- /dev/null +++ b/packages/ua-utils/.prettierignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/packages/ua-utils/README.md b/packages/ua-utils/README.md new file mode 100644 index 000000000..72f65b3b9 --- /dev/null +++ b/packages/ua-utils/README.md @@ -0,0 +1,129 @@ +

+ + LayerZero + +

+ +

@layerzerolabs/ua-utils

+ + +

+ + NPM Version + + Downloads + + NPM License +

+ +## Installation + +```bash +yarn add @layerzerolabs/ua-utils + +pnpm add @layerzerolabs/ua-utils + +npm install @layerzerolabs/ua-utils +``` + +## Usage + +### `createProperty` + +Creates a `Property` object - an abstract wrapper around a getter and setter of a (typically contract) property. + +`Property` objects can be evaluated - this evaluation results in a `PropertyState` object which comes in two varieties: + +- `Configured` object represents a property whose desired value matches the current, actual value +- `Misconfigured` object represents a property whose desired value does not match the current value. It can be further executed using its `configure` method to set the desired property value + +```typescript +import type { Contract } from "@etherspropject/contracts" +import { createProperty, isMisconfigured } from "@layerzerolabs/ua-utils" + +// In this example we'll creating a property that is executed within a context of a contract +// +// What this means is that the property requires a contract to be evaluated. This context +// is then passed to the getter, setter and the desired value getter +const myContractProperty = createProperty({ + get: (contract: Contract) => contract.getFavouriteAddress(), + set: (contract: Contract, favouriteAddress: string) => contract.setFavouriteAddress(favouriteAddress), + desired: (contract: Contract) => "0x00000000219ab540356cbb839cbe05303d7705fa", +}) + +// Let's pretend we have a contract at hand +declare const myContract: Contract + +// We'll evaluate this property by passing myContract in. This contract will then be passed +// to the getter, setter and desired value getter +// +// The result of this evaluation is a PropertyState object +const state = await myContractProperty(myContract) + +// This package comes with two type narrowing utilities for working with PropertyState objects: +// +// - isConfigured +// - isMisconfigured +// +// Using these we can discern between the two varieties of PropertyState +if (isMisconfigured(state)) { + await state.configure() +} +``` + +In the example above we used a simple `Contract` object as our context. We can use arbitrary context as the following example shows: + +```typescript +import { createProperty, isMisconfigured } from "@layerzerolabs/ua-utils" + +const myContractPropertyWithParams = createProperty({ + get: (contract: Contract, when: number, where: string) => contract.getFavouriteAddress(when, where), + set: (contract: Contract, when: number, where: string, favouriteAddress: string) => + contract.setFavouriteAddress(when, where, favouriteAddress), + desired: (contract: Contract, when: number, where: string) => "0x00000000219ab540356cbb839cbe05303d7705fa", +}) + +// We'll again pretend we have a contract at hand +declare const myContract: Contract +const when = Date.now() +const where = "Antractica" + +// We'll evaluate this property by passing the required context in - in this case +// the context consists of a contract, a numeric value and a string value +// +// The result of this evaluation is a PropertyState object +const state = await myContractPropertyWithParams(myContract, when, where) + +// The execution goes just like before +if (isMisconfigured(state)) { + await state.configure() +} +``` + +### `isConfigured` + +Helper type assertion utility that narrows down the `PropertyState` type to `Configured`: + +```typescript +import { PropertyState, isConfigured } from "@layerzerolabs/ua-utils" + +declare const state: PropertyState + +if (isConfigured(state)) { + // state is now Configured, no action is needed as the property is in its desired state +} +``` + +### `isMisconfigured` + +Helper type assertion utility that narrows down the `PropertyState` type to `Misconfigured`: + +```typescript +import { PropertyState, isMisconfigured } from "@layerzerolabs/ua-utils" + +declare const state: PropertyState + +if (isMisconfigured(state)) { + // state is now Misconfigured, we can e.g. call .configure +} +``` diff --git a/packages/ua-utils/package.json b/packages/ua-utils/package.json new file mode 100644 index 000000000..859afb4c6 --- /dev/null +++ b/packages/ua-utils/package.json @@ -0,0 +1,41 @@ +{ + "name": "@layerzerolabs/ua-utils", + "description": "Utilities for working with LayerZero projects", + "version": "0.1.0", + "license": "MIT", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "module": "./dist/index.mjs", + "exports": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + }, + "files": [ + "./dist/index.*" + ], + "scripts": { + "build": "npx tsup", + "clean": "rm -rf dist", + "dev": "npx tsup --watch", + "lint": "npx eslint '**/*.{js,ts,json}'", + "prebuild": "npx tsc --noEmit -p tsconfig.build.json", + "test": "mocha --parallel" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/LayerZero-Labs/lz-utils.git", + "directory": "packages/ua-utils" + }, + "devDependencies": { + "@types/mocha": "^10.0.1", + "@types/sinon": "^17.0.2", + "chai": "^4.3.10", + "mocha": "^10.2.0", + "sinon": "^17.0.1", + "ts-node": "^10.9.1", + "tsup": "~7.2.0", + "typescript": "^5.2.2" + } +} diff --git a/packages/ua-utils/src/index.ts b/packages/ua-utils/src/index.ts new file mode 100644 index 000000000..611bab50c --- /dev/null +++ b/packages/ua-utils/src/index.ts @@ -0,0 +1 @@ +export * from "./property" diff --git a/packages/ua-utils/src/property.ts b/packages/ua-utils/src/property.ts new file mode 100644 index 000000000..c51b94c26 --- /dev/null +++ b/packages/ua-utils/src/property.ts @@ -0,0 +1,110 @@ +import assert from "assert" + +/** + * The central concept of this module - a structure that can be evaluated into a `PropertyState` + * + * `Property` has three basic functions: + * + * - Property getter that gets the current value of the property + * - Property setter that sets value of the property + * - Property desired value getter that grabs the value this property should be set to + */ +export type Property = ( + ...context: TContext +) => Promise> + +/** + * Type encapsulating two states of a configurable property: `Configured` and `Misconfigured` + * + * Property property is understood as anything that has a getter and setter + * and its value needs to match a desired value (coming from some sort of a configuration). + */ +export type PropertyState = Configured | Misconfigured + +/** + * Interface for configured state of a configurable property. + * + * In configured state, the current value of the property matches its desired state + * and no action is necessary. + */ +export interface Configured { + value: TValue + desiredValue?: never + configure?: never +} + +/** + * Interface for misconfigured state of a configurable property. + * + * In misconfigured state, the current value of the property does not match its desired state + * and an action needs to be taken to synchronize these two. + */ +export interface Misconfigured { + value: TValue + desiredValue: TValue + configure: () => TResult | Promise +} + +export type GetPropertyValue = (...context: TContext) => TValue | Promise + +export type SetPropertyValue = ( + ...params: [...TContext, TValue] +) => TResult | Promise + +export interface PropertyOptions { + desired: GetPropertyValue + get: GetPropertyValue + set: SetPropertyValue +} + +/** + * Property factory, the central functional piece of this module. + * + * @param `PropertyOptions` + * + * @returns `Property` + */ +export const createProperty = + ({ + get, + set, + desired, + }: PropertyOptions): Property => + async (...context) => { + // First we grab the current & desired states of the property + const [value, desiredValue] = await Promise.all([get(...context), desired(...context)]) + + // Now we compare the current & desired states using value equality (i.e. values + // with the same shape will be considered equal) + // + // We'll use the native deep equality function that throws an AssertionError + // when things don't match so we need to try/catch and understand the catch branch + // as inequality + try { + assert.deepStrictEqual(value, desiredValue) + + // The values matched, we return a Configured + return { value } + } catch { + // The values did not match, we'll return a Misconfigured + return { value, desiredValue, configure: async () => set(...context, desiredValue) } + } + } + +/** + * Type assertion utility for narrowing the `PropertyState` type to `Misconfigured` type + * + * @param value `PropertyState` + * @returns `value is Misconfigured` + */ +export const isMisconfigured = ( + value: PropertyState +): value is Misconfigured => "configure" in value && "desiredValue" in value && typeof value.configure === "function" + +/** + * Type assertion utility for narrowing the `PropertyState` type to `Configured` type + * + * @param value `PropertyState` + * @returns `value is Configured` + */ +export const isConfigured = (value: PropertyState): value is Configured => !isMisconfigured(value) diff --git a/packages/ua-utils/test/property.test.ts b/packages/ua-utils/test/property.test.ts new file mode 100644 index 000000000..da4551ba3 --- /dev/null +++ b/packages/ua-utils/test/property.test.ts @@ -0,0 +1,96 @@ +import { expect } from "chai" +import { describe } from "mocha" +import sinon from "sinon" +import { createProperty, isConfigured, isMisconfigured } from "../src/property" + +describe("property", () => { + describe("isMisconfigured", () => { + it("should return true if value is Misconfigured", () => { + expect(isMisconfigured({ value: false, desiredValue: true, configure: () => {} })).to.be.true + expect(isMisconfigured({ value: null, desiredValue: null, configure: () => {} })).to.be.true + expect(isMisconfigured({ value: 0, desiredValue: 0, configure: () => {} })).to.be.true + }) + + it("should return false if value is Configured", () => { + expect(isMisconfigured({ value: false })).to.be.false + expect(isMisconfigured({ value: true })).to.be.false + expect(isMisconfigured({ value: 1 })).to.be.false + }) + }) + + describe("isConfigured", () => { + it("should return false if value is Configured", () => { + expect(isConfigured({ value: false, desiredValue: true, configure: () => {} })).to.be.false + expect(isConfigured({ value: null, desiredValue: null, configure: () => {} })).to.be.false + expect(isConfigured({ value: 0, desiredValue: 0, configure: () => {} })).to.be.false + }) + + it("should return true if value is Misconfigured", () => { + expect(isConfigured({ value: false })).to.be.true + expect(isConfigured({ value: true })).to.be.true + expect(isConfigured({ value: 1 })).to.be.true + }) + }) + + describe("createProperty", () => { + it("should return Configured if the current and desired values match", async () => { + const currentValue = [1, "two", { three: true }] + const desiredValue = [1, "two", { three: true }] + + const property = createProperty({ + desired: async () => desiredValue, + get: () => currentValue, + set: () => {}, + }) + + expect(isMisconfigured(await property())).to.be.false + }) + + it("should return Misconfigured if the current and desired don't match", async () => { + const currentValue = [1, "two", { three: true }] + const desiredValue = [1, "two", { three: false }] + + const property = createProperty({ + desired: async () => desiredValue, + get: () => currentValue, + set: () => {}, + }) + + expect(isMisconfigured(await property())).to.be.true + }) + + it("should call the setter with desired value when executed", async () => { + const currentValue = [1, "two", { three: true }] + const desiredValue = [1, "two", { three: false }] + + const set = sinon.spy() + const property = createProperty({ + desired: () => desiredValue, + get: () => currentValue, + set, + }) + + const state = await property() + await state.configure?.() + + expect(set.calledOnceWith(desiredValue)).to.be.true + }) + + it("should call the setter with context when executed", async () => { + const currentValue = [1, "two", { three: true }] + const desiredValue = [1, "two", { three: false }] + + const set = sinon.spy() + const property = createProperty({ + desired: (context: string) => desiredValue, + get: (context: string) => currentValue, + set, + }) + + const state = await property("context") + await state.configure?.() + + expect(set.calledOnceWith("context", desiredValue)).to.be.true + }) + }) +}) diff --git a/packages/ua-utils/tsconfig.build.json b/packages/ua-utils/tsconfig.build.json new file mode 100644 index 000000000..1e8f7493d --- /dev/null +++ b/packages/ua-utils/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "dist", "test"] +} diff --git a/packages/ua-utils/tsconfig.json b/packages/ua-utils/tsconfig.json new file mode 100644 index 000000000..084db5958 --- /dev/null +++ b/packages/ua-utils/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["dist", "node_modules"], + "include": ["src", "test", "*.config.ts"], + "compilerOptions": { + "module": "commonjs", + "types": ["node", "mocha"] + } +} diff --git a/packages/ua-utils/tsup.config.ts b/packages/ua-utils/tsup.config.ts new file mode 100644 index 000000000..16d909226 --- /dev/null +++ b/packages/ua-utils/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup" + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "./dist", + clean: true, + dts: true, + sourcemap: true, + splitting: false, + treeshake: true, + format: ["esm", "cjs"], +}) diff --git a/yarn.lock b/yarn.lock index aadb9bfcb..c77161157 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1622,6 +1622,18 @@ resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz" integrity sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg== +"@types/sinon@^17.0.2": + version "17.0.2" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.2.tgz#9a769f67e62b45b7233f1fe01cb1f231d2393e1c" + integrity sha512-Zt6heIGsdqERkxctIpvN5Pv3edgBrhoeb3yHyxffd4InN0AX2SVNKSrhdDZKGQICVOxWP/q4DyhpfPNMSrpIiA== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz#5fd3592ff10c1e9695d377020c033116cc2889f2" + integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== + "@types/tinycolor2@*", "@types/tinycolor2@^1.4.0": version "1.4.6" resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.6.tgz#670cbc0caf4e58dd61d1e3a6f26386e473087f06" @@ -5286,7 +5298,7 @@ mnemonist@^0.38.0: dependencies: obliterator "^2.0.0" -mocha@^10.0.0: +mocha@^10.0.0, mocha@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz" integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== From b777f4a19667a49cc177638325bbb25fda07916d Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 21 Nov 2023 11:18:04 -0800 Subject: [PATCH 2/5] feat: New version of ua-utils-evm with an example setPeer property --- packages/ua-utils-evm/.eslintignore | 2 + packages/ua-utils-evm/.gitignore | 3 ++ packages/ua-utils-evm/.mocharc.json | 5 +++ packages/ua-utils-evm/.prettierignore | 2 + packages/ua-utils-evm/README.md | 27 +++++++++++++ packages/ua-utils-evm/package.json | 49 +++++++++++++++++++++++ packages/ua-utils-evm/src/index.ts | 1 + packages/ua-utils-evm/src/oapp.ts | 14 +++++++ packages/ua-utils-evm/test/oapp.test.ts | 30 ++++++++++++++ packages/ua-utils-evm/tsconfig.build.json | 4 ++ packages/ua-utils-evm/tsconfig.json | 9 +++++ packages/ua-utils-evm/tsup.config.ts | 12 ++++++ 12 files changed, 158 insertions(+) create mode 100644 packages/ua-utils-evm/.eslintignore create mode 100644 packages/ua-utils-evm/.gitignore create mode 100644 packages/ua-utils-evm/.mocharc.json create mode 100644 packages/ua-utils-evm/.prettierignore create mode 100644 packages/ua-utils-evm/README.md create mode 100644 packages/ua-utils-evm/package.json create mode 100644 packages/ua-utils-evm/src/index.ts create mode 100644 packages/ua-utils-evm/src/oapp.ts create mode 100644 packages/ua-utils-evm/test/oapp.test.ts create mode 100644 packages/ua-utils-evm/tsconfig.build.json create mode 100644 packages/ua-utils-evm/tsconfig.json create mode 100644 packages/ua-utils-evm/tsup.config.ts diff --git a/packages/ua-utils-evm/.eslintignore b/packages/ua-utils-evm/.eslintignore new file mode 100644 index 000000000..db4c6d9b6 --- /dev/null +++ b/packages/ua-utils-evm/.eslintignore @@ -0,0 +1,2 @@ +dist +node_modules \ No newline at end of file diff --git a/packages/ua-utils-evm/.gitignore b/packages/ua-utils-evm/.gitignore new file mode 100644 index 000000000..803d4166c --- /dev/null +++ b/packages/ua-utils-evm/.gitignore @@ -0,0 +1,3 @@ +artifacts +cache +deployments \ No newline at end of file diff --git a/packages/ua-utils-evm/.mocharc.json b/packages/ua-utils-evm/.mocharc.json new file mode 100644 index 000000000..d4ee538e6 --- /dev/null +++ b/packages/ua-utils-evm/.mocharc.json @@ -0,0 +1,5 @@ +{ + "extensions": ["ts"], + "spec": ["**/*.test.*"], + "loader": "ts-node/esm" +} diff --git a/packages/ua-utils-evm/.prettierignore b/packages/ua-utils-evm/.prettierignore new file mode 100644 index 000000000..763301fc0 --- /dev/null +++ b/packages/ua-utils-evm/.prettierignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/packages/ua-utils-evm/README.md b/packages/ua-utils-evm/README.md new file mode 100644 index 000000000..309a5701f --- /dev/null +++ b/packages/ua-utils-evm/README.md @@ -0,0 +1,27 @@ +

+ + LayerZero + +

+ +

@layerzerolabs/ua-utils-evm

+ + +

+ + NPM Version + + Downloads + + NPM License +

+ +## Installation + +```bash +yarn add @layerzerolabs/ua-utils-evm + +pnpm add @layerzerolabs/ua-utils-evm + +npm install @layerzerolabs/ua-utils-evm +``` diff --git a/packages/ua-utils-evm/package.json b/packages/ua-utils-evm/package.json new file mode 100644 index 000000000..e70596212 --- /dev/null +++ b/packages/ua-utils-evm/package.json @@ -0,0 +1,49 @@ +{ + "name": "@layerzerolabs/ua-utils-evm", + "description": "Utilities for working with LayerZero EVM projects", + "version": "0.0.1", + "license": "MIT", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "module": "./dist/index.mjs", + "exports": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + }, + "files": [ + "./dist/index.*" + ], + "scripts": { + "build": "npx tsup", + "clean": "rm -rf dist", + "dev": "npx tsup --watch", + "lint": "npx eslint '**/*.{js,ts,json}'", + "prebuild": "npx tsc --noEmit -p tsconfig.build.json", + "test": "mocha --parallel" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/LayerZero-Labs/lz-utils.git", + "directory": "packages/ua-utils-evm" + }, + "devDependencies": { + "@ethersproject/contracts": "5.7.0", + "@layerzerolabs/lz-definitions": "~1.5.58", + "@layerzerolabs/ua-utils": "~0.1.0", + "@types/mocha": "^10.0.1", + "@types/sinon": "^17.0.2", + "chai": "^4.3.10", + "mocha": "^10.2.0", + "sinon": "^17.0.1", + "ts-node": "^10.9.1", + "tsup": "~7.2.0", + "typescript": "^5.2.2" + }, + "peerDependencies": { + "@ethersproject/contracts": "5.7.0", + "@layerzerolabs/lz-definitions": "~1.5.58", + "@layerzerolabs/ua-utils": "~0.1.0" + } +} diff --git a/packages/ua-utils-evm/src/index.ts b/packages/ua-utils-evm/src/index.ts new file mode 100644 index 000000000..9e4b10a93 --- /dev/null +++ b/packages/ua-utils-evm/src/index.ts @@ -0,0 +1 @@ +export * from "./oapp" diff --git a/packages/ua-utils-evm/src/oapp.ts b/packages/ua-utils-evm/src/oapp.ts new file mode 100644 index 000000000..c424baf6f --- /dev/null +++ b/packages/ua-utils-evm/src/oapp.ts @@ -0,0 +1,14 @@ +import { Contract } from "@ethersproject/contracts" +import { GetPropertyValue, createProperty } from "@layerzerolabs/ua-utils" +import { EndpointId } from "@layerzerolabs/lz-definitions" + +type SetPeerConfigurableContext = [oapp: Contract, endpointId: EndpointId] + +type SetPeerConfigurableValue = string + +export const createSetPeerProperty = (desired: GetPropertyValue) => + createProperty({ + desired, + get: (oapp, endpointId) => oapp.peers(endpointId), + set: (oapp, endpointId, peer) => oapp.setPeer(endpointId, peer), + }) diff --git a/packages/ua-utils-evm/test/oapp.test.ts b/packages/ua-utils-evm/test/oapp.test.ts new file mode 100644 index 000000000..8fc3e4d94 --- /dev/null +++ b/packages/ua-utils-evm/test/oapp.test.ts @@ -0,0 +1,30 @@ +import { Contract } from "@ethersproject/contracts" +import { EndpointId } from "@layerzerolabs/lz-definitions" +import { expect } from "chai" +import { describe } from "mocha" +import sinon from "sinon" +import { createSetPeerProperty } from "../src/oapp" +import { isMisconfigured } from "@layerzerolabs/ua-utils" + +describe("oapp", () => { + describe("createSetPeerProperty", () => { + it("should check peers and return Misconfigured if they don't match", async () => { + const peers = sinon.stub().resolves("peer-not-set") + const setPeer = sinon.stub().resolves("okay") + const oapp = { peers, setPeer } as unknown as Contract + + const desired = (oapp: Contract, endpointId: EndpointId) => `peer-on-${endpointId}` + const configurable = createSetPeerProperty(desired) + const state = await configurable(oapp, EndpointId.AAVEGOTCHI_TESTNET) + + expect(isMisconfigured(state)).to.be.true + expect(state.value).to.eql("peer-not-set") + expect(state.desiredValue).to.eql(`peer-on-${EndpointId.AAVEGOTCHI_TESTNET}`) + + const result = await state.configure?.() + + expect(result).to.eql("okay") + expect(setPeer.calledOnceWith(EndpointId.AAVEGOTCHI_TESTNET, `peer-on-${EndpointId.AAVEGOTCHI_TESTNET}`)).to.be.true + }) + }) +}) diff --git a/packages/ua-utils-evm/tsconfig.build.json b/packages/ua-utils-evm/tsconfig.build.json new file mode 100644 index 000000000..1e8f7493d --- /dev/null +++ b/packages/ua-utils-evm/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "dist", "test"] +} diff --git a/packages/ua-utils-evm/tsconfig.json b/packages/ua-utils-evm/tsconfig.json new file mode 100644 index 000000000..084db5958 --- /dev/null +++ b/packages/ua-utils-evm/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["dist", "node_modules"], + "include": ["src", "test", "*.config.ts"], + "compilerOptions": { + "module": "commonjs", + "types": ["node", "mocha"] + } +} diff --git a/packages/ua-utils-evm/tsup.config.ts b/packages/ua-utils-evm/tsup.config.ts new file mode 100644 index 000000000..16d909226 --- /dev/null +++ b/packages/ua-utils-evm/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup" + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "./dist", + clean: true, + dts: true, + sourcemap: true, + splitting: false, + treeshake: true, + format: ["esm", "cjs"], +}) From b4c63b3d1e642debbfbf88794240fc144aa19c35 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 21 Nov 2023 11:38:40 -0800 Subject: [PATCH 3/5] chore: Add one more example --- packages/ua-utils/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/ua-utils/README.md b/packages/ua-utils/README.md index 72f65b3b9..51f38cdfd 100644 --- a/packages/ua-utils/README.md +++ b/packages/ua-utils/README.md @@ -100,6 +100,38 @@ if (isMisconfigured(state)) { } ``` +The `createProperty` is completely abstract though and does not require us to get a single contract property or set it directly. in the following, completely made-up example we'll get multiple properties at once and instead of setting them, we'll just populate the transactions for further executions: + +```typescript +import { createProperty, isMisconfigured } from "@layerzerolabs/ua-utils" + +const myContractPropertyWithParams = createProperty({ + get: (contract: Contract) => Promise.all([contract.getA(), contract.getB()]), + set: (contract: Contract, [a, b, c]) => [ + contract.pupulateTransaction.setA(a), + contract.pupulateTransaction.setB(b), + contract.pupulateTransaction.setC(c), + ], + desired: (contract: Contract) => [7, 11, 17], +}) + +// We'll again pretend we have a contract at hand +declare const myContract: Contract + +// We'll evaluate this property by passing the required context in - in this case +// the context consists of a contract, a numeric value and a string value +// +// The result of this evaluation is a PropertyState object +const state = await myContractPropertyWithParams(myContract, when, where) + +// The execution goes just like before +if (isMisconfigured(state)) { + const transactions = await state.configure() + + // We now have a list of populated transactions to execute +} +``` + ### `isConfigured` Helper type assertion utility that narrows down the `PropertyState` type to `Configured`: From 1315d00f1c6c284a5af9e6c2a8b17c5db1e4a1bd Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 21 Nov 2023 11:55:55 -0800 Subject: [PATCH 4/5] chore: Get rid of gitignore --- packages/ua-utils-evm/.gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 packages/ua-utils-evm/.gitignore diff --git a/packages/ua-utils-evm/.gitignore b/packages/ua-utils-evm/.gitignore deleted file mode 100644 index 803d4166c..000000000 --- a/packages/ua-utils-evm/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -artifacts -cache -deployments \ No newline at end of file From e5afd843f44eb37b00d2dc2e749c33be7c873fa1 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 21 Nov 2023 16:50:42 -0800 Subject: [PATCH 5/5] chore: Add context to PropertyState --- packages/ua-utils/src/property.ts | 26 +++++++++++------- packages/ua-utils/test/property.test.ts | 35 +++++++++++++++---------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/packages/ua-utils/src/property.ts b/packages/ua-utils/src/property.ts index c51b94c26..c08c735cd 100644 --- a/packages/ua-utils/src/property.ts +++ b/packages/ua-utils/src/property.ts @@ -11,7 +11,7 @@ import assert from "assert" */ export type Property = ( ...context: TContext -) => Promise> +) => Promise> /** * Type encapsulating two states of a configurable property: `Configured` and `Misconfigured` @@ -19,7 +19,9 @@ export type Property = Configured | Misconfigured +export type PropertyState = + | Configured + | Misconfigured /** * Interface for configured state of a configurable property. @@ -27,7 +29,8 @@ export type PropertyState = Configured { +export interface Configured { + context: TContext value: TValue desiredValue?: never configure?: never @@ -39,7 +42,8 @@ export interface Configured { * In misconfigured state, the current value of the property does not match its desired state * and an action needs to be taken to synchronize these two. */ -export interface Misconfigured { +export interface Misconfigured { + context: TContext value: TValue desiredValue: TValue configure: () => TResult | Promise @@ -84,10 +88,10 @@ export const createProperty = assert.deepStrictEqual(value, desiredValue) // The values matched, we return a Configured - return { value } + return { context, value } } catch { // The values did not match, we'll return a Misconfigured - return { value, desiredValue, configure: async () => set(...context, desiredValue) } + return { context, value, desiredValue, configure: async () => set(...context, desiredValue) } } } @@ -97,9 +101,9 @@ export const createProperty = * @param value `PropertyState` * @returns `value is Misconfigured` */ -export const isMisconfigured = ( - value: PropertyState -): value is Misconfigured => "configure" in value && "desiredValue" in value && typeof value.configure === "function" +export const isMisconfigured = ( + value: PropertyState +): value is Misconfigured => "configure" in value && "desiredValue" in value && typeof value.configure === "function" /** * Type assertion utility for narrowing the `PropertyState` type to `Configured` type @@ -107,4 +111,6 @@ export const isMisconfigured = ( * @param value `PropertyState` * @returns `value is Configured` */ -export const isConfigured = (value: PropertyState): value is Configured => !isMisconfigured(value) +export const isConfigured = ( + value: PropertyState +): value is Configured => !isMisconfigured(value) diff --git a/packages/ua-utils/test/property.test.ts b/packages/ua-utils/test/property.test.ts index da4551ba3..c709002d8 100644 --- a/packages/ua-utils/test/property.test.ts +++ b/packages/ua-utils/test/property.test.ts @@ -6,29 +6,29 @@ import { createProperty, isConfigured, isMisconfigured } from "../src/property" describe("property", () => { describe("isMisconfigured", () => { it("should return true if value is Misconfigured", () => { - expect(isMisconfigured({ value: false, desiredValue: true, configure: () => {} })).to.be.true - expect(isMisconfigured({ value: null, desiredValue: null, configure: () => {} })).to.be.true - expect(isMisconfigured({ value: 0, desiredValue: 0, configure: () => {} })).to.be.true + expect(isMisconfigured({ context: [], value: false, desiredValue: true, configure: () => {} })).to.be.true + expect(isMisconfigured({ context: [], value: null, desiredValue: null, configure: () => {} })).to.be.true + expect(isMisconfigured({ context: [], value: 0, desiredValue: 0, configure: () => {} })).to.be.true }) it("should return false if value is Configured", () => { - expect(isMisconfigured({ value: false })).to.be.false - expect(isMisconfigured({ value: true })).to.be.false - expect(isMisconfigured({ value: 1 })).to.be.false + expect(isMisconfigured({ context: [], value: false })).to.be.false + expect(isMisconfigured({ context: [], value: true })).to.be.false + expect(isMisconfigured({ context: [], value: 1 })).to.be.false }) }) describe("isConfigured", () => { it("should return false if value is Configured", () => { - expect(isConfigured({ value: false, desiredValue: true, configure: () => {} })).to.be.false - expect(isConfigured({ value: null, desiredValue: null, configure: () => {} })).to.be.false - expect(isConfigured({ value: 0, desiredValue: 0, configure: () => {} })).to.be.false + expect(isConfigured({ context: [], value: false, desiredValue: true, configure: () => {} })).to.be.false + expect(isConfigured({ context: [], value: null, desiredValue: null, configure: () => {} })).to.be.false + expect(isConfigured({ context: [], value: 0, desiredValue: 0, configure: () => {} })).to.be.false }) it("should return true if value is Misconfigured", () => { - expect(isConfigured({ value: false })).to.be.true - expect(isConfigured({ value: true })).to.be.true - expect(isConfigured({ value: 1 })).to.be.true + expect(isConfigured({ context: [], value: false })).to.be.true + expect(isConfigured({ context: [], value: true })).to.be.true + expect(isConfigured({ context: [], value: 1 })).to.be.true }) }) @@ -43,7 +43,10 @@ describe("property", () => { set: () => {}, }) - expect(isMisconfigured(await property())).to.be.false + const state = await property() + + expect(isMisconfigured(state)).to.be.false + expect(state.context).to.eql([]) }) it("should return Misconfigured if the current and desired don't match", async () => { @@ -56,7 +59,9 @@ describe("property", () => { set: () => {}, }) - expect(isMisconfigured(await property())).to.be.true + const state = await property() + expect(isMisconfigured(state)).to.be.true + expect(state.context).to.eql([]) }) it("should call the setter with desired value when executed", async () => { @@ -74,6 +79,7 @@ describe("property", () => { await state.configure?.() expect(set.calledOnceWith(desiredValue)).to.be.true + expect(state.context).to.eql([]) }) it("should call the setter with context when executed", async () => { @@ -91,6 +97,7 @@ describe("property", () => { await state.configure?.() expect(set.calledOnceWith("context", desiredValue)).to.be.true + expect(state.context).to.eql(["context"]) }) }) })