From 06ef8af8666f897d5b293cf20d78e656dc9b2868 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:18:59 -0300 Subject: [PATCH 01/17] build: separate tsconfig for build stage --- tsconfig.base.json | 23 +++++++++++++++++++++++ tsconfig.build.json | 8 ++++++++ tsconfig.json | 22 ++-------------------- 3 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 tsconfig.base.json create mode 100644 tsconfig.build.json diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..14527c8 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,23 @@ +/* Based on total-typescript no-dom app config */ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": false, + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + /* If transpiling with TypeScript: */ + "module": "NodeNext", + "sourceMap": true, + /* If your code doesn't run in the DOM: */ + "lib": ["es2022"] + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..13862a6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,8 @@ +/* Based on total-typescript no-dom app config */ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "incremental": false, + "noEmit": false + } +} diff --git a/tsconfig.json b/tsconfig.json index d077d24..98d1e21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,5 @@ /* Based on total-typescript no-dom app config */ { - "compilerOptions": { - /* Base Options: */ - "esModuleInterop": true, - "skipLibCheck": true, - "target": "es2022", - "allowJs": true, - "resolveJsonModule": true, - "moduleDetection": "force", - "isolatedModules": true, - "verbatimModuleSyntax": true, - /* Strictness */ - "strict": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - /* If transpiling with TypeScript: */ - "module": "NodeNext", - "sourceMap": true, - /* If your code doesn't run in the DOM: */ - "lib": ["es2022"] - } + "extends": "./tsconfig.base.json", + "include": ["**/*", ".*.js"] } From 9802a896ca500ed5f9cf685765d3b6a8716c6841 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:19:49 -0300 Subject: [PATCH 02/17] feat: set up blocknumber module tests --- package.json | 7 +++++-- packages/.gitkeep | 0 packages/blocknumber/package.json | 17 +++++++++++++++++ packages/blocknumber/src/helloWorld.spec.ts | 7 +++++++ packages/blocknumber/tsconfig.build.json | 8 ++++++++ packages/blocknumber/tsconfig.json | 4 ++++ packages/blocknumber/vitest.config.ts | 21 +++++++++++++++++++++ 7 files changed, 62 insertions(+), 2 deletions(-) delete mode 100644 packages/.gitkeep create mode 100644 packages/blocknumber/package.json create mode 100644 packages/blocknumber/src/helloWorld.spec.ts create mode 100644 packages/blocknumber/tsconfig.build.json create mode 100644 packages/blocknumber/tsconfig.json create mode 100644 packages/blocknumber/vitest.config.ts diff --git a/package.json b/package.json index f2b9e60..66f1145 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,13 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "build": "turbo run build", + "test": "turbo run test", "prepare": "husky", "lint": "turbo run lint", - "format": "turbo run format" + "lint:fix": "turbo run lint:fix", + "format": "turbo run format", + "format:fix": "turbo run format:fix" }, "keywords": [], "author": "", diff --git a/packages/.gitkeep b/packages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json new file mode 100644 index 0000000..49b5377 --- /dev/null +++ b/packages/blocknumber/package.json @@ -0,0 +1,17 @@ +{ + "name": "blocknumber", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "build": "tsc", + "lint": "eslint .", + "lint:fix": "pnpm lint --fix", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "test": "vitest run" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts new file mode 100644 index 0000000..7216041 --- /dev/null +++ b/packages/blocknumber/src/helloWorld.spec.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from "vitest"; + +describe("test", () => { + it("pass", () => { + expect(1).toBe(1); + }); +}); diff --git a/packages/blocknumber/tsconfig.build.json b/packages/blocknumber/tsconfig.build.json new file mode 100644 index 0000000..7e50390 --- /dev/null +++ b/packages/blocknumber/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["**/*.spec.ts"] +} diff --git a/packages/blocknumber/tsconfig.json b/packages/blocknumber/tsconfig.json new file mode 100644 index 0000000..66bb87a --- /dev/null +++ b/packages/blocknumber/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/packages/blocknumber/vitest.config.ts b/packages/blocknumber/vitest.config.ts new file mode 100644 index 0000000..84e59e7 --- /dev/null +++ b/packages/blocknumber/vitest.config.ts @@ -0,0 +1,21 @@ +import path from "path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // Use Vitest's global API without importing it in each file + environment: "node", // Use the Node.js environment + include: ["src/**/*.spec.ts"], // Include test files + exclude: ["node_modules", "dist"], // Exclude certain directories + coverage: { + reporter: ["text", "json", "html"], // Coverage reporters + exclude: ["node_modules", "dist", "src/**/*.d.ts"], // Files to exclude from coverage + }, + }, + resolve: { + alias: { + // Setup path alias based on tsconfig paths + "@": path.resolve(__dirname, "src"), + }, + }, +}); From 546d76ea14735435f69ea23a77084bfff1f6932f Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:24:34 -0300 Subject: [PATCH 03/17] chore: set up blocknumber module linting --- packages/blocknumber/.lintstagedrc.js | 5 +++++ packages/blocknumber/.prettierignore | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 packages/blocknumber/.lintstagedrc.js create mode 100644 packages/blocknumber/.prettierignore diff --git a/packages/blocknumber/.lintstagedrc.js b/packages/blocknumber/.lintstagedrc.js new file mode 100644 index 0000000..85ad482 --- /dev/null +++ b/packages/blocknumber/.lintstagedrc.js @@ -0,0 +1,5 @@ +const baseConfig = require("../../.lintstagedrc.js"); + +module.exports = { + ...baseConfig, +}; diff --git a/packages/blocknumber/.prettierignore b/packages/blocknumber/.prettierignore new file mode 100644 index 0000000..319ebdd --- /dev/null +++ b/packages/blocknumber/.prettierignore @@ -0,0 +1,4 @@ +# Generated files +pnpm-lock.yaml +node_modules +dist \ No newline at end of file From f291b4582cb08c985d843b98238e2d59f3a6746c Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:48:56 -0300 Subject: [PATCH 04/17] style: remove total-typescript on non-base tsconfig --- packages/blocknumber/.lintstagedrc.js | 5 ----- tsconfig.build.json | 1 - tsconfig.json | 1 - 3 files changed, 7 deletions(-) delete mode 100644 packages/blocknumber/.lintstagedrc.js diff --git a/packages/blocknumber/.lintstagedrc.js b/packages/blocknumber/.lintstagedrc.js deleted file mode 100644 index 85ad482..0000000 --- a/packages/blocknumber/.lintstagedrc.js +++ /dev/null @@ -1,5 +0,0 @@ -const baseConfig = require("../../.lintstagedrc.js"); - -module.exports = { - ...baseConfig, -}; diff --git a/tsconfig.build.json b/tsconfig.build.json index 13862a6..45a228e 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,3 @@ -/* Based on total-typescript no-dom app config */ { "extends": "./tsconfig.base.json", "compilerOptions": { diff --git a/tsconfig.json b/tsconfig.json index 98d1e21..d48eef2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,3 @@ -/* Based on total-typescript no-dom app config */ { "extends": "./tsconfig.base.json", "include": ["**/*", ".*.js"] From 0e8fc5ad3e349a8546bdbe8a61b25b4e2cfa2459 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 18 Jul 2024 18:55:53 -0300 Subject: [PATCH 05/17] build: set up build script --- packages/blocknumber/.gitignore | 1 + packages/blocknumber/package.json | 2 +- packages/blocknumber/src/helloWorld.spec.ts | 6 +++++- packages/blocknumber/src/helloWorld.ts | 3 +++ 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 packages/blocknumber/.gitignore create mode 100644 packages/blocknumber/src/helloWorld.ts diff --git a/packages/blocknumber/.gitignore b/packages/blocknumber/.gitignore new file mode 100644 index 0000000..7773828 --- /dev/null +++ b/packages/blocknumber/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 49b5377..7df4b85 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.ts", "scripts": { - "build": "tsc", + "build": "tsc -p tsconfig.build.json", "lint": "eslint .", "lint:fix": "pnpm lint --fix", "format": "prettier --check .", diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts index 7216041..f2cafa8 100644 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ b/packages/blocknumber/src/helloWorld.spec.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from "vitest"; +import foo from "./helloWorld"; + describe("test", () => { it("pass", () => { - expect(1).toBe(1); + const result = foo(); + + expect(result).toBe("bar"); }); }); diff --git a/packages/blocknumber/src/helloWorld.ts b/packages/blocknumber/src/helloWorld.ts new file mode 100644 index 0000000..0838c22 --- /dev/null +++ b/packages/blocknumber/src/helloWorld.ts @@ -0,0 +1,3 @@ +export default function foo() { + return "bar"; +} From baa144abfccd29b6122d6e448bf9fba5a32302d8 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 09:50:38 -0300 Subject: [PATCH 06/17] fix: blocknumber module package config --- .gitignore | 5 ++++- packages/blocknumber/.gitignore | 1 - packages/blocknumber/.prettierignore | 1 - packages/blocknumber/package.json | 1 + packages/blocknumber/tsconfig.build.json | 3 ++- 5 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 packages/blocknumber/.gitignore diff --git a/.gitignore b/.gitignore index 4b6f676..8366b82 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ node_modules/ # Turbo -.turbo/ \ No newline at end of file +.turbo/ + +# Build +dist/ \ No newline at end of file diff --git a/packages/blocknumber/.gitignore b/packages/blocknumber/.gitignore deleted file mode 100644 index 7773828..0000000 --- a/packages/blocknumber/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist/ \ No newline at end of file diff --git a/packages/blocknumber/.prettierignore b/packages/blocknumber/.prettierignore index 319ebdd..3da65e6 100644 --- a/packages/blocknumber/.prettierignore +++ b/packages/blocknumber/.prettierignore @@ -1,4 +1,3 @@ # Generated files -pnpm-lock.yaml node_modules dist \ No newline at end of file diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 7df4b85..9ddd104 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.ts", + "type": "module", "scripts": { "build": "tsc -p tsconfig.build.json", "lint": "eslint .", diff --git a/packages/blocknumber/tsconfig.build.json b/packages/blocknumber/tsconfig.build.json index 7e50390..4b3e8f3 100644 --- a/packages/blocknumber/tsconfig.build.json +++ b/packages/blocknumber/tsconfig.build.json @@ -1,8 +1,9 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": { + "declaration": true, "outDir": "dist" }, "include": ["src/**/*"], - "exclude": ["**/*.spec.ts"] + "exclude": ["node_modules", "build", "tests", "vitest.config.ts"] } From 9965c35b38837e42c4b9c96f95e5833cbcb01f89 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 09:58:07 -0300 Subject: [PATCH 07/17] fix: dummy class import module --- packages/blocknumber/src/helloWorld.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts index f2cafa8..bdcee5e 100644 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ b/packages/blocknumber/src/helloWorld.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import foo from "./helloWorld"; +import foo from "./helloWorld.js"; describe("test", () => { it("pass", () => { From 6215697974ad0d7ac5b6da061e4db76de9b27f9f Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 15:14:36 -0300 Subject: [PATCH 08/17] chore: add coverage script and tasks --- .gitignore | 5 +- package.json | 2 + packages/blocknumber/package.json | 3 +- packages/blocknumber/vitest.config.ts | 1 + pnpm-lock.yaml | 305 ++++++++++++++++++++++++++ turbo.json | 1 + 6 files changed, 315 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8366b82..d9cc783 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ node_modules/ .turbo/ # Build -dist/ \ No newline at end of file +dist/ + +# Coverage +coverage/ \ No newline at end of file diff --git a/package.json b/package.json index 66f1145..abfffdc 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "turbo run build", "test": "turbo run test", + "coverage": "turbo run coverage", "prepare": "husky", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", @@ -21,6 +22,7 @@ "@ianvs/prettier-plugin-sort-imports": "4.3.1", "@typescript-eslint/eslint-plugin": "7.16.1", "@typescript-eslint/parser": "7.16.1", + "@vitest/coverage-v8": "^2.0.3", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.2.1", diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 9ddd104..90949f5 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -10,7 +10,8 @@ "lint:fix": "pnpm lint --fix", "format": "prettier --check .", "format:fix": "prettier --write .", - "test": "vitest run" + "test": "vitest run", + "coverage": "vitest run --coverage" }, "keywords": [], "author": "", diff --git a/packages/blocknumber/vitest.config.ts b/packages/blocknumber/vitest.config.ts index 84e59e7..fec3430 100644 --- a/packages/blocknumber/vitest.config.ts +++ b/packages/blocknumber/vitest.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ include: ["src/**/*.spec.ts"], // Include test files exclude: ["node_modules", "dist"], // Exclude certain directories coverage: { + provider: "v8", reporter: ["text", "json", "html"], // Coverage reporters exclude: ["node_modules", "dist", "src/**/*.d.ts"], // Files to exclude from coverage }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36a1010..56d01ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: "@typescript-eslint/parser": specifier: 7.16.1 version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + "@vitest/coverage-v8": + specifier: ^2.0.3 + version: 2.0.3(vitest@2.0.3(@types/node@20.14.11)) eslint: specifier: 8.57.0 version: 8.57.0 @@ -50,6 +53,8 @@ importers: specifier: 2.0.3 version: 2.0.3(@types/node@20.14.11) + packages/blocknumber: {} + packages: "@ampproject/remapping@2.3.0": resolution: @@ -208,6 +213,12 @@ packages: } engines: { node: ">=6.9.0" } + "@bcoe/v8-coverage@0.2.3": + resolution: + { + integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, + } + "@commitlint/cli@19.3.0": resolution: { @@ -599,6 +610,20 @@ packages: "@vue/compiler-sfc": optional: true + "@isaacs/cliui@8.0.2": + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, + } + engines: { node: ">=12" } + + "@istanbuljs/schema@0.1.3": + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, + } + engines: { node: ">=8" } + "@jridgewell/gen-mapping@0.3.5": resolution: { @@ -653,6 +678,13 @@ packages: } engines: { node: ">= 8" } + "@pkgjs/parseargs@0.11.0": + resolution: + { + integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, + } + engines: { node: ">=14" } + "@pkgr/core@0.1.1": resolution: { @@ -894,6 +926,14 @@ packages: integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==, } + "@vitest/coverage-v8@2.0.3": + resolution: + { + integrity: sha512-53d+6jXFdYbasXBmsL6qaGIfcY5eBQq0sP57AjdasOcSiGNj4qxkkpDKIitUNfjxcfAfUfQ8BD0OR2fSey64+g==, + } + peerDependencies: + vitest: 2.0.3 + "@vitest/expect@2.0.3": resolution: { @@ -1303,6 +1343,12 @@ packages: } engines: { node: ">=8" } + eastasianwidth@0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + } + electron-to-chromium@1.4.829: resolution: { @@ -1321,6 +1367,12 @@ packages: integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, } + emoji-regex@9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, + } + env-paths@2.2.1: resolution: { @@ -1549,6 +1601,13 @@ packages: integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, } + foreground-child@3.2.1: + resolution: + { + integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==, + } + engines: { node: ">=14" } + fs.realpath@1.0.0: resolution: { @@ -1619,6 +1678,13 @@ packages: } engines: { node: ">=10.13.0" } + glob@10.4.5: + resolution: + { + integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, + } + hasBin: true + glob@7.2.3: resolution: { @@ -1674,6 +1740,12 @@ packages: } engines: { node: ">=8" } + html-escaper@2.0.2: + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, + } + human-signals@5.0.0: resolution: { @@ -1818,6 +1890,40 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, + } + engines: { node: ">=8" } + + istanbul-lib-report@3.0.1: + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, + } + engines: { node: ">=10" } + + istanbul-lib-source-maps@5.0.6: + resolution: + { + integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==, + } + engines: { node: ">=10" } + + istanbul-reports@3.1.7: + resolution: + { + integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==, + } + engines: { node: ">=8" } + + jackspeak@3.4.3: + resolution: + { + integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, + } + jiti@1.21.6: resolution: { @@ -1831,6 +1937,12 @@ packages: integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } + js-tokens@9.0.0: + resolution: + { + integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==, + } + js-yaml@4.1.0: resolution: { @@ -2013,6 +2125,12 @@ packages: integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==, } + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } + lru-cache@5.1.1: resolution: { @@ -2025,6 +2143,19 @@ packages: integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==, } + magicast@0.3.4: + resolution: + { + integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==, + } + + make-dir@4.0.0: + resolution: + { + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, + } + engines: { node: ">=10" } + meow@12.1.1: resolution: { @@ -2085,6 +2216,13 @@ packages: integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, } + minipass@7.1.2: + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, + } + engines: { node: ">=16 || 14 >=14.17" } + ms@2.1.2: resolution: { @@ -2173,6 +2311,12 @@ packages: } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + package-json-from-dist@1.0.0: + resolution: + { + integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==, + } + parent-module@1.0.1: resolution: { @@ -2222,6 +2366,13 @@ packages: } engines: { node: ">=12" } + path-scurry@1.11.1: + resolution: + { + integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, + } + engines: { node: ">=16 || 14 >=14.18" } + path-type@4.0.0: resolution: { @@ -2484,6 +2635,13 @@ packages: } engines: { node: ">=8" } + string-width@5.1.2: + resolution: + { + integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, + } + engines: { node: ">=12" } + string-width@7.2.0: resolution: { @@ -2519,6 +2677,12 @@ packages: } engines: { node: ">=8" } + strip-literal@2.1.0: + resolution: + { + integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==, + } + supports-color@5.5.0: resolution: { @@ -2540,6 +2704,13 @@ packages: } engines: { node: ^14.18.0 || >=16.0.0 } + test-exclude@7.0.1: + resolution: + { + integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==, + } + engines: { node: ">=18" } + text-extensions@2.4.0: resolution: { @@ -2817,6 +2988,13 @@ packages: } engines: { node: ">=10" } + wrap-ansi@8.1.0: + resolution: + { + integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, + } + engines: { node: ">=12" } + wrap-ansi@9.0.0: resolution: { @@ -3018,6 +3196,8 @@ snapshots: "@babel/helper-validator-identifier": 7.24.7 to-fast-properties: 2.0.0 + "@bcoe/v8-coverage@0.2.3": {} + "@commitlint/cli@19.3.0(@types/node@20.14.11)(typescript@5.5.3)": dependencies: "@commitlint/format": 19.3.0 @@ -3245,6 +3425,17 @@ snapshots: transitivePeerDependencies: - supports-color + "@isaacs/cliui@8.0.2": + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + "@istanbuljs/schema@0.1.3": {} + "@jridgewell/gen-mapping@0.3.5": dependencies: "@jridgewell/set-array": 1.2.1 @@ -3274,6 +3465,9 @@ snapshots: "@nodelib/fs.scandir": 2.1.5 fastq: 1.17.1 + "@pkgjs/parseargs@0.11.0": + optional: true + "@pkgr/core@0.1.1": {} "@rollup/rollup-android-arm-eabi@4.18.1": @@ -3417,6 +3611,25 @@ snapshots: "@ungap/structured-clone@1.2.0": {} + "@vitest/coverage-v8@2.0.3(vitest@2.0.3(@types/node@20.14.11))": + dependencies: + "@ampproject/remapping": 2.3.0 + "@bcoe/v8-coverage": 0.2.3 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.10 + magicast: 0.3.4 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.0.3(@types/node@20.14.11) + transitivePeerDependencies: + - supports-color + "@vitest/expect@2.0.3": dependencies: "@vitest/spy": 2.0.3 @@ -3649,12 +3862,16 @@ snapshots: dependencies: is-obj: 2.0.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.4.829: {} emoji-regex@10.3.0: {} emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + env-paths@2.2.1: {} error-ex@1.3.2: @@ -3841,6 +4058,11 @@ snapshots: flatted@3.3.1: {} + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -3870,6 +4092,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3904,6 +4135,8 @@ snapshots: has-flag@4.0.0: {} + html-escaper@2.0.2: {} + human-signals@5.0.0: {} husky@9.1.0: {} @@ -3958,10 +4191,39 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + "@jridgewell/trace-mapping": 0.3.25 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + "@isaacs/cliui": 8.0.2 + optionalDependencies: + "@pkgjs/parseargs": 0.11.0 + jiti@1.21.6: {} js-tokens@4.0.0: {} + js-tokens@9.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -4057,6 +4319,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4065,6 +4329,16 @@ snapshots: dependencies: "@jridgewell/sourcemap-codec": 1.5.0 + magicast@0.3.4: + dependencies: + "@babel/parser": 7.24.8 + "@babel/types": 7.24.9 + source-map-js: 1.2.0 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + meow@12.1.1: {} merge-stream@2.0.0: {} @@ -4090,6 +4364,8 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.2: {} + ms@2.1.2: {} nanoid@3.3.7: {} @@ -4139,6 +4415,8 @@ snapshots: dependencies: p-limit: 4.0.0 + package-json-from-dist@1.0.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4160,6 +4438,11 @@ snapshots: path-key@4.0.0: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-type@4.0.0: {} pathe@1.1.2: {} @@ -4281,6 +4564,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.3.0 @@ -4299,6 +4588,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@2.1.0: + dependencies: + js-tokens: 9.0.0 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -4312,6 +4605,12 @@ snapshots: "@pkgr/core": 0.1.1 tslib: 2.6.3 + test-exclude@7.0.1: + dependencies: + "@istanbuljs/schema": 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@2.4.0: {} text-table@0.2.0: {} @@ -4462,6 +4761,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 diff --git a/turbo.json b/turbo.json index 692f067..541985e 100644 --- a/turbo.json +++ b/turbo.json @@ -6,6 +6,7 @@ "format": {}, "format:fix": {}, "test": {}, + "coverage": {}, "build": {} }, "globalDependencies": [".eslintrc", ".prettierrc"] From 2748ca2c37e4881bb0e7276778661a71940bff3e Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 12:33:46 -0300 Subject: [PATCH 09/17] chore: remove dummy classes --- packages/blocknumber/src/helloWorld.spec.ts | 11 ----------- packages/blocknumber/src/helloWorld.ts | 3 --- 2 files changed, 14 deletions(-) delete mode 100644 packages/blocknumber/src/helloWorld.spec.ts delete mode 100644 packages/blocknumber/src/helloWorld.ts diff --git a/packages/blocknumber/src/helloWorld.spec.ts b/packages/blocknumber/src/helloWorld.spec.ts deleted file mode 100644 index bdcee5e..0000000 --- a/packages/blocknumber/src/helloWorld.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import foo from "./helloWorld.js"; - -describe("test", () => { - it("pass", () => { - const result = foo(); - - expect(result).toBe("bar"); - }); -}); diff --git a/packages/blocknumber/src/helloWorld.ts b/packages/blocknumber/src/helloWorld.ts deleted file mode 100644 index 0838c22..0000000 --- a/packages/blocknumber/src/helloWorld.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function foo() { - return "bar"; -} From ab869a4011e61e11ac20e001d3dc86716a46ade3 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 19 Jul 2024 12:34:29 -0300 Subject: [PATCH 10/17] feat: implement caip-2 compliant chain ids --- packages/blocknumber/src/index.ts | 0 packages/blocknumber/src/utils/chainId.ts | 72 +++++++++++++++++++ packages/blocknumber/src/utils/index.ts | 1 + .../blocknumber/test/utils/chainId.spec.ts | 40 +++++++++++ 4 files changed, 113 insertions(+) create mode 100644 packages/blocknumber/src/index.ts create mode 100644 packages/blocknumber/src/utils/chainId.ts create mode 100644 packages/blocknumber/src/utils/index.ts create mode 100644 packages/blocknumber/test/utils/chainId.spec.ts diff --git a/packages/blocknumber/src/index.ts b/packages/blocknumber/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/chainId.ts new file mode 100644 index 0000000..ba6394d --- /dev/null +++ b/packages/blocknumber/src/utils/chainId.ts @@ -0,0 +1,72 @@ +// Based on https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md + +type ChainNamespace = string; +type ChainReference = string; + +interface ChainIdParams { + namespace: ChainNamespace; + reference: ChainReference; +} + +const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; +const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; + +export class InvalidChainId extends Error { + constructor(message: string) { + super(message); + + this.name = "InvalidChainId"; + } +} + +export class ChainId { + private namespace: string; + private reference: string; + + /** + * Creates a validated CAIP-2 compliant chain ID. + * + * @param chainId a CAIP-2 compliant string. + */ + constructor(chainId: string) { + const params = ChainId.parse(chainId); + + this.namespace = params.namespace; + this.reference = params.reference; + } + + /** + * Parses a CAIP-2 compliant string. + * + * @param chainId {string} a CAIP-2 compliant string + * @returns an object containing the namespace and the reference of the chain id + */ + public static parse(chainId: string): ChainIdParams { + const elements = chainId.split(":"); + + if (elements.length !== 2) { + throw new InvalidChainId("A CAIP-2 chain id should have exactly one colon."); + } + + const [namespace, reference] = elements; + + if (namespace === undefined || reference === undefined) { + throw new InvalidChainId("Both elements should be defined."); + } + + const isValidNamespace = NAMESPACE_FORMAT.test(namespace); + if (!isValidNamespace) throw new InvalidChainId("Chain ID namespace is not valid."); + + const isValidReference = REFERENCE_FORMAT.test(reference); + if (!isValidReference) throw new InvalidChainId("Chain ID reference is not valid."); + + return { + namespace, + reference, + }; + } + + public toString() { + return `${this.namespace}:${this.reference}`; + } +} diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts new file mode 100644 index 0000000..8e507d6 --- /dev/null +++ b/packages/blocknumber/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./chainId.js"; diff --git a/packages/blocknumber/test/utils/chainId.spec.ts b/packages/blocknumber/test/utils/chainId.spec.ts new file mode 100644 index 0000000..4e48c92 --- /dev/null +++ b/packages/blocknumber/test/utils/chainId.spec.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; + +import { ChainId, InvalidChainId } from "../../src/utils/chainId.js"; + +describe("ChainId", () => { + describe("constructor", () => { + it("creates a valid chain id instance", () => { + const chainId = new ChainId("eip155:1"); + + expect(chainId).toBeInstanceOf(ChainId); + }); + + it("fails when input chain id is not caip-2 compliant", () => { + const chainId = "foobar"; + + expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); + }); + + it("fails when input namespace is not caip-2 compliant", () => { + const chainId = "f:1"; + + expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); + }); + + it("fails when input reference is not caip-2 compliant", () => { + const chainId = "foo:!nval!d"; + + expect(() => new ChainId(chainId)).toThrowError(InvalidChainId); + }); + }); + + describe("toString", () => { + it("returns a CAIP-2 compliant string", () => { + const ethChainId = "eip155:1"; + const chainId = new ChainId(ethChainId); + + expect(chainId.toString()).toEqual(ethChainId); + }); + }); +}); From 212960ac35ec2e542a08fcacc709aeee765982ed Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 24 Jul 2024 11:55:51 -0300 Subject: [PATCH 11/17] refactor: create exceptions/ folder --- packages/blocknumber/src/exceptions/index.ts | 1 + packages/blocknumber/src/exceptions/invalidChain.ts | 7 +++++++ packages/blocknumber/src/utils/chainId.ts | 10 ++-------- packages/blocknumber/test/utils/chainId.spec.ts | 3 ++- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/index.ts create mode 100644 packages/blocknumber/src/exceptions/invalidChain.ts diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts new file mode 100644 index 0000000..0b490bb --- /dev/null +++ b/packages/blocknumber/src/exceptions/index.ts @@ -0,0 +1 @@ +export * from "./invalidChain.js"; diff --git a/packages/blocknumber/src/exceptions/invalidChain.ts b/packages/blocknumber/src/exceptions/invalidChain.ts new file mode 100644 index 0000000..1e3a128 --- /dev/null +++ b/packages/blocknumber/src/exceptions/invalidChain.ts @@ -0,0 +1,7 @@ +export class InvalidChainId extends Error { + constructor(message: string) { + super(message); + + this.name = "InvalidChainId"; + } +} diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/chainId.ts index ba6394d..4880fb2 100644 --- a/packages/blocknumber/src/utils/chainId.ts +++ b/packages/blocknumber/src/utils/chainId.ts @@ -1,5 +1,7 @@ // Based on https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md +import { InvalidChainId } from "../exceptions/invalidChain.js"; + type ChainNamespace = string; type ChainReference = string; @@ -11,14 +13,6 @@ interface ChainIdParams { const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; -export class InvalidChainId extends Error { - constructor(message: string) { - super(message); - - this.name = "InvalidChainId"; - } -} - export class ChainId { private namespace: string; private reference: string; diff --git a/packages/blocknumber/test/utils/chainId.spec.ts b/packages/blocknumber/test/utils/chainId.spec.ts index 4e48c92..5187675 100644 --- a/packages/blocknumber/test/utils/chainId.spec.ts +++ b/packages/blocknumber/test/utils/chainId.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; -import { ChainId, InvalidChainId } from "../../src/utils/chainId.js"; +import { InvalidChainId } from "../../src/exceptions/invalidChain.js"; +import { ChainId } from "../../src/utils/chainId.js"; describe("ChainId", () => { describe("constructor", () => { From ecd91e970442bd0db0d235af9ce57f6931fa46df Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 23 Jul 2024 18:37:47 -0300 Subject: [PATCH 12/17] feat: search block by timestamp with binsearch --- package.json | 5 +- packages/blocknumber/package.json | 5 +- packages/blocknumber/src/exceptions/index.ts | 3 + .../src/exceptions/timestampNotFound.ts | 7 + .../src/exceptions/unsupportedBlockNumber.ts | 7 + .../exceptions/unsupportedBlockTimestamps.ts | 7 + .../src/providers/blockNumberProvider.ts | 11 + .../blocknumber/src/providers/evmProvider.ts | 146 +++++++ packages/blocknumber/src/utils/chainId.ts | 2 +- packages/blocknumber/src/utils/index.ts | 1 + packages/blocknumber/src/utils/logger.ts | 15 + .../test/providers/evmProvider.spec.ts | 173 ++++++++ pnpm-lock.yaml | 405 +++++++++++++++++- 13 files changed, 783 insertions(+), 4 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/timestampNotFound.ts create mode 100644 packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts create mode 100644 packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts create mode 100644 packages/blocknumber/src/providers/blockNumberProvider.ts create mode 100644 packages/blocknumber/src/providers/evmProvider.ts create mode 100644 packages/blocknumber/src/utils/logger.ts create mode 100644 packages/blocknumber/test/providers/evmProvider.spec.ts diff --git a/package.json b/package.json index abfffdc..1aff387 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,8 @@ "*": "prettier --write --ignore-unknown", "*.js,*.ts": "eslint --fix" }, - "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" + "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903", + "dependencies": { + "winston": "^3.13.1" + } } diff --git a/packages/blocknumber/package.json b/packages/blocknumber/package.json index 90949f5..4e8339b 100644 --- a/packages/blocknumber/package.json +++ b/packages/blocknumber/package.json @@ -15,5 +15,8 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "dependencies": { + "viem": "2.17.10" + } } diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts index 0b490bb..f6e8159 100644 --- a/packages/blocknumber/src/exceptions/index.ts +++ b/packages/blocknumber/src/exceptions/index.ts @@ -1 +1,4 @@ export * from "./invalidChain.js"; +export * from "./timestampNotFound.js"; +export * from "./unsupportedBlockNumber.js"; +export * from "./unsupportedBlockTimestamps.js"; diff --git a/packages/blocknumber/src/exceptions/timestampNotFound.ts b/packages/blocknumber/src/exceptions/timestampNotFound.ts new file mode 100644 index 0000000..ad3251b --- /dev/null +++ b/packages/blocknumber/src/exceptions/timestampNotFound.ts @@ -0,0 +1,7 @@ +export class TimestampNotFound extends Error { + constructor(timestamp: number | bigint) { + super(`No block was processed during ${timestamp}.`); + + this.name = "TimestampNotFound"; + } +} diff --git a/packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts b/packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts new file mode 100644 index 0000000..ace46c2 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unsupportedBlockNumber.ts @@ -0,0 +1,7 @@ +export class UnsupportedBlockNumber extends Error { + constructor(timestamp: bigint) { + super(`Block with null block number at ${timestamp}`); + + this.name = "UnsupportedBlockNumber"; + } +} diff --git a/packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts b/packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts new file mode 100644 index 0000000..91c0f29 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unsupportedBlockTimestamps.ts @@ -0,0 +1,7 @@ +export class UnsupportedBlockTimestamps extends Error { + constructor(timestamp: number | bigint) { + super(`Found multiple blocks at ${timestamp}.`); + + this.name = "UnsupportedBlockTimestamps"; + } +} diff --git a/packages/blocknumber/src/providers/blockNumberProvider.ts b/packages/blocknumber/src/providers/blockNumberProvider.ts new file mode 100644 index 0000000..778ada2 --- /dev/null +++ b/packages/blocknumber/src/providers/blockNumberProvider.ts @@ -0,0 +1,11 @@ +export interface BlockNumberProvider { + /** + * Get the epoch block number on a chain at a specific timestamp. + * + * @param timestamp UTC timestamp in ms since UNIX epoch + * @param url url of the chain data provider + * + * @returns the corresponding block number of a chain at a specific timestamp + */ + getEpochBlockNumber(timestamp: number, searchParams: unknown): Promise; +} diff --git a/packages/blocknumber/src/providers/evmProvider.ts b/packages/blocknumber/src/providers/evmProvider.ts new file mode 100644 index 0000000..3940a6f --- /dev/null +++ b/packages/blocknumber/src/providers/evmProvider.ts @@ -0,0 +1,146 @@ +import { Block, PublicClient } from "viem"; + +import { + TimestampNotFound, + UnsupportedBlockNumber, + UnsupportedBlockTimestamps, +} from "../exceptions/index.js"; +import logger from "../utils/logger.js"; +import { BlockNumberProvider } from "./blockNumberProvider.js"; + +const BINARY_SEARCH_DELTA_MULTIPLIER = 2n; + +type BlockWithNumber = Omit & { number: bigint }; + +export class EvmProvider implements BlockNumberProvider { + client: PublicClient; + + constructor(client: PublicClient) { + this.client = client; + } + + async getEpochBlockNumber( + timestamp: number, + searchParams: { blocksLookback: bigint }, + ): Promise { + const _timestamp = BigInt(timestamp); + const upperBoundBlock = await this.client.getBlock({ blockTag: "finalized" }); + + this.validateBlockNumber(upperBoundBlock); + + logger.info( + `Working with latest block (number: ${upperBoundBlock.number}, timestamp: ${upperBoundBlock.timestamp})...`, + ); + + if (_timestamp >= upperBoundBlock.timestamp) return upperBoundBlock.number; + + const lowerBoundBlock = await this.calculateLowerBoundBlock( + _timestamp, + upperBoundBlock, + searchParams.blocksLookback, + ); + + return this.searchTimestamp(_timestamp, { + fromBlock: lowerBoundBlock.number, + toBlock: upperBoundBlock.number, + }); + } + + private validateBlockNumber(block: Block): block is BlockWithNumber { + if (block.number === null) throw new UnsupportedBlockNumber(block.timestamp); + + return true; + } + + private async calculateLowerBoundBlock( + timestamp: bigint, + lastBlock: BlockWithNumber, + blocksLookback: bigint = 10_000n, + ) { + const estimatedBlockTime = await this.estimateBlockTime(lastBlock, blocksLookback); + const timestampDelta = lastBlock.timestamp - timestamp; + let candidateBlockNumber = lastBlock.number - timestampDelta / estimatedBlockTime; + + const baseStep = (lastBlock.number - candidateBlockNumber) * BINARY_SEARCH_DELTA_MULTIPLIER; + + logger.info("Calculating lower bound for binary search..."); + + let searchCount = 0n; + while (candidateBlockNumber >= 0) { + const candidate = await this.client.getBlock({ blockNumber: candidateBlockNumber }); + + if (candidate.timestamp < timestamp) { + logger.info(`Estimated lower bound at block ${candidate.number}.`); + + return candidate; + } + + searchCount++; + candidateBlockNumber = lastBlock.number - baseStep * 2n ** searchCount; + } + + const firstBlock = await this.client.getBlock({ blockNumber: 0n }); + + if (firstBlock.timestamp <= timestamp) { + return firstBlock; + } + + throw new TimestampNotFound(timestamp); + } + + private async estimateBlockTime(lastBlock: BlockWithNumber, blocksLookback: bigint) { + logger.info("Estimating block time..."); + + const pastBlock = await this.client.getBlock({ + blockNumber: lastBlock.number - BigInt(blocksLookback), + }); + + const estimatedBlockTime = (lastBlock.timestamp - pastBlock.timestamp) / blocksLookback; + + logger.info(`Estimated block time: ${estimatedBlockTime}.`); + + return estimatedBlockTime; + } + + private async searchTimestamp( + timestamp: bigint, + between: { fromBlock: bigint; toBlock: bigint }, + ) { + let currentBlockNumber: bigint; + let { fromBlock: low, toBlock: high } = between; + + logger.debug(`Starting block binary search for timestamp ${timestamp}...`); + + while (low <= high) { + currentBlockNumber = (high + low) / 2n; + + const currentBlock = await this.client.getBlock({ blockNumber: currentBlockNumber }); + const nextBlock = await this.client.getBlock({ blockNumber: currentBlockNumber + 1n }); + + logger.debug( + `Analyzing block number #${currentBlock.number} with timestamp ${currentBlock.timestamp}`, + ); + + // We do not support blocks with equal timestamps (nor non linear or non sequential chains). + // We could support same timestamps blocks by defining a criteria based on block height + // apart from their timestamps. + if (nextBlock.timestamp <= currentBlock.timestamp) + throw new UnsupportedBlockTimestamps(timestamp); + + const blockContainsTimestamp = + currentBlock.timestamp <= timestamp && nextBlock.timestamp > timestamp; + + if (blockContainsTimestamp) { + logger.debug(`Block #${currentBlock.number} contains timestamp.`); + + return currentBlock.number; + } else if (currentBlock.timestamp <= timestamp) { + low = currentBlockNumber + 1n; + } else { + high = currentBlockNumber - 1n; + } + } + + throw new TimestampNotFound(timestamp); + } +} diff --git a/packages/blocknumber/src/utils/chainId.ts b/packages/blocknumber/src/utils/chainId.ts index 4880fb2..2b20a16 100644 --- a/packages/blocknumber/src/utils/chainId.ts +++ b/packages/blocknumber/src/utils/chainId.ts @@ -33,7 +33,7 @@ export class ChainId { * Parses a CAIP-2 compliant string. * * @param chainId {string} a CAIP-2 compliant string - * @returns an object containing the namespace and the reference of the chain id + * @returns an object containing the namespace and the reference of the chain id. */ public static parse(chainId: string): ChainIdParams { const elements = chainId.split(":"); diff --git a/packages/blocknumber/src/utils/index.ts b/packages/blocknumber/src/utils/index.ts index 8e507d6..2697322 100644 --- a/packages/blocknumber/src/utils/index.ts +++ b/packages/blocknumber/src/utils/index.ts @@ -1 +1,2 @@ export * from "./chainId.js"; +export * from "./logger.js"; diff --git a/packages/blocknumber/src/utils/logger.ts b/packages/blocknumber/src/utils/logger.ts new file mode 100644 index 0000000..c18686d --- /dev/null +++ b/packages/blocknumber/src/utils/logger.ts @@ -0,0 +1,15 @@ +import winston from "winston"; + +const logger = winston.createLogger({ + level: "info", + format: winston.format.json(), + defaultMeta: { service: "blocknumber" }, + transports: [ + new winston.transports.Console({ + format: winston.format.simple(), + silent: process.env.NODE_ENV == "test", + }), + ], +}); + +export default logger; diff --git a/packages/blocknumber/test/providers/evmProvider.spec.ts b/packages/blocknumber/test/providers/evmProvider.spec.ts new file mode 100644 index 0000000..6842757 --- /dev/null +++ b/packages/blocknumber/test/providers/evmProvider.spec.ts @@ -0,0 +1,173 @@ +import { Block, createPublicClient, GetBlockParameters, http } from "viem"; +import { mainnet } from "viem/chains"; +import { describe, expect, it, vi } from "vitest"; + +import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; +import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; +import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; +import { EvmProvider } from "../../src/providers/evmProvider.js"; + +describe("EvmProvider", () => { + describe("getEpochBlockNumber", () => { + let evmProvider: EvmProvider; + + it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { + const blockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); + const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5, { + blocksLookback: 2n, + }); + + expect(epochBlockNumber).toEqual(4n); + }); + + it("returns the block number when the timestamp is equal to block's timestamp", async () => { + const lastBlockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); + const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5, { + blocksLookback: 2n, + }); + + expect(epochBlockNumber).toEqual(4n); + }); + + it("returns the last block if timestamp is after the block's timestamp", async () => { + const lastBlockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); + const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); + + const blockNumber = await evmProvider.getEpochBlockNumber(futureTimestamp, { + blocksLookback: 2n, + }); + + expect(blockNumber).toEqual(lastBlockNumber); + }); + + it("fails if the timestamp is before the first block", async () => { + const lastBlockNumber = 10n; + const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); + const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); + + evmProvider = new EvmProvider(rpcProvider); + + const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); + + expect( + evmProvider.getEpochBlockNumber(futureTimestamp, { + blocksLookback: 2n, + }), + ).rejects.toBeInstanceOf(TimestampNotFound); + }); + + it("fails when finding multiple blocks with the same timestamp", () => { + const timestamp = BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)); + const afterTimestamp = BigInt(Date.UTC(2024, 1, 2, 0, 0, 0, 0)); + const rpcProvider = mockRpcProviderBlocks([ + { number: 0n, timestamp: timestamp }, + { number: 1n, timestamp: timestamp }, + { number: 2n, timestamp: timestamp }, + { number: 3n, timestamp: timestamp }, + { number: 4n, timestamp: afterTimestamp }, + ]); + + evmProvider = new EvmProvider(rpcProvider); + + expect( + evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), + ).rejects.toBeInstanceOf(UnsupportedBlockTimestamps); + }); + + it("fails when finding a block with no number", () => { + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + const rpcProvider = mockRpcProviderBlocks([ + { number: null, timestamp: BigInt(timestamp) }, + ]); + + evmProvider = new EvmProvider(rpcProvider); + + expect( + evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), + ).rejects.toBeInstanceOf(UnsupportedBlockNumber); + }); + + it("fails when the data provider fails", () => { + const client = createPublicClient({ chain: mainnet, transport: http() }); + + client.getBlock = vi.fn().mockRejectedValue(null); + + evmProvider = new EvmProvider(client); + const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); + + expect( + evmProvider.getEpochBlockNumber(timestamp, { blocksLookback: 2n }), + ).rejects.toBeDefined(); + }); + + it("fails when the chain did not reach to the block yet", async () => {}); + }); +}); + +function mockRpcProvider(lastBlock: bigint, startTimestamp: number, endTimestamp: number) { + const chainDuration = endTimestamp - startTimestamp; + const blockDuration = BigInt(chainDuration) / lastBlock; + + const rpcProvider = createPublicClient({ chain: mainnet, transport: http() }); + + rpcProvider.getBlock = vi + .fn() + .mockImplementation((args?: GetBlockParameters | undefined) => { + if (args?.blockTag == "finalized") { + return Promise.resolve({ + timestamp: BigInt(endTimestamp), + number: lastBlock, + }); + } else if (args?.blockNumber !== undefined) { + const blockNumber = args.blockNumber; + const blockTimestamp = BigInt(startTimestamp) + blockNumber * blockDuration; + + return Promise.resolve({ timestamp: blockTimestamp, number: blockNumber }); + } + + throw new Error("Unhandled getBlock mock case"); + }); + + return rpcProvider; +} + +function mockRpcProviderBlocks(blocks: Pick[]) { + const rpcProvider = createPublicClient({ chain: mainnet, transport: http() }); + + rpcProvider.getBlock = vi + .fn() + .mockImplementation((args?: GetBlockParameters | undefined) => { + if (args?.blockTag == "finalized") { + return Promise.resolve(blocks[blocks.length - 1]); + } else if (args?.blockNumber !== undefined) { + const blockNumber = Number(args.blockNumber); + + return Promise.resolve(blocks[blockNumber]); + } + + throw new Error("Unhandled getBlock mock case"); + }); + + return rpcProvider; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56d01ae..a78c010 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,10 @@ settings: importers: .: + dependencies: + winston: + specifier: ^3.13.1 + version: 3.13.1 devDependencies: "@commitlint/cli": specifier: 19.3.0 @@ -53,9 +57,19 @@ importers: specifier: 2.0.3 version: 2.0.3(@types/node@20.14.11) - packages/blocknumber: {} + packages/blocknumber: + dependencies: + viem: + specifier: ^2.17.10 + version: 2.17.10(typescript@5.5.3) packages: + "@adraffy/ens-normalize@1.10.0": + resolution: + { + integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==, + } + "@ampproject/remapping@2.3.0": resolution: { @@ -219,6 +233,13 @@ packages: integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, } + "@colors/colors@1.6.0": + resolution: + { + integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==, + } + engines: { node: ">=0.1.90" } + "@commitlint/cli@19.3.0": resolution: { @@ -339,6 +360,12 @@ packages: } engines: { node: ">=v18" } + "@dabh/diagnostics@2.0.3": + resolution: + { + integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==, + } + "@esbuild/aix-ppc64@0.21.5": resolution: { @@ -657,6 +684,19 @@ packages: integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, } + "@noble/curves@1.4.0": + resolution: + { + integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==, + } + + "@noble/hashes@1.4.0": + resolution: + { + integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==, + } + engines: { node: ">= 16" } + "@nodelib/fs.scandir@2.1.5": resolution: { @@ -820,6 +860,24 @@ packages: cpu: [x64] os: [win32] + "@scure/base@1.1.7": + resolution: + { + integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==, + } + + "@scure/bip32@1.4.0": + resolution: + { + integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==, + } + + "@scure/bip39@1.3.0": + resolution: + { + integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==, + } + "@types/conventional-commits-parser@5.0.0": resolution: { @@ -838,6 +896,12 @@ packages: integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==, } + "@types/triple-beam@1.3.5": + resolution: + { + integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==, + } + "@typescript-eslint/eslint-plugin@7.16.1": resolution: { @@ -977,6 +1041,20 @@ packages: } hasBin: true + abitype@1.0.5: + resolution: + { + integrity: sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==, + } + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + acorn-jsx@5.3.2: resolution: { @@ -1073,6 +1151,12 @@ packages: } engines: { node: ">=12" } + async@3.2.5: + resolution: + { + integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==, + } + balanced-match@1.0.2: resolution: { @@ -1207,12 +1291,30 @@ packages: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, } + color-string@1.9.1: + resolution: + { + integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, + } + + color@3.2.1: + resolution: + { + integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==, + } + colorette@2.0.20: resolution: { integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, } + colorspace@1.1.4: + resolution: + { + integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==, + } + commander@12.1.0: resolution: { @@ -1373,6 +1475,12 @@ packages: integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, } + enabled@2.0.0: + resolution: + { + integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==, + } + env-paths@2.2.1: resolution: { @@ -1560,6 +1668,12 @@ packages: integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==, } + fecha@4.2.3: + resolution: + { + integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==, + } + file-entry-cache@6.0.1: resolution: { @@ -1601,6 +1715,12 @@ packages: integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, } + fn.name@1.1.0: + resolution: + { + integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==, + } + foreground-child@3.2.1: resolution: { @@ -1814,6 +1934,12 @@ packages: integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, } + is-arrayish@0.3.2: + resolution: + { + integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==, + } + is-extglob@2.1.1: resolution: { @@ -1870,6 +1996,13 @@ packages: } engines: { node: ">=8" } + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, + } + engines: { node: ">=8" } + is-stream@3.0.0: resolution: { @@ -1890,6 +2023,14 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + isows@1.0.4: + resolution: + { + integrity: sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==, + } + peerDependencies: + ws: "*" + istanbul-lib-coverage@3.2.2: resolution: { @@ -2009,6 +2150,12 @@ packages: integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, } + kuler@2.0.0: + resolution: + { + integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==, + } + levn@0.4.1: resolution: { @@ -2119,6 +2266,13 @@ packages: } engines: { node: ">=18" } + logform@2.6.1: + resolution: + { + integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==, + } + engines: { node: ">= 12.0.0" } + loupe@3.1.1: resolution: { @@ -2262,6 +2416,12 @@ packages: integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, } + one-time@1.0.0: + resolution: + { + integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==, + } + onetime@5.1.2: resolution: { @@ -2456,6 +2616,13 @@ packages: integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, } + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: ">= 6" } + require-directory@2.1.1: resolution: { @@ -2526,6 +2693,19 @@ packages: integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, } + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } + + safe-stable-stringify@2.4.3: + resolution: + { + integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==, + } + engines: { node: ">=10" } + semver@6.3.1: resolution: { @@ -2574,6 +2754,12 @@ packages: } engines: { node: ">=14" } + simple-swizzle@0.2.2: + resolution: + { + integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==, + } + slash@3.0.0: resolution: { @@ -2609,6 +2795,12 @@ packages: } engines: { node: ">= 10.x" } + stack-trace@0.0.10: + resolution: + { + integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==, + } + stackback@0.0.2: resolution: { @@ -2649,6 +2841,12 @@ packages: } engines: { node: ">=18" } + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + strip-ansi@6.0.1: resolution: { @@ -2718,6 +2916,12 @@ packages: } engines: { node: ">=8" } + text-hex@1.0.0: + resolution: + { + integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==, + } + text-table@0.2.0: resolution: { @@ -2771,6 +2975,13 @@ packages: } engines: { node: ">=8.0" } + triple-beam@1.4.1: + resolution: + { + integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==, + } + engines: { node: ">= 14.0.0" } + ts-api-utils@1.3.0: resolution: { @@ -2891,6 +3102,23 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, } + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + viem@2.17.10: + resolution: + { + integrity: sha512-nubTeRBI3wsmYGemlg9PsbhunTSJSYUlq8VjzIQkbowCSPSd1bqzVeUU0qEOXsGtgRiFfBzXkkxFknZtBOAx/Q==, + } + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + vite-node@2.0.3: resolution: { @@ -2974,6 +3202,20 @@ packages: engines: { node: ">=8" } hasBin: true + winston-transport@4.7.1: + resolution: + { + integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==, + } + engines: { node: ">= 12.0.0" } + + winston@3.13.1: + resolution: + { + integrity: sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==, + } + engines: { node: ">= 12.0.0" } + word-wrap@1.2.5: resolution: { @@ -3008,6 +3250,21 @@ packages: integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, } + ws@8.17.1: + resolution: + { + integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==, + } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: { @@ -3058,6 +3315,8 @@ packages: engines: { node: ">=12.20" } snapshots: + "@adraffy/ens-normalize@1.10.0": {} + "@ampproject/remapping@2.3.0": dependencies: "@jridgewell/gen-mapping": 0.3.5 @@ -3198,6 +3457,8 @@ snapshots: "@bcoe/v8-coverage@0.2.3": {} + "@colors/colors@1.6.0": {} + "@commitlint/cli@19.3.0(@types/node@20.14.11)(typescript@5.5.3)": dependencies: "@commitlint/format": 19.3.0 @@ -3309,6 +3570,12 @@ snapshots: "@types/conventional-commits-parser": 5.0.0 chalk: 5.3.0 + "@dabh/diagnostics@2.0.3": + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + "@esbuild/aix-ppc64@0.21.5": optional: true @@ -3453,6 +3720,12 @@ snapshots: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.0 + "@noble/curves@1.4.0": + dependencies: + "@noble/hashes": 1.4.0 + + "@noble/hashes@1.4.0": {} + "@nodelib/fs.scandir@2.1.5": dependencies: "@nodelib/fs.stat": 2.0.5 @@ -3518,6 +3791,19 @@ snapshots: "@rollup/rollup-win32-x64-msvc@4.18.1": optional: true + "@scure/base@1.1.7": {} + + "@scure/bip32@1.4.0": + dependencies: + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/base": 1.1.7 + + "@scure/bip39@1.3.0": + dependencies: + "@noble/hashes": 1.4.0 + "@scure/base": 1.1.7 + "@types/conventional-commits-parser@5.0.0": dependencies: "@types/node": 20.14.11 @@ -3528,6 +3814,8 @@ snapshots: dependencies: undici-types: 5.26.5 + "@types/triple-beam@1.3.5": {} + "@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)": dependencies: "@eslint-community/regexpp": 4.11.0 @@ -3668,6 +3956,10 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + abitype@1.0.5(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -3712,6 +4004,8 @@ snapshots: assertion-error@2.0.1: {} + async@3.2.5: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -3790,8 +4084,23 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + colorette@2.0.20: {} + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + commander@12.1.0: {} compare-func@2.0.0: @@ -3872,6 +4181,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + env-paths@2.2.1: {} error-ex@1.3.2: @@ -4031,6 +4342,8 @@ snapshots: dependencies: reusify: 1.0.4 + fecha@4.2.3: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -4058,6 +4371,8 @@ snapshots: flatted@3.3.1: {} + fn.name@1.1.0: {} + foreground-child@3.2.1: dependencies: cross-spawn: 7.0.3 @@ -4163,6 +4478,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4183,6 +4500,8 @@ snapshots: is-path-inside@3.0.3: {} + is-stream@2.0.1: {} + is-stream@3.0.0: {} is-text-path@2.0.0: @@ -4191,6 +4510,10 @@ snapshots: isexe@2.0.0: {} + isows@1.0.4(ws@8.17.1): + dependencies: + ws: 8.17.1 + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -4248,6 +4571,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kuler@2.0.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -4315,6 +4640,15 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 + logform@2.6.1: + dependencies: + "@colors/colors": 1.6.0 + "@types/triple-beam": 1.3.5 + fecha: 4.2.3 + ms: 2.1.2 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + loupe@3.1.1: dependencies: get-func-name: 2.0.2 @@ -4382,6 +4716,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -4473,6 +4811,12 @@ snapshots: queue-microtask@1.2.3: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -4520,6 +4864,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.4.3: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -4536,6 +4884,10 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + slash@3.0.0: {} slice-ansi@5.0.0: @@ -4552,6 +4904,8 @@ snapshots: split2@4.2.0: {} + stack-trace@0.0.10: {} + stackback@0.0.2: {} std-env@3.7.0: {} @@ -4576,6 +4930,10 @@ snapshots: get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4613,6 +4971,8 @@ snapshots: text-extensions@2.4.0: {} + text-hex@1.0.0: {} + text-table@0.2.0: {} through@2.3.8: {} @@ -4631,6 +4991,8 @@ snapshots: dependencies: is-number: 7.0.0 + triple-beam@1.4.1: {} + ts-api-utils@1.3.0(typescript@5.5.3): dependencies: typescript: 5.5.3 @@ -4686,6 +5048,25 @@ snapshots: dependencies: punycode: 2.3.1 + util-deprecate@1.0.2: {} + + viem@2.17.10(typescript@5.5.3): + dependencies: + "@adraffy/ens-normalize": 1.10.0 + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/bip32": 1.4.0 + "@scure/bip39": 1.3.0 + abitype: 1.0.5(typescript@5.5.3) + isows: 1.0.4(ws@8.17.1) + ws: 8.17.1 + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-node@2.0.3(@types/node@20.14.11): dependencies: cac: 6.7.14 @@ -4753,6 +5134,26 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + winston-transport@4.7.1: + dependencies: + logform: 2.6.1 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.13.1: + dependencies: + "@colors/colors": 1.6.0 + "@dabh/diagnostics": 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.1 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -4775,6 +5176,8 @@ snapshots: wrappy@1.0.2: {} + ws@8.17.1: {} + y18n@5.0.8: {} yallist@3.1.1: {} From 4a38c039601a59e118f685eba2fc37116a273add Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 24 Jul 2024 16:23:56 -0300 Subject: [PATCH 13/17] fix: fixed winston dependency version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1aff387..92defad 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ }, "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903", "dependencies": { - "winston": "^3.13.1" + "winston": "3.13.1" } } From 711216dbb9f87484d510a762aea3540ce67c29cf Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 24 Jul 2024 16:56:21 -0300 Subject: [PATCH 14/17] refactor: rename evmProvider to evmBlockNumberProvider --- ...mProvider.ts => evmBlockNumberProvider.ts} | 2 +- ...spec.ts => evmBlockNumberProvider.spec.ts} | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) rename packages/blocknumber/src/providers/{evmProvider.ts => evmBlockNumberProvider.ts} (98%) rename packages/blocknumber/test/providers/{evmProvider.spec.ts => evmBlockNumberProvider.spec.ts} (91%) diff --git a/packages/blocknumber/src/providers/evmProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts similarity index 98% rename from packages/blocknumber/src/providers/evmProvider.ts rename to packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 3940a6f..283d49f 100644 --- a/packages/blocknumber/src/providers/evmProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -12,7 +12,7 @@ const BINARY_SEARCH_DELTA_MULTIPLIER = 2n; type BlockWithNumber = Omit & { number: bigint }; -export class EvmProvider implements BlockNumberProvider { +export class EvmBlockNumberProvider implements BlockNumberProvider { client: PublicClient; constructor(client: PublicClient) { diff --git a/packages/blocknumber/test/providers/evmProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts similarity index 91% rename from packages/blocknumber/test/providers/evmProvider.spec.ts rename to packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 6842757..6fd0184 100644 --- a/packages/blocknumber/test/providers/evmProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -5,11 +5,11 @@ import { describe, expect, it, vi } from "vitest"; import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; -import { EvmProvider } from "../../src/providers/evmProvider.js"; +import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; -describe("EvmProvider", () => { +describe("EvmBlockNumberProvider", () => { describe("getEpochBlockNumber", () => { - let evmProvider: EvmProvider; + let evmProvider: EvmBlockNumberProvider; it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { const blockNumber = 10n; @@ -17,7 +17,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5, { @@ -33,7 +33,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5, { @@ -49,7 +49,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); @@ -66,7 +66,7 @@ describe("EvmProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); @@ -88,7 +88,7 @@ describe("EvmProvider", () => { { number: 4n, timestamp: afterTimestamp }, ]); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); expect( evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), @@ -101,7 +101,7 @@ describe("EvmProvider", () => { { number: null, timestamp: BigInt(timestamp) }, ]); - evmProvider = new EvmProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider); expect( evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), @@ -113,7 +113,7 @@ describe("EvmProvider", () => { client.getBlock = vi.fn().mockRejectedValue(null); - evmProvider = new EvmProvider(client); + evmProvider = new EvmBlockNumberProvider(client); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); expect( From c7a9035c2617a09842d8eaaef81d7d237e05b51a Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 13:32:53 -0300 Subject: [PATCH 15/17] refactor: client and search params in evm provider constructor --- .../src/exceptions/lastBlockEpoch.ts | 11 ++ .../src/exceptions/unexpectedSearchRange.ts | 9 ++ .../src/providers/blockNumberProvider.ts | 8 +- .../src/providers/evmBlockNumberProvider.ts | 128 +++++++++++++++--- .../providers/evmBlockNumberProvider.spec.ts | 60 ++++---- 5 files changed, 158 insertions(+), 58 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/lastBlockEpoch.ts create mode 100644 packages/blocknumber/src/exceptions/unexpectedSearchRange.ts diff --git a/packages/blocknumber/src/exceptions/lastBlockEpoch.ts b/packages/blocknumber/src/exceptions/lastBlockEpoch.ts new file mode 100644 index 0000000..40ff8bd --- /dev/null +++ b/packages/blocknumber/src/exceptions/lastBlockEpoch.ts @@ -0,0 +1,11 @@ +import { Block } from "viem"; + +export class LastBlockEpoch extends Error { + constructor(block: Block) { + super( + `Cannot specify the start of the epoch with the last block only (number: ${block.number}), wait for it to be finalized.`, + ); + + this.name = "LastBlockEpoch"; + } +} diff --git a/packages/blocknumber/src/exceptions/unexpectedSearchRange.ts b/packages/blocknumber/src/exceptions/unexpectedSearchRange.ts new file mode 100644 index 0000000..1811c18 --- /dev/null +++ b/packages/blocknumber/src/exceptions/unexpectedSearchRange.ts @@ -0,0 +1,9 @@ +export class UnexpectedSearchRange extends Error { + constructor(low: bigint, high: bigint) { + super( + `Lower bound of search range (${low}) must be less than or equal to upper bound (${high})`, + ); + + this.name = "UnexpectedSearchRange"; + } +} diff --git a/packages/blocknumber/src/providers/blockNumberProvider.ts b/packages/blocknumber/src/providers/blockNumberProvider.ts index 778ada2..0b3b74e 100644 --- a/packages/blocknumber/src/providers/blockNumberProvider.ts +++ b/packages/blocknumber/src/providers/blockNumberProvider.ts @@ -1,11 +1,13 @@ export interface BlockNumberProvider { /** - * Get the epoch block number on a chain at a specific timestamp. + * Get the block number corresponding to the beginning of the epoch. + * + * The input timestamp falls between the timestamps of the found block and + * the immediately following block. * * @param timestamp UTC timestamp in ms since UNIX epoch - * @param url url of the chain data provider * * @returns the corresponding block number of a chain at a specific timestamp */ - getEpochBlockNumber(timestamp: number, searchParams: unknown): Promise; + getEpochBlockNumber(timestamp: number): Promise; } diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 283d49f..9568f96 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -1,29 +1,63 @@ -import { Block, PublicClient } from "viem"; +import { Block, fromBlobs, PublicClient } from "viem"; import { TimestampNotFound, UnsupportedBlockNumber, UnsupportedBlockTimestamps, } from "../exceptions/index.js"; +import { LastBlockEpoch } from "../exceptions/lastBlockEpoch.js"; +import { UnexpectedSearchRange } from "../exceptions/unexpectedSearchRange.js"; import logger from "../utils/logger.js"; import { BlockNumberProvider } from "./blockNumberProvider.js"; +const BINARY_SEARCH_BLOCKS_LOOKBACK = 10_000n; const BINARY_SEARCH_DELTA_MULTIPLIER = 2n; type BlockWithNumber = Omit & { number: bigint }; -export class EvmBlockNumberProvider implements BlockNumberProvider { - client: PublicClient; +interface SearchConfig { + /** + * Indicates how many blocks should be used for estimating the chain's block time + */ + blocksLookback: bigint; + + /** + * Multiplier to apply to the step, used while scanning blocks backwards, to find a + * lower bound block. + */ + deltaMultiplier: bigint; +} - constructor(client: PublicClient) { +export class EvmBlockNumberProvider implements BlockNumberProvider { + private client: PublicClient; + private searchConfig: SearchConfig; + private firstBlock: Block; + + /** + * Creates a new instance of PublicClient. + * + * @param client the viem client to use for EVM compatible RPC node calls. + * @param searchConfig.blocksLookback amount of blocks that should be used for + * estimating the chain's block time. Defaults to 10.000 blocks. + * @param searchConfig.deltaMultiplier multiplier to apply to the step, used + * while scanning blocks backwards during lower bound search. Defaults to 2. + */ + constructor( + client: PublicClient, + searchConfig: { blocksLookback?: bigint; deltaMultiplier?: bigint }, + ) { this.client = client; + this.searchConfig = { + blocksLookback: searchConfig.blocksLookback ?? BINARY_SEARCH_BLOCKS_LOOKBACK, + deltaMultiplier: searchConfig.deltaMultiplier ?? BINARY_SEARCH_DELTA_MULTIPLIER, + }; } - async getEpochBlockNumber( - timestamp: number, - searchParams: { blocksLookback: bigint }, - ): Promise { + async getEpochBlockNumber(timestamp: number): Promise { + // An optimized binary search is used to look for the epoch block. const _timestamp = BigInt(timestamp); + + // The EBO agent looks only for finalized blocks to avoid handling reorgs const upperBoundBlock = await this.client.getBlock({ blockTag: "finalized" }); this.validateBlockNumber(upperBoundBlock); @@ -32,36 +66,71 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { `Working with latest block (number: ${upperBoundBlock.number}, timestamp: ${upperBoundBlock.timestamp})...`, ); - if (_timestamp >= upperBoundBlock.timestamp) return upperBoundBlock.number; + const firstBlock = await this.getFirstBlock(); - const lowerBoundBlock = await this.calculateLowerBoundBlock( - _timestamp, - upperBoundBlock, - searchParams.blocksLookback, - ); + if (_timestamp < firstBlock.timestamp) throw new TimestampNotFound(_timestamp); + if (_timestamp >= upperBoundBlock.timestamp) throw new LastBlockEpoch(upperBoundBlock); + // Reduces the search space by estimating a lower bound for the binary search. + // + // Performing a binary search between block 0 and last block is not efficient. + const lowerBoundBlock = await this.calculateLowerBoundBlock(_timestamp, upperBoundBlock); + + // Searches for the timestamp with a binary search return this.searchTimestamp(_timestamp, { fromBlock: lowerBoundBlock.number, toBlock: upperBoundBlock.number, }); } + /** + * Fetches and caches the first block. Cached block will be returned if the cache is hit. + * + * @returns the chain's first block + */ + private async getFirstBlock(): Promise { + if (this.firstBlock !== undefined) return this.firstBlock; + + this.firstBlock = await this.client.getBlock({ blockNumber: 0n }); + + return this.firstBlock; + } + + /** + * Validates that a block contains a non-null number + * + * @param block viem block + * @throws {UnsupportedBlockNumber} when block contains a null number + * @returns true if the block contains a non-null number + */ private validateBlockNumber(block: Block): block is BlockWithNumber { if (block.number === null) throw new UnsupportedBlockNumber(block.timestamp); return true; } - private async calculateLowerBoundBlock( - timestamp: bigint, - lastBlock: BlockWithNumber, - blocksLookback: bigint = 10_000n, - ) { + /** + * Searches for an efficient lower bound to run the binary search, leveraging that + * the epoch start tends to be relatively near the last block. + * + * The amount of blocks to look back from the last block is estimated, using an + * estimated block-time based on the last `searchConfig.blocksLookback` blocks. + * + * Until a block with a timestamp before the input timestamp is found, backward + * exponentially grown steps are performed. + * + * @param timestamp timestamp of the epoch start + * @param lastBlock last block of the chain + * @returns an optimized lower bound for a binary search space + */ + private async calculateLowerBoundBlock(timestamp: bigint, lastBlock: BlockWithNumber) { + const { blocksLookback, deltaMultiplier } = this.searchConfig; + const estimatedBlockTime = await this.estimateBlockTime(lastBlock, blocksLookback); const timestampDelta = lastBlock.timestamp - timestamp; let candidateBlockNumber = lastBlock.number - timestampDelta / estimatedBlockTime; - const baseStep = (lastBlock.number - candidateBlockNumber) * BINARY_SEARCH_DELTA_MULTIPLIER; + const baseStep = (lastBlock.number - candidateBlockNumber) * deltaMultiplier; logger.info("Calculating lower bound for binary search..."); @@ -88,6 +157,13 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { throw new TimestampNotFound(timestamp); } + /** + * Estimates the chain's block time based on the last `blocksLookback` blocks. + * + * @param lastBlock last chain block + * @param blocksLookback amount of blocks to look back + * @returns the estimated block time + */ private async estimateBlockTime(lastBlock: BlockWithNumber, blocksLookback: bigint) { logger.info("Estimating block time..."); @@ -102,6 +178,16 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { return estimatedBlockTime; } + /** + * Performs a binary search in the specified block range to find the block corresponding to a timestamp. + * + * @param timestamp timestamp to find the block for + * @param between blocks search space + * @throws {UnsupportedBlockTimestamps} when two consecutive blocks with the same timestamp are found + * during the search. These chains are not supported at the moment. + * @throws {TimestampNotFound} when the search is finished and no block includes the searched timestamp + * @returns the block number + */ private async searchTimestamp( timestamp: bigint, between: { fromBlock: bigint; toBlock: bigint }, @@ -109,6 +195,8 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { let currentBlockNumber: bigint; let { fromBlock: low, toBlock: high } = between; + if (low > high) throw new UnexpectedSearchRange(low, high); + logger.debug(`Starting block binary search for timestamp ${timestamp}...`); while (low <= high) { diff --git a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 6fd0184..968a0ec 100644 --- a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -2,6 +2,7 @@ import { Block, createPublicClient, GetBlockParameters, http } from "viem"; import { mainnet } from "viem/chains"; import { describe, expect, it, vi } from "vitest"; +import { LastBlockEpoch } from "../../src/exceptions/lastBlockEpoch.js"; import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; @@ -9,6 +10,7 @@ import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvid describe("EvmBlockNumberProvider", () => { describe("getEpochBlockNumber", () => { + const searchConfig = { blocksLookback: 2n, deltaMultiplier: 2n }; let evmProvider: EvmBlockNumberProvider; it("returns the first of two consecutive blocks when their timestamp contains the searched timestamp", async () => { @@ -17,12 +19,10 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 11, 0, 0, 0, 0); const rpcProvider = mockRpcProvider(blockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const day5 = Date.UTC(2024, 1, 5, 2, 0, 0, 0); - const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5, { - blocksLookback: 2n, - }); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(day5); expect(epochBlockNumber).toEqual(4n); }); @@ -33,31 +33,27 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const exactDay5 = Date.UTC(2024, 1, 1, 0, 0, 5, 0); - const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5, { - blocksLookback: 2n, - }); + const epochBlockNumber = await evmProvider.getEpochBlockNumber(exactDay5); expect(epochBlockNumber).toEqual(4n); }); - it("returns the last block if timestamp is after the block's timestamp", async () => { + it("throws if the search timestamp is after the last block's timestamp", async () => { const lastBlockNumber = 10n; const startTimestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const futureTimestamp = Date.UTC(2025, 1, 1, 0, 0, 0, 0); - const blockNumber = await evmProvider.getEpochBlockNumber(futureTimestamp, { - blocksLookback: 2n, - }); - - expect(blockNumber).toEqual(lastBlockNumber); + expect(evmProvider.getEpochBlockNumber(futureTimestamp)).rejects.toBeInstanceOf( + LastBlockEpoch, + ); }); it("fails if the timestamp is before the first block", async () => { @@ -66,15 +62,13 @@ describe("EvmBlockNumberProvider", () => { const endTimestamp = Date.UTC(2024, 1, 1, 0, 0, 11, 0); const rpcProvider = mockRpcProvider(lastBlockNumber, startTimestamp, endTimestamp); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); - expect( - evmProvider.getEpochBlockNumber(futureTimestamp, { - blocksLookback: 2n, - }), - ).rejects.toBeInstanceOf(TimestampNotFound); + expect(evmProvider.getEpochBlockNumber(futureTimestamp)).rejects.toBeInstanceOf( + TimestampNotFound, + ); }); it("fails when finding multiple blocks with the same timestamp", () => { @@ -88,11 +82,11 @@ describe("EvmBlockNumberProvider", () => { { number: 4n, timestamp: afterTimestamp }, ]); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); - expect( - evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), - ).rejects.toBeInstanceOf(UnsupportedBlockTimestamps); + expect(evmProvider.getEpochBlockNumber(Number(timestamp))).rejects.toBeInstanceOf( + UnsupportedBlockTimestamps, + ); }); it("fails when finding a block with no number", () => { @@ -101,11 +95,11 @@ describe("EvmBlockNumberProvider", () => { { number: null, timestamp: BigInt(timestamp) }, ]); - evmProvider = new EvmBlockNumberProvider(rpcProvider); + evmProvider = new EvmBlockNumberProvider(rpcProvider, searchConfig); - expect( - evmProvider.getEpochBlockNumber(Number(timestamp), { blocksLookback: 2n }), - ).rejects.toBeInstanceOf(UnsupportedBlockNumber); + expect(evmProvider.getEpochBlockNumber(Number(timestamp))).rejects.toBeInstanceOf( + UnsupportedBlockNumber, + ); }); it("fails when the data provider fails", () => { @@ -113,15 +107,11 @@ describe("EvmBlockNumberProvider", () => { client.getBlock = vi.fn().mockRejectedValue(null); - evmProvider = new EvmBlockNumberProvider(client); + evmProvider = new EvmBlockNumberProvider(client, searchConfig); const timestamp = Date.UTC(2024, 1, 1, 0, 0, 0, 0); - expect( - evmProvider.getEpochBlockNumber(timestamp, { blocksLookback: 2n }), - ).rejects.toBeDefined(); + expect(evmProvider.getEpochBlockNumber(timestamp)).rejects.toBeDefined(); }); - - it("fails when the chain did not reach to the block yet", async () => {}); }); }); From 5ad13690dc0149f27f7644d39a8339b0bf486477 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 13:43:36 -0300 Subject: [PATCH 16/17] fix: pnpm dependecies mismatch --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a78c010..86ac1c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,7 @@ importers: .: dependencies: winston: - specifier: ^3.13.1 + specifier: 3.13.1 version: 3.13.1 devDependencies: "@commitlint/cli": @@ -60,7 +60,7 @@ importers: packages/blocknumber: dependencies: viem: - specifier: ^2.17.10 + specifier: 2.17.10 version: 2.17.10(typescript@5.5.3) packages: From 6875aeacb95d8dc462b876e72696980686287eed Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Thu, 25 Jul 2024 16:26:53 -0300 Subject: [PATCH 17/17] fix: throw InvalidTimestamp for timestamps prior first block --- packages/blocknumber/src/exceptions/index.ts | 1 + .../blocknumber/src/exceptions/invalidTimestamp.ts | 7 +++++++ .../src/providers/evmBlockNumberProvider.ts | 3 ++- .../test/providers/evmBlockNumberProvider.spec.ts | 12 +++++++----- 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 packages/blocknumber/src/exceptions/invalidTimestamp.ts diff --git a/packages/blocknumber/src/exceptions/index.ts b/packages/blocknumber/src/exceptions/index.ts index a9718bc..0354a89 100644 --- a/packages/blocknumber/src/exceptions/index.ts +++ b/packages/blocknumber/src/exceptions/index.ts @@ -1,4 +1,5 @@ export * from "./invalidChain.js"; +export * from "./invalidTimestamp.js"; export * from "./lastBlockEpoch.js"; export * from "./timestampNotFound.js"; export * from "./unexpectedSearchRange.js"; diff --git a/packages/blocknumber/src/exceptions/invalidTimestamp.ts b/packages/blocknumber/src/exceptions/invalidTimestamp.ts new file mode 100644 index 0000000..1eac699 --- /dev/null +++ b/packages/blocknumber/src/exceptions/invalidTimestamp.ts @@ -0,0 +1,7 @@ +export class InvalidTimestamp extends Error { + constructor(timestamp: number | bigint) { + super(`Timestamp ${timestamp} is prior the timestamp of the first block.`); + + this.name = "InvalidTimestamp"; + } +} diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 7dd2c08..8bbf88f 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -1,6 +1,7 @@ import { Block, PublicClient } from "viem"; import { + InvalidTimestamp, LastBlockEpoch, TimestampNotFound, UnexpectedSearchRange, @@ -69,7 +70,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { const firstBlock = await this.getFirstBlock(); - if (_timestamp < firstBlock.timestamp) throw new TimestampNotFound(_timestamp); + if (_timestamp < firstBlock.timestamp) throw new InvalidTimestamp(_timestamp); if (_timestamp >= upperBoundBlock.timestamp) throw new LastBlockEpoch(upperBoundBlock); // Reduces the search space by estimating a lower bound for the binary search. diff --git a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts index 968a0ec..e2ed814 100644 --- a/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts +++ b/packages/blocknumber/test/providers/evmBlockNumberProvider.spec.ts @@ -2,10 +2,12 @@ import { Block, createPublicClient, GetBlockParameters, http } from "viem"; import { mainnet } from "viem/chains"; import { describe, expect, it, vi } from "vitest"; -import { LastBlockEpoch } from "../../src/exceptions/lastBlockEpoch.js"; -import { TimestampNotFound } from "../../src/exceptions/timestampNotFound.js"; -import { UnsupportedBlockNumber } from "../../src/exceptions/unsupportedBlockNumber.js"; -import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; +import { + InvalidTimestamp, + LastBlockEpoch, + UnsupportedBlockNumber, + UnsupportedBlockTimestamps, +} from "../../src/exceptions/index.js"; import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider.js"; describe("EvmBlockNumberProvider", () => { @@ -67,7 +69,7 @@ describe("EvmBlockNumberProvider", () => { const futureTimestamp = Date.UTC(1970, 1, 1, 0, 0, 0, 0); expect(evmProvider.getEpochBlockNumber(futureTimestamp)).rejects.toBeInstanceOf( - TimestampNotFound, + InvalidTimestamp, ); });