diff --git a/.circleci/nightly.yml b/.circleci/nightly.yml index 72af6b2c0..934f0edf0 100644 --- a/.circleci/nightly.yml +++ b/.circleci/nightly.yml @@ -50,11 +50,11 @@ workflows: - monitor-e2e-holesky - verification-e2e-sepolia - verification-e2e-holesky - s3-backup-check: - when: - equal: [ master, << pipeline.git.branch >> ] - jobs: - - check-s3-backup + # s3-backup-check: + # when: + # equal: [ master, << pipeline.git.branch >> ] + # jobs: + # - check-s3-backup etherscan-instances: jobs: - check-etherscan-instances diff --git a/.circleci/scripts/publish_to_npm.sh b/.circleci/scripts/publish_to_npm.sh index f619e0b7a..d127cae26 100755 --- a/.circleci/scripts/publish_to_npm.sh +++ b/.circleci/scripts/publish_to_npm.sh @@ -1,35 +1,56 @@ #!/bin/bash -BYTECODE_UTILS_LOCAL_VERSION=$(cat packages/bytecode-utils/package.json \ - | grep version \ - | head -1 \ - | awk -F: '{ print $2 }' \ - | sed 's/[",]//g' \ - | tr -d '[[:space:]]') +# Set npm auth token +npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN} -BYTECODE_UTILS_NPM_VERSION=$(npm view @ethereum-sourcify/bytecode-utils dist-tags.latest) +# Define package directories and their corresponding npm package names, e.g. +# packages/bytecode-utils:@ethereum-sourcify/bytecode-utils +packages=( + "packages/bytecode-utils:@ethereum-sourcify/bytecode-utils" + "packages/lib-sourcify:@ethereum-sourcify/lib-sourcify" + "services/server:@ethereum-sourcify/server" +) -LIB_SOURCIFY_LOCAL_VERSION=$(cat packages/lib-sourcify/package.json \ - | grep version \ - | head -1 \ - | awk -F: '{ print $2 }' \ - | sed 's/[",]//g' \ - | tr -d '[[:space:]]') +# Publish packages +for package in "${packages[@]}"; do + IFS=':' read -r local_path npm_package <<<"$package" + if [[ $CIRCLE_TAG == ${npm_package}* ]]; then # Only publish if tag starts with package name. Otherwise it will publish all at once. + echo "$CIRCLE_TAG matches $npm_package, publishing $npm_package" + else + echo "Skipping $npm_package as CIRCLE_TAG doesn't start with $npm_package" + continue + fi -LIB_SOURCIFY_NPM_VERSION=$(npm view @ethereum-sourcify/lib-sourcify dist-tags.latest) + publish_if_new_version "$local_path" "$npm_package" +done -npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN} +# Helper Functions +# ---------------- -if [ $BYTECODE_UTILS_LOCAL_VERSION = $BYTECODE_UTILS_NPM_VERSION ]; then - echo "@ethereum-sourcify/bytecode-utils:" - echo "Latest npm version is equal to current package version. Up the version to publish to npm." -else - npm publish packages/bytecode-utils/ --verbose --access=public -fi +# Function to get local version +get_local_version() { + cat "$1/package.json" | + grep version | + head -1 | + awk -F: '{ print $2 }' | + sed 's/[",]//g' | + tr -d '[[:space:]]' +} + +# Function to get npm version +get_npm_version() { + npm view "$1" dist-tags.latest +} + +# Function to publish package if versions differ +publish_if_new_version() { + local_version=$(get_local_version "$1") + npm_version=$(get_npm_version "$2") -if [ $LIB_SOURCIFY_LOCAL_VERSION = $LIB_SOURCIFY_NPM_VERSION ]; then - echo "@ethereum-sourcify/lib-sourcify:" + if [ "$local_version" = "$npm_version" ]; then + echo "$2:" echo "Latest npm version is equal to current package version. Up the version to publish to npm." -else - npm publish packages/lib-sourcify/ --verbose --access=public -fi \ No newline at end of file + else + npm publish -w "$2" --verbose --access=public + fi +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f34f13e2..12bd767c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. This CHANGELOG will contain monorepo related changes such as CI configs, shared dependencies and the development setup. +## sourcify-monorepo@1.4.3 - 2024-10-29 + +- Publish sourcify-server to npm on CI runs +- Turn off S3 backup check tests temporarily +- Update packages + ## sourcify-monorepo@1.4.2 - 2024-10-14 - Build before testing in CI diff --git a/package-lock.json b/package-lock.json index 3488c7cf7..8463c43b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "lerna": "8.1.8", "prettier": "3.3.3", "ts-node": "10.9.2", - "typescript": "5.6.2" + "typescript": "5.6.3" }, "optionalDependencies": { "fsevents": "2.3.3" @@ -165,26 +165,26 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.666.0.tgz", - "integrity": "sha512-bjVWZJQcX7j1uSDnq9DxNlpSVFkRlI78tNe/R2zntXaQKZKZVKfLOKzOdhvK6mZSGIAbD9QZbpa81xRHINZxJg==", + "version": "3.680.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.680.0.tgz", + "integrity": "sha512-oABfvrJTVvlVnCEi6lMtxlC/ta5wxu79bB2ZACe/0AxFZrXgr+gscrUtSm1iNye5+6oeYvQPD7b+ft6UwFrSlw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.666.0", - "@aws-sdk/client-sts": "3.666.0", - "@aws-sdk/core": "3.666.0", - "@aws-sdk/credential-provider-node": "3.666.0", - "@aws-sdk/middleware-host-header": "3.664.0", - "@aws-sdk/middleware-logger": "3.664.0", - "@aws-sdk/middleware-recursion-detection": "3.664.0", - "@aws-sdk/middleware-user-agent": "3.666.0", - "@aws-sdk/region-config-resolver": "3.664.0", - "@aws-sdk/types": "3.664.0", - "@aws-sdk/util-endpoints": "3.664.0", - "@aws-sdk/util-user-agent-browser": "3.664.0", - "@aws-sdk/util-user-agent-node": "3.666.0", + "@aws-sdk/client-sso-oidc": "3.679.0", + "@aws-sdk/client-sts": "3.679.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.679.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.679.0", "@smithy/config-resolver": "^3.0.9", "@smithy/core": "^2.4.8", "@smithy/eventstream-serde-browser": "^3.0.10", @@ -222,23 +222,23 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.666.0.tgz", - "integrity": "sha512-+h5Xk64dM4on1MwjTYxlwtI8ilytU7zjTVRzMAYOysmH71Bc8YsLOfonFHvzhF/AXpKJu3f1BhM65S0tasPcrw==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.679.0.tgz", + "integrity": "sha512-/0cAvYnpOZTo/Y961F1kx2fhDDLUYZ0SQQ5/75gh3xVImLj7Zw+vp74ieqFbqWLYGMaq8z1Arr9A8zG95mbLdg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.666.0", - "@aws-sdk/middleware-host-header": "3.664.0", - "@aws-sdk/middleware-logger": "3.664.0", - "@aws-sdk/middleware-recursion-detection": "3.664.0", - "@aws-sdk/middleware-user-agent": "3.666.0", - "@aws-sdk/region-config-resolver": "3.664.0", - "@aws-sdk/types": "3.664.0", - "@aws-sdk/util-endpoints": "3.664.0", - "@aws-sdk/util-user-agent-browser": "3.664.0", - "@aws-sdk/util-user-agent-node": "3.666.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.679.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.679.0", "@smithy/config-resolver": "^3.0.9", "@smithy/core": "^2.4.8", "@smithy/fetch-http-handler": "^3.2.9", @@ -271,24 +271,24 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.666.0.tgz", - "integrity": "sha512-mW//v5EvHMU2SulW1FqmjJJPDNhzySRb/YUU+jq9AFDIYUdjF6j6wM+iavCW/4gLqOct0RT7B62z8jqyHkUCEQ==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.679.0.tgz", + "integrity": "sha512-/dBYWcCwbA/id4sFCIVZvf0UsvzHCC68SryxeNQk/PDkY9N4n5yRcMUkZDaEyQCjowc3kY4JOXp2AdUP037nhA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.666.0", - "@aws-sdk/credential-provider-node": "3.666.0", - "@aws-sdk/middleware-host-header": "3.664.0", - "@aws-sdk/middleware-logger": "3.664.0", - "@aws-sdk/middleware-recursion-detection": "3.664.0", - "@aws-sdk/middleware-user-agent": "3.666.0", - "@aws-sdk/region-config-resolver": "3.664.0", - "@aws-sdk/types": "3.664.0", - "@aws-sdk/util-endpoints": "3.664.0", - "@aws-sdk/util-user-agent-browser": "3.664.0", - "@aws-sdk/util-user-agent-node": "3.666.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.679.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.679.0", "@smithy/config-resolver": "^3.0.9", "@smithy/core": "^2.4.8", "@smithy/fetch-http-handler": "^3.2.9", @@ -320,29 +320,29 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.666.0" + "@aws-sdk/client-sts": "^3.679.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.666.0.tgz", - "integrity": "sha512-tw8yxcxvaj0d/A4YJXIh3mISzsQe8rThIVKvpyhEdl1lEoz81skCccX5u3gHajciSdga/V0DxhBbsO+eE1bZkw==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.679.0.tgz", + "integrity": "sha512-3CvrT8w1RjFu1g8vKA5Azfr5V83r2/b68Ock43WE003Bq/5Y38mwmYX7vk0fPHzC3qejt4YMAWk/C3fSKOy25g==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.666.0", - "@aws-sdk/core": "3.666.0", - "@aws-sdk/credential-provider-node": "3.666.0", - "@aws-sdk/middleware-host-header": "3.664.0", - "@aws-sdk/middleware-logger": "3.664.0", - "@aws-sdk/middleware-recursion-detection": "3.664.0", - "@aws-sdk/middleware-user-agent": "3.666.0", - "@aws-sdk/region-config-resolver": "3.664.0", - "@aws-sdk/types": "3.664.0", - "@aws-sdk/util-endpoints": "3.664.0", - "@aws-sdk/util-user-agent-browser": "3.664.0", - "@aws-sdk/util-user-agent-node": "3.666.0", + "@aws-sdk/client-sso-oidc": "3.679.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.679.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.679.0", "@smithy/config-resolver": "^3.0.9", "@smithy/core": "^2.4.8", "@smithy/fetch-http-handler": "^3.2.9", @@ -375,12 +375,12 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.666.0.tgz", - "integrity": "sha512-jxNjs0sAVX+CWwoa4kHUENLHuBwjT1EILBoctmQoiIb1v5KpKwZnSByHTpvUkFmbuwWQPEnJkJCqzIHjEmjisA==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.679.0.tgz", + "integrity": "sha512-CS6PWGX8l4v/xyvX8RtXnBisdCa5+URzKd0L6GvHChype9qKUVxO/Gg6N/y43Hvg7MNWJt9FBPNWIxUB+byJwg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/core": "^2.4.8", "@smithy/node-config-provider": "^3.1.8", "@smithy/property-provider": "^3.1.7", @@ -397,12 +397,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.664.0.tgz", - "integrity": "sha512-95rE+9Voaco0nmKJrXqfJAxSSkSWqlBy76zomiZrUrv7YuijQtHCW8jte6v6UHAFAaBzgFsY7QqBxs15u9SM7g==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.679.0.tgz", + "integrity": "sha512-EdlTYbzMm3G7VUNAMxr9S1nC1qUNqhKlAxFU8E7cKsAe8Bp29CD5HAs3POc56AVo9GC4yRIS+/mtlZSmrckzUA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/property-provider": "^3.1.7", "@smithy/types": "^3.5.0", "tslib": "^2.6.2" @@ -412,12 +413,13 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.666.0.tgz", - "integrity": "sha512-j1Cob+tYmJ/m9agSsFPdAhLfILBqZzolF17XJvmEzQC2edltQ6NR0Wd09GQvtiAFZy7gn1l40bKuxX6Tq5U6XQ==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.679.0.tgz", + "integrity": "sha512-ZoKLubW5DqqV1/2a3TSn+9sSKg0T8SsYMt1JeirnuLJF0mCoYFUaWMyvxxKuxPoqvUsaycxKru4GkpJ10ltNBw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/fetch-http-handler": "^3.2.9", "@smithy/node-http-handler": "^3.2.4", "@smithy/property-provider": "^3.1.7", @@ -432,17 +434,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.666.0.tgz", - "integrity": "sha512-u09aUZJQNK8zVAKJKEOQ2mLsv39YxR20US00/WAPNW9sMWWhl4raT97tsalOUc6ZTHOEqHHmEVZXuscINnkaww==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.679.0.tgz", + "integrity": "sha512-Rg7t8RwUzKcumpipG4neZqaeJ6DF+Bco1+FHn5BZB68jpvwvjBjcQUuWkxj18B6ctYHr1fkunnzeKEn/+vy7+w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.664.0", - "@aws-sdk/credential-provider-http": "3.666.0", - "@aws-sdk/credential-provider-process": "3.664.0", - "@aws-sdk/credential-provider-sso": "3.666.0", - "@aws-sdk/credential-provider-web-identity": "3.664.0", - "@aws-sdk/types": "3.664.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.679.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/credential-provider-imds": "^3.2.4", "@smithy/property-provider": "^3.1.7", "@smithy/shared-ini-file-loader": "^3.1.8", @@ -453,22 +456,22 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.666.0" + "@aws-sdk/client-sts": "^3.679.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.666.0.tgz", - "integrity": "sha512-C43L9kxAb2lvIZ+eKVuyX9xYrkpg+Zyq0fLasK1wekC6M/Qj/uqE1KFz9ddDE8Dv1HwiE+UZk5psM0KatQpPGQ==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.679.0.tgz", + "integrity": "sha512-E3lBtaqCte8tWs6Rkssc8sLzvGoJ10TLGvpkijOlz43wPd6xCRh1YLwg6zolf9fVFtEyUs/GsgymiASOyxhFtw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.664.0", - "@aws-sdk/credential-provider-http": "3.666.0", - "@aws-sdk/credential-provider-ini": "3.666.0", - "@aws-sdk/credential-provider-process": "3.664.0", - "@aws-sdk/credential-provider-sso": "3.666.0", - "@aws-sdk/credential-provider-web-identity": "3.664.0", - "@aws-sdk/types": "3.664.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-ini": "3.679.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.679.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/credential-provider-imds": "^3.2.4", "@smithy/property-provider": "^3.1.7", "@smithy/shared-ini-file-loader": "^3.1.8", @@ -480,12 +483,13 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.664.0.tgz", - "integrity": "sha512-sQicIw/qWTsmMw8EUQNJXdrWV5SXaZc2zGdCQsQxhR6wwNO2/rZ5JmzdcwUADmleBVyPYk3KGLhcofF/qXT2Ng==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.679.0.tgz", + "integrity": "sha512-u/p4TV8kQ0zJWDdZD4+vdQFTMhkDEJFws040Gm113VHa/Xo1SYOjbpvqeuFoz6VmM0bLvoOWjxB9MxnSQbwKpQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/property-provider": "^3.1.7", "@smithy/shared-ini-file-loader": "^3.1.8", "@smithy/types": "^3.5.0", @@ -496,14 +500,15 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.666.0.tgz", - "integrity": "sha512-aaa5Ig8hI7lSh1CSQP0oaLvjylz6+3mKUgdvw69zv0MdX3TUZiQRDCsfqK0P3VNsj/QSvBytSjuNDuSaYcACJg==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.679.0.tgz", + "integrity": "sha512-SAtWonhi9asxn0ukEbcE81jkyanKgqpsrtskvYPpO9Z9KOednM4Cqt6h1bfcS9zaHjN2zu815Gv8O7WiV+F/DQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.666.0", - "@aws-sdk/token-providers": "3.664.0", - "@aws-sdk/types": "3.664.0", + "@aws-sdk/client-sso": "3.679.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/token-providers": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/property-provider": "^3.1.7", "@smithy/shared-ini-file-loader": "^3.1.8", "@smithy/types": "^3.5.0", @@ -514,12 +519,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.664.0.tgz", - "integrity": "sha512-10ltP1BfSKRJVXd8Yr5oLbo+VSDskWbps0X3szSsxTk0Dju1xvkz7hoIjylWLvtGbvQ+yb2pmsJYKCudW/4DJg==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.679.0.tgz", + "integrity": "sha512-a74tLccVznXCaBefWPSysUcLXYJiSkeUmQGtalNgJ1vGkE36W5l/8czFiiowdWdKWz7+x6xf0w+Kjkjlj42Ung==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/property-provider": "^3.1.7", "@smithy/types": "^3.5.0", "tslib": "^2.6.2" @@ -528,16 +534,16 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.664.0" + "@aws-sdk/client-sts": "^3.679.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.664.0.tgz", - "integrity": "sha512-4tCXJ+DZWTq38eLmFgnEmO8X4jfWpgPbWoCyVYpRHCPHq6xbrU65gfwS9jGx25L4YdEce641ChI9TKLryuUgRA==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.679.0.tgz", + "integrity": "sha512-y176HuQ8JRY3hGX8rQzHDSbCl9P5Ny9l16z4xmaiLo+Qfte7ee4Yr3yaAKd7GFoJ3/Mhud2XZ37fR015MfYl2w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/protocol-http": "^4.1.4", "@smithy/types": "^3.5.0", "tslib": "^2.6.2" @@ -547,12 +553,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.664.0.tgz", - "integrity": "sha512-eNykMqQuv7eg9pAcaLro44fscIe1VkFfhm+gYnlxd+PH6xqapRki1E68VHehnIptnVBdqnWfEqLUSLGm9suqhg==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.679.0.tgz", + "integrity": "sha512-0vet8InEj7nvIvGKk+ch7bEF5SyZ7Us9U7YTEgXPrBNStKeRUsgwRm0ijPWWd0a3oz2okaEwXsFl7G/vI0XiEA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, @@ -561,12 +567,12 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.664.0.tgz", - "integrity": "sha512-jq27WMZhm+dY8BWZ9Ipy3eXtZj0lJzpaKQE3A3tH5AOIlUV/gqrmnJ9CdqVVef4EJsq9Yil4ZzQjKKmPsxveQg==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.679.0.tgz", + "integrity": "sha512-sQoAZFsQiW/LL3DfKMYwBoGjYDEnMbA9WslWN8xneCmBAwKo6IcSksvYs23PP8XMIoBGe2I2J9BSr654XWygTQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/protocol-http": "^4.1.4", "@smithy/types": "^3.5.0", "tslib": "^2.6.2" @@ -576,14 +582,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.666.0.tgz", - "integrity": "sha512-d8XJ103SGCMsFIKEowpOaZr0W8AkLNd+3CS7W95yb6YmN7lcNGL54RtTSy3m8YJI6W2jXftPFN2oLG4K3aywVQ==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.679.0.tgz", + "integrity": "sha512-4hdeXhPDURPqQLPd9jCpUEo9fQITXl3NM3W1MwcJpE0gdUM36uXkQOYsTPeeU/IRCLVjK8Htlh2oCaM9iJrLCA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.666.0", - "@aws-sdk/types": "3.664.0", - "@aws-sdk/util-endpoints": "3.664.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", "@smithy/core": "^2.4.8", "@smithy/protocol-http": "^4.1.4", "@smithy/types": "^3.5.0", @@ -594,12 +600,12 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.664.0.tgz", - "integrity": "sha512-o/B8dg8K+9714RGYPgMxZgAChPe/MTSMkf/eHXTUFHNik5i1HgVKfac22njV2iictGy/6GhpFsKa1OWNYAkcUg==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.679.0.tgz", + "integrity": "sha512-Ybx54P8Tg6KKq5ck7uwdjiKif7n/8g1x+V0V9uTjBjRWqaIgiqzXwKWoPj6NCNkE7tJNtqI4JrNxp/3S3HvmRw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/node-config-provider": "^3.1.8", "@smithy/types": "^3.5.0", "@smithy/util-config-provider": "^3.0.0", @@ -611,12 +617,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.664.0.tgz", - "integrity": "sha512-dBAvXW2/6bAxidvKARFxyCY2uCynYBKRFN00NhS1T5ggxm3sUnuTpWw1DTjl02CVPkacBOocZf10h8pQbHSK8w==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.679.0.tgz", + "integrity": "sha512-1/+Zso/x2jqgutKixYFQEGli0FELTgah6bm7aB+m2FAWH4Hz7+iMUsazg6nSWm714sG9G3h5u42Dmpvi9X6/hA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/property-provider": "^3.1.7", "@smithy/shared-ini-file-loader": "^3.1.8", "@smithy/types": "^3.5.0", @@ -626,13 +632,13 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.664.0" + "@aws-sdk/client-sso-oidc": "^3.679.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.664.0.tgz", - "integrity": "sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz", + "integrity": "sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.5.0", @@ -643,12 +649,12 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.664.0.tgz", - "integrity": "sha512-KrXoHz6zmAahVHkyWMRT+P6xJaxItgmklxEDrT+npsUB4d5C/lhw16Crcp9TDi828fiZK3GYKRAmmNhvmzvBNg==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.679.0.tgz", + "integrity": "sha512-YL6s4Y/1zC45OvddvgE139fjeWSKKPgLlnfrvhVL7alNyY9n7beR4uhoDpNrt5mI6sn9qiBF17790o+xLAXjjg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/types": "^3.5.0", "@smithy/util-endpoints": "^2.1.3", "tslib": "^2.6.2" @@ -668,25 +674,25 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.664.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.664.0.tgz", - "integrity": "sha512-c/PV3+f1ss4PpskHbcOxTZ6fntV2oXy/xcDR9nW+kVaz5cM1G702gF0rvGLKPqoBwkj2rWGe6KZvEBeLzynTUQ==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.679.0.tgz", + "integrity": "sha512-CusSm2bTBG1kFypcsqU8COhnYc6zltobsqs3nRrvYqYaOqtMnuE46K4XTWpnzKgwDejgZGOE+WYyprtAxrPvmQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.664.0", + "@aws-sdk/types": "3.679.0", "@smithy/types": "^3.5.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.666.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.666.0.tgz", - "integrity": "sha512-DzbOMcAqrn51Z0fz5FvofaYmQA+sOJKO2cb8zQrix3TkzsTw1vRLo/cgQUJuJRGptbQCe1gnj7+21Gd5hpU6Ag==", + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.679.0.tgz", + "integrity": "sha512-Bw4uXZ+NU5ed6TNfo4tBbhBSW+2eQxXYjYBGl5gLUNUpg2pDFToQAP6rXBFiwcG52V2ny5oLGiD82SoYuYkAVg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.666.0", - "@aws-sdk/types": "3.664.0", + "@aws-sdk/middleware-user-agent": "3.679.0", + "@aws-sdk/types": "3.679.0", "@smithy/node-config-provider": "^3.1.8", "@smithy/types": "^3.5.0", "tslib": "^2.6.2" @@ -922,87 +928,87 @@ } }, "node_modules/@cspell/cspell-bundled-dicts": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.14.4.tgz", - "integrity": "sha512-JHZOpCJzN6fPBapBOvoeMxZbr0ZA11ZAkwcqM4w0lKoacbi6TwK8GIYf66hHvwLmMeav75TNXWE6aPTvBLMMqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/dict-ada": "^4.0.2", - "@cspell/dict-aws": "^4.0.4", - "@cspell/dict-bash": "^4.1.4", - "@cspell/dict-companies": "^3.1.4", - "@cspell/dict-cpp": "^5.1.16", - "@cspell/dict-cryptocurrencies": "^5.0.0", - "@cspell/dict-csharp": "^4.0.2", - "@cspell/dict-css": "^4.0.13", - "@cspell/dict-dart": "^2.2.1", - "@cspell/dict-django": "^4.1.0", - "@cspell/dict-docker": "^1.1.7", - "@cspell/dict-dotnet": "^5.0.5", - "@cspell/dict-elixir": "^4.0.3", - "@cspell/dict-en_us": "^4.3.23", - "@cspell/dict-en-common-misspellings": "^2.0.4", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.15.4.tgz", + "integrity": "sha512-t5b2JwGeUmzmjl319mCuaeKGxTvmzLLRmrpdHr+ZZGRO4nf7L48Lbe9A6uwNUvsZe0cXohiNXsrrsuzRVXswVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.0.5", + "@cspell/dict-aws": "^4.0.7", + "@cspell/dict-bash": "^4.1.8", + "@cspell/dict-companies": "^3.1.7", + "@cspell/dict-cpp": "^5.1.22", + "@cspell/dict-cryptocurrencies": "^5.0.3", + "@cspell/dict-csharp": "^4.0.5", + "@cspell/dict-css": "^4.0.16", + "@cspell/dict-dart": "^2.2.4", + "@cspell/dict-django": "^4.1.3", + "@cspell/dict-docker": "^1.1.11", + "@cspell/dict-dotnet": "^5.0.8", + "@cspell/dict-elixir": "^4.0.6", + "@cspell/dict-en_us": "^4.3.26", + "@cspell/dict-en-common-misspellings": "^2.0.7", "@cspell/dict-en-gb": "1.1.33", - "@cspell/dict-filetypes": "^3.0.4", - "@cspell/dict-flutter": "^1.0.0", - "@cspell/dict-fonts": "^4.0.0", - "@cspell/dict-fsharp": "^1.0.1", - "@cspell/dict-fullstack": "^3.2.0", - "@cspell/dict-gaming-terms": "^1.0.5", - "@cspell/dict-git": "^3.0.0", - "@cspell/dict-golang": "^6.0.12", - "@cspell/dict-google": "^1.0.1", - "@cspell/dict-haskell": "^4.0.1", - "@cspell/dict-html": "^4.0.5", - "@cspell/dict-html-symbol-entities": "^4.0.0", - "@cspell/dict-java": "^5.0.7", - "@cspell/dict-julia": "^1.0.1", - "@cspell/dict-k8s": "^1.0.6", - "@cspell/dict-latex": "^4.0.0", - "@cspell/dict-lorem-ipsum": "^4.0.0", - "@cspell/dict-lua": "^4.0.3", - "@cspell/dict-makefile": "^1.0.0", - "@cspell/dict-monkeyc": "^1.0.6", - "@cspell/dict-node": "^5.0.1", - "@cspell/dict-npm": "^5.1.4", - "@cspell/dict-php": "^4.0.10", - "@cspell/dict-powershell": "^5.0.8", - "@cspell/dict-public-licenses": "^2.0.8", - "@cspell/dict-python": "^4.2.6", - "@cspell/dict-r": "^2.0.1", - "@cspell/dict-ruby": "^5.0.3", - "@cspell/dict-rust": "^4.0.5", - "@cspell/dict-scala": "^5.0.3", - "@cspell/dict-software-terms": "^4.1.3", - "@cspell/dict-sql": "^2.1.5", - "@cspell/dict-svelte": "^1.0.2", - "@cspell/dict-swift": "^2.0.1", - "@cspell/dict-terraform": "^1.0.1", - "@cspell/dict-typescript": "^3.1.6", - "@cspell/dict-vue": "^3.0.0" + "@cspell/dict-filetypes": "^3.0.7", + "@cspell/dict-flutter": "^1.0.3", + "@cspell/dict-fonts": "^4.0.3", + "@cspell/dict-fsharp": "^1.0.4", + "@cspell/dict-fullstack": "^3.2.3", + "@cspell/dict-gaming-terms": "^1.0.8", + "@cspell/dict-git": "^3.0.3", + "@cspell/dict-golang": "^6.0.16", + "@cspell/dict-google": "^1.0.4", + "@cspell/dict-haskell": "^4.0.4", + "@cspell/dict-html": "^4.0.9", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-java": "^5.0.10", + "@cspell/dict-julia": "^1.0.4", + "@cspell/dict-k8s": "^1.0.9", + "@cspell/dict-latex": "^4.0.3", + "@cspell/dict-lorem-ipsum": "^4.0.3", + "@cspell/dict-lua": "^4.0.6", + "@cspell/dict-makefile": "^1.0.3", + "@cspell/dict-monkeyc": "^1.0.9", + "@cspell/dict-node": "^5.0.4", + "@cspell/dict-npm": "^5.1.8", + "@cspell/dict-php": "^4.0.13", + "@cspell/dict-powershell": "^5.0.13", + "@cspell/dict-public-licenses": "^2.0.11", + "@cspell/dict-python": "^4.2.12", + "@cspell/dict-r": "^2.0.4", + "@cspell/dict-ruby": "^5.0.7", + "@cspell/dict-rust": "^4.0.9", + "@cspell/dict-scala": "^5.0.6", + "@cspell/dict-software-terms": "^4.1.11", + "@cspell/dict-sql": "^2.1.8", + "@cspell/dict-svelte": "^1.0.5", + "@cspell/dict-swift": "^2.0.4", + "@cspell/dict-terraform": "^1.0.5", + "@cspell/dict-typescript": "^3.1.10", + "@cspell/dict-vue": "^3.0.3" }, "engines": { "node": ">=18" } }, "node_modules/@cspell/cspell-json-reporter": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.14.4.tgz", - "integrity": "sha512-gJ6tQbGCNLyHS2iIimMg77as5MMAFv3sxU7W6tjLlZp8htiNZS7fS976g24WbT/hscsTT9Dd0sNHkpo8K3nvVw==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.15.4.tgz", + "integrity": "sha512-solraYhZG4l++NeVCOtpc8DMvwHc46TmJt8DQbgvKtk6wOjTEcFrwKfA6Ei9YKbvyebJlpWMenO3goOll0loYg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "8.14.4" + "@cspell/cspell-types": "8.15.4" }, "engines": { "node": ">=18" } }, "node_modules/@cspell/cspell-pipe": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.14.4.tgz", - "integrity": "sha512-CLLdouqfrQ4rqdQdPu0Oo+HHCU/oLYoEsK1nNPb28cZTFxnn0cuSPKB6AMPBJmMwdfJ6fMD0BCKNbEe1UNLHcw==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.15.4.tgz", + "integrity": "sha512-WfCmZVFC6mX6vYlf02hWwelcSBTbDQgc5YqY+1miuMk+OHSUAHSACjZId6/a4IAID94xScvFfj7jgrdejUVvIQ==", "dev": true, "license": "MIT", "engines": { @@ -1010,9 +1016,9 @@ } }, "node_modules/@cspell/cspell-resolver": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.14.4.tgz", - "integrity": "sha512-s3uZyymJ04yn8+zlTp7Pt1WRSlAel6XVo+iZRxls3LSvIP819KK64DoyjCD2Uon0Vg9P/K7aAPt8GcxDcnJtgA==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.15.4.tgz", + "integrity": "sha512-Zr428o+uUTqywrdKyjluJVnDPVAJEqZ1chQLKIrHggUah1cgs5aQ7rZ+0Rv5euYMlC2idZnP7IL6TDaIib80oA==", "dev": true, "license": "MIT", "dependencies": { @@ -1023,9 +1029,9 @@ } }, "node_modules/@cspell/cspell-service-bus": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.14.4.tgz", - "integrity": "sha512-i3UG+ep63akNsDXZrtGgICNF3MLBHtvKe/VOIH6+L+NYaAaVHqqQvOY9MdUwt1HXh8ElzfwfoRp36wc5aAvt6g==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.15.4.tgz", + "integrity": "sha512-pXYofnV/V9Y3LZdfFGbmhdxPX/ABjiD3wFjGHt5YhIU9hjVVuvjFlgY7pH2AvRjs4F8xKXv1ReWl44BJOL9gLA==", "dev": true, "license": "MIT", "engines": { @@ -1033,9 +1039,9 @@ } }, "node_modules/@cspell/cspell-types": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.14.4.tgz", - "integrity": "sha512-VXwikqdHgjOVperVVCn2DOe8W3rPIswwZtMHfRYnagpzZo/TOntIjkXPJSfTtl/cFyx5DnCBsDH8ytKGlMeHkw==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.15.4.tgz", + "integrity": "sha512-1hDtgYDQVW11zgtrr12EmGW45Deoi7IjZOhzPFLb+3WkhZ46ggWdbrRalWwBolQPDDo6+B2Q6WXz5hdND+Tpwg==", "dev": true, "license": "MIT", "engines": { @@ -1043,114 +1049,114 @@ } }, "node_modules/@cspell/dict-ada": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.0.2.tgz", - "integrity": "sha512-0kENOWQeHjUlfyId/aCM/mKXtkEgV0Zu2RhUXCBr4hHo9F9vph+Uu8Ww2b0i5a4ZixoIkudGA+eJvyxrG1jUpA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.0.5.tgz", + "integrity": "sha512-6/RtZ/a+lhFVmrx/B7bfP7rzC4yjEYe8o74EybXcvu4Oue6J4Ey2WSYj96iuodloj1LWrkNCQyX5h4Pmcj0Iag==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-aws": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.4.tgz", - "integrity": "sha512-6AWI/Kkf+RcX/J81VX8+GKLeTgHWEr/OMhGk3dHQzWK66RaqDJCGDqi7494ghZKcBB7dGa3U5jcKw2FZHL/u3w==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.7.tgz", + "integrity": "sha512-PoaPpa2NXtSkhGIMIKhsJUXB6UbtTt6Ao3x9JdU9kn7fRZkwD4RjHDGqulucIOz7KeEX/dNRafap6oK9xHe4RA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-bash": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.1.4.tgz", - "integrity": "sha512-W/AHoQcJYn3Vn/tUiXX2+6D/bhfzdDshwcbQWv9TdiNlXP9P6UJjDKWbxyA5ogJCsR2D0X9Kx11oV8E58siGKQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.1.8.tgz", + "integrity": "sha512-I2CM2pTNthQwW069lKcrVxchJGMVQBzru2ygsHCwgidXRnJL/NTjAPOFTxN58Jc1bf7THWghfEDyKX/oyfc0yg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-companies": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.1.4.tgz", - "integrity": "sha512-y9e0amzEK36EiiKx3VAA+SHQJPpf2Qv5cCt5eTUSggpTkiFkCh6gRKQ97rVlrKh5GJrqinDwYIJtTsxuh2vy2Q==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.1.7.tgz", + "integrity": "sha512-ncVs/efuAkP1/tLDhWbXukBjgZ5xOUfe03neHMWsE8zvXXc5+Lw6TX5jaJXZLOoES/f4j4AhRE20jsPCF5pm+A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-cpp": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-5.1.16.tgz", - "integrity": "sha512-32fU5RkuOM55IRcxjByiSoKbjr+C4danDfYjHaQNRWdvjzJzci3fLDGA2wTXiclkgDODxGiV8LCTUwCz+3TNWA==", + "version": "5.1.22", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-5.1.22.tgz", + "integrity": "sha512-g1/8P5/Q+xnIc8Js4UtBg3XOhcFrFlFbG3UWVtyEx49YTf0r9eyDtDt1qMMDBZT91pyCwLcAEbwS+4i5PIfNZw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-cryptocurrencies": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.0.tgz", - "integrity": "sha512-Z4ARIw5+bvmShL+4ZrhDzGhnc9znaAGHOEMaB/GURdS/jdoreEDY34wdN0NtdLHDO5KO7GduZnZyqGdRoiSmYA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.3.tgz", + "integrity": "sha512-bl5q+Mk+T3xOZ12+FG37dB30GDxStza49Rmoax95n37MTLksk9wBo1ICOlPJ6PnDUSyeuv4SIVKgRKMKkJJglA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-csharp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.2.tgz", - "integrity": "sha512-1JMofhLK+4p4KairF75D3A924m5ERMgd1GvzhwK2geuYgd2ZKuGW72gvXpIV7aGf52E3Uu1kDXxxGAiZ5uVG7g==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.5.tgz", + "integrity": "sha512-c/sFnNgtRwRJxtC3JHKkyOm+U3/sUrltFeNwml9VsxKBHVmvlg4tk4ar58PdpW9/zTlGUkWi2i85//DN1EsUCA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-css": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.13.tgz", - "integrity": "sha512-WfOQkqlAJTo8eIQeztaH0N0P+iF5hsJVKFuhy4jmARPISy8Efcv8QXk2/IVbmjJH0/ZV7dKRdnY5JFVXuVz37g==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.16.tgz", + "integrity": "sha512-70qu7L9z/JR6QLyJPk38fNTKitlIHnfunx0wjpWQUQ8/jGADIhMCrz6hInBjqPNdtGpYm8d1dNFyF8taEkOgrQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-dart": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.2.1.tgz", - "integrity": "sha512-yriKm7QkoPx3JPSSOcw6iX9gOb2N50bOo/wqWviqPYbhpMRh9Xiv6dkUy3+ot+21GuShZazO8X6U5+Vw67XEwg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.2.4.tgz", + "integrity": "sha512-of/cVuUIZZK/+iqefGln8G3bVpfyN6ZtH+LyLkHMoR5tEj+2vtilGNk9ngwyR8L4lEqbKuzSkOxgfVjsXf5PsQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-data-science": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.1.tgz", - "integrity": "sha512-xeutkzK0eBe+LFXOFU2kJeAYO6IuFUc1g7iRLr7HeCmlC4rsdGclwGHh61KmttL3+YHQytYStxaRBdGAXWC8Lw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.5.tgz", + "integrity": "sha512-nNSILXmhSJox9/QoXICPQgm8q5PbiSQP4afpbkBqPi/u/b3K9MbNH5HvOOa6230gxcGdbZ9Argl2hY/U8siBlg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-django": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.0.tgz", - "integrity": "sha512-bKJ4gPyrf+1c78Z0Oc4trEB9MuhcB+Yg+uTTWsvhY6O2ncFYbB/LbEZfqhfmmuK/XJJixXfI1laF2zicyf+l0w==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.3.tgz", + "integrity": "sha512-yBspeL3roJlO0a1vKKNaWABURuHdHZ9b1L8d3AukX0AsBy9snSggc8xCavPmSzNfeMDXbH+1lgQiYBd3IW03fg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-docker": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.7.tgz", - "integrity": "sha512-XlXHAr822euV36GGsl2J1CkBIVg3fZ6879ZOg5dxTIssuhUOCiV2BuzKZmt6aIFmcdPmR14+9i9Xq+3zuxeX0A==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.11.tgz", + "integrity": "sha512-s0Yhb16/R+UT1y727ekbR/itWQF3Qz275DR1ahOa66wYtPjHUXmhM3B/LT3aPaX+hD6AWmK23v57SuyfYHUjsw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-dotnet": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.5.tgz", - "integrity": "sha512-gjg0L97ee146wX47dnA698cHm85e7EOpf9mVrJD8DmEaqoo/k1oPy2g7c7LgKxK9XnqwoXxhLNnngPrwXOoEtQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.8.tgz", + "integrity": "sha512-MD8CmMgMEdJAIPl2Py3iqrx3B708MbCIXAuOeZ0Mzzb8YmLmiisY7QEYSZPg08D7xuwARycP0Ki+bb0GAkFSqg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-elixir": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.3.tgz", - "integrity": "sha512-g+uKLWvOp9IEZvrIvBPTr/oaO6619uH/wyqypqvwpmnmpjcfi8+/hqZH8YNKt15oviK8k4CkINIqNhyndG9d9Q==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.6.tgz", + "integrity": "sha512-TfqSTxMHZ2jhiqnXlVKM0bUADtCvwKQv2XZL/DI0rx3doG8mEMS8SGPOmiyyGkHpR/pGOq18AFH3BEm4lViHIw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-en_us": { - "version": "4.3.23", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.23.tgz", - "integrity": "sha512-l0SoEQBsi3zDSl3OuL4/apBkxjuj4hLIg/oy6+gZ7LWh03rKdF6VNtSZNXWAmMY+pmb1cGA3ouleTiJIglbsIg==", + "version": "4.3.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.26.tgz", + "integrity": "sha512-hDbHYJsi3UgU1J++B0WLiYhWQdsmve3CH53FIaMRAdhrWOHcuw7h1dYkQXHFEP5lOjaq53KUHp/oh5su6VkIZg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-en-common-misspellings": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.4.tgz", - "integrity": "sha512-lvOiRjV/FG4pAGZL3PN2GCVHSTCE92cwhfLGGkOsQtxSmef6WCHfHwp9auafkBlX0yFQSKDfq6/TlpQbjbJBtQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.7.tgz", + "integrity": "sha512-qNFo3G4wyabcwnM+hDrMYKN9vNVg/k9QkhqSlSst6pULjdvPyPs1mqz1689xO/v9t8e6sR4IKc3CgUXDMTYOpA==", "dev": true, "license": "CC BY-SA 4.0" }, @@ -1162,271 +1168,271 @@ "license": "MIT" }, "node_modules/@cspell/dict-filetypes": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.4.tgz", - "integrity": "sha512-IBi8eIVdykoGgIv5wQhOURi5lmCNJq0we6DvqKoPQJHthXbgsuO1qrHSiUVydMiQl/XvcnUWTMeAlVUlUClnVg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.7.tgz", + "integrity": "sha512-/DN0Ujp9/EXvpTcgih9JmBaE8n+G0wtsspyNdvHT5luRfpfol1xm/CIQb6xloCXCiLkWX+EMPeLSiVIZq+24dA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-flutter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.0.0.tgz", - "integrity": "sha512-W7k1VIc4KeV8BjEBxpA3cqpzbDWjfb7oXkEb0LecBCBp5Z7kcfnjT1YVotTx/U9PGyAOBhDaEdgZACVGNQhayw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.0.3.tgz", + "integrity": "sha512-52C9aUEU22ptpgYh6gQyIdA4MP6NPwzbEqndfgPh3Sra191/kgs7CVqXiO1qbtZa9gnYHUoVApkoxRE7mrXHfg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-fonts": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.0.tgz", - "integrity": "sha512-t9V4GeN/m517UZn63kZPUYP3OQg5f0OBLSd3Md5CU3eH1IFogSvTzHHnz4Wqqbv8NNRiBZ3HfdY/pqREZ6br3Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.3.tgz", + "integrity": "sha512-sPd17kV5qgYXLteuHFPn5mbp/oCHKgitNfsZLFC3W2fWEgZlhg4hK+UGig3KzrYhhvQ8wBnmZrAQm0TFKCKzsA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-fsharp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.0.1.tgz", - "integrity": "sha512-23xyPcD+j+NnqOjRHgW3IU7Li912SX9wmeefcY0QxukbAxJ/vAN4rBpjSwwYZeQPAn3fxdfdNZs03fg+UM+4yQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.0.4.tgz", + "integrity": "sha512-G5wk0o1qyHUNi9nVgdE1h5wl5ylq7pcBjX8vhjHcO4XBq20D5eMoXjwqMo/+szKAqzJ+WV3BgAL50akLKrT9Rw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-fullstack": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.0.tgz", - "integrity": "sha512-sIGQwU6G3rLTo+nx0GKyirR5dQSFeTIzFTOrURw51ISf+jKG9a3OmvsVtc2OANfvEAOLOC9Wfd8WYhmsO8KRDQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.3.tgz", + "integrity": "sha512-62PbndIyQPH11mAv0PyiyT0vbwD0AXEocPpHlCHzfb5v9SspzCCbzQ/LIBiFmyRa+q5LMW35CnSVu6OXdT+LKg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-gaming-terms": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.0.5.tgz", - "integrity": "sha512-C3riccZDD3d9caJQQs1+MPfrUrQ+0KHdlj9iUR1QD92FgTOF6UxoBpvHUUZ9YSezslcmpFQK4xQQ5FUGS7uWfw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.0.8.tgz", + "integrity": "sha512-7OL0zTl93WFWhhtpXFrtm9uZXItC3ncAs8d0iQDMMFVNU1rBr6raBNxJskxE5wx2Ant12fgI66ZGVagXfN+yfA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-git": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.0.tgz", - "integrity": "sha512-simGS/lIiXbEaqJu9E2VPoYW1OTC2xrwPPXNXFMa2uo/50av56qOuaxDrZ5eH1LidFXwoc8HROCHYeKoNrDLSw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.3.tgz", + "integrity": "sha512-LSxB+psZ0qoj83GkyjeEH/ZViyVsGEF/A6BAo8Nqc0w0HjD2qX/QR4sfA6JHUgQ3Yi/ccxdK7xNIo67L2ScW5A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-golang": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.12.tgz", - "integrity": "sha512-LEPeoqd+4O+vceHF73S7D7+LYfrAjOvp4Dqzh4MT30ruzlQ77yHRSuYOJtrFN1GK5ntAt/ILSVOKg9sgsz1Llg==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.16.tgz", + "integrity": "sha512-hZOBlgcguv2Hdc93n2zjdAQm1j3grsN9T9WhPnQ1wh2vUDoCLEujg+6gWhjcLb8ECOcwZTWgNyQLWeOxEsAj/w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-google": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.1.tgz", - "integrity": "sha512-dQr4M3n95uOhtloNSgB9tYYGXGGEGEykkFyRtfcp5pFuEecYUa0BSgtlGKx9RXVtJtKgR+yFT/a5uQSlt8WjqQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.4.tgz", + "integrity": "sha512-JThUT9eiguCja1mHHLwYESgxkhk17Gv7P3b1S7ZJzXw86QyVHPrbpVoMpozHk0C9o+Ym764B7gZGKmw9uMGduQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-haskell": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.1.tgz", - "integrity": "sha512-uRrl65mGrOmwT7NxspB4xKXFUenNC7IikmpRZW8Uzqbqcu7ZRCUfstuVH7T1rmjRgRkjcIjE4PC11luDou4wEQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.4.tgz", + "integrity": "sha512-EwQsedEEnND/vY6tqRfg9y7tsnZdxNqOxLXSXTsFA6JRhUlr8Qs88iUUAfsUzWc4nNmmzQH2UbtT25ooG9x4nA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-html": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.6.tgz", - "integrity": "sha512-cLWHfuOhE4wqwC12up6Doxo2u1xxVhX1A8zriR4CUD+osFQzUIcBK1ykNXppga+rt1WyypaJdTU2eV6OpzYrgQ==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.9.tgz", + "integrity": "sha512-BNp7w3m910K4qIVyOBOZxHuFNbVojUY6ES8Y8r7YjYgJkm2lCuQoVwwhPjurnomJ7BPmZTb+3LLJ58XIkgF7JQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.0.tgz", - "integrity": "sha512-HGRu+48ErJjoweR5IbcixxETRewrBb0uxQBd6xFGcxbEYCX8CnQFTAmKI5xNaIt2PKaZiJH3ijodGSqbKdsxhw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.3.tgz", + "integrity": "sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-java": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.7.tgz", - "integrity": "sha512-ejQ9iJXYIq7R09BScU2y5OUGrSqwcD+J5mHFOKbduuQ5s/Eh/duz45KOzykeMLI6KHPVxhBKpUPBWIsfewECpQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.10.tgz", + "integrity": "sha512-pVNcOnmoGiNL8GSVq4WbX/Vs2FGS0Nej+1aEeGuUY9CU14X8yAVCG+oih5ZoLt1jaR8YfR8byUF8wdp4qG4XIw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-julia": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.0.1.tgz", - "integrity": "sha512-4JsCLCRhhLMLiaHpmR7zHFjj1qOauzDI5ZzCNQS31TUMfsOo26jAKDfo0jljFAKgw5M2fEG7sKr8IlPpQAYrmQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.0.4.tgz", + "integrity": "sha512-bFVgNX35MD3kZRbXbJVzdnN7OuEqmQXGpdOi9jzB40TSgBTlJWA4nxeAKV4CPCZxNRUGnLH0p05T/AD7Aom9/w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-k8s": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.6.tgz", - "integrity": "sha512-srhVDtwrd799uxMpsPOQqeDJY+gEocgZpoK06EFrb4GRYGhv7lXo9Fb+xQMyQytzOW9dw4DNOEck++nacDuymg==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.9.tgz", + "integrity": "sha512-Q7GELSQIzo+BERl2ya/nBEnZeQC+zJP19SN1pI6gqDYraM51uYJacbbcWLYYO2Y+5joDjNt/sd/lJtLaQwoSlA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-latex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.0.tgz", - "integrity": "sha512-LPY4y6D5oI7D3d+5JMJHK/wxYTQa2lJMSNxps2JtuF8hbAnBQb3igoWEjEbIbRRH1XBM0X8dQqemnjQNCiAtxQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.3.tgz", + "integrity": "sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-lorem-ipsum": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.0.tgz", - "integrity": "sha512-1l3yjfNvMzZPibW8A7mQU4kTozwVZVw0AvFEdy+NcqtbxH+TvbSkNMqROOFWrkD2PjnKG0+Ea0tHI2Pi6Gchnw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.3.tgz", + "integrity": "sha512-WFpDi/PDYHXft6p0eCXuYnn7mzMEQLVeqpO+wHSUd+kz5ADusZ4cpslAA4wUZJstF1/1kMCQCZM6HLZic9bT8A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-lua": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.3.tgz", - "integrity": "sha512-lDHKjsrrbqPaea13+G9s0rtXjMO06gPXPYRjRYawbNmo4E/e3XFfVzeci3OQDQNDmf2cPOwt9Ef5lu2lDmwfJg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.6.tgz", + "integrity": "sha512-Jwvh1jmAd9b+SP9e1GkS2ACbqKKRo9E1f9GdjF/ijmooZuHU0hPyqvnhZzUAxO1egbnNjxS/J2T6iUtjAUK2KQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-makefile": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.0.tgz", - "integrity": "sha512-3W9tHPcSbJa6s0bcqWo6VisEDTSN5zOtDbnPabF7rbyjRpNo0uHXHRJQF8gAbFzoTzBBhgkTmrfSiuyQm7vBUQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.3.tgz", + "integrity": "sha512-R3U0DSpvTs6qdqfyBATnePj9Q/pypkje0Nj26mQJ8TOBQutCRAJbr2ZFAeDjgRx5EAJU/+8txiyVF97fbVRViw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-monkeyc": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.6.tgz", - "integrity": "sha512-oO8ZDu/FtZ55aq9Mb67HtaCnsLn59xvhO/t2mLLTHAp667hJFxpp7bCtr2zOrR1NELzFXmKln/2lw/PvxMSvrA==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.9.tgz", + "integrity": "sha512-Jvf6g5xlB4+za3ThvenYKREXTEgzx5gMUSzrAxIiPleVG4hmRb/GBSoSjtkGaibN3XxGx5x809gSTYCA/IHCpA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-node": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.1.tgz", - "integrity": "sha512-lax/jGz9h3Dv83v8LHa5G0bf6wm8YVRMzbjJPG/9rp7cAGPtdrga+XANFq+B7bY5+jiSA3zvj10LUFCFjnnCCg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.4.tgz", + "integrity": "sha512-Hz5hiuOvZTd7Cp1IBqUZ7/ChwJeQpD5BJuwCaDn4mPNq4iMcQ1iWBYMThvNVqCEDgKv63X52nT8RAWacss98qg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-npm": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.1.5.tgz", - "integrity": "sha512-oAOGWuJYU3DlO+cAsStKMWN8YEkBue25cRC9EwdiL5Z84nchU20UIoYrLfIQejMlZca+1GyrNeyxRAgn4KiivA==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.1.8.tgz", + "integrity": "sha512-AJELYXeB4fQdIoNfmuaQxB1Hli3cX6XPsQCjfBxlu0QYXhrjB/IrCLLQAjWIywDqJiWyGUFTz4DqaANm8C/r9Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-php": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.10.tgz", - "integrity": "sha512-NfTZdp6kcZDF1PvgQ6cY0zE4FUO5rSwNmBH/iwCBuaLfJAFQ97rgjxo+D2bic4CFwNjyHutnHPtjJBRANO5XQw==", + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.13.tgz", + "integrity": "sha512-P6sREMZkhElzz/HhXAjahnICYIqB/HSGp1EhZh+Y6IhvC15AzgtDP8B8VYCIsQof6rPF1SQrFwunxOv8H1e2eg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-powershell": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.9.tgz", - "integrity": "sha512-Vi0h0rlxS39tgTyUtxI6L3BPHH7MLPkLWCYkNfb/buQuNJYNFdHiF4bqoqVdJ/7ZrfIfNg4i6rzocnwGRn2ruw==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.13.tgz", + "integrity": "sha512-0qdj0XZIPmb77nRTynKidRJKTU0Fl+10jyLbAhFTuBWKMypVY06EaYFnwhsgsws/7nNX8MTEQuewbl9bWFAbsg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-public-licenses": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.8.tgz", - "integrity": "sha512-Sup+tFS7cDV0fgpoKtUqEZ6+fA/H+XUgBiqQ/Fbs6vUE3WCjJHOIVsP+udHuyMH7iBfJ4UFYOYeORcY4EaKdMg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.11.tgz", + "integrity": "sha512-rR5KjRUSnVKdfs5G+gJ4oIvQvm8+NJ6cHWY2N+GE69/FSGWDOPHxulCzeGnQU/c6WWZMSimG9o49i9r//lUQyA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-python": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.6.tgz", - "integrity": "sha512-Hkz399qDGEbfXi9GYa2hDl7GahglI86JmS2F1KP8sfjLXofUgtnknyC5NWc86nzHcP38pZiPqPbTigyDYw5y8A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.12.tgz", + "integrity": "sha512-U25eOFu+RE0aEcF2AsxZmq3Lic7y9zspJ9SzjrC0mfJz+yr3YmSCw4E0blMD3mZoNcf7H/vMshuKIY5AY36U+Q==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/dict-data-science": "^2.0.1" + "@cspell/dict-data-science": "^2.0.5" } }, "node_modules/@cspell/dict-r": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.0.1.tgz", - "integrity": "sha512-KCmKaeYMLm2Ip79mlYPc8p+B2uzwBp4KMkzeLd5E6jUlCL93Y5Nvq68wV5fRLDRTf7N1LvofkVFWfDcednFOgA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.0.4.tgz", + "integrity": "sha512-cBpRsE/U0d9BRhiNRMLMH1PpWgw+N+1A2jumgt1if9nBGmQw4MUpg2u9I0xlFVhstTIdzXiLXMxP45cABuiUeQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-ruby": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.3.tgz", - "integrity": "sha512-V1xzv9hN6u8r6SM4CkYdsxs4ov8gjXXo0Twfx5kWhLXbEVxTXDMt7ohLTqpy2XlF5mutixZdbHMeFiAww8v+Ug==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.7.tgz", + "integrity": "sha512-4/d0hcoPzi5Alk0FmcyqlzFW9lQnZh9j07MJzPcyVO62nYJJAGKaPZL2o4qHeCS/od/ctJC5AHRdoUm0ktsw6Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-rust": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.5.tgz", - "integrity": "sha512-DIvlPRDemjKQy8rCqftAgGNZxY5Bg+Ps7qAIJjxkSjmMETyDgl0KTVuaJPt7EK4jJt6uCZ4ILy96npsHDPwoXA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.9.tgz", + "integrity": "sha512-Dhr6TIZsMV92xcikKIWei6p/qswS4M+gTkivpWwz4/1oaVk2nRrxJmCdRoVkJlZkkAc17rjxrS12mpnJZI0iWw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-scala": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.3.tgz", - "integrity": "sha512-4yGb4AInT99rqprxVNT9TYb1YSpq58Owzq7zi3ZS5T0u899Y4VsxsBiOgHnQ/4W+ygi+sp+oqef8w8nABR2lkg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.6.tgz", + "integrity": "sha512-tl0YWAfjUVb4LyyE4JIMVE8DlLzb1ecHRmIWc4eT6nkyDqQgHKzdHsnusxFEFMVLIQomgSg0Zz6hJ5S1E4W4ww==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-software-terms": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-4.1.4.tgz", - "integrity": "sha512-AHS25sYEzWze/aFglp9ODKSu+phjkuGx+OLwIcmOnvyn8axtSq5GCn9UqS4XG1/Qn0UG2Lgb4i5PJbZ0QNPNXQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-4.1.11.tgz", + "integrity": "sha512-77CTHxWFTVw6tVoMN8WBMrlNW2F2FbgATwD/6vcOuiyrJUmh8klN5ZK3m+yyK3ZzsnaW2Bduoc0fw2Ckcm/riQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-sql": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.1.5.tgz", - "integrity": "sha512-FmxanytHXss7GAWAXmgaxl3icTCW7YxlimyOSPNfm+njqeUDjw3kEv4mFNDDObBJv8Ec5AWCbUDkWIpkE3IpKg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.1.8.tgz", + "integrity": "sha512-dJRE4JV1qmXTbbGm6WIcg1knmR6K5RXnQxF4XHs5HA3LAjc/zf77F95i5LC+guOGppVF6Hdl66S2UyxT+SAF3A==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-svelte": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.2.tgz", - "integrity": "sha512-rPJmnn/GsDs0btNvrRBciOhngKV98yZ9SHmg8qI6HLS8hZKvcXc0LMsf9LLuMK1TmS2+WQFAan6qeqg6bBxL2Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.5.tgz", + "integrity": "sha512-sseHlcXOqWE4Ner9sg8KsjxwSJ2yssoJNqFHR9liWVbDV+m7kBiUtn2EB690TihzVsEmDr/0Yxrbb5Bniz70mA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-swift": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.1.tgz", - "integrity": "sha512-gxrCMUOndOk7xZFmXNtkCEeroZRnS2VbeaIPiymGRHj5H+qfTAzAKxtv7jJbVA3YYvEzWcVE2oKDP4wcbhIERw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.4.tgz", + "integrity": "sha512-CsFF0IFAbRtYNg0yZcdaYbADF5F3DsM8C4wHnZefQy8YcHP/qjAF/GdGfBFBLx+XSthYuBlo2b2XQVdz3cJZBw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-terraform": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.0.1.tgz", - "integrity": "sha512-29lmUUnZgPh+ieZ5hunick8hzNIpNRtiJh9vAusNskPCrig3RTW6u7F+GG1a8uyslbzSw+Irjf40PTOan1OJJA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.0.5.tgz", + "integrity": "sha512-qH3epPB2d6d5w1l4hR2OsnN8qDQ4P0z6oDB7+YiNH+BoECXv4Z38MIV1H8cxIzD2wkzkt2JTcFYaVW72MDZAlg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-typescript": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.6.tgz", - "integrity": "sha512-1beC6O4P/j23VuxX+i0+F7XqPVc3hhiAzGJHEKqnWf5cWAXQtg0xz3xQJ5MvYx2a7iLaSa+lu7+05vG9UHyu9Q==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.10.tgz", + "integrity": "sha512-7Zek3w4Rh3ZYyhihJ34FdnUBwP3OmRldnEq3hZ+FgQ0PyYZjXv5ztEViRBBxXjiFx1nHozr6pLi74TxToD8xsg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-vue": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.0.tgz", - "integrity": "sha512-niiEMPWPV9IeRBRzZ0TBZmNnkK3olkOPYxC1Ny2AX4TGlYRajcW0WUtoSHmvvjZNfWLSg2L6ruiBeuPSbjnG6A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.3.tgz", + "integrity": "sha512-akmYbrgAGumqk1xXALtDJcEcOMYBYMnkjpmGzH13Ozhq1mkPF4VgllFQlm1xYde+BUKNnzMgPEzxrL2qZllgYA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dynamic-import": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.14.4.tgz", - "integrity": "sha512-GjKsBJvPXp4dYRqsMn7n1zpnKbnpfJnlKLOVeoFBh8fi4n06G50xYr+G25CWX1WT3WFaALAavvVICEUPrVsuqg==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.15.4.tgz", + "integrity": "sha512-tr0F6EYN6qtniNvt1Uib+PgYQHeo4dQHXE2Optap+hYTOoQ2VoQ+SwBVjZ+Q2bmSAB0fmOyf0AvgsUtnWIpavw==", "dev": true, "license": "MIT", "dependencies": { @@ -1437,9 +1443,9 @@ } }, "node_modules/@cspell/filetypes": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.14.4.tgz", - "integrity": "sha512-qd68dD7xTA4Mnf/wjIKYz2SkiTBshIM+yszOUtLa06YJm0aocoNQ25FHXyYEQYm9NQXCYnRWWA02sFMGs8Sv/w==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.15.4.tgz", + "integrity": "sha512-sNl6jr3ym/4151EY76qlI/00HHsiLZBqW7Vb1tqCzsgSg3EpL30ddjr74So6Sg2PN26Yf09hvxGTJzXn1R4aYw==", "dev": true, "license": "MIT", "engines": { @@ -1447,9 +1453,9 @@ } }, "node_modules/@cspell/strong-weak-map": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.14.4.tgz", - "integrity": "sha512-Uyfck64TfVU24wAP3BLGQ5EsAfzIZiLfN90NhttpEM7GlOBmbGrEJd4hNOwfpYsE/TT80eGWQVPRTLr5SDbXFA==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.15.4.tgz", + "integrity": "sha512-m5DeQksbhJFqcSYF8Q0Af/WXmXCMAJocCUShkzOXK+uZNXnvhBZN7VyQ9hL+GRzX8JTPEPdVcz2lFyVE5p+LzQ==", "dev": true, "license": "MIT", "engines": { @@ -1457,9 +1463,9 @@ } }, "node_modules/@cspell/url": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.14.4.tgz", - "integrity": "sha512-htHhNF8WrM/NfaLSWuTYw0NqVgFRVHYSyHlRT3i/Yv5xvErld8Gw7C6ldm+0TLjoGlUe6X1VV72JSir7+yLp/Q==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.15.4.tgz", + "integrity": "sha512-K2oZu/oLQPs5suRpLS8uu04O3YMUioSlEU1D66fRoOxzI5NzLt7i7yMg3HQHjChGa09N5bzqmrVdhmQrRZXwGg==", "dev": true, "license": "MIT", "engines": { @@ -2700,28 +2706,28 @@ } }, "node_modules/@nomicfoundation/edr": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.6.2.tgz", - "integrity": "sha512-yPUegN3sTWiAkRatCmGRkuvMgD9HSSpivl2ebAqq0aU2xgC7qmIO+YQPxQ3Z46MUoi7MrTf4e6GpbT4S/8x0ew==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.6.3.tgz", + "integrity": "sha512-hThe5ORR75WFYTXKL0K2AyLDxkTMrG+VQ1yL9BhQYsuh3OIH+3yNDxMz2LjfvrpOrMmJ4kk5NKdFewpqDojjXQ==", "dev": true, "license": "MIT", "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.6.2", - "@nomicfoundation/edr-darwin-x64": "0.6.2", - "@nomicfoundation/edr-linux-arm64-gnu": "0.6.2", - "@nomicfoundation/edr-linux-arm64-musl": "0.6.2", - "@nomicfoundation/edr-linux-x64-gnu": "0.6.2", - "@nomicfoundation/edr-linux-x64-musl": "0.6.2", - "@nomicfoundation/edr-win32-x64-msvc": "0.6.2" + "@nomicfoundation/edr-darwin-arm64": "0.6.3", + "@nomicfoundation/edr-darwin-x64": "0.6.3", + "@nomicfoundation/edr-linux-arm64-gnu": "0.6.3", + "@nomicfoundation/edr-linux-arm64-musl": "0.6.3", + "@nomicfoundation/edr-linux-x64-gnu": "0.6.3", + "@nomicfoundation/edr-linux-x64-musl": "0.6.3", + "@nomicfoundation/edr-win32-x64-msvc": "0.6.3" }, "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.6.2.tgz", - "integrity": "sha512-o4A9SaPlxJ1MS6u8Ozqq7Y0ri2XO0jASw+qkytQyBYowNFNReoGqVSs7SCwenYCDiN+1il8+M0VAUq7wOovnCQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.6.3.tgz", + "integrity": "sha512-hqtI7tYDqKG5PDmZ//Z65EH5cgH8VL/SAAu50rpHP7WAVfJWkOCcYbecywwF6nhHdonJbRTDGAeG1/+VOy6zew==", "dev": true, "license": "MIT", "engines": { @@ -2729,9 +2735,9 @@ } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.6.2.tgz", - "integrity": "sha512-WG8BeG2eR3rFC+2/9V1hoPGW7tmNRUcuztdHUijO1h2flRsf2YWv+kEHO+EEnhGkEbgBUiwOrwlwlSMxhe2cGA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.6.3.tgz", + "integrity": "sha512-4fGi79/lyOlRUORhCYsYb3sWqRHuHT7qqzyZfZuNOn8llaxmT1k36xNmvpyg37R8SzjnhT/DzoukSJrs23Ip9Q==", "dev": true, "license": "MIT", "engines": { @@ -2739,9 +2745,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.6.2.tgz", - "integrity": "sha512-wvHaTmOwuPjRIOqBB+paI3RBdNlG8f3e1F2zWj75EdeWwefimPzzFUs05JxOYuPO0JhDQIn2tbYUgdZbBQ+mqg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.6.3.tgz", + "integrity": "sha512-yFFTvGFMhfAvQ1Z2itUh1jpoUA+mVROyVELcaxjIq8fyg602lQmbS+NXkhQ+oaeDgJ+06mSENrHBg4fcfRf9cw==", "dev": true, "license": "MIT", "engines": { @@ -2749,9 +2755,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.6.2.tgz", - "integrity": "sha512-UrOAxnsywUcEngQM2ZxIuucci0VX29hYxX7jcpwZU50HICCjxNsxnuXYPxv+IM+6gbhBY1FYvYJGW4PJcP1Nyw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.6.3.tgz", + "integrity": "sha512-pOKmd0Fa3a6BHg5qbjbl/jMRELVi9oazbfiuU7Bvgn/dpTK+ID3jwT0SXiuC2zxjmPByWgXL6G9XRf5BPAM2rQ==", "dev": true, "license": "MIT", "engines": { @@ -2759,9 +2765,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.6.2.tgz", - "integrity": "sha512-gYxlPLi7fkNcmDmCwZWQa5eOfNcTDundE+TWjpyafxLAjodQuKBD4I0p4XbnuocHjoBEeNzLWdE5RShbZEXEJA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.6.3.tgz", + "integrity": "sha512-3AUferhkLIXtLV63w5GjpHttzdxZ36i656XMy+pkBZbbiqnzIVeKWg6DJv1A94fQY16gB4gqj9CLq4CWvbNN6w==", "dev": true, "license": "MIT", "engines": { @@ -2769,9 +2775,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.6.2.tgz", - "integrity": "sha512-ev5hy9wmiHZi1GKQ1l6PJ2+UpsUh+DvK9AwiCZVEdaicuhmTfO6fdL4szgE4An8RU+Ou9DeiI1tZcq6iw++Wuw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.6.3.tgz", + "integrity": "sha512-fr6bD872WIBXe9YnTDi0CzYepMcYRgSnkVqn0yK4wRnIvKrloWhxXNVY45GVIl51aNZguBnvoA4WEt6HIazs3A==", "dev": true, "license": "MIT", "engines": { @@ -2779,9 +2785,9 @@ } }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.6.2.tgz", - "integrity": "sha512-2ZXVVcmdmEeX0Hb3IAurHUjgU3H1GIk9h7Okosdjgl3tl+BaNHxi84Us+DblynO1LRj8nL/ATeVtSfBuW3Z1vw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.6.3.tgz", + "integrity": "sha512-sn34MvN1ajw2Oq1+Drpxej78Z0HfIzI4p4WlolupAV9dOZKzp2JAIQeLVfZpjIFbF3zuyxLPP4dUBrQoFPEqhA==", "dev": true, "license": "MIT", "engines": { @@ -4308,12 +4314,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.4.tgz", - "integrity": "sha512-MlWK8eqj0JlpZBnWmjQLqmFp71Ug00P+m72/1xQB3YByXD4zZ+y9N4hYrR0EDmrUCZIkyATWHOXFgtavwGDTzQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.5.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { @@ -4373,16 +4379,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.0.tgz", - "integrity": "sha512-LafbclHNKnsorMgUkKm7Tk7oJ7xizsZ1VwqhGKqoCIrXh4fqDDp73fK99HOEEgcsQbtemmeY/BPv0vTVYYUNEQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", + "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.7", + "@smithy/util-middleware": "^3.0.8", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -4409,9 +4415,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.5.0.tgz", - "integrity": "sha512-QN0twHNfe8mNJdH9unwsCK13GURU7oEAZqkBI+rsvpv1jrmserO+WnLE7jidR9W/1dxwZ0u/CB01mV2Gms/K2Q==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4540,12 +4546,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.7.tgz", - "integrity": "sha512-OVA6fv/3o7TMJTpTgOi1H5OTwnuUa8hzRzhSFDtZyNxi6OZ70L/FHattSmhE212I7b6WSOJAAmbYnvcjTHOJCA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.5.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { @@ -4876,12 +4882,12 @@ } }, "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "version": "22.8.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.2.tgz", + "integrity": "sha512-NzaRNFV+FZkvK/KLCsNdTvID0SThyrs5SHB6tsD/lajr22FGC73N2QeDPM2wHtVde8mgcXuSsHQkH5cX1pbPLw==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/normalize-package-data": { @@ -4963,12 +4969,25 @@ }, "node_modules/@types/sinon": { "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", "dev": true, "license": "MIT", "dependencies": { "@types/sinonjs__fake-timers": "*" } }, + "node_modules/@types/sinon-chai": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-4.0.0.tgz", + "integrity": "sha512-Uar+qk3TmeFsUWCwtqRNqNUE7vf34+MCJiQJR5M2rd4nCbhtE8RgTiHwN/mVwbfCjhmO6DiOel/MgzHkRMJJFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.5", "dev": true, @@ -5535,14 +5554,6 @@ "node": ">=8" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/arrify": { "version": "1.0.1", "dev": true, @@ -6130,6 +6141,8 @@ }, "node_modules/chai": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", "dependencies": { @@ -6966,7 +6979,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7208,31 +7223,30 @@ } }, "node_modules/cspell": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.14.4.tgz", - "integrity": "sha512-R5Awb3i/RKaVVcZzFt8dkN3M6VnifIEDYBcbzbmYjZ/Eq+ASF+QTmI0E9WPhMEcFM1nd7YOyXnETo560yRdoKw==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.15.4.tgz", + "integrity": "sha512-hUOxcwmNWuHzVeGHyN5v/T9MkyCE5gi0mvatxsM794B2wOuR1ZORgjZH62P2HY1uBkXe/x5C6ITWrSyh0WgAcg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-json-reporter": "8.14.4", - "@cspell/cspell-pipe": "8.14.4", - "@cspell/cspell-types": "8.14.4", - "@cspell/dynamic-import": "8.14.4", - "@cspell/url": "8.14.4", + "@cspell/cspell-json-reporter": "8.15.4", + "@cspell/cspell-pipe": "8.15.4", + "@cspell/cspell-types": "8.15.4", + "@cspell/dynamic-import": "8.15.4", + "@cspell/url": "8.15.4", "chalk": "^5.3.0", "chalk-template": "^1.1.0", "commander": "^12.1.0", - "cspell-dictionary": "8.14.4", - "cspell-gitignore": "8.14.4", - "cspell-glob": "8.14.4", - "cspell-io": "8.14.4", - "cspell-lib": "8.14.4", - "fast-glob": "^3.3.2", + "cspell-dictionary": "8.15.4", + "cspell-gitignore": "8.15.4", + "cspell-glob": "8.15.4", + "cspell-io": "8.15.4", + "cspell-lib": "8.15.4", "fast-json-stable-stringify": "^2.1.0", "file-entry-cache": "^9.1.0", "get-stdin": "^9.0.0", "semver": "^7.6.3", - "strip-ansi": "^7.1.0" + "tinyglobby": "^0.2.9" }, "bin": { "cspell": "bin.mjs", @@ -7246,30 +7260,30 @@ } }, "node_modules/cspell-config-lib": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.14.4.tgz", - "integrity": "sha512-cnUeJfniTiebqCaQmIUnbSrPrTH7xzKRQjJDHAEV0WYnOG2MhRXI13OzytdFdhkVBdStmgTzTCJKE7x+kmU2NA==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.15.4.tgz", + "integrity": "sha512-vUgikQTRkRMTdkZqSs7F2cTdPpX61cTjr/9L/VCkXkbW38ObCr4650ioiF1Wq3zDF3Gy2bc4ECTpD2PZUXX5SA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "8.14.4", + "@cspell/cspell-types": "8.15.4", "comment-json": "^4.2.5", - "yaml": "^2.5.1" + "yaml": "^2.6.0" }, "engines": { "node": ">=18" } }, "node_modules/cspell-dictionary": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.14.4.tgz", - "integrity": "sha512-pZvQHxpAW5fZAnt3ZKKy3s7M+3CX2t8tCS3uJrpEHIynlCawpG0fPF78rVE5o+g0dON36Lguc/BUuSN4IWKLmQ==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.15.4.tgz", + "integrity": "sha512-8+p/l9Saac7qyCbqtELneDoT7CwHu9gYmnI8uXMu34/lPGjhVhy10ZeI0+t1djaO2YyASK400YFKq5uP/5KulA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "8.14.4", - "@cspell/cspell-types": "8.14.4", - "cspell-trie-lib": "8.14.4", + "@cspell/cspell-pipe": "8.15.4", + "@cspell/cspell-types": "8.15.4", + "cspell-trie-lib": "8.15.4", "fast-equals": "^5.0.1" }, "engines": { @@ -7277,15 +7291,15 @@ } }, "node_modules/cspell-gitignore": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.14.4.tgz", - "integrity": "sha512-RwfQEW5hD7CpYwS7m3b0ONG0nTLKP6bL2tvMdl7qtaYkL7ztGdsBTtLD1pmwqUsCbiN5RuaOxhYOYeRcpFRIkQ==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.15.4.tgz", + "integrity": "sha512-9n5PpQ8gEf8YcvEtoZGZ2Ma6wnqSFkD2GrmyjISy39DfIX/jNLN7GX2wJm6OD2P4FjXer95ypmIb/JWTlfmbTw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "8.14.4", - "cspell-glob": "8.14.4", - "cspell-io": "8.14.4", + "@cspell/url": "8.15.4", + "cspell-glob": "8.15.4", + "cspell-io": "8.15.4", "find-up-simple": "^1.0.0" }, "bin": { @@ -7296,13 +7310,13 @@ } }, "node_modules/cspell-glob": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.14.4.tgz", - "integrity": "sha512-C/xTS5nujMRMuguibq92qMVP767mtxrur7DcVolCvpzcivm1RB5NtIN0OctQxTyMbnmKeQv1t4epRKQ9A8vWRg==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.15.4.tgz", + "integrity": "sha512-TTfRRHRAN+PN9drIz4MAEgKKYnPThBOlPMdFddyuisvU33Do1sPAnqkkOjTEFdi3jAA5KwnSva68SVH6IzzMBQ==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "8.14.4", + "@cspell/url": "8.15.4", "micromatch": "^4.0.8" }, "engines": { @@ -7310,14 +7324,14 @@ } }, "node_modules/cspell-grammar": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.14.4.tgz", - "integrity": "sha512-yaSKAAJDiamsw3FChbw4HXb2RvTQrDsLelh1+T4MavarOIcAxXrqAJ8ysqm++g+S/ooJz2YO8YWIyzJKxcMf8g==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.15.4.tgz", + "integrity": "sha512-MKiKyYi05mRtXOxPoTv3Ksi0GwYLiK84Uq0C+5PaMrnIjXeed0bsddSFXCT+7ywFJc7PdjhTtz0M/9WWK3UgbA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "8.14.4", - "@cspell/cspell-types": "8.14.4" + "@cspell/cspell-pipe": "8.15.4", + "@cspell/cspell-types": "8.15.4" }, "bin": { "cspell-grammar": "bin.mjs" @@ -7327,42 +7341,42 @@ } }, "node_modules/cspell-io": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.14.4.tgz", - "integrity": "sha512-o6OTWRyx/Az+PFhr1B0wMAwqG070hFC9g73Fkxd8+rHX0rfRS69QZH7LgSmZytqbZIMxCTDGdsLl33MFGWCbZQ==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.15.4.tgz", + "integrity": "sha512-rXIEREPTFV9dwwg4EKfvzqlCNOvT6910AYED5YrSt8Y68usRJ9lbqdx0BrDndVCd33bp1o+9JBfHuRiFIQC81g==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-service-bus": "8.14.4", - "@cspell/url": "8.14.4" + "@cspell/cspell-service-bus": "8.15.4", + "@cspell/url": "8.15.4" }, "engines": { "node": ">=18" } }, "node_modules/cspell-lib": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.14.4.tgz", - "integrity": "sha512-qdkUkKtm+nmgpA4jQbmQTuepDfjHBDWvs3zDuEwVIVFq/h8gnXrRr75gJ3RYdTy+vOOqHPoLLqgxyqkUUrUGXA==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.15.4.tgz", + "integrity": "sha512-iLp/625fvCyFFxSyZYLMgqHIKcrhN4hT7Hw5+ySa38Bp/OfA81ANqWHpsDQ0bGsALTRn/DHBpQYj4xCW/aN9tw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-bundled-dicts": "8.14.4", - "@cspell/cspell-pipe": "8.14.4", - "@cspell/cspell-resolver": "8.14.4", - "@cspell/cspell-types": "8.14.4", - "@cspell/dynamic-import": "8.14.4", - "@cspell/filetypes": "8.14.4", - "@cspell/strong-weak-map": "8.14.4", - "@cspell/url": "8.14.4", + "@cspell/cspell-bundled-dicts": "8.15.4", + "@cspell/cspell-pipe": "8.15.4", + "@cspell/cspell-resolver": "8.15.4", + "@cspell/cspell-types": "8.15.4", + "@cspell/dynamic-import": "8.15.4", + "@cspell/filetypes": "8.15.4", + "@cspell/strong-weak-map": "8.15.4", + "@cspell/url": "8.15.4", "clear-module": "^4.1.2", "comment-json": "^4.2.5", - "cspell-config-lib": "8.14.4", - "cspell-dictionary": "8.14.4", - "cspell-glob": "8.14.4", - "cspell-grammar": "8.14.4", - "cspell-io": "8.14.4", - "cspell-trie-lib": "8.14.4", + "cspell-config-lib": "8.15.4", + "cspell-dictionary": "8.15.4", + "cspell-glob": "8.15.4", + "cspell-grammar": "8.15.4", + "cspell-io": "8.15.4", + "cspell-trie-lib": "8.15.4", "env-paths": "^3.0.0", "fast-equals": "^5.0.1", "gensequence": "^7.0.0", @@ -7377,14 +7391,14 @@ } }, "node_modules/cspell-trie-lib": { - "version": "8.14.4", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.14.4.tgz", - "integrity": "sha512-zu8EJ33CH+FA5lwTRGqS//Q6phO0qtgEmODMR1KPlD7WlrfTFMb3bWFsLo/tiv5hjpsn7CM6dYDAAgBOSkoyhQ==", + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.15.4.tgz", + "integrity": "sha512-sg9klsNHyrfos0Boiio+qy5d6fI9cCNjBqFYrNxvpKpwZ4gEzDzjgEKdZY1C76RD2KoBQ8I1NF5YcGc0+hhhCw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "8.14.4", - "@cspell/cspell-types": "8.14.4", + "@cspell/cspell-pipe": "8.15.4", + "@cspell/cspell-types": "8.15.4", "gensequence": "^7.0.0" }, "engines": { @@ -8610,9 +8624,9 @@ } }, "node_modules/ethers": { - "version": "6.13.3", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.3.tgz", - "integrity": "sha512-/DzbZOLVtoO4fKvvQwpEucHAQgIwBGWuRvBdwE/lMXgXvvHHTSkn7XqAQ2b+gjJzZDJjWA9OD05bVceVOsBHbg==", + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", "funding": [ { "type": "individual", @@ -8628,9 +8642,9 @@ "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", - "@types/node": "18.15.13", + "@types/node": "22.7.5", "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", + "tslib": "2.7.0", "ws": "8.17.1" }, "engines": { @@ -8662,16 +8676,13 @@ } }, "node_modules/ethers/node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "license": "MIT" - }, - "node_modules/ethers/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "license": "0BSD" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/ethjs-util": { "version": "0.1.6", @@ -8744,7 +8755,9 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.21.0", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -8752,7 +8765,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8837,10 +8850,12 @@ } }, "node_modules/express-session": { - "version": "1.18.0", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", "license": "MIT", "dependencies": { - "cookie": "0.6.0", + "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", @@ -8855,10 +8870,14 @@ }, "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, "node_modules/express-session/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -8866,10 +8885,14 @@ }, "node_modules/express-session/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/express-session/node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -8886,8 +8909,19 @@ ], "license": "MIT" }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -8895,10 +8929,14 @@ }, "node_modules/express/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -9647,7 +9685,9 @@ } }, "node_modules/gh-pages": { - "version": "6.1.1", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.2.0.tgz", + "integrity": "sha512-HMXJ8th9u5wRXaZCnLcs/d3oVvCHiZkaP5KQExQljYGwJjQbSPyTdHe/Gc1IvYUR/rWiZLxNobIqfoMHKTKjHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9657,7 +9697,7 @@ "filenamify": "^4.3.0", "find-cache-dir": "^3.3.1", "fs-extra": "^11.1.1", - "globby": "^6.1.0" + "globby": "^11.1.0" }, "bin": { "gh-pages": "bin/gh-pages.js", @@ -9667,19 +9707,10 @@ "node": ">=10" } }, - "node_modules/gh-pages/node_modules/array-union": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gh-pages/node_modules/commander": { "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "license": "MIT", "engines": { @@ -9688,6 +9719,8 @@ }, "node_modules/gh-pages/node_modules/fs-extra": { "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "license": "MIT", "dependencies": { @@ -9699,29 +9732,6 @@ "node": ">=14.14" } }, - "node_modules/gh-pages/node_modules/globby": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gh-pages/node_modules/pify": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/git-raw-commits": { "version": "3.0.0", "dev": true, @@ -10049,15 +10059,15 @@ } }, "node_modules/hardhat": { - "version": "2.22.12", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.12.tgz", - "integrity": "sha512-yok65M+LsOeTBHQsjg//QreGCyrsaNmeLVzhTFqlOvZ4ZE5y69N0wRxH1b2BC9dGK8S8OPUJMNiL9X0RAvbm8w==", + "version": "2.22.13", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.13.tgz", + "integrity": "sha512-psVJX4FSXDpSXwsU8OcKTJN04pQEj9cFBMX5OPko+OFwbIoiOpvRmafa954/UaA1934npTj8sV3gaTSdx9bPbA==", "dev": true, "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.6.1", + "@nomicfoundation/edr": "^0.6.3", "@nomicfoundation/ethereumjs-common": "4.0.4", "@nomicfoundation/ethereumjs-tx": "5.0.4", "@nomicfoundation/ethereumjs-util": "9.0.4", @@ -10413,9 +10423,9 @@ } }, "node_modules/hardhat/node_modules/readdirp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", - "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", "dev": true, "license": "MIT", "engines": { @@ -14860,25 +14870,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "dev": true, @@ -16175,6 +16166,17 @@ "url": "https://opencollective.com/sinon" } }, + "node_modules/sinon-chai": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", + "dev": true, + "license": "(BSD-2-Clause OR WTFPL)", + "peerDependencies": { + "chai": "^4.0.0", + "sinon": ">=4.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "license": "MIT", @@ -16218,9 +16220,9 @@ } }, "node_modules/solc": { - "version": "0.8.27", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.27.tgz", - "integrity": "sha512-BNxMol2tUAbkH7HKlXBcBqrGi2aqgv+uMHz26mJyTtlVgWmBA4ktiw0qVKHfkjf2oaHbwtbtaSeE2dhn/gTAKw==", + "version": "0.8.28", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.28.tgz", + "integrity": "sha512-AFCiJ+b4RosyyNhnfdVH4ZR1+TxiL91iluPjw0EJslIu4LXGM9NYqi2z5y8TqochC4tcH9QsHfwWhOIC9jPDKA==", "license": "MIT", "dependencies": { "command-exists": "^1.2.8", @@ -16983,6 +16985,48 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tinyglobby": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.9.tgz", + "integrity": "sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.0.tgz", + "integrity": "sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.0.33", "license": "MIT", @@ -17488,9 +17532,9 @@ "license": "MIT" }, "node_modules/typedoc": { - "version": "0.26.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.8.tgz", - "integrity": "sha512-QBF0BMbnNeUc6U7pRHY7Jb8pjhmiNWZNQT8LU6uk9qP9t3goP9bJptdlNqMC0wBB2w9sQrxjZt835bpRSSq1LA==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.10.tgz", + "integrity": "sha512-xLmVKJ8S21t+JeuQLNueebEuTVphx6IrP06CdV7+0WVflUSW3SPmR+h1fnWVdAR/FQePEgsSWCUHXqKKjzuUAw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -17527,9 +17571,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -18290,7 +18334,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.5.1", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", "dev": true, "license": "ISC", "bin": { @@ -18422,7 +18468,7 @@ "devDependencies": { "@types/chai": "4.3.20", "@types/mocha": "10.0.9", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "c8": "10.1.2", "chai": "4.5.0", "cz-conventional-changelog": "3.3.0", @@ -18437,9 +18483,9 @@ } }, "packages/bytecode-utils/node_modules/@types/node": { - "version": "20.16.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", - "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", + "version": "20.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.2.tgz", + "integrity": "sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==", "dev": true, "license": "MIT", "dependencies": { @@ -18464,44 +18510,112 @@ "@fairdatasociety/bmt-js": "2.1.0", "abitype": "1.0.6", "bs58": "5.0.0", - "ethers": "6.13.3", + "ethers": "6.13.4", "http-status-codes": "2.3.0", "jszip": "3.10.1", "semver": "7.6.3" }, "devDependencies": { - "@types/chai": "4.3.20", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^7.1.2", "@types/debug": "4.1.12", "@types/mocha": "10.0.9", - "@types/node": "20.16.11", + "@types/node": "20.17.2", + "@types/sinon": "^17.0.3", + "@types/sinon-chai": "^4.0.0", "c8": "10.1.2", "chai": "4.5.0", - "cspell": "8.14.4", + "chai-as-promised": "^7.1.2", + "cspell": "8.15.4", "cz-conventional-changelog": "3.3.0", - "gh-pages": "6.1.1", - "hardhat": "2.22.12", + "gh-pages": "6.2.0", + "hardhat": "2.22.13", "mocha": "10.7.3", "nock": "14.0.0-beta.15", "npm-run-all2": "5.0.2", "open-cli": "8.0.0", - "solc": "0.8.27", + "sinon": "^19.0.2", + "sinon-chai": "^3.7.0", + "solc": "0.8.28", "tree-kill": "1.2.2", - "typedoc": "0.26.8" + "typedoc": "0.26.10" }, "engines": { "node": "22.5.1" } }, + "packages/lib-sourcify/node_modules/@sinonjs/fake-timers": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", + "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "packages/lib-sourcify/node_modules/@types/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*" + } + }, "packages/lib-sourcify/node_modules/@types/node": { - "version": "20.16.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", - "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", + "version": "20.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.2.tgz", + "integrity": "sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, + "packages/lib-sourcify/node_modules/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 6" + } + }, + "packages/lib-sourcify/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "packages/lib-sourcify/node_modules/sinon": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "services/database": { "name": "sourcify-database", "version": "2.0.0", @@ -18511,7 +18625,7 @@ "db-migrate": "0.11.14", "db-migrate-pg": "1.5.2", "dotenv": "16.4.5", - "ethers": "6.13.3", + "ethers": "6.13.4", "pg": "8.13.0", "readdirp": "3.6.0" } @@ -18526,17 +18640,17 @@ "chalk": "4.1.2", "commander": "12.1.0", "dotenv": "16.4.5", - "ethers": "6.13.3", + "ethers": "6.13.4", "winston": "3.15.0" }, "devDependencies": { "@types/chai": "4.3.20", "@types/mocha": "10.0.9", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "@types/sinon": "17.0.3", "c8": "10.1.2", "chai": "4.5.0", - "hardhat": "2.22.12", + "hardhat": "2.22.13", "mocha": "10.7.3", "nock": "14.0.0-beta.15", "open-cli": "8.0.0", @@ -18545,9 +18659,9 @@ } }, "services/monitor/node_modules/@types/node": { - "version": "20.16.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", - "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", + "version": "20.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.2.tgz", + "integrity": "sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==", "dev": true, "license": "MIT", "dependencies": { @@ -18559,7 +18673,7 @@ "version": "2.3.0", "license": "MIT", "dependencies": { - "@aws-sdk/client-lambda": "3.666.0", + "@aws-sdk/client-lambda": "3.680.0", "@ethereum-sourcify/bytecode-utils": "^1.2.12", "@ethereum-sourcify/lib-sourcify": "^1.9.3", "@google-cloud/cloud-sql-connector": "1.4.0", @@ -18571,19 +18685,19 @@ "cors": "2.8.5", "directory-tree": "3.5.2", "dotenv": "16.4.5", - "ethers": "6.13.3", - "express": "4.21.0", + "ethers": "6.13.4", + "express": "4.21.1", "express-fileupload": "1.5.1", "express-openapi-validator": "5.3.7", "express-rate-limit": "6.11.2", - "express-session": "1.18.0", + "express-session": "1.18.1", "http-status-codes": "2.3.0", "json-refs": "3.0.15", "memorystore": "1.6.7", "pg": "8.13.0", "semver": "7.6.3", "serve-index": "1.9.1", - "solc": "0.8.27", + "solc": "0.8.28", "swagger-ui-express": "5.0.1", "uuid": "10.0.0", "winston": "3.15.0", @@ -18602,7 +18716,7 @@ "@types/express-session": "1.18.0", "@types/mocha": "10.0.9", "@types/mochawesome": "6.2.4", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "@types/pg": "8.11.10", "@types/serve-index": "1.9.4", "@types/swagger-ui-express": "4.1.6", @@ -18613,7 +18727,7 @@ "chai-http": "4.4.0", "commander": "12.1.0", "copyfiles": "2.4.1", - "hardhat": "2.22.12", + "hardhat": "2.22.13", "mocha": "10.7.3", "mochawesome": "7.1.3", "nock": "14.0.0-beta.15", @@ -18639,9 +18753,9 @@ } }, "services/server/node_modules/@types/node": { - "version": "20.16.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", - "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", + "version": "20.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.2.tgz", + "integrity": "sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a0edacef1..0ccba3783 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "lerna": "8.1.8", "prettier": "3.3.3", "ts-node": "10.9.2", - "typescript": "5.6.2" + "typescript": "5.6.3" }, "optionalDependencies": { "fsevents": "2.3.3" diff --git a/packages/bytecode-utils/CHANGELOG.md b/packages/bytecode-utils/CHANGELOG.md index 8a12b34f3..6d4754269 100644 --- a/packages/bytecode-utils/CHANGELOG.md +++ b/packages/bytecode-utils/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## @ethereum-sourcify/bytecode-utils@1.2.13 - 2024-10-29 + +- Update packages + ## @ethereum-sourcify/bytecode-utils@1.2.12 - 2024-10-14 - Update packages diff --git a/packages/bytecode-utils/package.json b/packages/bytecode-utils/package.json index 876dbff4a..328ee4727 100644 --- a/packages/bytecode-utils/package.json +++ b/packages/bytecode-utils/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@types/chai": "4.3.20", "@types/mocha": "10.0.9", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "c8": "10.1.2", "chai": "4.5.0", "cz-conventional-changelog": "3.3.0", diff --git a/packages/lib-sourcify/CHANGELOG.md b/packages/lib-sourcify/CHANGELOG.md index 7d1ad775d..4fa1be22c 100644 --- a/packages/lib-sourcify/CHANGELOG.md +++ b/packages/lib-sourcify/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## @ethereum-sourcify/lib-sourcify@1.10.0 - 2024-10-29 + +- Add RPCs with trace support in SourcifyChains +- Add support for getting the tx traces from `trace_transaction` and `debug_traceTransaction` type traces. +- Update packages + ## @ethereum-sourcify/lib-sourcify@1.9.3 - 2024-10-14 - Add routescan for creatorTx fetching and types diff --git a/packages/lib-sourcify/package.json b/packages/lib-sourcify/package.json index 7fcf8ae7a..d5928e25b 100644 --- a/packages/lib-sourcify/package.json +++ b/packages/lib-sourcify/package.json @@ -51,29 +51,35 @@ "@fairdatasociety/bmt-js": "2.1.0", "abitype": "1.0.6", "bs58": "5.0.0", - "ethers": "6.13.3", + "ethers": "6.13.4", "http-status-codes": "2.3.0", "jszip": "3.10.1", "semver": "7.6.3" }, "devDependencies": { - "@types/chai": "4.3.20", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^7.1.2", "@types/debug": "4.1.12", "@types/mocha": "10.0.9", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "c8": "10.1.2", "chai": "4.5.0", - "cspell": "8.14.4", + "cspell": "8.15.4", + "@types/sinon": "^17.0.3", + "@types/sinon-chai": "^4.0.0", + "chai-as-promised": "^7.1.2", "cz-conventional-changelog": "3.3.0", - "gh-pages": "6.1.1", - "hardhat": "2.22.12", + "gh-pages": "6.2.0", + "hardhat": "2.22.13", "mocha": "10.7.3", "nock": "14.0.0-beta.15", "npm-run-all2": "5.0.2", "open-cli": "8.0.0", - "solc": "0.8.27", + "solc": "0.8.28", + "sinon": "^19.0.2", + "sinon-chai": "^3.7.0", "tree-kill": "1.2.2", - "typedoc": "0.26.8" + "typedoc": "0.26.10" }, "files": [ "build/main", diff --git a/packages/lib-sourcify/src/lib/SourcifyChain.ts b/packages/lib-sourcify/src/lib/SourcifyChain.ts index 63ecbfa88..def25f490 100644 --- a/packages/lib-sourcify/src/lib/SourcifyChain.ts +++ b/packages/lib-sourcify/src/lib/SourcifyChain.ts @@ -7,9 +7,11 @@ import { getAddress, } from 'ethers'; import { + CallFrame, Chain, FetchContractCreationTxMethods, SourcifyChainExtension, + TraceSupportedRPC, } from './types'; import { logDebug, logError, logInfo, logWarn } from './logger'; @@ -27,19 +29,21 @@ interface JsonRpcProviderWithUrl extends JsonRpcProvider { export type SourcifyChainInstance = Omit & Omit & { rpc: Array; + rpcWithoutApiKeys?: Array; + traceSupportedRPCs?: TraceSupportedRPC[]; }; -class CreatorTransactionMismatchError extends Error { - constructor() { - super("Creator transaction doesn't match the contract"); - } -} - export default class SourcifyChain { name: string; title?: string | undefined; chainId: number; rpc: Array; + rpcWithoutApiKeys?: Array; + /** Whether the chain supports tracing, used for fetching the creation bytecode for factory contracts */ + traceSupport?: boolean; + /** The RPCs that support tracing. Needed in a separate field than `this.rpc` because the `rpc` was an array of strings or FetchRequest. Modifying the `rpc` to be something else would have caused a breaking change. */ + // TODO: in a future breaking change, merge traceSupportedRPCs with rpc and make rpc an array of objects with url and type. + traceSupportedRPCs?: TraceSupportedRPC[]; supported: boolean; providers: JsonRpcProviderWithUrl[]; fetchContractCreationTxUsing?: FetchContractCreationTxMethods; @@ -53,11 +57,16 @@ export default class SourcifyChain { this.title = sourcifyChainObj.title; this.chainId = sourcifyChainObj.chainId; this.rpc = sourcifyChainObj.rpc; + this.rpcWithoutApiKeys = sourcifyChainObj?.rpcWithoutApiKeys; this.supported = sourcifyChainObj.supported; this.providers = []; this.fetchContractCreationTxUsing = sourcifyChainObj.fetchContractCreationTxUsing; this.etherscanApi = sourcifyChainObj.etherscanApi; + this.traceSupportedRPCs = sourcifyChainObj.traceSupportedRPCs; + this.traceSupport = + sourcifyChainObj.traceSupportedRPCs && + sourcifyChainObj.traceSupportedRPCs.length > 0; if (!this.supported) return; // Don't create providers if chain is not supported @@ -190,50 +199,207 @@ export default class SourcifyChain { ); }; - getTxTraces = async (creatorTxHash: string) => { - // Try sequentially all providers - for (const provider of this.providers) { - try { - // Race the RPC call with a timeout - const traces = await Promise.race([ - provider.send('trace_transaction', [creatorTxHash]), - this.rejectInMs(RPC_TIMEOUT, provider.url), - ]); - if (traces instanceof Array && traces.length > 0) { - logInfo('Fetched tx traces', { + /** + * Tries to fetch the creation bytecode for a factory contract with the available methods. + * Not limited to traces but might fetch it from other resources too. + */ + getCreationBytecodeForFactory = async ( + creatorTxHash: string, + address: string, + ) => { + // TODO: Alternative methods e.g. getting from Coleslaw. Not only traces. + + if (!this.traceSupport || !this.traceSupportedRPCs) { + throw new Error( + `No trace support for chain ${this.chainId}. No other method to get the creation bytecode`, + ); + } + + // Try sequentially all providers with trace support + for (const traceSupportedRPCObj of this.traceSupportedRPCs) { + const { index, type } = traceSupportedRPCObj; + const provider = this.providers[index]; + // Parity type `trace_transaction` + if (type === 'trace_transaction') { + logDebug('Fetching creation bytecode from parity traces', { + creatorTxHash, + address, + providerUrl: provider.url, + chainId: this.chainId, + }); + try { + const creationBytecode = await this.extractFromParityTraceProvider( + creatorTxHash, + address, + provider, + ); + return creationBytecode; + } catch (e: any) { + // Catch to continue with the next provider + logWarn('Failed to fetch creation bytecode from parity traces', { creatorTxHash, + address, providerUrl: provider.url, chainId: this.chainId, + error: e.message, }); - return traces; - } else { - throw new Error( - `Transaction's traces of ${creatorTxHash} on RPC ${provider.url} and chain ${this.chainId} received empty or malformed response`, - ); + continue; } - } catch (err) { - if (err instanceof Error) { - logWarn('Failed to fetch tx traces', { + } + // Geth type `debug_traceTransaction` + else if (type === 'debug_traceTransaction') { + logDebug('Fetching creation bytecode from geth traces', { + creatorTxHash, + address, + providerUrl: provider.url, + chainId: this.chainId, + }); + try { + const creationBytecode = await this.extractFromGethTraceProvider( + creatorTxHash, + address, + provider, + ); + return creationBytecode; + } catch (e: any) { + // Catch to continue with the next provider + logWarn('Failed to fetch creation bytecode from geth traces', { creatorTxHash, + address, providerUrl: provider.url, chainId: this.chainId, - error: err.message, + error: e.message, }); continue; - } else { - throw err; } } } - throw new Error( - 'None of the RPCs could successfully fetch tx traces for ' + + 'Couldnt get the creation bytecode for factory ' + + address + + ' with tx ' + creatorTxHash + ' on chain ' + this.chainId, ); }; + /** + * For Parity style traces `trace_transaction` + * Extracts the creation bytecode from the traces of a transaction + */ + extractFromParityTraceProvider = async ( + creatorTxHash: string, + address: string, + provider: JsonRpcProviderWithUrl, + ) => { + // Race the RPC call with a timeout + const traces = await Promise.race([ + provider.send('trace_transaction', [creatorTxHash]), + this.rejectInMs(RPC_TIMEOUT, provider.url), + ]); + if (traces instanceof Array && traces.length > 0) { + logInfo('Fetched tx traces', { + creatorTxHash, + providerUrl: provider.url, + chainId: this.chainId, + }); + } else { + throw new Error( + `Transaction's traces of ${creatorTxHash} on RPC ${provider.url} and chain ${this.chainId} received empty or malformed response`, + ); + } + + const createTraces = traces.filter((trace: any) => trace.type === 'create'); + // This line makes sure the tx in question is indeed for the contract being verified and not a random tx. + const contractTrace = createTraces.find( + (trace) => + (trace.result.address as string).toLowerCase() === + address.toLowerCase(), + ); + if (!contractTrace) { + throw new Error( + `Provided tx ${creatorTxHash} does not create the expected contract ${address}. Created contracts by this tx: ${createTraces.map((t) => t.result.address).join(', ')}`, + ); + } + logDebug('Found contract bytecode in traces', { + address, + creatorTxHash, + chainId: this.chainId, + }); + if (contractTrace.action.init) { + return contractTrace.action.init as string; + } else { + throw new Error('.action.init not found in traces'); + } + }; + + extractFromGethTraceProvider = async ( + creatorTxHash: string, + address: string, + provider: JsonRpcProviderWithUrl, + ) => { + const traces = await Promise.race([ + provider.send('debug_traceTransaction', [ + creatorTxHash, + { tracer: 'callTracer' }, + ]), + this.rejectInMs(RPC_TIMEOUT, provider.url), + ]); + if (traces?.calls instanceof Array && traces.calls.length > 0) { + logInfo('Fetched tx traces', { + creatorTxHash, + providerUrl: provider.url, + chainId: this.chainId, + }); + } else { + throw new Error( + `Transaction's traces of ${creatorTxHash} on RPC ${provider.url} and chain ${this.chainId} received empty or malformed response`, + ); + } + + const createCalls: CallFrame[] = []; + this.findCreateInDebugTraceTransactionCalls( + traces.calls as CallFrame[], + createCalls, + ); + + if (createCalls.length === 0) { + throw new Error( + `No CREATE or CREATE2 calls found in the traces of ${creatorTxHash} on RPC ${provider.url} and chain ${this.chainId}`, + ); + } + + // A call can have multiple contracts created. We need the one that matches the address we are verifying. + const ourCreateCall = createCalls.find( + (createCall) => createCall.to.toLowerCase() === address.toLowerCase(), + ); + + if (!ourCreateCall) { + throw new Error( + `No CREATE or CREATE2 call found for the address ${address} in the traces of ${creatorTxHash} on RPC ${provider.url} and chain ${this.chainId}`, + ); + } + + return ourCreateCall.input; + }; + + /** + * Find CREATE or CREATE2 operations recursively in the call frames. Because a call can have nested calls. + * Pushes the found call frames to the createCalls array. + */ + findCreateInDebugTraceTransactionCalls( + calls: CallFrame[], + createCalls: CallFrame[], + ) { + calls.forEach((call) => { + if (call?.type === 'CREATE' || call?.type === 'CREATE2') { + createCalls.push(call); + } else if (call?.calls?.length > 0) { + this.findCreateInDebugTraceTransactionCalls(call.calls, createCalls); + } + }); + } /** * Fetches the contract's deployed bytecode from SourcifyChain's rpc's. * Tries to fetch sequentially if the first RPC is a local eth node. Fetches in parallel otherwise. @@ -385,44 +551,24 @@ export default class SourcifyChain { // Non null txreceipt.contractAddress means that the contract was created with an EOA if (txReceipt.contractAddress !== null) { if (txReceipt.contractAddress !== address) { - throw new CreatorTransactionMismatchError(); + // we need to check if this contract creation tx actually yields the same contract address https://github.com/ethereum/sourcify/issues/887 + throw new Error( + `Address of the contract being verified ${address} doesn't match the address ${txReceipt.contractAddress} created by this transaction ${transactionHash}`, + ); } creationBytecode = creatorTx.data; logDebug(`Contract ${address} created with an EOA`); } else { - // Factory created - let traces; - logDebug(`Contract ${address} created with a factory. Fetching traces`); - try { - traces = await this.getTxTraces(transactionHash); - } catch (e: any) { - logInfo(e.message); - traces = []; - } - - // If traces are available check, otherwise lets just trust - if (traces.length > 0) { - const createTraces = traces.filter( - (trace: any) => trace.type === 'create', + // Else, contract was created with a factory + if (!this.traceSupport) { + throw new Error( + `No trace support for chain ${this.chainId}. No other method to get the creation bytecode`, ); - const createdContractAddressesInTx = createTraces.find( - (trace) => getAddress(trace.result.address) === address, - ); - if (createdContractAddressesInTx === undefined) { - throw new CreatorTransactionMismatchError(); - } - logDebug('Found contract bytecode in traces', { - address, - transactionHash, - chainId: this.chainId, - }); - creationBytecode = createdContractAddressesInTx.result.code; } - } - - if (!creationBytecode) { - throw new Error( - `Cannot get the creation bytecode for ${address} from the transaction hash ${transactionHash} on chain ${this.chainId}`, + logDebug(`Contract ${address} created with a factory. Fetching traces`); + creationBytecode = await this.getCreationBytecodeForFactory( + transactionHash, + address, ); } diff --git a/packages/lib-sourcify/src/lib/types.ts b/packages/lib-sourcify/src/lib/types.ts index e27fe6268..5d12164e3 100644 --- a/packages/lib-sourcify/src/lib/types.ts +++ b/packages/lib-sourcify/src/lib/types.ts @@ -1,5 +1,6 @@ import { Abi } from 'abitype'; import SourcifyChain from './SourcifyChain'; +import { FetchRequest } from 'ethers'; export interface PathBuffer { path: string; buffer: Buffer; @@ -322,16 +323,33 @@ export interface FetchContractCreationTxMethods { export type FetchContractCreationTxMethod = keyof FetchContractCreationTxMethods; -export type AlchemyInfuraRPC = { - type: 'Alchemy' | 'Infura'; +export type TraceSupport = 'trace_transaction' | 'debug_traceTransaction'; + +export type BaseRPC = { url: string; + type: 'BaseRPC'; + traceSupport?: TraceSupport; +}; + +// override the type of BaseRPC to add the type field +export type APIKeyRPC = Omit & { + type: 'APIKeyRPC'; apiKeyEnvName: string; + subDomainEnvName?: string; }; -export type FetchRequestRPC = { +// override the type of BaseRPC to add the type field +export type FetchRequestRPC = Omit & { type: 'FetchRequest'; - url: string; - headers?: Array<{ headerName: string; headerEnvName: string }>; + headers?: Array<{ + headerName: string; + headerEnvName: string; + }>; +}; + +export type TraceSupportedRPC = { + type: TraceSupport; + index: number; }; export type SourcifyChainExtension = { @@ -342,7 +360,12 @@ export type SourcifyChainExtension = { apiKeyEnvName?: string; }; fetchContractCreationTxUsing?: FetchContractCreationTxMethods; - rpc?: Array; + rpc?: Array; +}; + +export type RPCObjectWithTraceSupport = { + rpc: string | FetchRequest; + traceSupport?: TraceSupport; }; export interface SourcifyChainsExtensionsObject { @@ -586,3 +609,18 @@ export interface IpfsGateway { url: string; headers?: HeadersInit; } + +// https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#call-tracer +export interface CallFrame { + type: string; + from: string; + to: string; + value: string; + gas: string; + gasUsed: string; + input: string; + output: string; + error: string; + revertReason: string; + calls: CallFrame[]; +} diff --git a/packages/lib-sourcify/src/lib/verification.ts b/packages/lib-sourcify/src/lib/verification.ts index f86052729..6199c54a3 100644 --- a/packages/lib-sourcify/src/lib/verification.ts +++ b/packages/lib-sourcify/src/lib/verification.ts @@ -20,12 +20,7 @@ import { decode as bytecodeDecode, splitAuxdata, } from '@ethereum-sourcify/bytecode-utils'; -import { - getAddress, - getCreateAddress, - keccak256, - id as keccak256Str, -} from 'ethers'; +import { getAddress, keccak256, id as keccak256Str } from 'ethers'; import { hexZeroPad, isHexString } from '@ethersproject/bytes'; import { BigNumber } from '@ethersproject/bignumber'; import { defaultAbiCoder as abiCoder, ParamType } from '@ethersproject/abi'; @@ -688,16 +683,6 @@ export async function matchWithCreationTx( abiEncodedConstructorArguments; } - // we need to check if this contract creation tx actually yields the same contract address https://github.com/ethereum/sourcify/issues/887 - const createdContractAddress = getCreateAddress({ - from: creatorTx.from, - nonce: creatorTx.nonce, - }); - if (createdContractAddress.toLowerCase() !== address.toLowerCase()) { - match.creationMatch = null; - match.message = `The address being verified ${address} doesn't match the expected ddress of the contract ${createdContractAddress} that will be created by the transaction ${creatorTxHash}.`; - return; - } match.libraryMap = libraryMap; match.abiEncodedConstructorArguments = abiEncodedConstructorArguments; diff --git a/packages/lib-sourcify/test/SourcifyChain.spec.ts b/packages/lib-sourcify/test/SourcifyChain.spec.ts new file mode 100644 index 000000000..a0e99233a --- /dev/null +++ b/packages/lib-sourcify/test/SourcifyChain.spec.ts @@ -0,0 +1,281 @@ +import { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import chai from 'chai'; +import sinonChai from 'sinon-chai'; +import sinon from 'sinon'; +import { SourcifyChain, TraceSupportedRPC } from '../src'; +import { JsonRpcProvider } from 'ethers'; + +chai.use(chaiAsPromised); +chai.use(sinonChai); + +describe('SourcifyChain', () => { + let sourcifyChain: SourcifyChain; + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sourcifyChain = new SourcifyChain({ + name: 'TestChain', + chainId: 1, + rpc: ['http://localhost:8545'], + supported: true, + traceSupportedRPCs: [{ index: 0, type: 'trace_transaction' }], + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getCreationBytecodeForFactory', () => { + it('should throw an error if trace support is not available', async () => { + sourcifyChain = new SourcifyChain({ + name: 'TestChain', + chainId: 1, + rpc: ['http://localhost:8545'], + supported: true, + }); + await expect( + sourcifyChain.getCreationBytecodeForFactory('0xhash', '0xaddress'), + ).to.be.rejectedWith( + 'No trace support for chain 1. No other method to get the creation bytecode', + ); + }); + + it('should extract creation bytecode from parity traces', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves([ + { + type: 'create', + result: { address: '0xaddress' }, + action: { init: '0xcreationBytecode' }, + }, + ]); + + const result = await sourcifyChain.getCreationBytecodeForFactory( + '0xhash', + '0xaddress', + ); + expect(result).to.equal('0xcreationBytecode'); + expect(mockProvider.send).to.have.been.calledWith('trace_transaction', [ + '0xhash', + ]); + }); + + it('should throw an error if no create trace is found', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox + .stub(mockProvider, 'send') + .resolves([{ type: 'call' }, { type: 'suicide' }]); + + await expect( + sourcifyChain.getCreationBytecodeForFactory('0xhash', '0xaddress'), + ).to.be.rejectedWith('Couldnt get the creation bytecode for factory'); + }); + + it('should try multiple trace-supported RPCs if the first one fails', async () => { + sourcifyChain.traceSupportedRPCs = [ + { index: 0, type: 'trace_transaction' }, + { index: 1, type: 'trace_transaction' }, + ] as TraceSupportedRPC[]; + sourcifyChain.providers.push( + new JsonRpcProvider('http://localhost:8546'), + ); + + const mockProvider1 = sourcifyChain.providers[0] as JsonRpcProvider; + const mockProvider2 = sourcifyChain.providers[1] as JsonRpcProvider; + + sandbox.stub(mockProvider1, 'send').rejects(new Error('RPC error')); + sandbox.stub(mockProvider2, 'send').resolves([ + { + type: 'create', + result: { address: '0xaddress' }, + action: { init: '0xcreationBytecode' }, + }, + ]); + + const result = await sourcifyChain.getCreationBytecodeForFactory( + '0xhash', + '0xaddress', + ); + expect(result).to.equal('0xcreationBytecode'); + expect(mockProvider1.send).to.have.been.called; + expect(mockProvider2.send).to.have.been.called; + }); + + it('should extract creation bytecode from geth traces', async () => { + sourcifyChain.traceSupportedRPCs = [ + { index: 0, type: 'debug_traceTransaction' }, + ] as TraceSupportedRPC[]; + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves({ + calls: [ + { + type: 'CREATE', + to: '0xaddress', + input: '0xcreationBytecode', + }, + ], + }); + + const result = await sourcifyChain.getCreationBytecodeForFactory( + '0xhash', + '0xaddress', + ); + expect(result).to.equal('0xcreationBytecode'); + expect(mockProvider.send).to.have.been.calledWith( + 'debug_traceTransaction', + ['0xhash', { tracer: 'callTracer' }], + ); + }); + + it('should throw an error if no CREATE or CREATE2 calls are found in geth traces', async () => { + sourcifyChain.traceSupportedRPCs = [ + { index: 0, type: 'debug_traceTransaction' }, + ] as TraceSupportedRPC[]; + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves({ + calls: [ + { + type: 'CALL', + to: '0xsomeaddress', + input: '0xsomeinput', + }, + ], + }); + + await expect( + sourcifyChain.getCreationBytecodeForFactory('0xhash', '0xaddress'), + ).to.be.rejectedWith( + 'Couldnt get the creation bytecode for factory 0xaddress with tx 0xhash on chain 1', + ); + }); + + it('should throw an error if the contract address is not found in geth traces', async () => { + sourcifyChain.traceSupportedRPCs = [ + { index: 0, type: 'debug_traceTransaction' }, + ] as TraceSupportedRPC[]; + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves({ + calls: [ + { + type: 'CREATE', + to: '0xdifferentaddress', + input: '0xcreationBytecode', + }, + ], + }); + + await expect( + sourcifyChain.getCreationBytecodeForFactory('0xhash', '0xaddress'), + ).to.be.rejectedWith( + 'Couldnt get the creation bytecode for factory 0xaddress with tx 0xhash on chain 1', + ); + }); + }); + + describe('extractFromParityTraceProvider', () => { + it('should throw an error if the contract address does not match', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves([ + { + type: 'create', + result: { address: '0xdifferentAddress' }, + action: { init: '0xcreationBytecode' }, + }, + ]); + + await expect( + sourcifyChain.extractFromParityTraceProvider( + '0xhash', + '0xaddress', + mockProvider, + ), + ).to.be.rejectedWith( + `Provided tx 0xhash does not create the expected contract 0xaddress. Created contracts by this tx: 0xdifferentAddress`, + ); + }); + + it('should throw an error when .action.init is not found', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves([ + { + type: 'create', + result: { address: '0xaddress' }, + action: {}, // Missing 'init' property + }, + ]); + + await expect( + sourcifyChain.extractFromParityTraceProvider( + '0xhash', + '0xaddress', + mockProvider, + ), + ).to.be.rejectedWith('.action.init not found'); + }); + + // Add more tests for extractFromParityTraceProvider here if needed + }); + + describe('extractFromGethTraceProvider', () => { + it('should extract creation bytecode from geth traces', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves({ + calls: [ + { + type: 'CREATE', + to: '0xaddress', + input: '0xcreationBytecode', + }, + ], + }); + + const result = await sourcifyChain.extractFromGethTraceProvider( + '0xhash', + '0xaddress', + mockProvider, + ); + expect(result).to.equal('0xcreationBytecode'); + }); + + it('should handle nested CREATE calls in geth traces', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves({ + calls: [ + { + type: 'CALL', + calls: [ + { + type: 'CREATE', + to: '0xaddress', + input: '0xcreationBytecode', + }, + ], + }, + ], + }); + + const result = await sourcifyChain.extractFromGethTraceProvider( + '0xhash', + '0xaddress', + mockProvider, + ); + expect(result).to.equal('0xcreationBytecode'); + }); + + it('should throw an error if traces response is empty or malformed', async () => { + const mockProvider = sourcifyChain.providers[0] as JsonRpcProvider; + sandbox.stub(mockProvider, 'send').resolves({}); + + await expect( + sourcifyChain.extractFromGethTraceProvider( + '0xhash', + '0xaddress', + mockProvider, + ), + ).to.be.rejectedWith('received empty or malformed response'); + }); + }); +}); diff --git a/scripts/release/main.sh b/scripts/release/main.sh index 2b1f73509..9dcf2a2b8 100755 --- a/scripts/release/main.sh +++ b/scripts/release/main.sh @@ -49,3 +49,4 @@ prompt_execute_or_skip "choosing same versions in lerna to create git tags and u prompt_execute_or_skip "pushing the tags to GitHub" push_tags_in_order prompt_execute_or_skip "pushing the commits to master" push_to_master prompt_execute_or_skip "creating GitHub releases" create_github_releases +prompt_execute_or_skip "creating a PR to merge the new versions to the staging branch" open_pr_to_staging master diff --git a/serverless/compiler-lambda/package-lock.json b/serverless/compiler-lambda/package-lock.json index 6a0074cc6..6fdeb45bb 100644 --- a/serverless/compiler-lambda/package-lock.json +++ b/serverless/compiler-lambda/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "solc": "0.8.27" + "solc": "0.8.28" } }, "node_modules/command-exists": { @@ -74,9 +74,9 @@ } }, "node_modules/solc": { - "version": "0.8.27", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.27.tgz", - "integrity": "sha512-BNxMol2tUAbkH7HKlXBcBqrGi2aqgv+uMHz26mJyTtlVgWmBA4ktiw0qVKHfkjf2oaHbwtbtaSeE2dhn/gTAKw==", + "version": "0.8.28", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.28.tgz", + "integrity": "sha512-AFCiJ+b4RosyyNhnfdVH4ZR1+TxiL91iluPjw0EJslIu4LXGM9NYqi2z5y8TqochC4tcH9QsHfwWhOIC9jPDKA==", "license": "MIT", "dependencies": { "command-exists": "^1.2.8", diff --git a/serverless/compiler-lambda/package.json b/serverless/compiler-lambda/package.json index 188bb75ad..de7766574 100644 --- a/serverless/compiler-lambda/package.json +++ b/serverless/compiler-lambda/package.json @@ -10,6 +10,6 @@ "author": "", "license": "ISC", "dependencies": { - "solc": "0.8.27" + "solc": "0.8.28" } } diff --git a/services/database/CHANGELOG.md b/services/database/CHANGELOG.md index e4492ac58..86aae6748 100644 --- a/services/database/CHANGELOG.md +++ b/services/database/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for `sourcify-database` +## sourcify-database@2.0.1 - 2024-10-29 + +- Update packages + ## sourcify-database@2.0.0 - 2024-10-14 - Update the Sourcify Database to incorporate the new Verifier Alliance Database schema diff --git a/services/database/package.json b/services/database/package.json index 346c0d48f..5f41486b0 100644 --- a/services/database/package.json +++ b/services/database/package.json @@ -17,7 +17,7 @@ "db-migrate": "0.11.14", "db-migrate-pg": "1.5.2", "dotenv": "16.4.5", - "ethers": "6.13.3", + "ethers": "6.13.4", "pg": "8.13.0", "readdirp": "3.6.0" } diff --git a/services/monitor/CHANGELOG.md b/services/monitor/CHANGELOG.md index 19ba4896f..fd00df8ba 100644 --- a/services/monitor/CHANGELOG.md +++ b/services/monitor/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## sourcify-monitor@1.3.3 - 2024-10-29 + +- Update monitor RPCs to public ones +- Update packages + ## sourcify-monitor@1.3.2 - 2024-10-14 - Update monitor RPCs diff --git a/services/monitor/monitorChains.json b/services/monitor/monitorChains.json index 507ada7c6..3e842c4e4 100644 --- a/services/monitor/monitorChains.json +++ b/services/monitor/monitorChains.json @@ -14,167 +14,129 @@ "name": "Ethereum Testnet Sepolia", "title": "Ethereum Testnet Sepolia", "chainId": 11155111, - "rpc": [ - { - "type": "ApiKey", - "url": "https://eth-sepolia.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } - ] + "rpc": ["https://sepolia.drpc.org", "https://1rpc.io/sepolia"] }, { "name": "Ethereum Testnet Holesky", "title": "Ethereum Testnet Holesky", "chainId": 17000, - "rpc": [ - { - "type": "ApiKey", - "url": "https://eth-holesky.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } - ] + "rpc": ["https://holesky.drpc.org", "https://1rpc.io/holesky"] }, { "name": "Arbitrum One", "chainId": 42161, "rpc": [ - { - "type": "ApiKey", - "url": "https://arb-mainnet.g.alchemy.com/v2/{API_KEY}", - "apiKeyEnvName": "ALCHEMY_API_KEY_ARBITRUM" - } + "https://arb1.arbitrum.io/rpc", + "https://1rpc.io/arb", + "https://arbitrum.meowrpc.com", + "https://arbitrum.drpc.org" ] }, { "name": "Arbitrum Sepolia Testnet", "chainId": 421614, "rpc": [ - { - "type": "ApiKey", - "url": "https://arb-sepolia.g.alchemy.com/v2/{API_KEY}", - "apiKeyEnvName": "ALCHEMY_API_KEY_ARBITRUM" - } + "https://sepolia-rollup.arbitrum.io/rpc", + "https://api.zan.top/arb-sepolia", + "https://arbitrum-sepolia.gateway.tenderly.co" ] }, { "name": "Arbitrum Nova", "chainId": 42170, "rpc": [ - { - "type": "ApiKey", - "url": "https://arbnova-mainnet.g.alchemy.com/v2/{API_KEY}", - "apiKeyEnvName": "ALCHEMY_API_KEY_ARBITRUM" - } + "https://nova.arbitrum.io/rpc", + "https://arbitrum-nova.public.blastapi.io", + "https://arbitrum-nova.gateway.tenderly.co" ] }, { "name": "Gnosis", "chainId": 100, "rpc": [ - { - "type": "ApiKey", - "url": "https://gnosis-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://rpc.gnosischain.com", + "https://1rpc.io/gnosis", + "https://gnosis.blockpi.network/v1/rpc/public" ] }, { "name": "OP Mainnet", "chainId": 10, "rpc": [ - { - "type": "ApiKey", - "url": "https://optimism-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://mainnet.optimism.io", + "https://optimism.gateway.tenderly.co", + "https://optimism.llamarpc.com" ] }, { "name": "OP Sepolia Testnet", "chainId": 11155420, "rpc": [ - { - "type": "ApiKey", - "url": "https://optimism-sepolia.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://sepolia.optimism.io", + "https://optimism-sepolia.gateway.tenderly.co", + "https://optimism-sepolia.blockpi.network/v1/rpc/public" ] }, { "name": "Polygon Mainnet", "chainId": 137, "rpc": [ - { - "type": "ApiKey", - "url": "https://polygon-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://polygon.llamarpc.com", + "https://1rpc.io/matic", + "https://polygon.meowrpc.com" ] }, { "name": "Polygon Amoy Testnet", "chainId": 80002, "rpc": [ - { - "type": "ApiKey", - "url": "https://polygon-amoy.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://rpc-amoy.polygon.technology", + "https://rpc.ankr.com/polygon_amoy" ] }, { "name": "Polygon zkEVM Mainnet", "chainId": 1101, "rpc": [ - { - "type": "ApiKey", - "url": "https://polygon-zkevm-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://rpc.ankr.com/polygon_zkevm", + "https://zkevm-rpc.com", + "https://rpc.polygon-zkevm.gateway.fm" ] }, { "name": "Avalanche C-Chain", "chainId": 43114, "rpc": [ - { - "type": "ApiKey", - "url": "https://ava-mainnet.blastapi.io/{API_KEY}/ext/bc/C/rpc", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://avalanche-c-chain-rpc.publicnode.com", + "https://1rpc.io/avax/c", + "https://api.avax.network/ext/bc/C/rpc" ] }, { "name": "Avalanche Fuji Testnet", "chainId": 43113, "rpc": [ - { - "type": "ApiKey", - "url": "https://ava-testnet.blastapi.io/{API_KEY}/ext/bc/C/rpc", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://avalanche-fuji-c-chain-rpc.publicnode.com", + "https://ava-testnet.public.blastapi.io/ext/bc/C/rpc", + "https://rpc.ankr.com/avalanche_fuji" ] }, { "name": "Base", "chainId": 8453, "rpc": [ - { - "type": "ApiKey", - "url": "https://base-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://mainnet.base.org", + "https://base.llamarpc.com", + "https://base-mainnet.public.blastapi.io" ] }, { "name": "Base Sepolia Testnet", - "chainId": 84531, + "chainId": 84532, "rpc": [ - { - "type": "ApiKey", - "url": "https://base-sepolia.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://sepolia.base.org", + "https://base-sepolia-rpc.publicnode.com", + "https://base-sepolia.blockpi.network/v1/rpc/public" ] }, { @@ -201,44 +163,36 @@ "name": "Linea Mainnet", "chainId": 59144, "rpc": [ - { - "type": "ApiKey", - "url": "https://linea-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://rpc.linea.build", + "https://linea-rpc.publicnode.com", + "https://linea.blockpi.network/v1/rpc/public" ] }, { "name": "Linea Sepolia Testnet", "chainId": 59141, "rpc": [ - { - "type": "ApiKey", - "url": "https://linea-sepolia.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://rpc.sepolia.linea.build", + "https://linea-sepolia.blockpi.network/v1/rpc/public", + "https://linea-sepolia-rpc.publicnode.com" ] }, { "name": "Scroll Mainnet", "chainId": 534352, "rpc": [ - { - "type": "ApiKey", - "url": "https://scroll-mainnet.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://rpc.scroll.io", + "https://scroll.drpc.org", + "https://1rpc.io/scroll" ] }, { "name": "Scroll Testnet", "chainId": 534351, "rpc": [ - { - "type": "ApiKey", - "url": "https://scroll-sepolia.blastapi.io/{API_KEY}", - "apiKeyEnvName": "BLAST_API_KEY" - } + "https://sepolia-rpc.scroll.io", + "https://rpc.ankr.com/scroll_sepolia_testnet", + "https://scroll-sepolia-rpc.publicnode.com" ] } ] diff --git a/services/monitor/package.json b/services/monitor/package.json index 33fa6e862..bcfaf5989 100644 --- a/services/monitor/package.json +++ b/services/monitor/package.json @@ -42,17 +42,17 @@ "chalk": "4.1.2", "commander": "12.1.0", "dotenv": "16.4.5", - "ethers": "6.13.3", + "ethers": "6.13.4", "winston": "3.15.0" }, "devDependencies": { "@types/chai": "4.3.20", "@types/mocha": "10.0.9", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "@types/sinon": "17.0.3", "c8": "10.1.2", "chai": "4.5.0", - "hardhat": "2.22.12", + "hardhat": "2.22.13", "mocha": "10.7.3", "nock": "14.0.0-beta.15", "open-cli": "8.0.0", diff --git a/services/server/.env.dev b/services/server/.env.dev index 6bd182ee1..af25b0cd2 100644 --- a/services/server/.env.dev +++ b/services/server/.env.dev @@ -27,6 +27,7 @@ SOURCIFY_POSTGRES_DB="" SOURCIFY_POSTGRES_USER="" SOURCIFY_POSTGRES_PASSWORD="" SOURCIFY_POSTGRES_PORT=5432 +# SOURCIFY_POSTGRES_SCHEMA=custom-schema # by default "public" is used # Verifier Alliance database postgres connection # ALLIANCE_POSTGRES_HOST= @@ -34,6 +35,7 @@ SOURCIFY_POSTGRES_PORT=5432 # ALLIANCE_POSTGRES_USER= # ALLIANCE_POSTGRES_PASSWORD= # ALLIANCE_POSTGRES_PORT= +# # ALLIANCE_POSTGRES_SCHEMA=custom-schema # by default "public" is used # Simple authentication token for the /change-log-level endpoint SETLOGGING_TOKEN= @@ -46,6 +48,9 @@ SESSION_SECRET=CHANGE_ME INFURA_API_KEY= # Alchemy used for Arbitrum, Optimism, Polygon, and fallback for Ethereum. See sourcify-chains.ts ALCHEMY_API_KEY= +QUICKNODE_API_KEY= +# If you are using different subdomains (endpoints) for different enviroments (staging, production, etc) +QUICKNODE_SUBDOMAIN= # Optional, if not set will use ALCHEMY_API_KEY ALCHEMY_API_KEY_OPTIMISM= # Optional, if not set will use ALCHEMY_API_KEY @@ -54,6 +59,7 @@ ALCHEMY_API_KEY_ARBITRUM= # Optional, Needed for the Import from Etherscan functionality for each Etherscan instance ETHERSCAN_API_KEY= ARBISCAN_API_KEY= +ARBISCAN_NOVA_API_KEY= POLYGONSCAN_API_KEY= ZKEVM_POLYGONSCAN_API_KEY= BSCSCAN_API_KEY= diff --git a/services/server/CHANGELOG.md b/services/server/CHANGELOG.md index 2c441a6a3..546fb0c37 100644 --- a/services/server/CHANGELOG.md +++ b/services/server/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. +## sourcify-server@2.4.0 - 2024-10-29 + + +- Refactor database utils into class #1689 +- Add chains that have trace support in Quicknode with trace support +- Change `AlchemyInfura` type RPCs to generic API key RPCs +- Add `subdomain` env support for Quicknode RPCs +- New chains + - Zircuit Mainnet (48900) + - Zircuit Testnet (48899) + - Metis Andromeda Mainnet (1088) + - Metis Sepolia Testnet (59902) +- Turn Flare Mainnet back on + ## sourcify-server@2.3.0 - 2024-10-14 - Incorporate the new DB schema with separate sources table diff --git a/services/server/README.md b/services/server/README.md index 737f79725..24715bc9f 100644 --- a/services/server/README.md +++ b/services/server/README.md @@ -26,16 +26,14 @@ See the [Config](#config) section below for details. const { RWStorageIdentifiers, } = require("../server/services/storageServices/identifiers"); - + module.exports = { storage: { read: RWStorageIdentifiers.RepositoryV1, writeOrWarn: [], - writeOrErr: [ - RWStorageIdentifiers.RepositoryV1 - ] - } -} + writeOrErr: [RWStorageIdentifiers.RepositoryV1], + }, +}; ``` 3. Build the monorepo's packages @@ -188,13 +186,13 @@ A full example of a chain entry is as follows: ] }, { - "type": "Alchemy", // Alchemy RPCs - "url": "https://eth-mainnet.alchemyapi.io/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", // Alchemy RPCs + "url": "https://eth-mainnet.alchemyapi.io/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY" }, { - "type": "Infura", // Infura RPCs - "url": "https://palm-mainnet.infura.io/v3/{INFURA_API_KEY}", + "type": "APIKeyRPC", // Infura RPCs + "url": "https://palm-mainnet.infura.io/v3/{API_KEY}", "apiKeyEnvName": "INFURA_API_KEY" } ] diff --git a/services/server/package.json b/services/server/package.json index c91691ae7..cc902dbde 100644 --- a/services/server/package.json +++ b/services/server/package.json @@ -51,7 +51,7 @@ }, "homepage": "https://github.com/ethereum/sourcify#readme", "dependencies": { - "@aws-sdk/client-lambda": "3.666.0", + "@aws-sdk/client-lambda": "3.680.0", "@ethereum-sourcify/bytecode-utils": "^1.2.12", "@ethereum-sourcify/lib-sourcify": "^1.9.3", "@google-cloud/cloud-sql-connector": "1.4.0", @@ -63,19 +63,19 @@ "cors": "2.8.5", "directory-tree": "3.5.2", "dotenv": "16.4.5", - "ethers": "6.13.3", - "express": "4.21.0", + "ethers": "6.13.4", + "express": "4.21.1", "express-fileupload": "1.5.1", "express-openapi-validator": "5.3.7", "express-rate-limit": "6.11.2", - "express-session": "1.18.0", + "express-session": "1.18.1", "http-status-codes": "2.3.0", "json-refs": "3.0.15", "memorystore": "1.6.7", "pg": "8.13.0", "semver": "7.6.3", "serve-index": "1.9.1", - "solc": "0.8.27", + "solc": "0.8.28", "swagger-ui-express": "5.0.1", "uuid": "10.0.0", "winston": "3.15.0", @@ -91,7 +91,7 @@ "@types/express-session": "1.18.0", "@types/mocha": "10.0.9", "@types/mochawesome": "6.2.4", - "@types/node": "20.16.11", + "@types/node": "20.17.2", "@types/pg": "8.11.10", "@types/serve-index": "1.9.4", "@types/swagger-ui-express": "4.1.6", @@ -102,7 +102,7 @@ "chai-http": "4.4.0", "commander": "12.1.0", "copyfiles": "2.4.1", - "hardhat": "2.22.12", + "hardhat": "2.22.13", "mocha": "10.7.3", "mochawesome": "7.1.3", "nock": "14.0.0-beta.15", diff --git a/services/server/src/server/routes.ts b/services/server/src/server/routes.ts index ae87df2a1..595259a99 100644 --- a/services/server/src/server/routes.ts +++ b/services/server/src/server/routes.ts @@ -36,26 +36,21 @@ router.get("/chains", (_req, res) => { const chainRepository = _req.app.get("chainRepository") as ChainRepository; const sourcifyChainsArray = chainRepository.sourcifyChainsArray; const sourcifyChains = sourcifyChainsArray.map( - ({ rpc, name, title, chainId, supported, etherscanApi }) => { - // Don't publish providers - // Don't show Alchemy & Infura IDs - rpc = rpc.map((url) => { - if (typeof url === "string") { - if (url.includes("alchemy")) - return url.replace(/\/[^/]*$/, "/{ALCHEMY_API_KEY}"); - else if (url.includes("infura")) - return url.replace(/\/[^/]*$/, "/{INFURA_API_KEY}"); - else return url; - } else { - // FetchRequest - return url.url; - } - }); + ({ + rpcWithoutApiKeys, + name, + title, + chainId, + supported, + etherscanApi, + traceSupportedRPCs, + }) => { return { name, title, chainId, - rpc, + rpc: rpcWithoutApiKeys, + traceSupportedRPCs, supported, etherscanAPI: etherscanApi?.apiURL, // Needed in the UI }; diff --git a/services/server/src/server/services/storageServices/AbstractDatabaseService.ts b/services/server/src/server/services/storageServices/AbstractDatabaseService.ts index fb0ce6db7..8ae4ceea8 100644 --- a/services/server/src/server/services/storageServices/AbstractDatabaseService.ts +++ b/services/server/src/server/services/storageServices/AbstractDatabaseService.ts @@ -1,15 +1,14 @@ import { Match, CheckedContract } from "@ethereum-sourcify/lib-sourcify"; import { keccak256 } from "ethers"; -import * as Database from "../utils/database-util"; +import * as DatabaseUtil from "../utils/database-util"; import { bytesFromString, normalizeRecompiledBytecodes, } from "../utils/database-util"; -import { Pool, QueryResult } from "pg"; -import { AuthTypes, Connector } from "@google-cloud/cloud-sql-connector"; -import logger from "../../../common/logger"; -import { Bytes, BytesKeccak, Nullable } from "../../types"; +import { Bytes, BytesKeccak, Nullable } from "../../types"; +import { Database } from "../utils/Database"; +import { QueryResult } from "pg"; export interface DatabaseServiceOptions { googleCloudSql?: { instanceName: string; @@ -27,92 +26,15 @@ export interface DatabaseServiceOptions { } export default abstract class AbstractDatabaseService { - abstract databasePool: Pool; + public database: Database; abstract IDENTIFIER: string; - googleCloudSqlInstanceName?: string; - googleCloudSqlIamAccount?: string; - googleCloudSqlDatabase?: string; - postgresHost?: string; - postgresPort?: number; - postgresDatabase?: string; - postgresUser?: string; - postgresPassword?: string; - schema: string = "public"; - constructor(options: DatabaseServiceOptions) { - this.googleCloudSqlInstanceName = options.googleCloudSql?.instanceName; - this.googleCloudSqlIamAccount = options.googleCloudSql?.iamAccount; - this.googleCloudSqlDatabase = options.googleCloudSql?.database; - this.postgresHost = options.postgres?.host; - this.postgresPort = options.postgres?.port; - this.postgresDatabase = options.postgres?.database; - this.postgresUser = options.postgres?.user; - this.postgresPassword = options.postgres?.password; - if (options.schema) { - this.schema = options.schema; - } + this.database = new Database(options); } async init() { - return await this.initDatabasePool(); - } - - async initDatabasePool(): Promise { - // if the database is already initialized - if (this.databasePool != undefined) { - return true; - } - - logger.debug(`Initializing database pool for ${this.IDENTIFIER}`); - - if (this.googleCloudSqlInstanceName) { - const connector = new Connector(); - const clientOpts = await connector.getOptions({ - instanceConnectionName: this.googleCloudSqlInstanceName, // "verifier-alliance:europe-west3:test-verifier-alliance", - authType: AuthTypes.IAM, - }); - this.databasePool = new Pool({ - ...clientOpts, - user: this.googleCloudSqlIamAccount, // "marco.castignoli@ethereum.org", - database: this.googleCloudSqlDatabase, // "postgres", - max: 5, - }); - } else if (this.postgresHost) { - this.databasePool = new Pool({ - host: this.postgresHost, - port: this.postgresPort, - database: this.postgresDatabase, - user: this.postgresUser, - password: this.postgresPassword, - max: 5, - }); - } else { - throw new Error("Alliance Database is disabled"); - } - - // Checking pool health before continuing - try { - logger.debug(`Checking database pool health for ${this.IDENTIFIER}`); - await this.databasePool.query("SELECT 1;"); - } catch (error) { - logger.error(`Cannot connect to ${this.IDENTIFIER}`, { - host: this.postgresHost, - port: this.postgresPort, - database: this.postgresDatabase, - user: this.postgresUser, - error, - }); - throw new Error(`Cannot connect to ${this.IDENTIFIER}`); - } - - logger.info(`${this.IDENTIFIER} initialized`, { - host: this.postgresHost, - port: this.postgresPort, - database: this.postgresDatabase, - schema: this.schema, - }); - return true; + return await this.database.initDatabasePool(this.IDENTIFIER); } validateBeforeStoring( @@ -166,7 +88,7 @@ export default abstract class AbstractDatabaseService { async getDatabaseColumns( recompiledContract: CheckedContract, match: Match, - ): Promise { + ): Promise { const { keccak256OnchainCreationBytecode, keccak256OnchainRuntimeBytecode, @@ -232,7 +154,7 @@ export default abstract class AbstractDatabaseService { } // Prepare compilation_artifacts.sources by removing everything except id - let sources: Nullable = null; + let sources: Nullable = null; if (recompiledContract.compilerOutput?.sources) { sources = {}; for (const source of Object.keys( @@ -274,7 +196,7 @@ export default abstract class AbstractDatabaseService { } let recompiledCreationCode: - | Omit + | Omit | undefined; if ( recompiledContract.normalizedCreationBytecode && @@ -291,7 +213,7 @@ export default abstract class AbstractDatabaseService { } let onchainCreationCode: - | Omit + | Omit | undefined; if (match.onchainCreationBytecode && keccak256OnchainCreationBytecode) { @@ -343,7 +265,8 @@ export default abstract class AbstractDatabaseService { compiledContract: { language, compiler: "solc", - compiler_settings: Database.prepareCompilerSettings(recompiledContract), + compiler_settings: + DatabaseUtil.prepareCompilerSettings(recompiledContract), name: recompiledContract.name, version: recompiledContract.compilerVersion, fully_qualified_name: `${compilationTargetPath}:${compilationTargetName}`, @@ -367,73 +290,64 @@ export default abstract class AbstractDatabaseService { } async insertNewVerifiedContract( - recompiledContract: CheckedContract, match: Match, - databaseColumns: Database.DatabaseColumns, + databaseColumns: DatabaseUtil.DatabaseColumns, ): Promise { // Get a client from the pool, so that we can execute all the insert queries within the same transaction - const client = await this.databasePool.connect(); + const client = await this.database.pool.connect(); try { // Start the sql transaction await client.query("BEGIN"); let recompiledCreationCodeInsertResult: - | QueryResult> + | QueryResult> | undefined; let onchainCreationCodeInsertResult: - | QueryResult> + | QueryResult> | undefined; // Add recompiled bytecodes if (databaseColumns.recompiledCreationCode) { - recompiledCreationCodeInsertResult = await Database.insertCode( + recompiledCreationCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.recompiledCreationCode, ); } - const recompiledRuntimeCodeInsertResult = await Database.insertCode( + const recompiledRuntimeCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.recompiledRuntimeCode, ); // Add onchain bytecodes if (databaseColumns.onchainCreationCode) { - onchainCreationCodeInsertResult = await Database.insertCode( + onchainCreationCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.onchainCreationCode, ); } - const onchainRuntimeCodeInsertResult = await Database.insertCode( + const onchainRuntimeCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.onchainRuntimeCode, ); // Add the onchain contract in contracts - const contractInsertResult = await Database.insertContract( - client, - this.schema, - { - creation_bytecode_hash: - onchainCreationCodeInsertResult?.rows[0].bytecode_hash, - runtime_bytecode_hash: - onchainRuntimeCodeInsertResult.rows[0].bytecode_hash, - }, - ); + const contractInsertResult = await this.database.insertContract(client, { + creation_bytecode_hash: + onchainCreationCodeInsertResult?.rows[0].bytecode_hash, + runtime_bytecode_hash: + onchainRuntimeCodeInsertResult.rows[0].bytecode_hash, + }); // add the onchain contract in contract_deployments const contractDeploymentInsertResult = - await Database.insertContractDeployment(client, this.schema, { + await this.database.insertContractDeployment(client, { ...databaseColumns.contractDeployment, contract_id: contractInsertResult.rows[0].id, }); // insert new recompiled contract const compiledContractsInsertResult = - await Database.insertCompiledContract(client, this.schema, { + await this.database.insertCompiledContract(client, { ...databaseColumns.compiledContract, creation_code_hash: recompiledCreationCodeInsertResult?.rows[0].bytecode_hash, @@ -443,14 +357,14 @@ export default abstract class AbstractDatabaseService { const compiledContractId = compiledContractsInsertResult.rows[0].id; - await Database.insertCompiledContractsSources(client, { + await this.database.insertCompiledContractsSources(client, { sourcesInformation: databaseColumns.sourcesInformation, compilation_id: compiledContractId, }); // insert new recompiled contract with newly added contract and compiledContract const verifiedContractInsertResult = - await Database.insertVerifiedContract(client, this.schema, { + await this.database.insertVerifiedContract(client, { ...databaseColumns.verifiedContract, compilation_id: compiledContractId, deployment_id: contractDeploymentInsertResult.rows[0].id, @@ -470,10 +384,10 @@ export default abstract class AbstractDatabaseService { } async updateExistingVerifiedContract( - existingVerifiedContractResult: Database.GetVerifiedContractByChainAndAddressResult[], + existingVerifiedContractResult: DatabaseUtil.GetVerifiedContractByChainAndAddressResult[], recompiledContract: CheckedContract, match: Match, - databaseColumns: Database.DatabaseColumns, + databaseColumns: DatabaseUtil.DatabaseColumns, ): Promise { // runtime bytecodes must exist if (recompiledContract.normalizedRuntimeBytecode === undefined) { @@ -484,16 +398,16 @@ export default abstract class AbstractDatabaseService { } // Get a client from the pool, so that we can execute all the insert queries within the same transaction - const client = await this.databasePool.connect(); + const client = await this.database.pool.connect(); try { // Start the sql transaction await client.query("BEGIN"); let recompiledCreationCodeInsertResult: - | QueryResult> + | QueryResult> | undefined; let onchainCreationCodeInsertResult: - | QueryResult> + | QueryResult> | undefined; // Check if contracts_deployed needs to be updated if ( @@ -501,22 +415,19 @@ export default abstract class AbstractDatabaseService { match.creatorTxHash != null && databaseColumns.onchainCreationCode ) { - onchainCreationCodeInsertResult = await Database.insertCode( + onchainCreationCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.onchainCreationCode, ); - const onchainRuntimeCodeInsertResult = await Database.insertCode( + const onchainRuntimeCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.onchainRuntimeCode, ); // Add the onchain contract in contracts - const contractInsertResult = await Database.insertContract( + const contractInsertResult = await this.database.insertContract( client, - this.schema, { creation_bytecode_hash: onchainCreationCodeInsertResult.rows[0].bytecode_hash, @@ -526,7 +437,7 @@ export default abstract class AbstractDatabaseService { ); // add the onchain contract in contract_deployments - await Database.updateContractDeployment(client, this.schema, { + await this.database.updateContractDeployment(client, { ...databaseColumns.contractDeployment, contract_id: contractInsertResult.rows[0].id, id: existingVerifiedContractResult[0].deployment_id, @@ -538,21 +449,19 @@ export default abstract class AbstractDatabaseService { recompiledContract.normalizedCreationBytecode && databaseColumns.recompiledCreationCode ) { - recompiledCreationCodeInsertResult = await Database.insertCode( + recompiledCreationCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.recompiledCreationCode, ); } - const recompiledRuntimeCodeInsertResult = await Database.insertCode( + const recompiledRuntimeCodeInsertResult = await this.database.insertCode( client, - this.schema, databaseColumns.recompiledRuntimeCode, ); // insert new recompiled contract const compiledContractsInsertResult = - await Database.insertCompiledContract(client, this.schema, { + await this.database.insertCompiledContract(client, { ...databaseColumns.compiledContract, creation_code_hash: recompiledCreationCodeInsertResult?.rows[0].bytecode_hash, @@ -562,14 +471,14 @@ export default abstract class AbstractDatabaseService { const compiledContractId = compiledContractsInsertResult.rows[0].id; - await Database.insertCompiledContractsSources(client, { + await this.database.insertCompiledContractsSources(client, { sourcesInformation: databaseColumns.sourcesInformation, compilation_id: compiledContractId, }); // update verified contract with the newly added recompiled contract const verifiedContractInsertResult = - await Database.insertVerifiedContract(client, this.schema, { + await this.database.insertVerifiedContract(client, { ...databaseColumns.verifiedContract, compilation_id: compiledContractsInsertResult.rows[0].id, deployment_id: existingVerifiedContractResult[0].deployment_id, @@ -599,7 +508,7 @@ export default abstract class AbstractDatabaseService { }> { this.validateBeforeStoring(recompiledContract, match); - await this.initDatabasePool(); + await this.init(); // Normalize both creation and runtime recompiled bytecodes before storing them to the database normalizeRecompiledBytecodes(recompiledContract, match); @@ -609,11 +518,9 @@ export default abstract class AbstractDatabaseService { match, ); - // Get all the verified contracts existing in the Database for these exact onchain bytecodes. + // Get all the verified contracts existing in the DatabaseUtil for these exact onchain bytecodes. const existingVerifiedContractResult = - await Database.getVerifiedContractByChainAndAddress( - this.databasePool, - this.schema, + await this.database.getVerifiedContractByChainAndAddress( parseInt(match.chainId), bytesFromString(match.address)!, ); @@ -622,7 +529,6 @@ export default abstract class AbstractDatabaseService { return { type: "insert", verifiedContractId: await this.insertNewVerifiedContract( - recompiledContract, match, databaseColumns, ), diff --git a/services/server/src/server/services/storageServices/AllianceDatabaseService.ts b/services/server/src/server/services/storageServices/AllianceDatabaseService.ts index d5f191afc..77f479a3c 100644 --- a/services/server/src/server/services/storageServices/AllianceDatabaseService.ts +++ b/services/server/src/server/services/storageServices/AllianceDatabaseService.ts @@ -1,5 +1,4 @@ import logger from "../../../common/logger"; -import { Pool } from "pg"; import AbstractDatabaseService from "./AbstractDatabaseService"; import { WStorageService } from "../StorageService"; import { CheckedContract, Match } from "@ethereum-sourcify/lib-sourcify"; @@ -10,8 +9,6 @@ export class AllianceDatabaseService implements WStorageService { IDENTIFIER = WStorageIdentifiers.AllianceDatabase; - databasePool!: Pool; - async storeMatch(recompiledContract: CheckedContract, match: Match) { if (!match.creationMatch) { throw new Error("Can't store to AllianceDatabase without creationMatch"); diff --git a/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts b/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts index 9d1d84337..20426d4ea 100644 --- a/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts +++ b/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts @@ -5,8 +5,6 @@ import { StringMap, } from "@ethereum-sourcify/lib-sourcify"; import logger from "../../../common/logger"; -import * as Database from "../utils/database-util"; -import { Pool } from "pg"; import AbstractDatabaseService, { DatabaseServiceOptions, } from "./AbstractDatabaseService"; @@ -38,7 +36,6 @@ export class SourcifyDatabaseService storageService: StorageService; serverUrl: string; IDENTIFIER = RWStorageIdentifiers.SourcifyDatabase; - databasePool!: Pool; constructor( storageService_: StorageService, @@ -69,12 +66,10 @@ export class SourcifyDatabaseService chainId: string, onlyPerfectMatches: boolean = false, ): Promise { - await this.initDatabasePool(); + await this.init(); const existingVerifiedContractResult = - await Database.getSourcifyMatchByChainAddress( - this.databasePool, - this.schema, + await this.database.getSourcifyMatchByChainAddress( parseInt(chainId), bytesFromString(address)!, onlyPerfectMatches, @@ -97,18 +92,14 @@ export class SourcifyDatabaseService } getContracts = async (chainId: string): Promise => { - await this.initDatabasePool(); + await this.init(); const res: ContractData = { full: [], partial: [], }; const matchAddressesCountResult = - await Database.countSourcifyMatchAddresses( - this.databasePool, - this.schema, - parseInt(chainId), - ); + await this.database.countSourcifyMatchAddresses(parseInt(chainId)); if (matchAddressesCountResult.rowCount === 0) { return res; @@ -134,9 +125,7 @@ export class SourcifyDatabaseService if (fullTotal > 0) { const perfectMatchAddressesResult = - await Database.getSourcifyMatchAddressesByChainAndMatch( - this.databasePool, - this.schema, + await this.database.getSourcifyMatchAddressesByChainAndMatch( parseInt(chainId), "full_match", 0, @@ -155,9 +144,7 @@ export class SourcifyDatabaseService if (partialTotal > 0) { const partialMatchAddressesResult = - await Database.getSourcifyMatchAddressesByChainAndMatch( - this.databasePool, - this.schema, + await this.database.getSourcifyMatchAddressesByChainAndMatch( parseInt(chainId), "partial_match", 0, @@ -184,7 +171,7 @@ export class SourcifyDatabaseService limit: number, descending: boolean = false, ): Promise => { - await this.initDatabasePool(); + await this.init(); // Initialize empty result const res: PaginatedContractData = { @@ -202,11 +189,7 @@ export class SourcifyDatabaseService // Count perfect and partial matches const matchAddressesCountResult = - await Database.countSourcifyMatchAddresses( - this.databasePool, - this.schema, - parseInt(chainId), - ); + await this.database.countSourcifyMatchAddresses(parseInt(chainId)); if (matchAddressesCountResult.rowCount === 0) { return res; @@ -235,9 +218,7 @@ export class SourcifyDatabaseService // Now make the real query for addresses const matchAddressesResult = - await Database.getSourcifyMatchAddressesByChainAndMatch( - this.databasePool, - this.schema, + await this.database.getSourcifyMatchAddressesByChainAndMatch( parseInt(chainId), match, page, @@ -267,14 +248,13 @@ export class SourcifyDatabaseService * by getTree, getContent and getFile. */ getFiles = async (chainId: string, address: string): Promise => { - await this.initDatabasePool(); + await this.init(); - const sourcifyMatchResult = await Database.getSourcifyMatchByChainAddress( - this.databasePool, - this.schema, - parseInt(chainId), - bytesFromString(address)!, - ); + const sourcifyMatchResult = + await this.database.getSourcifyMatchByChainAddress( + parseInt(chainId), + bytesFromString(address)!, + ); if (sourcifyMatchResult.rowCount === 0) { // This is how you handle a non existing contract @@ -290,8 +270,7 @@ export class SourcifyDatabaseService ? "full" : "partial"; - const sourcesResult = await Database.getCompiledContractSources( - this.databasePool, + const sourcesResult = await this.database.getCompiledContractSources( sourcifyMatch.compilation_id, ); const sources = sourcesResult.rows.reduce( @@ -550,7 +529,7 @@ export class SourcifyDatabaseService "VerifiedContractId undefined before inserting sourcify match", ); } - await Database.insertSourcifyMatch(this.databasePool, this.schema, { + await this.database.insertSourcifyMatch({ verified_contract_id: verifiedContractId, creation_match: match.creationMatch, runtime_match: match.runtimeMatch, @@ -579,9 +558,7 @@ export class SourcifyDatabaseService "oldVerifiedContractId undefined before updating sourcify match", ); } - await Database.updateSourcifyMatch( - this.databasePool, - this.schema, + await this.database.updateSourcifyMatch( { verified_contract_id: verifiedContractId, creation_match: match.creationMatch, diff --git a/services/server/src/server/services/utils/Database.ts b/services/server/src/server/services/utils/Database.ts new file mode 100644 index 000000000..352f05e1b --- /dev/null +++ b/services/server/src/server/services/utils/Database.ts @@ -0,0 +1,662 @@ +import { Pool, PoolClient, QueryResult } from "pg"; +import { Bytes } from "../../types"; +import { + bytesFromString, + GetSourcifyMatchByChainAddressResult, + GetVerifiedContractByChainAndAddressResult, + SourceInformation, + Tables, +} from "./database-util"; +import { createHash } from "crypto"; +import { AuthTypes, Connector } from "@google-cloud/cloud-sql-connector"; +import logger from "../../../common/logger"; + +export interface DatabaseOptions { + googleCloudSql?: { + instanceName: string; + iamAccount: string; + database: string; + }; + postgres?: { + host: string; + port: number; + database: string; + user: string; + password: string; + }; + schema?: string; +} + +export class Database { + private _pool?: Pool; + private schema: string = "public"; + private googleCloudSqlInstanceName?: string; + private googleCloudSqlIamAccount?: string; + private googleCloudSqlDatabase?: string; + private postgresHost?: string; + private postgresPort?: number; + private postgresDatabase?: string; + private postgresUser?: string; + private postgresPassword?: string; + + constructor(options: DatabaseOptions) { + this.googleCloudSqlInstanceName = options.googleCloudSql?.instanceName; + this.googleCloudSqlIamAccount = options.googleCloudSql?.iamAccount; + this.googleCloudSqlDatabase = options.googleCloudSql?.database; + this.postgresHost = options.postgres?.host; + this.postgresPort = options.postgres?.port; + this.postgresDatabase = options.postgres?.database; + this.postgresUser = options.postgres?.user; + this.postgresPassword = options.postgres?.password; + if (options.schema) { + this.schema = options.schema; + } + } + + get pool(): Pool { + if (!this._pool) throw new Error("Pool not initialized!"); + return this._pool; + } + + async initDatabasePool(identifier: string): Promise { + // if the database is already initialized + if (this._pool != undefined) { + return true; + } + + logger.debug(`Initializing database pool for ${identifier}`); + + if (this.googleCloudSqlInstanceName) { + const connector = new Connector(); + const clientOpts = await connector.getOptions({ + instanceConnectionName: this.googleCloudSqlInstanceName, + authType: AuthTypes.IAM, + }); + this._pool = new Pool({ + ...clientOpts, + user: this.googleCloudSqlIamAccount, + database: this.googleCloudSqlDatabase, + max: 5, + }); + } else if (this.postgresHost) { + this._pool = new Pool({ + host: this.postgresHost, + port: this.postgresPort, + database: this.postgresDatabase, + user: this.postgresUser, + password: this.postgresPassword, + max: 5, + }); + } else { + throw new Error("Alliance Database is disabled"); + } + + // Checking pool health before continuing + try { + logger.debug(`Checking database pool health for ${identifier}`); + await this._pool.query("SELECT 1;"); + } catch (error) { + logger.error(`Cannot connect to ${identifier}`, { + host: this.postgresHost, + port: this.postgresPort, + database: this.postgresDatabase, + user: this.postgresUser, + error, + }); + throw new Error(`Cannot connect to ${identifier}`); + } + + logger.info(`${identifier} initialized`, { + host: this.postgresHost, + port: this.postgresPort, + database: this.postgresDatabase, + schema: this.schema, + }); + return true; + } + + async getSourcifyMatchByChainAddress( + chain: number, + address: Bytes, + onlyPerfectMatches: boolean = false, + ): Promise> { + return await this.pool.query( + ` + SELECT + sourcify_matches.created_at, + sourcify_matches.creation_match, + sourcify_matches.runtime_match, + sourcify_matches.metadata, + verified_contracts.creation_values, + verified_contracts.runtime_values, + verified_contracts.compilation_id, + compiled_contracts.runtime_code_artifacts, + contract_deployments.transaction_hash + FROM ${this.schema}.sourcify_matches + JOIN ${this.schema}.verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id + JOIN ${this.schema}.compiled_contracts ON compiled_contracts.id = verified_contracts.compilation_id + JOIN ${this.schema}.contract_deployments ON + contract_deployments.id = verified_contracts.deployment_id + AND contract_deployments.chain_id = $1 + AND contract_deployments.address = $2 +${ + onlyPerfectMatches + ? "WHERE sourcify_matches.creation_match = 'perfect' OR sourcify_matches.runtime_match = 'perfect'" + : "" +} + `, + [chain, address], + ); + } + + async getCompiledContractSources( + compilation_id: string, + ): Promise< + QueryResult< + Tables.CompiledContractsSources & Pick + > + > { + return await this.pool.query( + ` + SELECT + compiled_contracts_sources.*, + sources.content + FROM ${this.schema}.compiled_contracts_sources + LEFT JOIN ${this.schema}.sources ON sources.source_hash = compiled_contracts_sources.source_hash + WHERE compilation_id = $1 + `, + [compilation_id], + ); + } + + async getVerifiedContractByChainAndAddress( + chain: number, + address: Bytes, + ): Promise> { + return await this.pool.query( + ` + SELECT + verified_contracts.*, + contract_deployments.transaction_hash, + contract_deployments.contract_id + FROM ${this.schema}.verified_contracts + JOIN ${this.schema}.contract_deployments ON contract_deployments.id = verified_contracts.deployment_id + WHERE 1=1 + AND contract_deployments.chain_id = $1 + AND contract_deployments.address = $2 + `, + [chain, address], + ); + } + + async insertSourcifyMatch({ + verified_contract_id, + runtime_match, + creation_match, + metadata, + }: Omit) { + await this.pool.query( + `INSERT INTO ${this.schema}.sourcify_matches ( + verified_contract_id, + creation_match, + runtime_match, + metadata + ) VALUES ($1, $2, $3, $4)`, + [verified_contract_id, creation_match, runtime_match, metadata], + ); + } + + // Update sourcify_matches to the latest (and better) match in verified_contracts, + // you need to pass the old verified_contract_id to be updated. + // The old verified_contracts are not deleted from the verified_contracts table. + async updateSourcifyMatch( + { + verified_contract_id, + runtime_match, + creation_match, + metadata, + }: Omit, + oldVerifiedContractId: number, + ) { + await this.pool.query( + `UPDATE ${this.schema}.sourcify_matches SET + verified_contract_id = $1, + creation_match=$2, + runtime_match=$3, + metadata=$4 + WHERE verified_contract_id = $5`, + [ + verified_contract_id, + creation_match, + runtime_match, + metadata, + oldVerifiedContractId, + ], + ); + } + + async countSourcifyMatchAddresses(chain: number): Promise< + QueryResult< + Pick & { + full_total: number; + partial_total: number; + } + > + > { + return await this.pool.query( + ` + SELECT + contract_deployments.chain_id, + CAST(SUM(CASE + WHEN COALESCE(sourcify_matches.creation_match, '') = 'perfect' OR sourcify_matches.runtime_match = 'perfect' THEN 1 ELSE 0 END) AS INTEGER) AS full_total, + CAST(SUM(CASE + WHEN COALESCE(sourcify_matches.creation_match, '') != 'perfect' AND sourcify_matches.runtime_match != 'perfect' THEN 1 ELSE 0 END) AS INTEGER) AS partial_total + FROM ${this.schema}.sourcify_matches + JOIN ${this.schema}.verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id + JOIN ${this.schema}.contract_deployments ON contract_deployments.id = verified_contracts.deployment_id + WHERE contract_deployments.chain_id = $1 + GROUP BY contract_deployments.chain_id;`, + [chain], + ); + } + + async getSourcifyMatchAddressesByChainAndMatch( + chain: number, + match: "full_match" | "partial_match" | "any_match", + page: number, + paginationSize: number, + descending: boolean = false, + ): Promise> { + let queryWhere = ""; + switch (match) { + case "full_match": { + queryWhere = + "WHERE COALESCE(sourcify_matches.creation_match, '') = 'perfect' OR sourcify_matches.runtime_match = 'perfect'"; + break; + } + case "partial_match": { + queryWhere = + "WHERE COALESCE(sourcify_matches.creation_match, '') != 'perfect' AND sourcify_matches.runtime_match != 'perfect'"; + break; + } + case "any_match": { + queryWhere = ""; + break; + } + default: { + throw new Error("Match type not supported"); + } + } + + const orderBy = descending + ? "ORDER BY verified_contracts.id DESC" + : "ORDER BY verified_contracts.id ASC"; + + return await this.pool.query( + ` + SELECT + concat('0x',encode(contract_deployments.address, 'hex')) as address + FROM ${this.schema}.sourcify_matches + JOIN ${this.schema}.verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id + JOIN ${this.schema}.contract_deployments ON + contract_deployments.id = verified_contracts.deployment_id + AND contract_deployments.chain_id = $1 + ${queryWhere} + ${orderBy} + OFFSET $2 LIMIT $3 + `, + [chain, page * paginationSize, paginationSize], + ); + } + + async insertCode( + poolClient: PoolClient, + { bytecode_hash_keccak, bytecode }: Omit, + ): Promise>> { + let codeInsertResult = await poolClient.query( + `INSERT INTO ${this.schema}.code (code_hash, code, code_hash_keccak) VALUES (digest($1::bytea, 'sha256'), $1::bytea, $2) ON CONFLICT (code_hash) DO NOTHING RETURNING code_hash as bytecode_hash`, + [bytecode, bytecode_hash_keccak], + ); + + // If there is a conflict (ie. code already exists), the response will be empty. We still need to return the object to fill other tables + if (codeInsertResult.rows.length === 0) { + codeInsertResult = await poolClient.query( + `SELECT + code_hash as bytecode_hash + FROM ${this.schema}.code + WHERE code_hash = digest($1::bytea, 'sha256')`, + [bytecode], + ); + } + return codeInsertResult; + } + + async insertContract( + poolClient: PoolClient, + { + creation_bytecode_hash, + runtime_bytecode_hash, + }: Omit, + ): Promise>> { + let contractInsertResult = await poolClient.query( + `INSERT INTO ${this.schema}.contracts (creation_code_hash, runtime_code_hash) VALUES ($1, $2) ON CONFLICT (creation_code_hash, runtime_code_hash) DO NOTHING RETURNING *`, + [creation_bytecode_hash, runtime_bytecode_hash], + ); + + if (contractInsertResult.rows.length === 0) { + contractInsertResult = await poolClient.query( + ` + SELECT + id + FROM ${this.schema}.contracts + WHERE creation_code_hash = $1 AND runtime_code_hash = $2 + `, + [creation_bytecode_hash, runtime_bytecode_hash], + ); + } + return contractInsertResult; + } + + async insertContractDeployment( + poolClient: PoolClient, + { + chain_id, + address, + transaction_hash, + contract_id, + block_number, + txindex, + deployer, + }: Omit, + ): Promise>> { + let contractDeploymentInsertResult = await poolClient.query( + `INSERT INTO + ${this.schema}.contract_deployments ( + chain_id, + address, + transaction_hash, + contract_id, + block_number, + transaction_index, + deployer + ) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (chain_id, address, transaction_hash) DO NOTHING RETURNING *`, + [ + chain_id, + address, + transaction_hash, + contract_id, + block_number, + txindex, + deployer, + ], + ); + + if (contractDeploymentInsertResult.rows.length === 0) { + contractDeploymentInsertResult = await poolClient.query( + ` + SELECT + id + FROM ${this.schema}.contract_deployments + WHERE 1=1 + AND chain_id = $1 + AND address = $2 + AND transaction_hash = $3 + `, + [chain_id, address, transaction_hash], + ); + } + return contractDeploymentInsertResult; + } + + async insertCompiledContract( + poolClient: PoolClient, + { + compiler, + version, + language, + name, + fully_qualified_name, + compilation_artifacts, + compiler_settings, + creation_code_hash, + runtime_code_hash, + creation_code_artifacts, + runtime_code_artifacts, + }: Omit, + ): Promise>> { + let compiledContractsInsertResult = await poolClient.query( + ` + INSERT INTO ${this.schema}.compiled_contracts ( + compiler, + version, + language, + name, + fully_qualified_name, + compilation_artifacts, + compiler_settings, + creation_code_hash, + runtime_code_hash, + creation_code_artifacts, + runtime_code_artifacts + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (compiler, language, creation_code_hash, runtime_code_hash) DO NOTHING RETURNING * + `, + [ + compiler, + version, + language, + name, + fully_qualified_name, + compilation_artifacts, + compiler_settings, + creation_code_hash, + runtime_code_hash, + creation_code_artifacts, + runtime_code_artifacts, + ], + ); + + if (compiledContractsInsertResult.rows.length === 0) { + compiledContractsInsertResult = await poolClient.query( + ` + SELECT + id + FROM ${this.schema}.compiled_contracts + WHERE 1=1 + AND compiler = $1 + AND language = $2 + AND (creation_code_hash = $3 OR (creation_code_hash IS NULL AND $3 IS NULL)) + AND runtime_code_hash = $4 + `, + [compiler, language, creation_code_hash, runtime_code_hash], + ); + } + return compiledContractsInsertResult; + } + + async insertCompiledContractsSources( + poolClient: PoolClient, + { + sourcesInformation, + compilation_id, + }: { + sourcesInformation: SourceInformation[]; + compilation_id: string; + }, + ) { + const sourceCodesQueryIndexes: string[] = []; + const sourceCodesQueryValues: any[] = []; + + // Loop through each `sourceInformation` to generate the `INSERT INTO sources` query placeholders and values + sourcesInformation.forEach((sourceCode, sourceCodesQueryIndex) => { + sourceCodesQueryIndexes.push( + // `sourceCodesQueryIndex * 2` comes from the number of unique values in the insert query, `sourceCode.content` is used for the first two columns + `(digest($${sourceCodesQueryIndex * 2 + 1}, 'sha256'), $${sourceCodesQueryIndex * 2 + 1}, $${sourceCodesQueryIndex * 2 + 2}::bytea)`, + ); + sourceCodesQueryValues.push(sourceCode.content); + sourceCodesQueryValues.push(sourceCode.source_hash_keccak); + }); + const sourceCodesQuery = `INSERT INTO ${this.schema}.sources ( + source_hash, + content, + source_hash_keccak + ) VALUES ${sourceCodesQueryIndexes.join(",")} ON CONFLICT (source_hash) DO NOTHING RETURNING *`; + const sourceCodesQueryResult = await poolClient.query( + sourceCodesQuery, + sourceCodesQueryValues, + ); + + // If some source codes already exist, fetch their hashes from the database + if (sourceCodesQueryResult.rows.length < sourcesInformation.length) { + const existingSourcesQuery = ` + SELECT * + FROM ${this.schema}.sources + WHERE source_hash = ANY($1::bytea[]) + `; + const existingSourcesResult = await poolClient.query( + existingSourcesQuery, + [ + sourcesInformation.map((source) => + bytesFromString( + createHash("sha256").update(source.content).digest("hex"), + ), + ), + ], + ); + sourceCodesQueryResult.rows = existingSourcesResult.rows; + } + + const compiledContractsSourcesQueryIndexes: string[] = []; + const compiledContractsSourcesQueryValues: any[] = []; + + // Loop through each `sourceInformation` to generate the query placeholders and values for the `INSERT INTO compiled_contracts_sources` query. + // We separate these into two steps because we first need to batch insert into `sources`. + // After that, we use the newly inserted `sources.source_hash` to perform the batch insert into `compiled_contracts_sources`. + sourcesInformation.forEach( + (compiledContractsSource, compiledContractsSourcesQueryIndex) => { + compiledContractsSourcesQueryIndexes.push( + // `sourceCodesQueryIndex * 3` comes from the number of unique values in the insert query + `($${compiledContractsSourcesQueryIndex * 3 + 1}, $${compiledContractsSourcesQueryIndex * 3 + 2}, $${compiledContractsSourcesQueryIndex * 3 + 3})`, + ); + compiledContractsSourcesQueryValues.push(compilation_id); + const contentHash = createHash("sha256") + .update(compiledContractsSource.content) + .digest("hex"); + const source = sourceCodesQueryResult.rows.find( + (sc) => sc.source_hash.toString("hex") === contentHash, + ); + if (!source) { + logger.error( + "Source not found while inserting compiled contracts sources", + { + compilation_id, + compiledContractsSource, + }, + ); + throw new Error( + "Source not found while inserting compiled contracts sources", + ); + } + compiledContractsSourcesQueryValues.push(source?.source_hash); + compiledContractsSourcesQueryValues.push(compiledContractsSource.path); + }, + ); + + const compiledContractsSourcesQuery = `INSERT INTO compiled_contracts_sources ( + compilation_id, + source_hash, + path + ) VALUES ${compiledContractsSourcesQueryIndexes.join(",")} ON CONFLICT (compilation_id, path) DO NOTHING`; + await poolClient.query( + compiledContractsSourcesQuery, + compiledContractsSourcesQueryValues, + ); + } + + async insertVerifiedContract( + poolClient: PoolClient, + { + compilation_id, + deployment_id, + creation_transformations, + creation_values, + runtime_transformations, + runtime_values, + runtime_match, + creation_match, + runtime_metadata_match, + creation_metadata_match, + }: Omit, + ): Promise>> { + let verifiedContractsInsertResult = await poolClient.query( + `INSERT INTO ${this.schema}.verified_contracts ( + compilation_id, + deployment_id, + creation_transformations, + creation_values, + runtime_transformations, + runtime_values, + runtime_match, + creation_match, + runtime_metadata_match, + creation_metadata_match + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (compilation_id, deployment_id) DO NOTHING RETURNING *`, + [ + compilation_id, + deployment_id, + // transformations needs to be converted to string as a workaround: + // arrays are not treated as jsonb types by pg module + // then they are correctly stored as jsonb by postgresql + creation_transformations + ? JSON.stringify(creation_transformations) + : null, + creation_values, + runtime_transformations + ? JSON.stringify(runtime_transformations) + : null, + runtime_values, + runtime_match, + creation_match, + runtime_metadata_match, + creation_metadata_match, + ], + ); + if (verifiedContractsInsertResult.rows.length === 0) { + verifiedContractsInsertResult = await poolClient.query( + ` + SELECT + id + FROM ${this.schema}.verified_contracts + WHERE 1=1 + AND compilation_id = $1 + AND deployment_id = $2 + `, + [compilation_id, deployment_id], + ); + } + return verifiedContractsInsertResult; + } + + async updateContractDeployment( + poolClient: PoolClient, + { + id, + transaction_hash, + block_number, + txindex, + deployer, + contract_id, + }: Omit, + ) { + return await poolClient.query( + `UPDATE ${this.schema}.contract_deployments + SET + transaction_hash = $2, + block_number = $3, + transaction_index = $4, + deployer = $5, + contract_id = $6 + WHERE id = $1`, + [id, transaction_hash, block_number, txindex, deployer, contract_id], + ); + } +} diff --git a/services/server/src/server/services/utils/database-util.ts b/services/server/src/server/services/utils/database-util.ts index 75b340695..5e0022521 100644 --- a/services/server/src/server/services/utils/database-util.ts +++ b/services/server/src/server/services/utils/database-util.ts @@ -10,7 +10,6 @@ import { TransformationValues, CompiledContractCborAuxdata, } from "@ethereum-sourcify/lib-sourcify"; -import { Pool, PoolClient, QueryResult } from "pg"; import { Abi } from "abitype"; import { Bytes, @@ -19,8 +18,6 @@ import { BytesTypes, Nullable, } from "../../types"; -import logger from "../../../common/logger"; -import { createHash } from "crypto"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Tables { @@ -153,28 +150,6 @@ export type GetVerifiedContractByChainAndAddressResult = contract_id: string; }; -export async function getVerifiedContractByChainAndAddress( - pool: Pool, - schema: string, - chain: number, - address: Bytes, -): Promise> { - return await pool.query( - ` - SELECT - verified_contracts.*, - contract_deployments.transaction_hash, - contract_deployments.contract_id - FROM ${schema}.verified_contracts - JOIN ${schema}.contract_deployments ON contract_deployments.id = verified_contracts.deployment_id - WHERE 1=1 - AND contract_deployments.chain_id = $1 - AND contract_deployments.address = $2 - `, - [chain, address], - ); -} - export type GetSourcifyMatchByChainAddressResult = Tables.SourcifyMatch & Pick< Tables.VerifiedContract, @@ -183,440 +158,6 @@ export type GetSourcifyMatchByChainAddressResult = Tables.SourcifyMatch & Pick & Pick; -export async function getSourcifyMatchByChainAddress( - pool: Pool, - schema: string, - chain: number, - address: Bytes, - onlyPerfectMatches: boolean = false, -): Promise> { - return await pool.query( - ` - SELECT - sourcify_matches.created_at, - sourcify_matches.creation_match, - sourcify_matches.runtime_match, - sourcify_matches.metadata, - verified_contracts.creation_values, - verified_contracts.runtime_values, - verified_contracts.compilation_id, - compiled_contracts.runtime_code_artifacts, - contract_deployments.transaction_hash - FROM ${schema}.sourcify_matches - JOIN ${schema}.verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id - JOIN ${schema}.compiled_contracts ON compiled_contracts.id = verified_contracts.compilation_id - JOIN ${schema}.contract_deployments ON - contract_deployments.id = verified_contracts.deployment_id - AND contract_deployments.chain_id = $1 - AND contract_deployments.address = $2 -${ - onlyPerfectMatches - ? "WHERE sourcify_matches.creation_match = 'perfect' OR sourcify_matches.runtime_match = 'perfect'" - : "" -} - `, - [chain, address], - ); -} - -export async function getCompiledContractSources( - pool: Pool, - compilation_id: string, -): Promise< - QueryResult> -> { - return await pool.query( - ` - SELECT - compiled_contracts_sources.*, - sources.content - FROM compiled_contracts_sources - LEFT JOIN sources ON sources.source_hash = compiled_contracts_sources.source_hash - WHERE compilation_id = $1 - `, - [compilation_id], - ); -} - -export async function insertCode( - pool: PoolClient, - schema: string, - { bytecode_hash_keccak, bytecode }: Omit, -): Promise>> { - let codeInsertResult = await pool.query( - `INSERT INTO ${schema}.code (code_hash, code, code_hash_keccak) VALUES (digest($1::bytea, 'sha256'), $1::bytea, $2) ON CONFLICT (code_hash) DO NOTHING RETURNING code_hash as bytecode_hash`, - [bytecode, bytecode_hash_keccak], - ); - - // If there is a conflict (ie. code already exists), the response will be empty. We still need to return the object to fill other tables - if (codeInsertResult.rows.length === 0) { - codeInsertResult = await pool.query( - `SELECT - code_hash as bytecode_hash - FROM ${schema}.code - WHERE code_hash = digest($1::bytea, 'sha256')`, - [bytecode], - ); - } - return codeInsertResult; -} - -export async function insertContract( - pool: PoolClient, - schema: string, - { - creation_bytecode_hash, - runtime_bytecode_hash, - }: Omit, -): Promise>> { - let contractInsertResult = await pool.query( - `INSERT INTO ${schema}.contracts (creation_code_hash, runtime_code_hash) VALUES ($1, $2) ON CONFLICT (creation_code_hash, runtime_code_hash) DO NOTHING RETURNING *`, - [creation_bytecode_hash, runtime_bytecode_hash], - ); - - if (contractInsertResult.rows.length === 0) { - contractInsertResult = await pool.query( - ` - SELECT - id - FROM ${schema}.contracts - WHERE creation_code_hash = $1 AND runtime_code_hash = $2 - `, - [creation_bytecode_hash, runtime_bytecode_hash], - ); - } - return contractInsertResult; -} - -export async function insertContractDeployment( - pool: PoolClient, - schema: string, - { - chain_id, - address, - transaction_hash, - contract_id, - block_number, - txindex, - deployer, - }: Omit, -): Promise>> { - let contractDeploymentInsertResult = await pool.query( - `INSERT INTO - ${schema}.contract_deployments ( - chain_id, - address, - transaction_hash, - contract_id, - block_number, - transaction_index, - deployer - ) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (chain_id, address, transaction_hash) DO NOTHING RETURNING *`, - [ - chain_id, - address, - transaction_hash, - contract_id, - block_number, - txindex, - deployer, - ], - ); - - if (contractDeploymentInsertResult.rows.length === 0) { - contractDeploymentInsertResult = await pool.query( - ` - SELECT - id - FROM ${schema}.contract_deployments - WHERE 1=1 - AND chain_id = $1 - AND address = $2 - AND transaction_hash = $3 - `, - [chain_id, address, transaction_hash], - ); - } - return contractDeploymentInsertResult; -} - -export async function insertCompiledContract( - pool: PoolClient, - schema: string, - { - compiler, - version, - language, - name, - fully_qualified_name, - compilation_artifacts, - compiler_settings, - creation_code_hash, - runtime_code_hash, - creation_code_artifacts, - runtime_code_artifacts, - }: Omit, -): Promise>> { - let compiledContractsInsertResult = await pool.query( - ` - INSERT INTO ${schema}.compiled_contracts ( - compiler, - version, - language, - name, - fully_qualified_name, - compilation_artifacts, - compiler_settings, - creation_code_hash, - runtime_code_hash, - creation_code_artifacts, - runtime_code_artifacts - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (compiler, language, creation_code_hash, runtime_code_hash) DO NOTHING RETURNING * - `, - [ - compiler, - version, - language, - name, - fully_qualified_name, - compilation_artifacts, - compiler_settings, - creation_code_hash, - runtime_code_hash, - creation_code_artifacts, - runtime_code_artifacts, - ], - ); - - if (compiledContractsInsertResult.rows.length === 0) { - compiledContractsInsertResult = await pool.query( - ` - SELECT - id - FROM ${schema}.compiled_contracts - WHERE 1=1 - AND compiler = $1 - AND language = $2 - AND (creation_code_hash = $3 OR (creation_code_hash IS NULL AND $3 IS NULL)) - AND runtime_code_hash = $4 - `, - [compiler, language, creation_code_hash, runtime_code_hash], - ); - } - return compiledContractsInsertResult; -} - -export async function insertCompiledContractsSources( - pool: PoolClient, - { - sourcesInformation, - compilation_id, - }: { - sourcesInformation: SourceInformation[]; - compilation_id: string; - }, -) { - const sourceCodesQueryIndexes: string[] = []; - const sourceCodesQueryValues: any[] = []; - - // Loop through each `sourceInformation` to generate the `INSERT INTO sources` query placeholders and values - sourcesInformation.forEach((sourceCode, sourceCodesQueryIndex) => { - sourceCodesQueryIndexes.push( - // `sourceCodesQueryIndex * 2` comes from the number of unique values in the insert query, `sourceCode.content` is used for the first two columns - `(digest($${sourceCodesQueryIndex * 2 + 1}, 'sha256'), $${sourceCodesQueryIndex * 2 + 1}, $${sourceCodesQueryIndex * 2 + 2}::bytea)`, - ); - sourceCodesQueryValues.push(sourceCode.content); - sourceCodesQueryValues.push(sourceCode.source_hash_keccak); - }); - const sourceCodesQuery = `INSERT INTO sources ( - source_hash, - content, - source_hash_keccak - ) VALUES ${sourceCodesQueryIndexes.join(",")} ON CONFLICT (source_hash) DO NOTHING RETURNING *`; - const sourceCodesQueryResult = await pool.query( - sourceCodesQuery, - sourceCodesQueryValues, - ); - - // If some source codes already exist, fetch their hashes from the database - if (sourceCodesQueryResult.rows.length < sourcesInformation.length) { - const existingSourcesQuery = ` - SELECT * - FROM sources - WHERE source_hash = ANY($1::bytea[]) - `; - const existingSourcesResult = await pool.query(existingSourcesQuery, [ - sourcesInformation.map((source) => - bytesFromString( - createHash("sha256").update(source.content).digest("hex"), - ), - ), - ]); - sourceCodesQueryResult.rows = existingSourcesResult.rows; - } - - const compiledContractsSourcesQueryIndexes: string[] = []; - const compiledContractsSourcesQueryValues: any[] = []; - - // Loop through each `sourceInformation` to generate the query placeholders and values for the `INSERT INTO compiled_contracts_sources` query. - // We separate these into two steps because we first need to batch insert into `sources`. - // After that, we use the newly inserted `sources.source_hash` to perform the batch insert into `compiled_contracts_sources`. - sourcesInformation.forEach( - (compiledContractsSource, compiledContractsSourcesQueryIndex) => { - compiledContractsSourcesQueryIndexes.push( - // `sourceCodesQueryIndex * 3` comes from the number of unique values in the insert query - `($${compiledContractsSourcesQueryIndex * 3 + 1}, $${compiledContractsSourcesQueryIndex * 3 + 2}, $${compiledContractsSourcesQueryIndex * 3 + 3})`, - ); - compiledContractsSourcesQueryValues.push(compilation_id); - const contentHash = createHash("sha256") - .update(compiledContractsSource.content) - .digest("hex"); - const source = sourceCodesQueryResult.rows.find( - (sc) => sc.source_hash.toString("hex") === contentHash, - ); - if (!source) { - logger.error( - "Source not found while inserting compiled contracts sources", - { - compilation_id, - compiledContractsSource, - }, - ); - throw new Error( - "Source not found while inserting compiled contracts sources", - ); - } - compiledContractsSourcesQueryValues.push(source?.source_hash); - compiledContractsSourcesQueryValues.push(compiledContractsSource.path); - }, - ); - - const compiledContractsSourcesQuery = `INSERT INTO compiled_contracts_sources ( - compilation_id, - source_hash, - path - ) VALUES ${compiledContractsSourcesQueryIndexes.join(",")} ON CONFLICT (compilation_id, path) DO NOTHING`; - await pool.query( - compiledContractsSourcesQuery, - compiledContractsSourcesQueryValues, - ); -} - -export async function insertVerifiedContract( - pool: PoolClient, - schema: string, - { - compilation_id, - deployment_id, - creation_transformations, - creation_values, - runtime_transformations, - runtime_values, - runtime_match, - creation_match, - runtime_metadata_match, - creation_metadata_match, - }: Omit, -): Promise>> { - let verifiedContractsInsertResult = await pool.query( - `INSERT INTO ${schema}.verified_contracts ( - compilation_id, - deployment_id, - creation_transformations, - creation_values, - runtime_transformations, - runtime_values, - runtime_match, - creation_match, - runtime_metadata_match, - creation_metadata_match - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (compilation_id, deployment_id) DO NOTHING RETURNING *`, - [ - compilation_id, - deployment_id, - // transformations needs to be converted to string as a workaround: - // arrays are not treated as jsonb types by pg module - // then they are correctly stored as jsonb by postgresql - creation_transformations - ? JSON.stringify(creation_transformations) - : null, - creation_values, - runtime_transformations ? JSON.stringify(runtime_transformations) : null, - runtime_values, - runtime_match, - creation_match, - runtime_metadata_match, - creation_metadata_match, - ], - ); - if (verifiedContractsInsertResult.rows.length === 0) { - verifiedContractsInsertResult = await pool.query( - ` - SELECT - id - FROM ${schema}.verified_contracts - WHERE 1=1 - AND compilation_id = $1 - AND deployment_id = $2 - `, - [compilation_id, deployment_id], - ); - } - return verifiedContractsInsertResult; -} - -export async function insertSourcifyMatch( - pool: Pool, - schema: string, - { - verified_contract_id, - runtime_match, - creation_match, - metadata, - }: Omit, -) { - await pool.query( - `INSERT INTO ${schema}.sourcify_matches ( - verified_contract_id, - creation_match, - runtime_match, - metadata - ) VALUES ($1, $2, $3, $4)`, - [verified_contract_id, creation_match, runtime_match, metadata], - ); -} - -// Update sourcify_matches to the latest (and better) match in verified_contracts, -// you need to pass the old verified_contract_id to be updated. -// The old verified_contracts are not deleted from the verified_contracts table. -export async function updateSourcifyMatch( - pool: Pool, - schema: string, - { - verified_contract_id, - runtime_match, - creation_match, - metadata, - }: Omit, - oldVerifiedContractId: number, -) { - await pool.query( - `UPDATE ${schema}.sourcify_matches SET - verified_contract_id = $1, - creation_match=$2, - runtime_match=$3, - metadata=$4 - WHERE verified_contract_id = $5`, - [ - verified_contract_id, - creation_match, - runtime_match, - metadata, - oldVerifiedContractId, - ], - ); -} - // Function overloads export function bytesFromString(str: string): T; export function bytesFromString( @@ -646,7 +187,6 @@ export function bytesFromString( // Creation bytecode: // 1. Replace library address placeholders ("__$53aea86b7d70b31448b230b20ae141a537$__") with zeros // 2. Immutables are already set to zeros - export function normalizeRecompiledBytecodes( recompiledContract: CheckedContract, match: Match, @@ -740,108 +280,3 @@ export function prepareCompilerSettings(recompiledContract: CheckedContract) { return restSettings; } - -export async function countSourcifyMatchAddresses( - pool: Pool, - schema: string, - chain: number, -): Promise< - QueryResult< - Pick & { - full_total: number; - partial_total: number; - } - > -> { - return await pool.query( - ` - SELECT - contract_deployments.chain_id, - CAST(SUM(CASE - WHEN COALESCE(sourcify_matches.creation_match, '') = 'perfect' OR sourcify_matches.runtime_match = 'perfect' THEN 1 ELSE 0 END) AS INTEGER) AS full_total, - CAST(SUM(CASE - WHEN COALESCE(sourcify_matches.creation_match, '') != 'perfect' AND sourcify_matches.runtime_match != 'perfect' THEN 1 ELSE 0 END) AS INTEGER) AS partial_total - FROM ${schema}.sourcify_matches - JOIN ${schema}.verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id - JOIN ${schema}.contract_deployments ON contract_deployments.id = verified_contracts.deployment_id - WHERE contract_deployments.chain_id = $1 - GROUP BY contract_deployments.chain_id;`, - [chain], - ); -} - -export async function getSourcifyMatchAddressesByChainAndMatch( - pool: Pool, - schema: string, - chain: number, - match: "full_match" | "partial_match" | "any_match", - page: number, - paginationSize: number, - descending: boolean = false, -): Promise> { - let queryWhere = ""; - switch (match) { - case "full_match": { - queryWhere = - "WHERE COALESCE(sourcify_matches.creation_match, '') = 'perfect' OR sourcify_matches.runtime_match = 'perfect'"; - break; - } - case "partial_match": { - queryWhere = - "WHERE COALESCE(sourcify_matches.creation_match, '') != 'perfect' AND sourcify_matches.runtime_match != 'perfect'"; - break; - } - case "any_match": { - queryWhere = ""; - break; - } - default: { - throw new Error("Match type not supported"); - } - } - - const orderBy = descending - ? "ORDER BY verified_contracts.id DESC" - : "ORDER BY verified_contracts.id ASC"; - - return await pool.query( - ` - SELECT - concat('0x',encode(contract_deployments.address, 'hex')) as address - FROM ${schema}.sourcify_matches - JOIN ${schema}.verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id - JOIN ${schema}.contract_deployments ON - contract_deployments.id = verified_contracts.deployment_id - AND contract_deployments.chain_id = $1 - ${queryWhere} - ${orderBy} - OFFSET $2 LIMIT $3 - `, - [chain, page * paginationSize, paginationSize], - ); -} - -export async function updateContractDeployment( - pool: PoolClient, - schema: string, - { - id, - transaction_hash, - block_number, - txindex, - deployer, - contract_id, - }: Omit, -) { - return await pool.query( - `UPDATE ${schema}.contract_deployments - SET - transaction_hash = $2, - block_number = $3, - transaction_index = $4, - deployer = $5, - contract_id = $6 - WHERE id = $1`, - [id, transaction_hash, block_number, txindex, deployer, contract_id], - ); -} diff --git a/services/server/src/sourcify-chains-default.json b/services/server/src/sourcify-chains-default.json index 1f57551f6..20c4b88c6 100644 --- a/services/server/src/sourcify-chains-default.json +++ b/services/server/src/sourcify-chains-default.json @@ -32,9 +32,16 @@ ] }, { - "type": "Alchemy", - "url": "https://eth-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://eth-mainnet.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" } ] }, @@ -57,9 +64,16 @@ "rpc": [ "https://rpc.holesky.ethpandaops.io", { - "type": "Alchemy", - "url": "https://eth-holesky.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://eth-holesky.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.ethereum-holesky.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" } ] }, @@ -90,9 +104,16 @@ ] }, { - "type": "Alchemy", - "url": "https://eth-sepolia.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://eth-sepolia.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.ethereum-sepolia.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" } ], "fetchContractCreationTxUsing": { @@ -140,7 +161,28 @@ }, "fetchContractCreationTxUsing": { "etherscanApi": true - } + }, + "rpc": [ + "https://bsc-dataseed1.bnbchain.org", + "https://bsc-dataseed2.bnbchain.org", + "https://bsc-dataseed3.bnbchain.org", + "https://bsc-dataseed4.bnbchain.org", + "https://bsc-dataseed1.defibit.io", + "https://bsc-dataseed2.defibit.io", + "https://bsc-dataseed3.defibit.io", + "https://bsc-dataseed4.defibit.io", + "https://bsc-dataseed1.ninicoin.io", + "https://bsc-dataseed2.ninicoin.io", + "https://bsc-dataseed3.ninicoin.io", + "https://bsc-dataseed4.ninicoin.io", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.bsc.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "61": { "sourcifyName": "Ethereum Classic Mainnet", @@ -182,7 +224,23 @@ }, "fetchContractCreationTxUsing": { "etherscanApi": true - } + }, + "rpc": [ + "https://data-seed-prebsc-1-s1.bnbchain.org:8545", + "https://data-seed-prebsc-2-s1.bnbchain.org:8545", + "https://data-seed-prebsc-1-s2.bnbchain.org:8545", + "https://data-seed-prebsc-2-s2.bnbchain.org:8545", + "https://data-seed-prebsc-1-s3.bnbchain.org:8545", + "https://data-seed-prebsc-2-s3.bnbchain.org:8545", + "https://bsc-testnet-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.bsc-testnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "100": { "sourcifyName": "Gnosis Mainnet", @@ -196,7 +254,23 @@ "url": "https://gnosis.blockscout.com/" }, "etherscanApi": true - } + }, + "rpc": [ + "https://rpc.gnosischain.com", + "https://rpc.gnosis.gateway.fm", + "https://rpc.ankr.com/gnosis", + "https://gnosischain-rpc.gateway.pokt.network", + "https://gnosis-mainnet.public.blastapi.io", + "https://gnosis.api.onfinality.io/public", + "https://gnosis.blockpi.network/v1/rpc/public", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.xdai.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "295": { "sourcifyName": "Hedera Mainnet", @@ -229,9 +303,16 @@ }, "rpc": [ { - "type": "Alchemy", - "url": "https://polygon-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://polygon-mainnet.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.matic.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" } ] }, @@ -251,7 +332,18 @@ "blockscoutScrape": { "url": "https://explorer.celo.org/mainnet/" } - } + }, + "rpc": [ + "https://forno.celo.org", + "https://rpc.ankr.com/celo", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.celo-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "44787": { "sourcifyName": "Celo Alfajores Testnet", @@ -295,9 +387,16 @@ }, "rpc": [ { - "type": "Alchemy", - "url": "https://arb-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://arb-mainnet.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY_ARBITRUM" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.arbitrum-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" } ] }, @@ -320,7 +419,18 @@ "type": "testnet" }, "etherscanApi": true - } + }, + "rpc": [ + "https://sepolia-rollup.arbitrum.io/rpc", + "https://arbitrum-sepolia.blockpi.network/v1/rpc/public", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.arbitrum-sepolia.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "43113": { "sourcifyName": "Avalanche Fuji Testnet", @@ -335,7 +445,18 @@ "routescanApi": { "type": "testnet" } - } + }, + "rpc": [ + "https://api.avax-test.network/ext/bc/C/rpc", + "https://avalanche-fuji-c-chain-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.avalanche-testnet.quiknode.pro/{API_KEY}/ext/bc/C/rpc/", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "43114": { "sourcifyName": "Avalanche C-Chain Mainnet", @@ -350,7 +471,18 @@ "routescanApi": { "type": "mainnet" } - } + }, + "rpc": [ + "https://api.avax.network/ext/bc/C/rpc", + "https://avalanche-c-chain-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.avalanche-mainnet.quiknode.pro/{API_KEY}/ext/bc/C/rpc/", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "57": { "sourcifyName": "Syscoin Mainnet", @@ -443,9 +575,16 @@ }, "rpc": [ { - "type": "Alchemy", - "url": "https://opt-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://opt-mainnet.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY_OPTIMISM" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.optimism.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" } ] }, @@ -468,9 +607,16 @@ }, "rpc": [ { - "type": "Alchemy", - "url": "https://opt-sepolia.g.alchemy.com/v2/{ALCHEMY_API_KEY}", + "type": "APIKeyRPC", + "url": "https://opt-sepolia.g.alchemy.com/v2/{API_KEY}", "apiKeyEnvName": "ALCHEMY_API_KEY_OPTIMISM" + }, + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.optimism-sepolia.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" } ] }, @@ -586,8 +732,8 @@ }, "rpc": [ { - "type": "Infura", - "url": "https://palm-mainnet.infura.io/v3/{INFURA_API_KEY}", + "type": "APIKeyRPC", + "url": "https://palm-mainnet.infura.io/v3/{API_KEY}", "apiKeyEnvName": "INFURA_API_KEY" } ] @@ -602,8 +748,8 @@ }, "rpc": [ { - "type": "Infura", - "url": "https://palm-testnet.infura.io/v3/{INFURA_API_KEY}", + "type": "APIKeyRPC", + "url": "https://palm-testnet.infura.io/v3/{API_KEY}", "apiKeyEnvName": "INFURA_API_KEY" } ] @@ -941,7 +1087,7 @@ }, "14": { "sourcifyName": "Flare Mainnet", - "supported": false, + "supported": true, "fetchContractCreationTxUsing": { "blockscoutScrape": { "url": "https://flare-explorer.flare.network/" @@ -1024,7 +1170,19 @@ "type": "mainnet" }, "etherscanApi": true - } + }, + "rpc": [ + "https://mainnet.base.org/", + "https://developer-access-mainnet.base.org/", + "https://base.gateway.tenderly.co", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.base-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "252": { "sourcifyName": "Fraxtal", @@ -1265,7 +1423,26 @@ }, "42170": { "sourcifyName": "Arbitrum Nova", - "supported": true + "supported": true, + "fetchContractCreationTxUsing": { + "etherscanApi": true + }, + "etherscanApi": { + "apiURL": "https://api-nova.arbiscan.io", + "apiKeyEnvName": "ARBISCAN_NOVA_API_KEY" + }, + "rpc": [ + "https://nova.arbitrum.io/rpc", + "https://arbitrum-nova.gateway.tenderly.co", + "https://arbitrum-nova-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.nova-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "2037": { "sourcifyName": "Kiwi Subnet", @@ -1452,7 +1629,18 @@ "routescanApi": { "type": "mainnet" } - } + }, + "rpc": [ + "https://rpc.mantle.xyz", + "https://mantle-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.mantle-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "5003": { "sourcifyName": "Mantle Sepolia Testnet", @@ -1469,7 +1657,17 @@ "routescanApi": { "type": "testnet" } - } + }, + "rpc": [ + "https://rpc.sepolia.mantle.xyz", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.mantle-sepolia.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "3737": { "sourcifyName": "Crossball Mainnet", @@ -1517,7 +1715,18 @@ "url": "https://zkevm.blockscout.com/" }, "etherscanApi": true - } + }, + "rpc": [ + "https://zkevm-rpc.com", + "https://polygon-zkevm.drpc.org", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.zkevm-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "534352": { "sourcifyName": "Scroll", @@ -1528,7 +1737,19 @@ }, "fetchContractCreationTxUsing": { "etherscanApi": true - } + }, + "rpc": [ + "https://rpc.scroll.io", + "https://scroll.blockpi.network/v1/rpc/public", + "https://1rpc.io/scroll", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.scroll-mainnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "534351": { "sourcifyName": "Scroll Sepolia Testnet", @@ -1539,7 +1760,19 @@ }, "fetchContractCreationTxUsing": { "etherscanApi": true - } + }, + "rpc": [ + "https://sepolia-rpc.scroll.io", + "https://scroll-sepolia-rpc.publicnode.com", + "https://rpc.ankr.com/scroll_sepolia_testnet", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.scroll-testnet.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "debug_traceTransaction" + } + ] }, "34443": { "sourcifyName": "Mode", @@ -1696,7 +1929,18 @@ }, "fetchContractCreationTxUsing": { "etherscanApi": true - } + }, + "rpc": [ + "https://rpc-amoy.polygon.technology", + "https://polygon-amoy-bor-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.matic-amoy.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "666666666": { "sourcifyName": "DEGEN Chain", @@ -1829,7 +2073,18 @@ }, "etherscanApi": { "apiURL": "https://api-sepolia.basescan.org/" - } + }, + "rpc": [ + "https://sepolia.base.org", + "https://base-sepolia-rpc.publicnode.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.base-sepolia.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] }, "59144": { "sourcifyName": "Linea Mainnet", @@ -1915,6 +2170,56 @@ }, "fetchContractCreationTxUsing": { "etherscanApi": true + }, + "rpc": [ + "https://rpc.cardona.zkevm-rpc.com", + { + "type": "APIKeyRPC", + "url": "https://{SUBDOMAIN}.zkevm-cardona.quiknode.pro/{API_KEY}", + "apiKeyEnvName": "QUICKNODE_API_KEY", + "subDomainEnvName": "QUICKNODE_SUBDOMAIN", + "traceSupport": "trace_transaction" + } + ] + }, + "1088": { + "sourcifyName": "Metis Andromeda Mainnet", + "supported": true, + "fetchContractCreationTxUsing": { + "blockscoutApi": { + "url": "https://andromeda-explorer.metis.io" + }, + "routescanApi": { + "type": "mainnet" + } + }, + "rpc": [ + "https://andromeda.metis.io/?owner=1088", + "https://metis.drpc.org", + "wss://metis.drpc.org", + { + "type": "APIKeyRPC", + "url": "https://metis-mainnet.g.alchemy.com/v2/{API_KEY}", + "apiKeyEnvName": "ALCHEMY_API_KEY" + } + ] + }, + "59902": { + "sourcifyName": "Metis Sepolia Testnet", + "supported": true, + "fetchContractCreationTxUsing": { + "blockscoutApi": { + "url": "https://sepolia-explorer.metisdevops.link" + } } + }, + "48900": { + "sourcifyName": "Zircuit Mainnet", + "supported": true + }, + "48899": { + "sourcifyName": "Zircuit Testnet", + "supported": true, + "rpc": ["https://zircuit1-testnet.p2pify.com/"] } } diff --git a/services/server/src/sourcify-chains.ts b/services/server/src/sourcify-chains.ts index d4ed3bda4..c1bba0b88 100644 --- a/services/server/src/sourcify-chains.ts +++ b/services/server/src/sourcify-chains.ts @@ -3,8 +3,10 @@ import { SourcifyChainMap, SourcifyChainsExtensionsObject, Chain, - AlchemyInfuraRPC, + APIKeyRPC, FetchRequestRPC, + BaseRPC, + TraceSupportedRPC, } from "@ethereum-sourcify/lib-sourcify"; import { FetchRequest } from "ethers"; import chainsRaw from "./chains.json"; @@ -69,40 +71,73 @@ export const LOCAL_CHAINS: SourcifyChain[] = [ * SourcifyChain expects url strings or ethers.js FetchRequest objects. */ function buildCustomRpcs( - rpc: Array, + sourcifyRpcs: Array, ) { - return rpc.map((rpc) => { - // simple url - if (typeof rpc === "string") { - return rpc; + const traceSupportedRPCs: TraceSupportedRPC[] = []; + const rpc: (string | FetchRequest)[] = []; + const rpcWithoutApiKeys: string[] = []; + sourcifyRpcs.forEach((sourcifyRpc, index) => { + // simple url, can't have traceSupport + if (typeof sourcifyRpc === "string") { + rpc.push(sourcifyRpc); + rpcWithoutApiKeys.push(sourcifyRpc); + return; + } + + if (sourcifyRpc.traceSupport) { + traceSupportedRPCs.push({ + type: sourcifyRpc.traceSupport, + index, + }); + } + + if (sourcifyRpc.type === "BaseRPC") { + rpc.push(sourcifyRpc.url); + rpcWithoutApiKeys.push(sourcifyRpc.url); + return; } // Fill in the api keys - else if (rpc.type === "Alchemy") { - return rpc.url.replace( - "{ALCHEMY_API_KEY}", - process.env[rpc.apiKeyEnvName] || process.env["ALCHEMY_API_KEY"] || "", - ); - } else if (rpc.type === "Infura") { - return rpc.url.replace( - "{INFURA_API_KEY}", - process.env[rpc.apiKeyEnvName] || "", - ); + else if (sourcifyRpc.type === "APIKeyRPC") { + const apiKey = + process.env[sourcifyRpc.apiKeyEnvName] || process.env["API_KEY"] || ""; + if (!apiKey) { + // API key is required for all APIKeyRPCs + throw new Error(`API key not found for ${sourcifyRpc.apiKeyEnvName}`); + } + let url = sourcifyRpc.url.replace("{API_KEY}", apiKey); + + const subDomain = process.env[sourcifyRpc.subDomainEnvName || ""]; + if (subDomain) { + // subDomain is optional + url = url.replace("{SUBDOMAIN}", subDomain); + } + rpc.push(url); + rpcWithoutApiKeys.push(sourcifyRpc.url); + return; } // Build ethers.js FetchRequest object for custom rpcs with auth headers - else if (rpc.type === "FetchRequest") { - const ethersFetchReq = new FetchRequest(rpc.url); + else if (sourcifyRpc.type === "FetchRequest") { + const ethersFetchReq = new FetchRequest(sourcifyRpc.url); ethersFetchReq.setHeader("Content-Type", "application/json"); - const headers = rpc.headers; + const headers = sourcifyRpc.headers; if (headers) { headers.forEach(({ headerName, headerEnvName }) => { const headerValue = process.env[headerEnvName]; ethersFetchReq.setHeader(headerName, headerValue || ""); }); } - return ethersFetchReq; + rpc.push(ethersFetchReq); + rpcWithoutApiKeys.push(sourcifyRpc.url); + return; } - throw new Error(`Invalid rpc type: ${rpc.type}`); + throw new Error(`Invalid rpc type: ${JSON.stringify(sourcifyRpc)}`); }); + return { + rpc, + rpcWithoutApiKeys, + traceSupportedRPCs: + traceSupportedRPCs.length > 0 ? traceSupportedRPCs : undefined, + }; } const sourcifyChainsMap: SourcifyChainMap = {}; @@ -134,13 +169,16 @@ for (const i in allChains) { if (chainId in sourcifyChainsExtensions) { const sourcifyExtension = sourcifyChainsExtensions[chainId]; + const { rpc, rpcWithoutApiKeys, traceSupportedRPCs } = buildCustomRpcs( + sourcifyExtension.rpc || chain.rpc, + ); // sourcifyExtension is spread later to overwrite chains.json values, rpc specifically const sourcifyChain = new SourcifyChain({ ...chain, ...sourcifyExtension, - rpc: sourcifyExtension.rpc - ? buildCustomRpcs(sourcifyExtension.rpc) - : chain.rpc, // avoid rpc ending up as undefined + rpc, + rpcWithoutApiKeys, + traceSupportedRPCs, }); sourcifyChainsMap[chainId] = sourcifyChain; } @@ -175,11 +213,16 @@ if (missingChains.length > 0) { `Chain ${chainId} is missing rpc in sourcify-chains.json`, ); } + const { rpc, rpcWithoutApiKeys, traceSupportedRPCs } = buildCustomRpcs( + chain.rpc, + ); sourcifyChainsMap[chainId] = new SourcifyChain({ name: chain.sourcifyName, chainId: parseInt(chainId), supported: chain.supported, - rpc: buildCustomRpcs(chain.rpc), + rpc, + rpcWithoutApiKeys, + traceSupportedRPCs, }); }); } diff --git a/services/server/test/chains/chain-tests.spec.ts b/services/server/test/chains/chain-tests.spec.ts index c57d08fd2..6034215ed 100644 --- a/services/server/test/chains/chain-tests.spec.ts +++ b/services/server/test/chains/chain-tests.spec.ts @@ -758,14 +758,13 @@ describe("Test Supported Chains", function () { "shared/", ); - // // Flare Mainnet - // verifyContract( - // "0xbBc2EdeDc9d2d97970eE20d0Dc7216216a27e635", - // "14", - // "Flare Mainnet", - // "shared/" - // - // ); + // Flare Mainnet + verifyContract( + "0x24AaDc3168a88a0058DF9437CAD3275170CDd581", + "14", + "Flare Mainnet", + "shared/", + ); // Oasis Sapphire Mainnet verifyContract( @@ -1664,6 +1663,38 @@ describe("Test Supported Chains", function () { "shared/", ); + // Metis Andromeda Mainnet + verifyContract( + "0xaCe60DF34CEeb11B52B0901Be3F58871A5E83D64", + "1088", + "Metis Andromeda Mainnet", + "shared/", + ); + + // Metis Sepolia Testnet + verifyContract( + "0xF282C3784C0187C48747c779C362eCBaddB5F020", + "59902", + "Metis Sepolia Testnet", + "shared/", + ); + + // Zircuit Testnet + verifyContract( + "0x0cfE351147DEb353a57623859F7b2A4984645433", + "48899", + "Zircuit Testnet", + "shared/", + ); + + // Zircuit Mainnet + verifyContract( + "0x0cfE351147DEb353a57623859F7b2A4984645433", + "48900", + "Zircuit Mainnet", + "shared/", + ); + it("should have included Etherscan contracts for all testedChains having etherscanAPI", function (done) { const missingEtherscanTests: ChainApiResponse[] = []; supportedChains diff --git a/services/server/test/helpers/ServerFixture.ts b/services/server/test/helpers/ServerFixture.ts index 3631fb6d1..ee8bbda59 100644 --- a/services/server/test/helpers/ServerFixture.ts +++ b/services/server/test/helpers/ServerFixture.ts @@ -37,7 +37,7 @@ export class ServerFixture { this.server.services.storage.rwServices[ RWStorageIdentifiers.SourcifyDatabase ] as SourcifyDatabaseService - ).databasePool; + ).database.pool; if (!_sourcifyDatabase) throw new Error("sourcifyDatabase not initialized!"); return _sourcifyDatabase; diff --git a/services/server/test/helpers/etherscanInstanceContracts.json b/services/server/test/helpers/etherscanInstanceContracts.json index 17a05ea35..e55ccc0a4 100644 --- a/services/server/test/helpers/etherscanInstanceContracts.json +++ b/services/server/test/helpers/etherscanInstanceContracts.json @@ -510,7 +510,7 @@ { "address": "0xd5a515CE2dF4C69aA809De5b9318a3D4452D86BE", "type": "standard-json", - "expectedStatus": "perfect" + "expectedStatus": "partial" } ] } diff --git a/services/server/test/integration/database.spec.ts b/services/server/test/integration/database.spec.ts index 61800e4d9..5be782716 100644 --- a/services/server/test/integration/database.spec.ts +++ b/services/server/test/integration/database.spec.ts @@ -5,7 +5,7 @@ import { id as keccak256str, keccak256 } from "ethers"; import { LocalChainFixture } from "../helpers/LocalChainFixture"; import { ServerFixture } from "../helpers/ServerFixture"; import type { MetadataSourceMap } from "@ethereum-sourcify/lib-sourcify"; -import * as databaseUtil from "../../src/server/services/utils/database-util"; +import { Database } from "../../src/server/services/utils/Database"; import { bytesFromString } from "../../src/server/services/utils/database-util"; import crypto from "crypto"; import { Bytes } from "../../src/server/types"; @@ -383,7 +383,7 @@ describe("Sourcify database", function () { it("When inserting a new match, nothing should be stored if an error occurs in the middle of the sql transaction", async () => { // Sinon will throw an error if the function is called sandbox - .stub(databaseUtil, "insertVerifiedContract") + .stub(Database.prototype, "insertVerifiedContract") .throws(new Error("Simulated database error")); const res = await chai @@ -475,7 +475,7 @@ describe("Sourcify database", function () { // Sinon will throw an error if the function is called sandbox - .stub(databaseUtil, "insertVerifiedContract") + .stub(Database.prototype, "insertVerifiedContract") .throws(new Error("Simulated database error")); res = await chai diff --git a/services/server/test/unit/utils/contract-creation-util.spec.ts b/services/server/test/unit/utils/contract-creation-util.spec.ts index 418dfb61c..3863c0e6f 100644 --- a/services/server/test/unit/utils/contract-creation-util.spec.ts +++ b/services/server/test/unit/utils/contract-creation-util.spec.ts @@ -173,7 +173,10 @@ describe("contract creation util", function () { } // Remove all other fetchContractCreationTxUsing methods except the one we're testing - const testChain = { ...sourcifyChain }; + const testChain = Object.create( + Object.getPrototypeOf(sourcifyChain), + Object.getOwnPropertyDescriptors(sourcifyChain), + ); if (testChain.fetchContractCreationTxUsing) { testChain.fetchContractCreationTxUsing = { [testCase]: testChain.fetchContractCreationTxUsing[testCase],