Commit 07d7010d authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

packages: remove unmaintained javascript (#10613)

* packages: remove unmaintained javascript

This commit removes the following packages from the monorepo:
- `common-ts`
- `contracts-ts`
- `core-utils`
- `fee-estimation`
- `web3js-plugin`

These packages are not maintained. `chain-mon` and the `sdk` still
exist but are pending deprecation.

* readme: update

* mergify: leftover references
parent f2e5a7a5
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"changelog": ["@changesets/changelog-github", { "repo": "ethereum-optimism/optimism" }], "changelog": ["@changesets/changelog-github", { "repo": "ethereum-optimism/optimism" }],
"commit": false, "commit": false,
"fixed": [], "fixed": [],
"linked": [["@eth-optimism/contracts-bedrock", "@eth-optimism/contracts-ts"]], "linked": [],
"access": "public", "access": "public",
"baseBranch": "develop", "baseBranch": "develop",
"updateInternalDependencies": "patch", "updateInternalDependencies": "patch",
......
...@@ -725,28 +725,6 @@ jobs: ...@@ -725,28 +725,6 @@ jobs:
name: Upload coverage name: Upload coverage
command: codecov --verbose --clean --flags <<parameters.coverage_flag>> command: codecov --verbose --clean --flags <<parameters.coverage_flag>>
contracts-ts-tests:
docker:
- image: <<pipeline.parameters.ci_builder_image>>
resource_class: medium
steps:
- checkout
- attach_workspace: { at: "." }
- restore_cache:
name: Restore pnpm Package Cache
keys:
- pnpm-packages-v2-{{ checksum "pnpm.lock.yaml" }}
- check-changed:
patterns: sdk,contracts-bedrock,contracts
# populate node modules from the cache
- run:
name: Install dependencies
command: pnpm install:ci
- run:
name: Check generated and build
command: pnpm generate:check
working_directory: packages/contracts-ts
sdk-next-tests: sdk-next-tests:
docker: docker:
- image: <<pipeline.parameters.ci_builder_image>> - image: <<pipeline.parameters.ci_builder_image>>
...@@ -863,14 +841,6 @@ jobs: ...@@ -863,14 +841,6 @@ jobs:
- pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }} - pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
- check-changed: - check-changed:
patterns: packages patterns: packages
- run:
name: Check common-ts
command: npx depcheck
working_directory: packages/common-ts
- run:
name: Check core-utils
command: npx depcheck
working_directory: packages/core-utils
- run: - run:
name: Check sdk name: Check sdk
command: npx depcheck command: npx depcheck
...@@ -1675,18 +1645,6 @@ workflows: ...@@ -1675,18 +1645,6 @@ workflows:
jobs: jobs:
- pnpm-monorepo: - pnpm-monorepo:
name: pnpm-monorepo name: pnpm-monorepo
- js-lint-test:
name: common-ts-tests
coverage_flag: common-ts-tests
package_name: common-ts
requires:
- pnpm-monorepo
- js-lint-test:
name: core-utils-tests
coverage_flag: core-utils-tests
package_name: core-utils
requires:
- pnpm-monorepo
- contracts-bedrock-tests: - contracts-bedrock-tests:
requires: requires:
- pnpm-monorepo - pnpm-monorepo
...@@ -1701,21 +1659,14 @@ workflows: ...@@ -1701,21 +1659,14 @@ workflows:
name: chain-mon-tests name: chain-mon-tests
coverage_flag: chain-mon-tests coverage_flag: chain-mon-tests
package_name: chain-mon package_name: chain-mon
dependencies: "(common-ts|contracts-bedrock|core-utils|sdk)" dependencies: "(contracts-bedrock|sdk)"
requires:
- pnpm-monorepo
- js-lint-test:
name: contracts-ts-tests
coverage_flag: contracts-ts-tests
package_name: contracts-ts
dependencies: '(contracts-bedrock|contracts-ts)'
requires: requires:
- pnpm-monorepo - pnpm-monorepo
- js-lint-test: - js-lint-test:
name: sdk-tests name: sdk-tests
coverage_flag: sdk-tests coverage_flag: sdk-tests
package_name: sdk package_name: sdk
dependencies: "(contracts-bedrock|core-utils)" dependencies: "contracts-bedrock"
requires: requires:
- pnpm-monorepo - pnpm-monorepo
- depcheck: - depcheck:
......
# Packages # Packages
/packages/chain-mon @ethereum-optimism/security-reviewers /packages/chain-mon @ethereum-optimism/security-reviewers
/packages/chain-mon/internal/balance-mon @ethereum-optimism/infra-reviewers /packages/chain-mon/internal/balance-mon @ethereum-optimism/infra-reviewers
/packages/common-ts @ethereum-optimism/typescript-reviewers
/packages/contracts-bedrock @ethereum-optimism/contract-reviewers /packages/contracts-bedrock @ethereum-optimism/contract-reviewers
/packages/core-utils @ethereum-optimism/legacy-reviewers
/packages/sdk @ethereum-optimism/devxpod /packages/sdk @ethereum-optimism/devxpod
# Bedrock codebases # Bedrock codebases
......
...@@ -221,17 +221,6 @@ pull_request_rules: ...@@ -221,17 +221,6 @@ pull_request_rules:
label: label:
add: add:
- A-pkg-chain-mon - A-pkg-chain-mon
- name: Add A-pkg-common-ts label and ecopod reviewers
conditions:
- 'files~=^packages/common-ts/'
- '#label<5'
actions:
label:
add:
- A-pkg-common-ts
request_reviews:
users:
- roninjin10
- name: Add A-pkg-contracts-bedrock label - name: Add A-pkg-contracts-bedrock label
conditions: conditions:
- 'files~=^packages/contracts-bedrock/' - 'files~=^packages/contracts-bedrock/'
...@@ -239,30 +228,6 @@ pull_request_rules: ...@@ -239,30 +228,6 @@ pull_request_rules:
label: label:
add: add:
- A-pkg-contracts-bedrock - A-pkg-contracts-bedrock
- name: Add A-pkg-contracts-ts label
conditions:
- 'files~=^packages/contracts-ts/'
- '#label<5'
actions:
label:
add:
- A-pkg-contracts-ts
- name: Add A-pkg-core-utils label
conditions:
- 'files~=^packages/core-utils/'
- '#label<5'
actions:
label:
add:
- A-pkg-core-utils
- name: Add A-pkg-fee-estimation label
conditions:
- 'files~=^packages/fee-estimation/'
- '#label<5'
actions:
label:
add:
- A-pkg-fee-estimation
- name: Add A-pkg-sdk label and ecopod reviewers - name: Add A-pkg-sdk label and ecopod reviewers
conditions: conditions:
- 'files~=^packages/sdk/' - 'files~=^packages/sdk/'
...@@ -274,14 +239,6 @@ pull_request_rules: ...@@ -274,14 +239,6 @@ pull_request_rules:
request_reviews: request_reviews:
users: users:
- roninjin10 - roninjin10
- name: Add A-pkg-web3js-plugin label
conditions:
- 'files~=^packages/web3js-plugin/'
- '#label<5'
actions:
label:
add:
- A-pkg-web3js-plugin
- name: Add A-proxyd label - name: Add A-proxyd label
conditions: conditions:
- 'files~=^proxyd/' - 'files~=^proxyd/'
......
...@@ -4,18 +4,6 @@ ...@@ -4,18 +4,6 @@
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"eslint.workingDirectories": [ "eslint.workingDirectories": [
{
"directory": "packages/core-utils",
"changeProcessCWD": true
},
{
"directory": "packages/common-ts",
"changeProcessCWD": true
},
{
"directory": "packages/contracts",
"changeProcessCWD": true
},
{ {
"directory": "packages/chain-mon", "directory": "packages/chain-mon",
"changeProcessCWD": true "changeProcessCWD": true
...@@ -25,4 +13,4 @@ ...@@ -25,4 +13,4 @@
"eslint.format.enable": true, "eslint.format.enable": true,
"editorconfig.generateAuto": false, "editorconfig.generateAuto": false,
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true
} }
\ No newline at end of file
...@@ -81,13 +81,8 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln ...@@ -81,13 +81,8 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln
├── <a href="./ops-bedrock">ops-bedrock</a>: Bedrock devnet work ├── <a href="./ops-bedrock">ops-bedrock</a>: Bedrock devnet work
├── <a href="./packages">packages</a> ├── <a href="./packages">packages</a>
│ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services │ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ ├── <a href="./packages/common-ts">common-ts</a>: Common tools for building apps in TypeScript
│ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: Bedrock smart contracts │ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: Bedrock smart contracts
│ ├── <a href="./packages/contracts-ts">contracts-ts</a>: ABI and Address constants
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/fee-estimation">fee-estimation</a>: Tools for estimating gas on OP chains
│ ├── <a href="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism │ ├── <a href="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism
│ └── <a href="./packages/web3js-plugin">web3js-plugin</a>: Adds functions to estimate L1 and L2 gas
├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy ├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy
├── <a href="./specs">specs</a>: Specs of the rollup starting at the Bedrock upgrade ├── <a href="./specs">specs</a>: Specs of the rollup starting at the Bedrock upgrade
└── <a href="./ufm-test-services">ufm-test-services</a>: Runs a set of tasks to generate metrics └── <a href="./ufm-test-services">ufm-test-services</a>: Runs a set of tasks to generate metrics
......
...@@ -38,8 +38,6 @@ flag_management: ...@@ -38,8 +38,6 @@ flag_management:
- type: patch - type: patch
target: 100% target: 100%
- name: bedrock-go-tests - name: bedrock-go-tests
- name: common-ts-tests
- name: contracts-tests - name: contracts-tests
- name: core-utils-tests
- name: chain-mon-tests - name: chain-mon-tests
- name: sdk-tests - name: sdk-tests
ignores: [
"@babel/eslint-parser",
"@typescript-eslint/parser",
"eslint-plugin-import",
"eslint-plugin-unicorn",
"eslint-plugin-jsdoc",
"eslint-plugin-prefer-arrow",
"eslint-plugin-react",
"@typescript-eslint/eslint-plugin",
"eslint-config-prettier",
"eslint-plugin-prettier"
]
module.exports = {
extends: '../../.eslintrc.js',
}
node_modules/
build/
\ No newline at end of file
module.exports = {
...require('../../.prettierrc.js'),
};
\ No newline at end of file
# @eth-optimism/common-ts
## 0.8.9
### Patch Changes
- [#9964](https://github.com/ethereum-optimism/optimism/pull/9964) [`8241220898128e1f61064f22dcb6fdd0a5f043c3`](https://github.com/ethereum-optimism/optimism/commit/8241220898128e1f61064f22dcb6fdd0a5f043c3) Thanks [@roninjin10](https://github.com/roninjin10)! - Removed only-allow command from package.json
- Updated dependencies [[`8241220898128e1f61064f22dcb6fdd0a5f043c3`](https://github.com/ethereum-optimism/optimism/commit/8241220898128e1f61064f22dcb6fdd0a5f043c3)]:
- @eth-optimism/core-utils@0.13.2
## 0.8.8
### Patch Changes
- [#9334](https://github.com/ethereum-optimism/optimism/pull/9334) [`1ed50c44a5c4fb7244ede3b4c45ea7bbf144c1e5`](https://github.com/ethereum-optimism/optimism/commit/1ed50c44a5c4fb7244ede3b4c45ea7bbf144c1e5) Thanks [@smartcontracts](https://github.com/smartcontracts)! - Adds a new validator for address types.
## 0.8.7
### Patch Changes
- [#7450](https://github.com/ethereum-optimism/optimism/pull/7450) [`ac90e16a7`](https://github.com/ethereum-optimism/optimism/commit/ac90e16a7f85c4f73661ae6023135c3d00421c1e) Thanks [@roninjin10](https://github.com/roninjin10)! - Updated dev dependencies related to testing that is causing audit tooling to report failures
- Updated dependencies [[`ac90e16a7`](https://github.com/ethereum-optimism/optimism/commit/ac90e16a7f85c4f73661ae6023135c3d00421c1e)]:
- @eth-optimism/core-utils@0.13.1
## 0.8.6
### Patch Changes
- Updated dependencies [[`210b2c81d`](https://github.com/ethereum-optimism/optimism/commit/210b2c81dd383bad93480aa876b283d9a0c991c2)]:
- @eth-optimism/core-utils@0.13.0
## 0.8.5
### Patch Changes
- [#6887](https://github.com/ethereum-optimism/optimism/pull/6887) [`33eb63b10`](https://github.com/ethereum-optimism/optimism/commit/33eb63b10559a2267c814eda8129447c72940839) Thanks [@roninjin10](https://github.com/roninjin10)! - Updated npm dependencies of common-ts
## 0.8.4
### Patch Changes
- Updated dependencies [[`dfa309e34`](https://github.com/ethereum-optimism/optimism/commit/dfa309e3430ebc8790b932554dde120aafc4161e)]:
- @eth-optimism/core-utils@0.12.3
## 0.8.3
### Patch Changes
- Updated dependencies [[`c11039060`](https://github.com/ethereum-optimism/optimism/commit/c11039060bc037a88916c2cba602687b6d69ad1a), [`77da6edc6`](https://github.com/ethereum-optimism/optimism/commit/77da6edc643e0b5e39f7b6bb41c3c7ead418a876)]:
- @eth-optimism/core-utils@0.12.2
## 0.8.2
### Patch Changes
- Updated dependencies [8d7dcc70c]
- Updated dependencies [d6388be4a]
- @eth-optimism/core-utils@0.12.1
## 0.8.1
### Patch Changes
- fecd42d67: Fix BaseServiceV2 configuration for caseCase options
## 0.8.0
### Minor Changes
- 4ae94b412: Add option to configure body parser
### Patch Changes
- 0e179781b: Fixes a minor bug where the provider name was incorrectly logged when using waitForProvider
## 0.7.1
### Patch Changes
- f04e5db2d: Fix unknown option error in base service v2
## 0.7.0
### Minor Changes
- ab8ec365c: Updates BaseServiceV2 so that options are secret by default. Services will have to explicitly mark options as "public" for those options to be logged and included in the metadata metric.
- 9b2891852: Refactors BaseServiceV2 slightly, merges standard options with regular options
### Patch Changes
- e23f60f63: Fixes a bug in BaseServiceV2 where options were not being parsed correctly when passed into the constructor rather than via environment variables or command line arguments
- c6c9c7dbf: Adds a function for waiting for ethers providers
- ffcee1013: Make logLevel a default option of BaseServiceV2
- eceb0de1d: Adds new standard options to disable parsing variables from environment and command line.
## 0.6.8
### Patch Changes
- Updated dependencies [c975c9620]
- Updated dependencies [136ea1785]
- @eth-optimism/core-utils@0.12.0
## 0.6.7
### Patch Changes
- Updated dependencies [1e76cdb86]
- @eth-optimism/core-utils@0.11.0
## 0.6.6
### Patch Changes
- ce7da914: Minor update to BaseServiceV2 to keep the raw body around when requests are made.
## 0.6.5
### Patch Changes
- 7215f4ce: Bump ethers to 5.7.0 globally
- d7679ca4: Add source maps
- Updated dependencies [7215f4ce]
- Updated dependencies [206f6033]
- @eth-optimism/core-utils@0.10.1
## 0.6.4
### Patch Changes
- Updated dependencies [dbfea116]
- @eth-optimism/core-utils@0.10.0
## 0.6.3
### Patch Changes
- Updated dependencies [0df744f6]
- Updated dependencies [8ae39154]
- Updated dependencies [dac4a9f0]
- @eth-optimism/core-utils@0.9.3
## 0.6.2
### Patch Changes
- Updated dependencies [0bf3b9b4]
- Updated dependencies [8d26459b]
- Updated dependencies [4477fe9f]
- @eth-optimism/core-utils@0.9.2
## 0.6.1
### Patch Changes
- Updated dependencies [f9fee446]
- @eth-optimism/core-utils@0.9.1
## 0.6.0
### Minor Changes
- 3d1cb720: Add version to healthz for convenience
### Patch Changes
- Updated dependencies [700dcbb0]
- @eth-optimism/core-utils@0.9.0
## 0.5.0
### Minor Changes
- cb71fcde: Make typescript type more permissive for MetricsV2
### Patch Changes
- 10e41522: Fix potential metrics DoS vector in recent commit to BSV2
## 0.4.0
### Minor Changes
- 52b26878: More gracefully shut down base service
### Patch Changes
- c201f3f1: Collect default node metrics
- 29ff7462: Revert es target back to 2017
- Updated dependencies [29ff7462]
- @eth-optimism/core-utils@0.8.7
## 0.3.1
### Patch Changes
- 9ba869a7: Log server messages to logger instead of stdout
- 050859fd: Include default options in metadata metric
## 0.3.0
### Minor Changes
- d9e39931: Minor upgrade to BaseServiceV2 to expose a full customizable server, instead of just metrics.
- 84a8934c: BaseServiceV2 exposes service name and version as standard synthetic metric
## 0.2.10
### Patch Changes
- 9ecbf3e5: Expose service internal options as environment or cli options
## 0.2.9
### Patch Changes
- Updated dependencies [17962ca9]
- @eth-optimism/core-utils@0.8.6
## 0.2.8
### Patch Changes
- f16383f2: Have legacy BaseService metrics bind to 0.0.0.0 by default
- d18ae135: Updates all ethers versions in response to BN.js bug
- Updated dependencies [d18ae135]
- @eth-optimism/core-utils@0.8.5
## 0.2.7
### Patch Changes
- Updated dependencies [5cb3a5f7]
- Updated dependencies [6b9fc055]
- @eth-optimism/core-utils@0.8.4
## 0.2.6
### Patch Changes
- b57014d1: Update to typescript@4.6.2
- Updated dependencies [b57014d1]
- @eth-optimism/core-utils@0.8.3
## 0.2.5
### Patch Changes
- e36b085c: Adds hard stop to BaseServiceV2 when multiple exit signals are received
- c1957126: Update Dockerfile to use Alpine
- 51673b90: Have BaseServiceV2 throw when options are undefined
- 7a179003: Adds the jsonRpcProvider validator as an input validator
- Updated dependencies [c1957126]
- @eth-optimism/core-utils@0.8.2
## 0.2.4
### Patch Changes
- f981b8da: Properly exposes metrics as part of a metrics server at port 7300
## 0.2.3
### Patch Changes
- f7761058: Update log lines for service shutdown
- 5ae15042: Update metric names to include proper snake_case for strings that include "L1" or "L2"
- 5cd1e996: Have BaseServiceV2 add spaces to environment variable names
## 0.2.2
### Patch Changes
- b3f9bdef: Have BaseServiceV2 gracefully catch exit signals
- e53b5783: Introduces the new BaseServiceV2 class.
## 0.2.1
### Patch Changes
- 243f33e5: Standardize package json file format
## 0.2.0
### Minor Changes
- 81ccd6e4: `regenesis/0.5.0` release
## 0.1.6
### Patch Changes
- 6d3e1d7f: Update dependencies
## 0.1.5
### Patch Changes
- c73c3939: Update the typescript version to `4.3.5`
## 0.1.4
### Patch Changes
- 5c89c45f: Move the metric prefix string to a label #1047
## 0.1.3
### Patch Changes
- baa3b761: Improve Sentry support, initializing as needed and ensuring ERROR logs route to Sentry
## 0.1.2
### Patch Changes
- 0c16805: add metrics server to common-ts and batch submitter
## 0.1.1
### Patch Changes
- 1d40586: Removed various unused dependencies
- 575bcf6: add environment and network to dtl, move metric init to app from base-service
## 0.1.0
### Minor Changes
- 28dc442: move metrics, logger, and base-service to new common-ts package
(The MIT License)
Copyright 2020-2021 Optimism
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# @eth-optimism/common-ts
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=common-ts-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
## What is this?
`@eth-optimism/common-ts` contains useful tools for logging, metrics, and other Node stuff.
{
"name": "@eth-optimism/common-ts",
"version": "0.8.9",
"description": "[Optimism] Advanced typescript tooling used by various services",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist/*",
"src/*"
],
"scripts": {
"all": "pnpm clean && pnpm build && pnpm test && pnpm lint:fix && pnpm lint",
"build": "tsc -p tsconfig.json",
"clean": "rimraf dist/ ./tsconfig.tsbuildinfo",
"lint:check": "eslint . --max-warnings=0",
"lint:fix": "pnpm lint:check --fix",
"lint": "pnpm lint:fix && pnpm lint:check",
"pre-commit": "lint-staged",
"test": "ts-mocha test/*.spec.ts",
"test:coverage": "nyc ts-mocha test/*.spec.ts && nyc merge .nyc_output coverage.json"
},
"keywords": [
"optimism",
"ethereum",
"common",
"typescript"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/common-ts#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/core-utils": "^0.13.2",
"@sentry/node": "^7.99.0",
"bcfg": "^0.2.1",
"body-parser": "^1.20.2",
"commander": "^11.1.0",
"dotenv": "^16.4.5",
"envalid": "^8.0.0",
"ethers": "^5.7.2",
"express": "^4.19.2",
"express-prom-bundle": "^7.0.0",
"lodash": "^4.17.21",
"morgan": "^1.10.0",
"pino": "^8.19.0",
"pino-multi-stream": "^6.0.0",
"pino-sentry": "^0.14.0",
"prom-client": "^14.2.0"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/pino": "^7.0.5",
"@types/pino-multi-stream": "^5.1.6",
"chai": "^4.3.10",
"supertest": "^6.3.4"
}
}
This diff is collapsed.
/* Imports: Internal */
import { Logger } from '../common/logger'
import { LegacyMetrics } from '../common/metrics'
type OptionSettings<TOptions> = {
[P in keyof TOptions]?: {
default?: TOptions[P]
validate?: (val: any) => boolean
}
}
type BaseServiceOptions<T> = T & {
logger?: Logger
metrics?: LegacyMetrics
}
/**
* Base for other "Service" objects. Handles your standard initialization process, can dynamically
* start and stop.
*/
export class BaseService<T> {
protected name: string
protected options: T
protected logger: Logger
protected metrics: LegacyMetrics
protected initialized = false
protected running = false
constructor(
name: string,
options: BaseServiceOptions<T>,
optionSettings: OptionSettings<T>
) {
validateOptions(options, optionSettings)
this.name = name
this.options = mergeDefaultOptions(options, optionSettings)
this.logger = options.logger || new Logger({ name })
if (options.metrics) {
this.metrics = options.metrics
}
}
/**
* Initializes the service.
*/
public async init(): Promise<void> {
if (this.initialized) {
return
}
this.logger.info('Service is initializing...')
await this._init()
this.logger.info('Service has initialized.')
this.initialized = true
}
/**
* Starts the service (initializes it if needed).
*/
public async start(): Promise<void> {
if (this.running) {
return
}
this.logger.info('Service is starting...')
await this.init()
// set the service to running
this.running = true
await this._start()
this.logger.info('Service has started')
}
/**
* Stops the service.
*/
public async stop(): Promise<void> {
if (!this.running) {
return
}
this.logger.info('Service is stopping...')
await this._stop()
this.logger.info('Service has stopped')
this.running = false
}
/**
* Internal init function. Parent should implement.
*/
protected async _init(): Promise<void> {
return
}
/**
* Internal start function. Parent should implement.
*/
protected async _start(): Promise<void> {
return
}
/**
* Internal stop function. Parent should implement.
*/
protected async _stop(): Promise<void> {
return
}
}
/**
* Combines user provided and default options.
*/
const mergeDefaultOptions = <T>(
options: T,
optionSettings: OptionSettings<T>
): T => {
for (const optionName of Object.keys(optionSettings)) {
const optionDefault = optionSettings[optionName].default
if (optionDefault === undefined) {
continue
}
if (options[optionName] !== undefined && options[optionName] !== null) {
continue
}
options[optionName] = optionDefault
}
return options
}
/**
* Performs option validation against the option settings
*/
const validateOptions = <T>(options: T, optionSettings: OptionSettings<T>) => {
for (const optionName of Object.keys(optionSettings)) {
const optionValidationFunction = optionSettings[optionName].validate
if (optionValidationFunction === undefined) {
continue
}
const optionValue = options[optionName]
if (optionValidationFunction(optionValue) === false) {
throw new Error(
`Provided input for option "${optionName}" is invalid: ${optionValue}`
)
}
}
}
export * from './base-service'
export * from './base-service-v2'
export * from './validators'
export * from './metrics'
export * from './options'
export * from './router'
import {
Gauge as PGauge,
Counter as PCounter,
Histogram as PHistogram,
Summary as PSummary,
} from 'prom-client'
import { OptionsSpec, getPublicOptions } from './options'
// Prometheus metrics re-exported.
export class Gauge extends PGauge<string> {}
export class Counter extends PCounter<string> {}
export class Histogram extends PHistogram<string> {}
export class Summary extends PSummary<string> {}
export type Metric = Gauge | Counter | Histogram | Summary
/**
* Metrics that are available for a given service.
*/
export type Metrics = Record<any, Metric>
/**
* Specification for metrics.
*/
export type MetricsSpec<TMetrics extends Metrics> = {
[P in keyof Required<TMetrics>]: {
type: new (configuration: any) => TMetrics[P]
desc: string
labels?: string[]
}
}
/**
* Standard metrics that are always available.
*/
export type StandardMetrics = {
metadata: Gauge
unhandledErrors: Counter
}
/**
* Generates a standard metrics specification. Needs to be a function because the labels for
* service metadata are dynamic dependent on the list of given options.
*
* @param options Options to include in the service metadata.
* @returns Metrics specification.
*/
export const makeStdMetricsSpec = (
optionsSpec: OptionsSpec<any>
): MetricsSpec<StandardMetrics> => {
return {
// Users cannot set these options.
metadata: {
type: Gauge,
desc: 'Service metadata',
labels: ['name', 'version'].concat(getPublicOptions(optionsSpec)),
},
unhandledErrors: {
type: Counter,
desc: 'Unhandled errors',
},
}
}
import { ValidatorSpec, Spec } from 'envalid'
import { LogLevel } from '../common/logger'
import { validators } from './validators'
/**
* Options for a service.
*/
export type Options = {
[key: string]: any
}
/**
* Specification for options.
*/
export type OptionsSpec<TOptions extends Options> = {
[P in keyof Required<TOptions>]: {
validator: (spec?: Spec<TOptions[P]>) => ValidatorSpec<TOptions[P]>
desc: string
default?: TOptions[P]
public?: boolean
}
}
/**
* Standard options shared by all services.
*/
export type StandardOptions = {
loopIntervalMs?: number
port?: number
hostname?: string
logLevel?: LogLevel
useEnv?: boolean
useArgv?: boolean
}
/**
* Specification for standard options.
*/
export const stdOptionsSpec: OptionsSpec<StandardOptions> = {
loopIntervalMs: {
validator: validators.num,
desc: 'Loop interval in milliseconds, only applies if service is set to loop',
default: 0,
public: true,
},
port: {
validator: validators.num,
desc: 'Port for the app server',
default: 7300,
public: true,
},
hostname: {
validator: validators.str,
desc: 'Hostname for the app server',
default: '0.0.0.0',
public: true,
},
logLevel: {
validator: validators.logLevel,
desc: 'Log level',
default: 'debug',
public: true,
},
useEnv: {
validator: validators.bool,
desc: 'For programmatic use, whether to use environment variables',
default: true,
public: true,
},
useArgv: {
validator: validators.bool,
desc: 'For programmatic use, whether to use command line arguments',
default: true,
public: true,
},
}
/**
* Gets the list of public option names from an options specification.
*
* @param optionsSpec Options specification.
* @returns List of public option names.
*/
export const getPublicOptions = (
optionsSpec: OptionsSpec<Options>
): string[] => {
return Object.keys(optionsSpec).filter((key) => {
return optionsSpec[key].public
})
}
import { Router } from 'express'
/**
* Express router re-exported.
*/
export type ExpressRouter = Router
import {
str,
bool,
num,
email,
host,
port,
url,
json,
makeValidator,
} from 'envalid'
import { Provider } from '@ethersproject/abstract-provider'
import { Signer } from '@ethersproject/abstract-signer'
import { ethers } from 'ethers'
import { LogLevel, logLevels } from '../common'
const provider = makeValidator<Provider>((input) => {
const parsed = url()._parse(input)
return new ethers.providers.JsonRpcProvider(parsed)
})
const jsonRpcProvider = makeValidator<ethers.providers.JsonRpcProvider>(
(input) => {
const parsed = url()._parse(input)
return new ethers.providers.JsonRpcProvider(parsed)
}
)
const staticJsonRpcProvider =
makeValidator<ethers.providers.StaticJsonRpcProvider>((input) => {
const parsed = url()._parse(input)
return new ethers.providers.StaticJsonRpcProvider(parsed)
})
const wallet = makeValidator<Signer>((input) => {
if (!ethers.utils.isHexString(input)) {
throw new Error(`expected wallet to be a hex string`)
} else {
return new ethers.Wallet(input)
}
})
const logLevel = makeValidator<LogLevel>((input) => {
if (!logLevels.includes(input as LogLevel)) {
throw new Error(`expected log level to be one of ${logLevels.join(', ')}`)
} else {
return input as LogLevel
}
})
const address = makeValidator<string>((input) => {
if (!ethers.utils.isHexString(input, 20)) {
throw new Error(`expected input to be an address: ${input}`)
} else {
return input as `0x${string}`
}
})
export const validators = {
str,
bool,
num,
email,
host,
port,
url,
json,
wallet,
provider,
jsonRpcProvider,
staticJsonRpcProvider,
logLevel,
address,
}
export * from './logger'
export * from './metrics'
export * from './provider'
import pino, { LoggerOptions as PinoLoggerOptions } from 'pino'
import pinoms, { Streams } from 'pino-multi-stream'
import { createWriteStream } from 'pino-sentry'
import { NodeOptions } from '@sentry/node'
export const logLevels = [
'trace',
'debug',
'info',
'warn',
'error',
'fatal',
] as const
export type LogLevel = (typeof logLevels)[number]
export interface LoggerOptions {
name: string
level?: LogLevel
sentryOptions?: NodeOptions
streams?: Streams
}
/**
* Temporary wrapper class to maintain earlier module interface.
*/
export class Logger {
options: LoggerOptions
inner: pino.Logger
constructor(options: LoggerOptions) {
this.options = options
const loggerOptions: PinoLoggerOptions = {
name: options.name,
level: options.level || 'debug',
// Remove pid and hostname considering production runs inside docker
base: null,
}
let loggerStreams: Streams = [{ stream: process.stdout }]
if (options.sentryOptions) {
loggerStreams.push({
level: 'error',
stream: createWriteStream({
...options.sentryOptions,
stackAttributeKey: 'err',
}),
})
}
if (options.streams) {
loggerStreams = loggerStreams.concat(options.streams)
}
this.inner = pino(loggerOptions, pinoms.multistream(loggerStreams))
}
child(bindings: pino.Bindings): Logger {
const inner = this.inner.child(bindings)
const logger = new Logger(this.options)
logger.inner = inner
return logger
}
trace(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.trace(o, msg, ...args)
} else {
this.inner.trace(msg, ...args)
}
}
debug(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.debug(o, msg, ...args)
} else {
this.inner.debug(msg, ...args)
}
}
info(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.info(o, msg, ...args)
} else {
this.inner.info(msg, ...args)
}
}
warn(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.warn(o, msg, ...args)
} else {
this.inner.warn(msg, ...args)
}
}
warning(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.warn(o, msg, ...args)
} else {
this.inner.warn(msg, ...args)
}
}
error(msg: string, o?: object, ...args: any[]): void {
if (o) {
// Formatting error log for Sentry
const context = {
extra: { ...o },
}
this.inner.error(context, msg, ...args)
} else {
this.inner.error(msg, ...args)
}
}
fatal(msg: string, o?: object, ...args: any[]): void {
if (o) {
const context = {
extra: { ...o },
}
this.inner.fatal(context, msg, ...args)
} else {
this.inner.fatal(msg, ...args)
}
}
}
import { Server } from 'net'
import prometheus, {
collectDefaultMetrics,
DefaultMetricsCollectorConfiguration,
Registry,
} from 'prom-client'
import express from 'express'
import { Logger } from './logger'
export interface MetricsOptions {
prefix?: string
labels?: Object
}
export class LegacyMetrics {
options: MetricsOptions
client: typeof prometheus
registry: Registry
constructor(options: MetricsOptions) {
this.options = options
const metricsOptions: DefaultMetricsCollectorConfiguration = {
prefix: options.prefix,
labels: options.labels,
}
this.client = prometheus
this.registry = prometheus.register
// Collect default metrics (event loop lag, memory, file descriptors etc.)
collectDefaultMetrics(metricsOptions)
}
}
export interface MetricsServerOptions {
logger: Logger
registry: Registry
port?: number
route?: string
hostname?: string
}
export const createMetricsServer = async (
options: MetricsServerOptions
): Promise<Server> => {
const logger = options.logger.child({ component: 'MetricsServer' })
const app = express()
const route = options.route || '/metrics'
app.get(route, async (_, res) => {
res.status(200).send(await options.registry.metrics())
})
const port = options.port || 7300
const hostname = options.hostname || '0.0.0.0'
const server = app.listen(port, hostname, () => {
logger.info('Metrics server started', {
port,
hostname,
route,
})
})
return server
}
import { Provider } from '@ethersproject/abstract-provider'
import { sleep } from '@eth-optimism/core-utils'
import { Logger } from './logger'
/**
* Waits for an Ethers provider to be connected.
*
* @param provider Ethers provider to check.
* @param opts Options for the function.
* @param opts.logger Logger to use.
* @param opts.intervalMs Interval to wait between checks.
* @param opts.name Name of the provider for logs.
*/
export const waitForProvider = async (
provider: Provider,
opts?: {
logger?: Logger
intervalMs?: number
name?: string
}
) => {
const name = opts?.name || 'target'
opts?.logger?.info(`waiting for ${name} provider...`)
let connected = false
while (!connected) {
try {
await provider.getBlockNumber()
connected = true
} catch (e) {
opts?.logger?.info(`${name} provider not connected, retrying...`)
await sleep(opts?.intervalMs || 15000)
}
}
opts?.logger?.info(`${name} provider connected`)
}
export * from './common'
export * from './base-service'
import request from 'supertest'
// Setup
import chai = require('chai')
const expect = chai.expect
import { Logger, LegacyMetrics, createMetricsServer } from '../src'
describe('Metrics', () => {
it('should serve metrics', async () => {
const metrics = new LegacyMetrics({
prefix: 'test_metrics',
})
const registry = metrics.registry
const logger = new Logger({ name: 'test_logger' })
const server = await createMetricsServer({
logger,
registry,
port: 42069,
})
try {
// Create two metrics for testing
const counter = new metrics.client.Counter({
name: 'counter',
help: 'counter help',
registers: [registry],
})
const gauge = new metrics.client.Gauge({
name: 'gauge',
help: 'gauge help',
registers: [registry],
})
counter.inc()
counter.inc()
gauge.set(100)
// Verify that the registered metrics are served at `/`
const response = await request(server).get('/metrics').send()
expect(response.status).eq(200)
expect(response.text).match(/counter 2/)
expect(response.text).match(/gauge 100/)
} finally {
server.close()
registry.clear()
}
})
})
import { validators } from '../dist'
import { BaseServiceV2 } from '../src'
type ServiceOptions = {
camelCase: string
}
class Service extends BaseServiceV2<ServiceOptions, {}, {}> {
constructor(options?: Partial<ServiceOptions>) {
super({
name: 'test-service',
version: '0.0',
options,
optionsSpec: {
camelCase: { validator: validators.str, desc: 'test' },
},
metricsSpec: {},
})
}
protected async main() {
/* eslint-disable @typescript-eslint/no-empty-function */
}
}
describe('BaseServiceV2', () => {
it('base service ctor does not throw on camel case options', async () => {
new Service({ camelCase: 'test' })
})
})
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"skipLibCheck": true
},
"include": [
"src/**/*"
]
}
artifacts
cache
typechain
.deps
.envrc
.env
/dist/
coverage
artifacts
cache
typechain
.deps
.envrc
.env
/dist/
module.exports = {
...require('../../.prettierrc.js'),
}
# @eth-optimism/contracts-ts
## 0.17.2
### Patch Changes
- [#9964](https://github.com/ethereum-optimism/optimism/pull/9964) [`8241220898128e1f61064f22dcb6fdd0a5f043c3`](https://github.com/ethereum-optimism/optimism/commit/8241220898128e1f61064f22dcb6fdd0a5f043c3) Thanks [@roninjin10](https://github.com/roninjin10)! - Removed only-allow command from package.json
## 0.17.0
### Minor Changes
- [#7644](https://github.com/ethereum-optimism/optimism/pull/7644) [`86bdaa075`](https://github.com/ethereum-optimism/optimism/commit/86bdaa075f424974531ce98ac9e21037e43db1bb) Thanks [@roninjin10](https://github.com/roninjin10)! - Removed unused hooks and actions
## 0.16.2
### Patch Changes
- [#7450](https://github.com/ethereum-optimism/optimism/pull/7450) [`ac90e16a7`](https://github.com/ethereum-optimism/optimism/commit/ac90e16a7f85c4f73661ae6023135c3d00421c1e) Thanks [@roninjin10](https://github.com/roninjin10)! - Updated dev dependencies related to testing that is causing audit tooling to report failures
# Code gen
Summary -
- This package is generated from [contracts-bedrock](../contracts-bedrock/)
- Its version is kept in sync with contracts bedrock via the [changeset config](../../.changeset/config.json) e.g. if contracts-bedrock is `4.2.0` this package will have the same version.
## Code gen instructions
To run the code gen run the `generate` script from [package.json](./package.json). Make sure node modules is installed.
```bash
pnpm i && pnpm generate
```
MIT License
Copyright (c) 2022 Optimism
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Contracts TS
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=contracts-bedrock-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
ABI and Address constants + generated code from [@eth-optimism/contracts-bedrock/](../contracts-bedrock/) for use in TypeScript.
Much of this package is generated. See [CODE_GEN.md](./CODE_GEN.md) for instructions on how to generate.
#### @eth-optimism/contracts-ts
The main entrypoint exports constants related to contracts bedrock as const. As const allows it to be [used in TypeScript with stronger typing than importing JSON](https://github.com/microsoft/TypeScript/issues/32063).
- Exports contract abis.
- Exports contract addresses
```typescript
import {
l2OutputOracleProxyABI,
l2OutputOracleAddresses,
} from '@eth-optimism/contracts-ts'
console.log(l2OutputOracleAddresses[10], abi)
```
Addresses are also exported as an object for convenience.
```typescript
import { addresses } from '@eth-optimism/contracts-ts'
console.log(addresses.l2OutputOracle[10])
```
#### @eth-optimism/contracts-ts/react
- All [React hooks](https://wagmi.sh/cli/plugins/react) `@eth-optimism/contracts-ts/react`
```typescript
import { useAddressManagerAddress } from '@eth-optimism/contracts-ts/react'
const component = () => {
const { data, error, loading } = useAddressManagerAddress()
if (loading) {
return <div>Loading</div>
}
if (err) {
return <div>Error</div>
}
return <div>{data}</div>
}
```
#### @eth-optimism/contracts-ts/actions
- All [wagmi actions](https://wagmi.sh/react/actions) for use in Vanilla JS or non react code
```typescript
import { readSystemConfig } from '@eth-optimism/contracts-ts/actions'
console.log(await readSystemConfig())
```
#### See Also
- [Contracts bedrock specs](../../specs/)
- [Wagmi](https://wagmi.sh)
This diff is collapsed.
{
"AddressManager": {
"1": "0xdE1FCfB0851916CA5101820A69b13a4E276bd81F",
"5": "0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"
},
"AssetReceiver": {
"1": "0x15DdA60616Ffca20371ED1659dBB78E888f65556",
"10": "0x15DdA60616Ffca20371ED1659dBB78E888f65556"
},
"AttestationStation": {
"10": "0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77",
"420": "0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"
},
"BaseFeeVault": {
"420": "0x4200000000000000000000000000000000000019"
},
"CheckBalanceHigh": {
"1": "0x7eC64a8a591bFf829ff6C8be76074D540ACb813F",
"5": "0x7eC64a8a591bFf829ff6C8be76074D540ACb813F",
"420": "0x5d7103853f12109A7d27F118e54BbC654ad847E9"
},
"CheckBalanceLow": {
"1": "0x381a4eFC2A2C914eA1889722bB4B44Fa6BD5b640",
"5": "0x381a4eFC2A2C914eA1889722bB4B44Fa6BD5b640",
"420": "0x7Ce13D154FAEE5C8B3E6b19d4Add16f21d884474"
},
"CheckGelatoLow": {
"1": "0x4f7CFc43f6D262a085F3b946cAC69E7a8E39BBAa",
"5": "0x4f7CFc43f6D262a085F3b946cAC69E7a8E39BBAa",
"420": "0xF9c8a4Cb4021f57F9f6d69799cA9BefF64524862"
},
"CheckTrue": {
"1": "0x5c741a38cb11424711231777D71689C458eE835D",
"5": "0x5c741a38cb11424711231777D71689C458eE835D",
"420": "0x47443D0C184e022F19BD1578F5bca6B8a9F58E32"
},
"Drippie": {
"1": "0x44b3A2a040057eBafC601A78647e805fd58B1f50"
},
"Drippie_goerli": {
"5": "0x44b3A2a040057eBafC601A78647e805fd58B1f50"
},
"Drippie_optimism-goerli": {
"420": "0x8D8d533C16D23847EB04EEB0925be8900Dd3af86"
},
"EAS": {
"10": "0x4E0275Ea5a89e7a3c1B58411379D1a0eDdc5b088",
"420": "0x5A633F1cc84B03F7588486CF2F386c102061E6e1"
},
"GasPriceOracle": {
"420": "0x420000000000000000000000000000000000000F"
},
"L1Block": {
"420": "0x4200000000000000000000000000000000000015"
},
"L1CrossDomainMessenger": {
"1": "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1",
"5": "0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"
},
"L1ERC721Bridge": {
"1": "0x5a7749f83b81B301cAb5f48EB8516B986DAef23D",
"5": "0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"
},
"L1FeeVault": {
"420": "0x420000000000000000000000000000000000001a"
},
"L1StandardBridge": {
"1": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
"5": "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"
},
"L2CrossDomainMessenger": {
"420": "0x4200000000000000000000000000000000000007"
},
"L2ERC721Bridge": {
"10": "0x4200000000000000000000000000000000000014"
},
"L2ERC721Bridge_optimism-goerli": {
"420": "0x4200000000000000000000000000000000000014"
},
"L2OutputOracle": {
"1": "0xdfe97868233d1aa22e815a266982f2cf17685a27",
"5": "0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"
},
"L2StandardBridge": {
"420": "0x4200000000000000000000000000000000000010"
},
"L2ToL1MessagePasser": {
"420": "0x4200000000000000000000000000000000000016"
},
"MintManager": {
"10": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
"420": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76"
},
"OptimismMintableERC20Factory": {
"1": "0x4200000000000000000000000000000000000012"
},
"OptimismMintableERC20Factory_goerli": {
"5": "0x4200000000000000000000000000000000000012"
},
"OptimismMintableERC20Factory_optimism-goerli": {
"420": "0x4200000000000000000000000000000000000012"
},
"OptimismMintableERC721Factory": {
"10": "0x4200000000000000000000000000000000000017"
},
"OptimismMintableERC721Factory_optimism-goerli": {
"420": "0x4200000000000000000000000000000000000017"
},
"OptimismPortal": {
"1": "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed",
"5": "0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"
},
"Optimist": {
"10": "0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5",
"420": "0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5"
},
"OptimistAllowlist": {
"10": "0x482b1945D58f2E9Db0CEbe13c7fcFc6876b41180",
"420": "0x482b1945D58f2E9Db0CEbe13c7fcFc6876b41180"
},
"OptimistInviter": {
"10": "0x073031A1E1b8F5458Ed41Ce56331F5fd7e1de929",
"420": "0x073031A1E1b8F5458Ed41Ce56331F5fd7e1de929"
},
"PortalSender": {
"1": "0x0A893d9576b9cFD9EF78595963dc973238E78210",
"5": "0xe7FACd39531ee3C313330E93B4d7a8B8A3c84Aa4"
},
"ProtocolVersions": {
"5": "0x0C24F5098774aA366827D667494e9F889f7cFc08"
},
"ProxyAdmin": {
"1": "0x4200000000000000000000000000000000000018",
"5": "0x4200000000000000000000000000000000000018"
},
"SchemaRegistry": {
"10": "0x6232208d66bAc2305b46b4Cb6BCB3857B298DF13",
"420": "0x2545fa928d5d278cA75Fd47306e4a89096ff6403"
},
"SequencerFeeVault": {
"420": "0x4200000000000000000000000000000000000011"
},
"SystemConfig": {
"1": "0x229047fed2591dbec1eF1118d64F7aF3dB9EB290",
"5": "0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60"
},
"SystemDictator": {
"1": "0xB4453CEb33d2e67FA244A24acf2E50CEF31F53cB"
},
"SystemDictator_goerli": {
"5": "0x1f0613A44c9a8ECE7B3A2e0CdBdF0F5B47A50971"
},
"TeleportrWithdrawer": {
"1": "0x78A25524D90E3D0596558fb43789bD800a5c3007"
}
}
VITE_RPC_URL_L2_GOERLI=
VITE_RPC_URL_L2_MAINNET=
VITE_RPC_URL_L1_GOERLI=
VITE_RPC_URL_L1_MAINNET=
{
"name": "@eth-optimism/contracts-ts",
"version": "0.17.2",
"description": "TypeScript interface for Contracts Bedrock",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git",
"directory": "packages/contracts-ts"
},
"homepage": "https://optimism.io",
"type": "module",
"main": "dist/constants.cjs",
"module": "dist/constants.js",
"types": "src/constants.ts",
"exports": {
".": {
"types": "./src/constants.ts",
"import": "./dist/constants.js",
"require": "./dist/constants.cjs"
},
"./actions": {
"types": "./src/actions.ts",
"import": "./dist/actions.js",
"require": "./dist/actions.cjs"
},
"./react": {
"types": "./src/react.ts",
"import": "./dist/react.js",
"require": "./dist/react.cjs"
}
},
"files": [
"dist/",
"src/"
],
"scripts": {
"build": "tsup",
"clean": "rm -rf ./dist",
"generate": "wagmi generate && pnpm build && pnpm lint:fix",
"generate:check": "pnpm generate && git diff --exit-code ./addresses.json && git diff --exit-code ./abis.json",
"lint": "prettier --check .",
"lint:fix": "prettier --write .",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@eth-optimism/contracts-bedrock": "workspace:*",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^1.2.2",
"@wagmi/cli": "^2.1.4",
"@wagmi/core": "^2.6.3",
"abitype": "^1.0.2",
"glob": "^10.3.10",
"isomorphic-fetch": "^3.0.0",
"jest-dom": "link:@types/@testing-library/jest-dom",
"jsdom": "^24.0.0",
"tsup": "^8.0.1",
"typescript": "^5.4.5",
"vite": "^5.1.7",
"wagmi": "^2.5.19",
"vitest": "^1.2.2"
},
"peerDependencies": {
"@wagmi/core": "^2.6.3",
"wagmi": "^2.5.19"
},
"peerDependenciesMeta": {
"wagmi": {
"optional": true
},
"@wagmi/core": {
"optional": true
}
},
"dependencies": {
"@testing-library/react": "^14.2.1",
"@types/change-case": "^2.3.1",
"change-case": "4.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"viem": "^2.8.13"
}
}
import fetch from 'isomorphic-fetch'
// viem needs this
global.fetch = fetch
This diff is collapsed.
import { test, expect } from 'vitest'
import { addresses } from './constants'
import { readFileSync } from 'fs'
import { join } from 'path'
const jsonAddresses = JSON.parse(
readFileSync(join(__dirname, '../addresses.json'), 'utf8')
)
test('should have generated addresses', () => {
expect(addresses).toEqual(jsonAddresses)
})
This diff is collapsed.
This diff is collapsed.
/// <reference types="vite/client" />
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"baseUrl": "./src",
"strict": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "node",
"jsx": "react",
"target": "ESNext",
"noEmit": true
},
"include": ["./src"]
}
import { defineConfig } from 'tsup'
import packageJson from './package.json'
export default defineConfig({
name: packageJson.name,
entry: ['src/constants.ts', 'src/actions.ts', 'src/react.ts'],
outDir: 'dist',
format: ['esm', 'cjs'],
splitting: false,
sourcemap: true,
clean: false,
})
import { defineConfig } from 'vitest/config'
// @see https://vitest.dev/config/
export default defineConfig({
test: {
setupFiles: './setupVitest.ts',
environment: 'jsdom',
coverage: {
provider: 'istanbul',
},
},
})
This diff is collapsed.
ignores: [
"@babel/eslint-parser",
"@typescript-eslint/parser",
"eslint-plugin-import",
"eslint-plugin-unicorn",
"eslint-plugin-jsdoc",
"eslint-plugin-prefer-arrow",
"eslint-plugin-react",
"@typescript-eslint/eslint-plugin",
"eslint-config-prettier",
"eslint-plugin-prettier",
"chai"
]
module.exports = {
extends: '../../.eslintrc.js',
}
module.exports = {
...require('../../.prettierrc.js'),
};
\ No newline at end of file
# @eth-optimism/core-utils
## 0.13.2
### Patch Changes
- [#9964](https://github.com/ethereum-optimism/optimism/pull/9964) [`8241220898128e1f61064f22dcb6fdd0a5f043c3`](https://github.com/ethereum-optimism/optimism/commit/8241220898128e1f61064f22dcb6fdd0a5f043c3) Thanks [@roninjin10](https://github.com/roninjin10)! - Removed only-allow command from package.json
## 0.13.1
### Patch Changes
- [#7450](https://github.com/ethereum-optimism/optimism/pull/7450) [`ac90e16a7`](https://github.com/ethereum-optimism/optimism/commit/ac90e16a7f85c4f73661ae6023135c3d00421c1e) Thanks [@roninjin10](https://github.com/roninjin10)! - Updated dev dependencies related to testing that is causing audit tooling to report failures
## 0.13.0
### Minor Changes
- [#7336](https://github.com/ethereum-optimism/optimism/pull/7336) [`210b2c81d`](https://github.com/ethereum-optimism/optimism/commit/210b2c81dd383bad93480aa876b283d9a0c991c2) Thanks [@tynes](https://github.com/tynes)! - Delete unmaintained geth types
## 0.12.3
### Patch Changes
- [#6797](https://github.com/ethereum-optimism/optimism/pull/6797) [`dfa309e34`](https://github.com/ethereum-optimism/optimism/commit/dfa309e3430ebc8790b932554dde120aafc4161e) Thanks [@roninjin10](https://github.com/roninjin10)! - Upgraded npm dependencies to latest
## 0.12.2
### Patch Changes
- [#6164](https://github.com/ethereum-optimism/optimism/pull/6164) [`c11039060`](https://github.com/ethereum-optimism/optimism/commit/c11039060bc037a88916c2cba602687b6d69ad1a) Thanks [@pengin7384](https://github.com/pengin7384)! - fix typo
- [#6198](https://github.com/ethereum-optimism/optimism/pull/6198) [`77da6edc6`](https://github.com/ethereum-optimism/optimism/commit/77da6edc643e0b5e39f7b6bb41c3c7ead418a876) Thanks [@tremarkley](https://github.com/tremarkley)! - Delete dead typescript https://github.com/ethereum-optimism/optimism/pull/6148.
## 0.12.1
### Patch Changes
- 8d7dcc70c: Delete legacy core-utils
- d6388be4a: Added a new service wallet-mon to identify unexpected transfers from key accounts
## 0.12.0
### Minor Changes
- c975c9620: Add suppory for finalizing legacy withdrawals after the Bedrock migration
### Patch Changes
- 136ea1785: Refactors the L2OutputOracle to key the l2Outputs mapping by index instead of by L2 block number.
## 0.11.0
### Minor Changes
- 1e76cdb86: Changes the type for Bedrock withdrawal proofs
## 0.10.1
### Patch Changes
- 7215f4ce: Bump ethers to 5.7.0 globally
- 206f6033: Fix outdated references to 'withdrawal contract'
## 0.10.0
### Minor Changes
- dbfea116: Removes ethers as a dependency in favor of individual ethers sub-packages
## 0.9.3
### Patch Changes
- 0df744f6: Implement basic OpNodeProvider
- 8ae39154: Update deposit transaction type
- dac4a9f0: Updates the SDK to be compatible with Bedrock (via the "bedrock: true" constructor param). Updates the build pipeline for contracts-bedrock to export a properly formatted dist folder that matches our other packages.
## 0.9.2
### Patch Changes
- 0bf3b9b4: Add encoding and hashing functions for bedrock
- 8d26459b: Remove subversion byte from deposit tx
- 4477fe9f: Update deposit transaction serialization
## 0.9.1
### Patch Changes
- f9fee446: Move the `DepositTx` type to `core-utils`. This way it can be more easily used across projects
## 0.9.0
### Minor Changes
- 700dcbb0: Update geth's Genesis type to work with modern geth
## 0.8.7
### Patch Changes
- 29ff7462: Revert es target back to 2017
## 0.8.6
### Patch Changes
- 17962ca9: Update geth genesis type
## 0.8.5
### Patch Changes
- d18ae135: Updates all ethers versions in response to BN.js bug
## 0.8.4
### Patch Changes
- 5cb3a5f7: Add a `calldataCost` function that computes the cost of calldata
- 6b9fc055: Adds a one-liner for getting chain ID from provider
## 0.8.3
### Patch Changes
- b57014d1: Update to typescript@4.6.2
## 0.8.2
### Patch Changes
- c1957126: Update Dockerfile to use Alpine
## 0.8.1
### Patch Changes
- 5a6f539c: Add toJSON methods to the batch primitives
- 27d8942e: Update batch serialization with typed batches and zlib compression
## 0.8.0
### Minor Changes
- 0b4453f7: Deletes the Watcher and injectL2Context functions. Use the SDK instead.
## 0.7.7
### Patch Changes
- b4165299: Added tests and docstrings to misc functions
- 3c2acd91: Refactor folder structure of @eth-optimism/core-utils.
## 0.7.6
### Patch Changes
- ba14c59d: Updates various ethers dependencies to their latest versions
## 0.7.5
### Patch Changes
- ad94b9d1: test/docs: Improve docstrings and tests for utils inside of hex-strings.ts
## 0.7.4
### Patch Changes
- ba96a455: Improved docstrings for BCFG typings
- c3e85fef: Cleans up the internal file and folder structure for the typings exported by core-utils
## 0.7.3
### Patch Changes
- 584cbc25: Clean up the L1 => L2 address aliasing utilities
## 0.7.2
### Patch Changes
- 8e634b49: Fix package JSON issues
## 0.7.1
### Patch Changes
- 243f33e5: Standardize package json file format
## 0.7.0
### Minor Changes
- 896168e2: Parse optimistic ethereum specific fields on transaction receipts
- 83a449c4: Change the expectApprox interface to allow setting an absoluteexpected deviation range
- 81ccd6e4: `regenesis/0.5.0` release
### Patch Changes
- 3ce62c81: Export bnToAddress
- cee2a464: Add awaitCondition to core utils
- 222a3eef: Add 'User-Agent' to the http headers for ethers providers
- 7c352b1e: Add bytes32ify
- b70ee70c: upgraded to solidity 0.8.9
- 20c8969b: Correctly move chai into deps instead of dev deps
- 6d32d701: Expose lower level API for tx fees
## 0.6.1
### Patch Changes
- 6d3e1d7f: Update dependencies
- 2e929aa9: Parse the L1 timestamp in `injectContext`
## 0.6.0
### Minor Changes
- 8da04505: Allow a configurable L1 and L2 blocks to fetch in the watcher
### Patch Changes
- e0be02e1: Add fallback provider support to DTL using helper function in core-utils
## 0.5.5
### Patch Changes
- eb0854e7: increased coverage of core-utils
- 21b17edd: Added coverage for packages
- dfe3598f: Lower per tx fee overhead to more accurately represent L1 costs
## 0.5.4
### Patch Changes
- 085b35ba: Watcher: Even lower num blocks to fetch
## 0.5.3
### Patch Changes
- 2aa4416e: Watcher: Make blocks to fetch a config option
- 0b8180b0: Lower NUM_BLOCKS_TO_FETCH in Watcher
## 0.5.2
### Patch Changes
- 918c08ca: Bump ethers dependency to 5.4.x to support eip1559
## 0.5.1
### Patch Changes
- c73c3939: Update the typescript version to `4.3.5`
## 0.5.0
### Minor Changes
- 049200f4: removed unused functions from core-utils
## 0.4.7
### Patch Changes
- 224b04c0: Adds a pollInterval delay to watcher.ts
## 0.4.6
### Patch Changes
- d9644c34: Minor fix on watchers to pick up finalization of transactions on L1
- df5ff890: improved watcher ability to find transactions during periods of high load
## 0.4.5
### Patch Changes
- a64f8161: Implement the next fee spec in both geth and in core-utils
- 750a5021: Delete dead transaction coders. These are no longer used now that RLP encoded transactions are used
- c2b6e14b: Implement the latest fee spec such that the L2 gas limit is scaled and the tx.gasPrice/tx.gasLimit show correctly in metamask
## 0.4.4
### Patch Changes
- f091e86: Have watcher correctly handle failed L1 => L2 messages
- f880479: End to end fee integration with recoverable L2 gas limit
## 0.4.3
### Patch Changes
- 96a586e: Migrate bcfg interface to core-utils
## 0.4.2
### Patch Changes
- b799caa: Update toRpcHexString to accept ethers.BigNumber and add tests
## 0.4.1
### Patch Changes
- 1d40586: Removed various unused dependencies
- ce7fa52: Add an additional enum for EthSign transactions as they now are batch submitted with 2 different enum values
## 0.4.0
### Minor Changes
- 28dc442: move metrics, logger, and base-service to new common-ts package
### Patch Changes
- a0a0052: Update toRpcHexString to accept ethers.BigNumber and add tests
## 0.3.2
### Patch Changes
- 6daa408: update hardhat versions so that solc is resolved correctly
- dee74ef: migrate batch submitter types to core-utils
- d64b66d: reformat error context for Sentry
## 0.3.1
### Patch Changes
- 5077441: - Use raw transaction in batch submitter -- incompatible with L2Geth v0.1.2.1
- Pass through raw transaction in l2context
## 0.3.0
### Minor Changes
- 91460d9: add Metrics and use in base-service, rename DTL services to avoid spaces
- a0a7956: initialize Sentry and streams in Logger, remove Sentry from Batch Submitter
### Patch Changes
- 0497d7d: Re-organize event typings to core-utils
## 0.2.3
### Patch Changes
- 35b99b0: add Sentry to TypeScript services for error tracking
## 0.2.2
### Patch Changes
- 01eaf2c: added extra logs to base-service / dtl to improve observability
## 0.2.1
### Patch Changes
- 5362d38: adds build files which were not published before to npm
## 0.2.0
### Minor Changes
- 6cbc54d: allow injecting L2 transaction and block context via core-utils (this removes the need to import the now deprecated @eth-optimism/provider package)
(The MIT License)
Copyright 2020-2021 Optimism
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# @eth-optimism/core-utils
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=core-utils-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
## What is this?
`@eth-optimism/core-utils` contains the Optimistic Virtual Machine core utilities.
## Getting started
### Building and usage
After cloning and switching to the repository, install dependencies:
```bash
$ pnpm i
```
Use the following commands to build, use, test, and lint:
```bash
$ pnpm build
$ pnpm start
$ pnpm test
$ pnpm lint
```
{
"name": "@eth-optimism/core-utils",
"version": "0.13.2",
"description": "[Optimism] Core typescript utilities",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist/*"
],
"scripts": {
"all": "pnpm clean && pnpm build && pnpm test && pnpm lint:fix && pnpm lint",
"build": "tsc -p tsconfig.json",
"clean": "rimraf dist/ ./tsconfig.tsbuildinfo",
"lint": "pnpm lint:fix && pnpm lint:check",
"lint:check": "eslint . --max-warnings=0",
"lint:fix": "pnpm lint:check --fix",
"pre-commit": "lint-staged",
"test": "ts-mocha test/**/*.spec.ts",
"test:coverage": "nyc ts-mocha test/**/*.spec.ts && nyc merge .nyc_output coverage.json"
},
"keywords": [
"optimism",
"ethereum",
"core",
"utils"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/core-utils#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@ethersproject/abi": "^5.7.0",
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/address": "^5.7.0",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/constants": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/properties": "^5.7.0",
"@ethersproject/rlp": "^5.7.0",
"@ethersproject/web": "^5.7.1",
"chai": "^4.3.10",
"ethers": "^5.7.2",
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@types/node": "^20.11.17",
"mocha": "^10.2.0"
}
}
// Use this file for simple types that aren't necessarily associated with a specific project or
// package. Often used for alias types like Address = string.
export interface Signature {
r: string
s: string
v: number
}
export type Bytes32 = string
export type Uint16 = number
export type Uint8 = number
export type Uint24 = number
export type Address = string
import { BigNumber } from '@ethersproject/bignumber'
import { getAddress } from '@ethersproject/address'
import { remove0x, add0x } from './hex-strings'
/**
* Converts an ethers BigNumber into an equivalent Ethereum address representation.
*
* @param bn BigNumber to convert to an address.
* @return BigNumber converted to an address, represented as a hex string.
*/
export const bnToAddress = (bn: BigNumber | number): string => {
// Coerce numbers into a BigNumber.
bn = BigNumber.from(bn)
// Negative numbers are converted to addresses by adding MAX_ADDRESS + 1.
// TODO: Explain this in more detail, it's basically just matching the behavior of doing
// addr(uint256(addr) - some_number) in Solidity where some_number > uint256(addr).
if (bn.isNegative()) {
bn = BigNumber.from('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
.add(bn)
.add(1)
}
// Convert to a hex string
let addr = bn.toHexString()
// Remove leading 0x so we can mutate the address a bit
addr = remove0x(addr)
// Make sure it's 40 characters (= 20 bytes)
addr = addr.padStart(40, '0')
// Only take the last 40 characters (= 20 bytes)
addr = addr.slice(addr.length - 40, addr.length)
// Add 0x again
addr = add0x(addr)
// Convert into a checksummed address
addr = getAddress(addr)
return addr
}
/* Imports: External */
import { BigNumber } from '@ethersproject/bignumber'
import { isHexString, hexZeroPad } from '@ethersproject/bytes'
/**
* Removes "0x" from start of a string if it exists.
*
* @param str String to modify.
* @returns the string without "0x".
*/
export const remove0x = (str: string): string => {
if (str === undefined) {
return str
}
return str.startsWith('0x') ? str.slice(2) : str
}
/**
* Adds "0x" to the start of a string if necessary.
*
* @param str String to modify.
* @returns the string with "0x".
*/
export const add0x = (str: string): string => {
if (str === undefined) {
return str
}
return str.startsWith('0x') ? str : '0x' + str
}
/**
* Casts a hex string to a buffer.
*
* @param inp Input to cast to a buffer.
* @return Input cast as a buffer.
*/
export const fromHexString = (inp: Buffer | string): Buffer => {
if (typeof inp === 'string' && inp.startsWith('0x')) {
return Buffer.from(inp.slice(2), 'hex')
}
return Buffer.from(inp)
}
/**
* Casts an input to a hex string.
*
* @param inp Input to cast to a hex string.
* @return Input cast as a hex string.
*/
export const toHexString = (inp: Buffer | string | number | null): string => {
if (typeof inp === 'number') {
return BigNumber.from(inp).toHexString()
} else {
return '0x' + fromHexString(inp).toString('hex')
}
}
/**
* Casts a number to a hex string without zero padding.
*
* @param n Number to cast to a hex string.
* @return Number cast as a hex string.
*/
export const toRpcHexString = (n: number | BigNumber): string => {
let num
if (typeof n === 'number') {
num = '0x' + n.toString(16)
} else {
num = n.toHexString()
}
if (num === '0x0') {
return num
} else {
// BigNumber pads a single 0 to keep hex length even
return num.replace(/^0x0/, '0x')
}
}
/**
* Zero pads a hex string if str.length !== 2 + length * 2. Pads to length * 2.
*
* @param str Hex string to pad
* @param length Half the length of the desired padded hex string
* @return Hex string with length of 2 + length * 2
*/
export const padHexString = (str: string, length: number): string => {
if (str.length === 2 + length * 2) {
return str
} else {
return '0x' + str.slice(2).padStart(length * 2, '0')
}
}
/**
* Casts an input to hex string without '0x' prefix with conditional padding.
* Hex string will always start with a 0.
*
* @param val Input to cast to a hex string.
* @param len Desired length to pad hex string. Ignored if less than hex string length.
* @return Hex string with '0' prefix
*/
export const encodeHex = (val: any, len: number): string =>
remove0x(BigNumber.from(val).toHexString()).padStart(len, '0')
/**
* Case insensitive hex string equality check
*
* @param stringA Hex string A
* @param stringB Hex string B
* @throws {Error} Inputs must be valid hex strings
* @return True if equal
*/
export const hexStringEquals = (stringA: string, stringB: string): boolean => {
if (!isHexString(stringA)) {
throw new Error(`input is not a hex string: ${stringA}`)
}
if (!isHexString(stringB)) {
throw new Error(`input is not a hex string: ${stringB}`)
}
return stringA.toLowerCase() === stringB.toLowerCase()
}
/**
* Casts a number to a 32-byte, zero padded hex string.
*
* @param value Number to cast to a hex string.
* @return Number cast as a hex string.
*/
export const bytes32ify = (value: number | BigNumber): string => {
return hexZeroPad(BigNumber.from(value).toHexString(), 32)
}
/**
* Common JavaScript/TypeScript utilities
*/
export * from './basic-types'
export * from './bn'
export * from './hex-strings'
export * from './misc'
export * from './test-utils'
/**
* Basic timeout-based async sleep function.
*
* @param ms Number of milliseconds to sleep.
*/
export const sleep = async (ms: number): Promise<void> => {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve(null)
}, ms)
})
}
/**
* Returns a clone of the object.
*
* @param obj Object to clone.
* @returns Clone of the object.
*/
export const clone = (obj: any): any => {
if (typeof obj === 'undefined') {
throw new Error(`Trying to clone undefined object`)
}
return { ...obj }
}
/**
* Loads a variable from the environment and throws if the variable is not defined.
*
* @param name Name of the variable to load.
* @returns Value of the variable as a string.
*/
export const reqenv = (name: string): string => {
const value = process.env[name]
if (value === undefined) {
throw new Error(`missing env var ${name}`)
}
return value
}
/**
* Loads a variable from the environment and returns a fallback if not found.
*
* @param name Name of the variable to load.
* @param [fallback] Optional value to be returned as fallback.
* @returns Value of the variable as a string, fallback or undefined.
*/
export const getenv = (name: string, fallback?: string): string | undefined => {
return process.env[name] || fallback
}
/**
* Returns true if the given string is a valid address.
*
* @param a First address to check.
* @param b Second address to check.
* @returns True if the given addresses match.
*/
export const compareAddrs = (a: string, b: string): boolean => {
return a.toLowerCase() === b.toLowerCase()
}
import { expect } from 'chai'
import { BigNumber } from '@ethersproject/bignumber'
import { sleep } from './misc'
interface deviationRanges {
percentUpperDeviation?: number
percentLowerDeviation?: number
absoluteUpperDeviation?: number
absoluteLowerDeviation?: number
}
export const awaitCondition = async (
cond: () => Promise<boolean>,
rate = 1000,
attempts = 10
) => {
for (let i = 0; i < attempts; i++) {
const ok = await cond()
if (ok) {
return
}
await sleep(rate)
}
throw new Error('Timed out.')
}
/**
* Assert that a number lies within a custom defined range of the target.
*/
export const expectApprox = (
actual: BigNumber | number,
target: BigNumber | number,
{
percentUpperDeviation,
percentLowerDeviation,
absoluteUpperDeviation,
absoluteLowerDeviation,
}: deviationRanges
): void => {
actual = BigNumber.from(actual)
target = BigNumber.from(target)
// Ensure at least one deviation parameter is defined
const nonNullDeviations =
percentUpperDeviation ||
percentLowerDeviation ||
absoluteUpperDeviation ||
absoluteLowerDeviation
if (!nonNullDeviations) {
throw new Error(
'Must define at least one parameter to limit the deviation of the actual value.'
)
}
// Upper bound calculation.
let upper: BigNumber
// Set the two possible upper bounds if and only if they are defined.
const upperPcnt: BigNumber = !percentUpperDeviation
? null
: target.mul(100 + percentUpperDeviation).div(100)
const upperAbs: BigNumber = !absoluteUpperDeviation
? null
: target.add(absoluteUpperDeviation)
if (upperPcnt && upperAbs) {
// If both are set, take the lesser of the two upper bounds.
upper = upperPcnt.lte(upperAbs) ? upperPcnt : upperAbs
} else {
// Else take whichever is not undefined or set to null.
upper = upperPcnt || upperAbs
}
// Lower bound calculation.
let lower: BigNumber
// Set the two possible lower bounds if and only if they are defined.
const lowerPcnt: BigNumber = !percentLowerDeviation
? null
: target.mul(100 - percentLowerDeviation).div(100)
const lowerAbs: BigNumber = !absoluteLowerDeviation
? null
: target.sub(absoluteLowerDeviation)
if (lowerPcnt && lowerAbs) {
// If both are set, take the greater of the two lower bounds.
lower = lowerPcnt.gte(lowerAbs) ? lowerPcnt : lowerAbs
} else {
// Else take whichever is not undefined or set to null.
lower = lowerPcnt || lowerAbs
}
// Apply the assertions if they are non-null.
if (upper) {
expect(
actual.lte(upper),
`Actual value (${actual}) is greater than the calculated upper bound of (${upper})`
).to.be.true
}
if (lower) {
expect(
actual.gte(lower),
`Actual value (${actual}) is less than the calculated lower bound of (${lower})`
).to.be.true
}
}
import fetch from 'node-fetch'
interface NetworkData {
chainId: number
names: string[]
etherscanApiUrl: string
}
const networks: {
[id: number]: NetworkData
} = {
1: {
chainId: 1,
names: ['mainnet', 'main', 'eth', 'ethereum'],
etherscanApiUrl: 'https://api.etherscan.io',
},
3: {
chainId: 3,
names: ['ropsten'],
etherscanApiUrl: 'https://api-ropsten.etherscan.io',
},
4: {
chainId: 4,
names: ['rinkeby'],
etherscanApiUrl: 'https://api-rinkeby.etherscan.io',
},
5: {
chainId: 5,
names: ['goerli'],
etherscanApiUrl: 'https://api-goerli.etherscan.io',
},
10: {
chainId: 10,
names: ['optimism'],
etherscanApiUrl: 'https://api-optimistic.etherscan.io',
},
42: {
chainId: 42,
names: ['kovan'],
etherscanApiUrl: 'https://api-kovan.etherscan.io',
},
69: {
chainId: 69,
names: ['opkovan', 'kovan-optimism', 'optimistic-kovan'],
etherscanApiUrl: 'https://api-kovan-optimistic.etherscan.io',
},
}
export class Etherscan {
net: NetworkData
constructor(
private readonly apiKey: string,
private readonly network: string | number
) {
if (typeof network === 'string') {
this.net = Object.values(networks).find((net) => {
return net.names.includes(network)
})
} else {
this.net = networks[this.network]
}
}
public async getContractSource(address: string): Promise<any> {
const url = new URL(`${this.net.etherscanApiUrl}/api`)
url.searchParams.append('module', 'contract')
url.searchParams.append('action', 'getsourcecode')
url.searchParams.append('address', address)
url.searchParams.append('apikey', this.apiKey)
const response = await fetch(url)
const result = await response.json()
return (result as { result: number[] }).result[0]
}
public async getContractABI(address: string): Promise<any> {
const source = await this.getContractSource(address)
if (source.Proxy === '1') {
const impl = await this.getContractSource(source.Implementation)
return impl.ABI
} else {
return source.ABI
}
}
}
/**
* TypeScript typings for bcoin's BCFG config parser (https://github.com/bcoin-org/bcfg)
* This is NOT a complete set of typings, just what we use at Optimism at the moment.
* We could consider expanding this into a full set of typings in the future.
*/
export interface Bcfg {
/**
* Loads configuration values from the environment. Must be called before environment variables
* can be accessed with other methods like str(...) or uint(...).
*
* @param options Options to use when loading arguments.
* @param options.env Boolean, whether or not to load from process.env.
* @param options.argv Boolean, whether or not to load from process.argv.
*/
load: (options: { env?: boolean; argv?: boolean }) => void
/**
* Returns the variable with the given name and casts it as a string. Queries from the
* environment or from argv depending on which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a string.
*/
str: (name: string, defaultValue?: string) => string
/**
* Returns the variable with the given name and casts it as a uint. Will throw an error if the
* variable cannot be cast into a uint. Queries from the environment or from argv depending on
* which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a uint.
*/
uint: (name: string, defaultValue?: number) => number
/**
* Returns the variable with the given name and casts it as a bool. Will throw an error if the
* variable cannot be cast into a bool. Queries from the environment or from argv depending on
* which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a bool.
*/
bool: (name: string, defaultValue?: boolean) => boolean
/**
* Returns the variable with the given name and casts it as a ufloat. Will throw an error if the
* variable cannot be cast into a ufloat. Queries from the environment or from argv depending on
* which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a ufloat.
*/
ufloat: (name: string, defaultValue?: number) => number
/**
* Checks if the given variable exists.
*
* @param name Name of the variable to query.
* @returns True if the variable exists, false otherwise.
*/
has: (name: string) => boolean
}
/**
* Utilities related to BCFG
*/
export * from './bcfg-types'
/**
* Utilities that extend or enhance the ethers.js library
*/
export * from './network'
import { Provider } from '@ethersproject/abstract-provider'
export const getChainId = async (provider: Provider): Promise<number> => {
const network = await provider.getNetwork()
return network.chainId
}
/**
* Utilities related to specific external projects
*/
export * from './bcfg'
export * from './ethers'
import { ethers } from 'ethers'
// Slightly modified from:
// https://github.com/safe-global/safe-react-apps/blob/development/apps/tx-builder/src/lib/checksum.ts
const stringifyReplacer = (_: string, value: any) =>
value === undefined ? null : value
const serializeJSONObject = (json: any): string => {
if (Array.isArray(json)) {
return `[${json.map((el) => serializeJSONObject(el)).join(',')}]`
}
if (typeof json === 'object' && json !== null) {
let acc = ''
const keys = Object.keys(json).sort()
acc += `{${JSON.stringify(keys, stringifyReplacer)}`
for (const key of keys) {
acc += `${serializeJSONObject(json[key])},`
}
return `${acc}}`
}
return `${JSON.stringify(json, stringifyReplacer)}`
}
const calculateChecksum = (batchFile: any): string | undefined => {
const serialized = serializeJSONObject({
...batchFile,
meta: { ...batchFile.meta, name: null },
})
const sha = ethers.utils.solidityKeccak256(['string'], [serialized])
return sha || undefined
}
export const addChecksum = (batchFile: any): any => {
return {
...batchFile,
meta: {
...batchFile.meta,
checksum: calculateChecksum(batchFile),
},
}
}
import assert from 'assert'
import { ethers, utils } from 'ethers'
const { getAddress } = utils
type ProxyConfig = {
targetImplAddress: string
targetProxyOwnerAddress: string
postUpgradeCallCalldata?: string
}
// Sets up the newly deployed proxy contract such that:
// 1. The proxy's implementation is set to the target implementation
// 2. The proxy's admin is set to the target proxy owner
//
// If the values are set correctly already, it makes no transactions.
const setupProxyContract = async (
proxyContract: ethers.Contract,
signer: ethers.Signer,
{
targetImplAddress,
targetProxyOwnerAddress,
postUpgradeCallCalldata,
}: ProxyConfig
) => {
const currentAdmin = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.admin()
const signerAddress = await signer.getAddress()
// Gets the current implementation address the proxy is pointing to.
// callStatic is used since the `Proxy.implementation()` is not a view function and ethers will
// try to make a transaction if we don't use callStatic. Using the zero address as `from` lets us
// call functions on the proxy and not trigger the delegatecall. See Proxy.sol proxyCallIfNotAdmin
// modifier for more details.
const currentImplementation = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.implementation()
console.log(`implementation currently set to ${currentImplementation}`)
if (getAddress(currentImplementation) !== getAddress(targetImplAddress)) {
// If the proxy isn't pointing to the correct implementation, we need to set it to the correct
// one, then call initialize() in the proxy's context.
console.log('implementation not set to correct contract')
console.log(`Setting implementation to ${targetImplAddress}`)
// The signer needs to be the current admin, otherwise we don't have permission
// to update the implementation or admin
assert(
signerAddress === currentAdmin,
'the passed signer is not the admin, cannot update implementation'
)
let tx: ethers.providers.TransactionResponse
if (!postUpgradeCallCalldata) {
console.log(
'postUpgradeCallCalldata is not provided. Using Proxy.upgrade()'
)
// Point the proxy to the target implementation
tx = await proxyContract.connect(signer).upgradeTo(targetImplAddress)
} else {
console.log(
'postUpgradeCallCalldata is provided. Using Proxy.upgradeAndCall()'
)
// Point the proxy to the target implementation,
// and call function in the proxy's context
tx = await proxyContract
.connect(signer)
.upgradeToAndCall(targetImplAddress, postUpgradeCallCalldata)
}
const receipt = await tx.wait()
console.log(`implementation set in ${receipt.transactionHash}`)
} else {
console.log(`implementation already set correctly to ${targetImplAddress}`)
}
console.log(`admin set to ${currentAdmin}`)
if (getAddress(currentAdmin) !== getAddress(targetProxyOwnerAddress)) {
// If the proxy admin isn't the l2ProxyOwnerAddress, we need to update it
// We're assuming that the proxy admin is the ddd right now.
console.log('detected admin is not set correctly')
console.log(`Setting admin to ${targetProxyOwnerAddress}`)
// The signer needs to be the current admin, otherwise we don't have permission
// to update the implementation or admin
assert(
signerAddress === currentAdmin,
'proxyOwnerSigner is not the admin, cannot update admin'
)
// change admin to the l2ProxyOwnerAddress
const tx = await proxyContract
.connect(signer)
.changeAdmin(targetProxyOwnerAddress)
const receipt = await tx.wait()
console.log(`admin set in ${receipt.transactionHash}`)
} else {
console.log(`admin already set correctly to ${targetProxyOwnerAddress}`)
}
const updatedImplementation = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.implementation()
const updatedAdmin = await proxyContract
.connect(ethers.constants.AddressZero)
.callStatic.admin()
assert(
getAddress(updatedAdmin) === getAddress(targetProxyOwnerAddress),
'Something went wrong - admin not set correctly after transaction'
)
assert(
getAddress(updatedImplementation) === getAddress(targetImplAddress),
'Something went wrong - implementation not set correctly after transaction'
)
console.log(
`Proxy at ${proxyContract.address} is set up with implementation: ${updatedImplementation} and admin: ${updatedAdmin}`
)
}
export { setupProxyContract }
export * from './common'
export * from './external'
export * from './optimism'
export * from './gnosis-safe-checksum'
export * from './etherscan'
export * from './helpers/setupProxyContract'
import { isAddress } from '@ethersproject/address'
import { BigNumber } from '@ethersproject/bignumber'
import { bnToAddress } from '../common'
// Constant representing the alias to apply to the msg.sender when a contract sends an L1 => L2
// message. We need this aliasing scheme because a contract can be deployed to the same address
// on both L1 and L2 but with different bytecode (address is not dependent on bytecode when using
// the standard CREATE opcode). We want to treat L1 contracts as having a different address while
// still making it possible for L2 contracts to easily reverse the aliasing scheme and figure out
// the real address of the contract that sent the L1 => L2 message.
export const L1_TO_L2_ALIAS_OFFSET =
'0x1111000000000000000000000000000000001111'
/**
* Applies the L1 => L2 aliasing scheme to an address.
*
* @param address Address to apply the scheme to.
* @returns Address with the scheme applied.
*/
export const applyL1ToL2Alias = (address: string): string => {
if (!isAddress(address)) {
throw new Error(`not a valid address: ${address}`)
}
return bnToAddress(BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET))
}
/**
* Reverses the L1 => L2 aliasing scheme from an address.
*
* @param address Address to reverse the scheme from.
* @returns Alias with the scheme reversed.
*/
export const undoL1ToL2Alias = (address: string): string => {
if (!isAddress(address)) {
throw new Error(`not a valid address: ${address}`)
}
return bnToAddress(BigNumber.from(address).sub(L1_TO_L2_ALIAS_OFFSET))
}
import { ethers } from 'ethers'
/**
* Predeploys are Solidity contracts that are injected into the initial L2 state and provide
* various useful functions.
* Notes:
* 0x42...04 was the address of the OVM_ProxySequencerEntrypoint. This contract is no longer in
* use and has therefore been removed. We may place a new predeployed contract at this address
* in the future. See https://github.com/ethereum-optimism/optimism/pull/549 for more info.
*/
export const predeploys = {
L2ToL1MessagePasser: '0x4200000000000000000000000000000000000016',
DeployerWhitelist: '0x4200000000000000000000000000000000000002',
L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007',
GasPriceOracle: '0x420000000000000000000000000000000000000F',
L2StandardBridge: '0x4200000000000000000000000000000000000010',
SequencerFeeVault: '0x4200000000000000000000000000000000000011',
OptimismMintableERC20Factory: '0x4200000000000000000000000000000000000012',
L1BlockNumber: '0x4200000000000000000000000000000000000013',
L1Block: '0x4200000000000000000000000000000000000015',
LegacyERC20ETH: '0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000',
WETH9: '0x4200000000000000000000000000000000000006',
GovernanceToken: '0x4200000000000000000000000000000000000042',
LegacyMessagePasser: '0x4200000000000000000000000000000000000000',
L2ERC721Bridge: '0x4200000000000000000000000000000000000014',
OptimismMintableERC721Factory: '0x4200000000000000000000000000000000000017',
ProxyAdmin: '0x4200000000000000000000000000000000000018',
BaseFeeVault: '0x4200000000000000000000000000000000000019',
L1FeeVault: '0x420000000000000000000000000000000000001a',
}
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
export const defaultResourceConfig = {
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
systemTxMaxGas: 1_000_000,
maximumBaseFee: uint128Max,
}
import { getAddress } from '@ethersproject/address'
import { ContractReceipt, Event } from '@ethersproject/contracts'
import { BigNumber, BigNumberish } from '@ethersproject/bignumber'
import { keccak256 } from '@ethersproject/keccak256'
import { Zero } from '@ethersproject/constants'
import * as RLP from '@ethersproject/rlp'
import {
arrayify,
BytesLike,
hexDataSlice,
stripZeros,
hexConcat,
zeroPad,
} from '@ethersproject/bytes'
const formatBoolean = (value: boolean): Uint8Array => {
return value ? new Uint8Array([1]) : new Uint8Array([])
}
const formatNumber = (value: BigNumberish, name: string): Uint8Array => {
const result = stripZeros(BigNumber.from(value).toHexString())
if (result.length > 32) {
throw new Error(`invalid length for ${name}`)
}
return result
}
const handleBoolean = (value: string): boolean => {
if (value === '0x') {
return false
}
if (value === '0x01') {
return true
}
throw new Error(`invalid boolean RLP hex value ${value}`)
}
const handleNumber = (value: string): BigNumber => {
if (value === '0x') {
return Zero
}
return BigNumber.from(value)
}
const handleAddress = (value: string): string => {
if (value === '0x') {
// @ts-ignore
return null
}
return getAddress(value)
}
export enum SourceHashDomain {
UserDeposit = 0,
L1InfoDeposit = 1,
}
interface DepositTxOpts {
sourceHash?: string
from: string
to: string | null
mint: BigNumberish
value: BigNumberish
gas: BigNumberish
isSystemTransaction: boolean
data: string
domain?: SourceHashDomain
l1BlockHash?: string
logIndex?: BigNumberish
sequenceNumber?: BigNumberish
}
interface DepositTxExtraOpts {
domain?: SourceHashDomain
l1BlockHash?: string
logIndex?: BigNumberish
sequenceNumber?: BigNumberish
}
export class DepositTx {
public type = 0x7e
public version = 0x00
private _sourceHash?: string
public from: string
public to: string | null
public mint: BigNumberish
public value: BigNumberish
public gas: BigNumberish
public isSystemTransaction: boolean
public data: BigNumberish
public domain?: SourceHashDomain
public l1BlockHash?: string
public logIndex?: BigNumberish
public sequenceNumber?: BigNumberish
constructor(opts: Partial<DepositTxOpts> = {}) {
this._sourceHash = opts.sourceHash
this.from = opts.from!
this.to = opts.to!
this.mint = opts.mint!
this.value = opts.value!
this.gas = opts.gas!
this.isSystemTransaction = opts.isSystemTransaction || false
this.data = opts.data!
this.domain = opts.domain
this.l1BlockHash = opts.l1BlockHash
this.logIndex = opts.logIndex
this.sequenceNumber = opts.sequenceNumber
}
hash() {
const encoded = this.encode()
return keccak256(encoded)
}
sourceHash() {
if (!this._sourceHash) {
let marker: string
switch (this.domain) {
case SourceHashDomain.UserDeposit:
marker = BigNumber.from(this.logIndex).toHexString()
break
case SourceHashDomain.L1InfoDeposit:
marker = BigNumber.from(this.sequenceNumber).toHexString()
break
default:
throw new Error(`Unknown domain: ${this.domain}`)
}
if (!this.l1BlockHash) {
throw new Error('Need l1BlockHash to compute sourceHash')
}
const l1BlockHash = this.l1BlockHash
const input = hexConcat([l1BlockHash, zeroPad(marker, 32)])
const depositIDHash = keccak256(input)
const domain = BigNumber.from(this.domain).toHexString()
const domainInput = hexConcat([zeroPad(domain, 32), depositIDHash])
this._sourceHash = keccak256(domainInput)
}
return this._sourceHash
}
encode() {
const fields: any = [
this.sourceHash() || '0x',
getAddress(this.from) || '0x',
this.to != null ? getAddress(this.to) : '0x',
formatNumber(this.mint || 0, 'mint'),
formatNumber(this.value || 0, 'value'),
formatNumber(this.gas || 0, 'gas'),
formatBoolean(this.isSystemTransaction),
this.data || '0x',
]
return hexConcat([
BigNumber.from(this.type).toHexString(),
RLP.encode(fields),
])
}
decode(raw: BytesLike, extra: DepositTxExtraOpts = {}) {
const payload = arrayify(raw)
if (payload[0] !== this.type) {
throw new Error(`Invalid type ${payload[0]}`)
}
this.version = payload[1]
const transaction = RLP.decode(payload.slice(1))
this._sourceHash = transaction[0]
this.from = handleAddress(transaction[1])
this.to = handleAddress(transaction[2])
this.mint = handleNumber(transaction[3])
this.value = handleNumber(transaction[4])
this.gas = handleNumber(transaction[5])
this.isSystemTransaction = handleBoolean(transaction[6])
this.data = transaction[7]
if ('l1BlockHash' in extra) {
this.l1BlockHash = extra.l1BlockHash
}
if ('domain' in extra) {
this.domain = extra.domain
}
if ('logIndex' in extra) {
this.logIndex = extra.logIndex
}
if ('sequenceNumber' in extra) {
this.sequenceNumber = extra.sequenceNumber
}
return this
}
static decode(raw: BytesLike, extra?: DepositTxExtraOpts): DepositTx {
return new this().decode(raw, extra)
}
fromL1Receipt(receipt: ContractReceipt, index: number): DepositTx {
if (!receipt.events) {
throw new Error('cannot parse receipt')
}
const event = receipt.events[index]
if (!event) {
throw new Error(`event index ${index} does not exist`)
}
return this.fromL1Event(event)
}
static fromL1Receipt(receipt: ContractReceipt, index: number): DepositTx {
return new this({}).fromL1Receipt(receipt, index)
}
fromL1Event(event: Event): DepositTx {
if (event.event !== 'TransactionDeposited') {
throw new Error(`incorrect event type: ${event.event}`)
}
if (typeof event.args === 'undefined') {
throw new Error('no event args')
}
if (typeof event.args.from === 'undefined') {
throw new Error('"from" undefined')
}
this.from = event.args.from
if (typeof event.args.to === 'undefined') {
throw new Error('"to" undefined')
}
if (typeof event.args.version === 'undefined') {
throw new Error(`"verison" undefined`)
}
if (!event.args.version.eq(0)) {
throw new Error(`Unsupported version ${event.args.version.toString()}`)
}
if (typeof event.args.opaqueData === 'undefined') {
throw new Error(`"opaqueData" undefined`)
}
const opaqueData = event.args.opaqueData
if (opaqueData.length < 32 + 32 + 8 + 1) {
throw new Error(`invalid opaqueData size: ${opaqueData.length}`)
}
let offset = 0
this.mint = BigNumber.from(hexDataSlice(opaqueData, offset, offset + 32))
offset += 32
this.value = BigNumber.from(hexDataSlice(opaqueData, offset, offset + 32))
offset += 32
this.gas = BigNumber.from(hexDataSlice(opaqueData, offset, offset + 8))
offset += 8
const isCreation = BigNumber.from(opaqueData[offset]).eq(1)
offset += 1
this.to = isCreation === true ? null : event.args.to
const length = opaqueData.length - offset
this.isSystemTransaction = false
this.data = hexDataSlice(opaqueData, offset, offset + length)
this.domain = SourceHashDomain.UserDeposit
this.l1BlockHash = event.blockHash
this.logIndex = event.logIndex
return this
}
static fromL1Event(event: Event): DepositTx {
return new this({}).fromL1Event(event)
}
}
import { BigNumberish, BigNumber } from '@ethersproject/bignumber'
import { Interface } from '@ethersproject/abi'
const iface = new Interface([
'function relayMessage(address,address,bytes,uint256)',
'function relayMessage(uint256,address,address,uint256,uint256,bytes)',
])
const nonceMask = BigNumber.from(
'0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
)
/**
* Encodes the version into the nonce.
*
* @param nonce
* @param version
*/
export const encodeVersionedNonce = (
nonce: BigNumber,
version: BigNumber
): BigNumber => {
return version.or(nonce.shl(240))
}
/**
* Decodes the version from the nonce and returns the unversioned nonce as well
* as the version. The version is encoded in the first byte of
* the nonce. Note that this nonce is the nonce held in the
* CrossDomainMessenger.
*
* @param nonce
*/
export const decodeVersionedNonce = (
nonce: BigNumber
): {
version: BigNumber
nonce: BigNumber
} => {
return {
version: nonce.shr(240),
nonce: nonce.and(nonceMask),
}
}
/**
* Encodes a V1 cross domain message. This message format was used before
* bedrock and does not support value transfer because ETH was represented as an
* ERC20 natively.
*
* @param target The target of the cross domain message
* @param sender The sender of the cross domain message
* @param data The data passed along with the cross domain message
* @param nonce The cross domain message nonce
*/
export const encodeCrossDomainMessageV0 = (
target: string,
sender: string,
data: string,
nonce: BigNumber
) => {
return iface.encodeFunctionData(
'relayMessage(address,address,bytes,uint256)',
[target, sender, data, nonce]
)
}
/**
* Encodes a V1 cross domain message. This message format shipped with bedrock
* and supports value transfer with native ETH.
*
* @param nonce The cross domain message nonce
* @param sender The sender of the cross domain message
* @param target The target of the cross domain message
* @param value The value being sent with the cross domain message
* @param gasLimit The gas limit of the cross domain execution
* @param data The data passed along with the cross domain message
*/
export const encodeCrossDomainMessageV1 = (
nonce: BigNumber,
sender: string,
target: string,
value: BigNumberish,
gasLimit: BigNumberish,
data: string
) => {
return iface.encodeFunctionData(
'relayMessage(uint256,address,address,uint256,uint256,bytes)',
[nonce, sender, target, value, gasLimit, data]
)
}
/**
* Encodes a cross domain message. The version byte in the nonce determines
* the serialization format that is used.
*
* @param nonce The cross domain message nonce
* @param sender The sender of the cross domain message
* @param target The target of the cross domain message
* @param value The value being sent with the cross domain message
* @param gasLimit The gas limit of the cross domain execution
* @param data The data passed along with the cross domain message
*/
export const encodeCrossDomainMessage = (
nonce: BigNumber,
sender: string,
target: string,
value: BigNumber,
gasLimit: BigNumber,
data: string
) => {
const { version } = decodeVersionedNonce(nonce)
if (version.eq(0)) {
return encodeCrossDomainMessageV0(target, sender, data, nonce)
} else if (version.eq(1)) {
return encodeCrossDomainMessageV1(
nonce,
sender,
target,
value,
gasLimit,
data
)
}
throw new Error(`unknown version ${version.toString()}`)
}
/**
* Fee related serialization and deserialization
*/
import { BigNumber } from '@ethersproject/bignumber'
import { remove0x } from '../common'
export const txDataZeroGas = 4
export const txDataNonZeroGasEIP2028 = 16
const big10 = BigNumber.from(10)
export const scaleDecimals = (
value: number | BigNumber,
decimals: number | BigNumber
): BigNumber => {
value = BigNumber.from(value)
decimals = BigNumber.from(decimals)
// 10**decimals
const divisor = big10.pow(decimals)
return value.div(divisor)
}
// data is the RLP encoded unsigned transaction
export const calculateL1GasUsed = (
data: string | Buffer,
overhead: number | BigNumber
): BigNumber => {
const [zeroes, ones] = zeroesAndOnes(data)
const zeroesCost = zeroes * txDataZeroGas
// Add a buffer to account for the signature
const onesCost = (ones + 68) * txDataNonZeroGasEIP2028
return BigNumber.from(onesCost).add(zeroesCost).add(overhead)
}
export const calculateL1Fee = (
data: string | Buffer,
overhead: number | BigNumber,
l1GasPrice: number | BigNumber,
scalar: number | BigNumber,
decimals: number | BigNumber
): BigNumber => {
const l1GasUsed = calculateL1GasUsed(data, overhead)
const l1Fee = l1GasUsed.mul(l1GasPrice)
const scaled = l1Fee.mul(scalar)
const result = scaleDecimals(scaled, decimals)
return result
}
// Count the number of zero bytes and non zero bytes in a buffer
export const zeroesAndOnes = (data: Buffer | string): Array<number> => {
if (typeof data === 'string') {
data = Buffer.from(remove0x(data), 'hex')
}
let zeros = 0
let ones = 0
for (const byte of data) {
if (byte === 0) {
zeros++
} else {
ones++
}
}
return [zeros, ones]
}
/**
* Computes the L1 calldata cost of bytes based
* on the London hardfork.
*
* @param data {Buffer|string} Bytes
* @returns {BigNumber} Gas consumed by the bytes
*/
export const calldataCost = (data: Buffer | string): BigNumber => {
const [zeros, ones] = zeroesAndOnes(data)
const zeroCost = BigNumber.from(zeros).mul(txDataZeroGas)
const nonZeroCost = BigNumber.from(ones).mul(txDataNonZeroGasEIP2028)
return zeroCost.add(nonZeroCost)
}
import { BigNumberish, BigNumber } from '@ethersproject/bignumber'
import { keccak256 } from '@ethersproject/keccak256'
import { defaultAbiCoder } from '@ethersproject/abi'
import {
decodeVersionedNonce,
encodeCrossDomainMessageV0,
encodeCrossDomainMessageV1,
} from './encoding'
/**
* Bedrock output oracle data.
*/
export interface BedrockOutputData {
outputRoot: string
l1Timestamp: number
l2BlockNumber: number
l2OutputIndex: number
}
/**
* Bedrock state commitment
*/
export interface OutputRootProof {
version: string
stateRoot: string
messagePasserStorageRoot: string
latestBlockhash: string
}
/**
* Bedrock proof data required to finalize an L2 to L1 message.
*/
export interface BedrockCrossChainMessageProof {
l2OutputIndex: number
outputRootProof: OutputRootProof
withdrawalProof: string[]
}
/**
* Parameters that govern the L2OutputOracle.
*/
export type L2OutputOracleParameters = {
submissionInterval: number
startingBlockNumber: number
l2BlockTime: number
}
/**
* Hahses a cross domain message.
*
* @param nonce The cross domain message nonce
* @param sender The sender of the cross domain message
* @param target The target of the cross domain message
* @param value The value being sent with the cross domain message
* @param gasLimit The gas limit of the cross domain execution
* @param data The data passed along with the cross domain message
*/
export const hashCrossDomainMessage = (
nonce: BigNumber,
sender: string,
target: string,
value: BigNumber,
gasLimit: BigNumber,
message: string
) => {
const { version } = decodeVersionedNonce(nonce)
if (version.eq(0)) {
return hashCrossDomainMessagev0(target, sender, message, nonce)
} else if (version.eq(1)) {
return hashCrossDomainMessagev1(
nonce,
sender,
target,
value,
gasLimit,
message
)
}
throw new Error(`unknown version ${version.toString()}`)
}
/**
* Hahses a V0 cross domain message
*
* @param target The target of the cross domain message
* @param sender The sender of the cross domain message
* @param message The message passed along with the cross domain message
* @param nonce The cross domain message nonce
*/
export const hashCrossDomainMessagev0 = (
target: string,
sender: string,
message: string,
nonce: BigNumber
) => {
return keccak256(encodeCrossDomainMessageV0(target, sender, message, nonce))
}
/**
* Hahses a V1 cross domain message
*
* @param nonce The cross domain message nonce
* @param sender The sender of the cross domain message
* @param target The target of the cross domain message
* @param value The value being sent with the cross domain message
* @param gasLimit The gas limit of the cross domain execution
* @param message The message passed along with the cross domain message
*/
export const hashCrossDomainMessagev1 = (
nonce: BigNumber,
sender: string,
target: string,
value: BigNumberish,
gasLimit: BigNumberish,
message: string
) => {
return keccak256(
encodeCrossDomainMessageV1(nonce, sender, target, value, gasLimit, message)
)
}
/**
* Hashes a withdrawal
*
* @param nonce The cross domain message nonce
* @param sender The sender of the cross domain message
* @param target The target of the cross domain message
* @param value The value being sent with the cross domain message
* @param gasLimit The gas limit of the cross domain execution
* @param message The message passed along with the cross domain message
*/
export const hashWithdrawal = (
nonce: BigNumber,
sender: string,
target: string,
value: BigNumber,
gasLimit: BigNumber,
message: string
): string => {
const types = ['uint256', 'address', 'address', 'uint256', 'uint256', 'bytes']
const encoded = defaultAbiCoder.encode(types, [
nonce,
sender,
target,
value,
gasLimit,
message,
])
return keccak256(encoded)
}
/**
* Hahses an output root proof
*
* @param proof OutputRootProof
*/
export const hashOutputRootProof = (proof: OutputRootProof): string => {
return keccak256(
defaultAbiCoder.encode(
['bytes32', 'bytes32', 'bytes32', 'bytes32'],
[
proof.version,
proof.stateRoot,
proof.messagePasserStorageRoot,
proof.latestBlockhash,
]
)
)
}
/**
* Utils specifically related to Optimism.
*/
export * from './alias'
export * from './fees'
export * from './op-node'
export * from './deposit-transaction'
export * from './encoding'
export * from './hashing'
export * from './op-provider'
export * from './constants'
export interface OpNodeConfig {
genesis: {
l1: {
hash: string
number: number
}
l2: {
hash: string
number: number
}
l2_time: number
}
block_time: number
max_sequencer_drift: number
seq_window_size: number
channel_timeout: number
l1_chain_id: number
l2_chain_id: number
p2p_sequencer_address: string
batch_inbox_address: string
batch_sender_address: string
deposit_contract_address: string
}
import EventEmitter from 'events'
import { BigNumber } from '@ethersproject/bignumber'
import { deepCopy } from '@ethersproject/properties'
import { ConnectionInfo, fetchJson } from '@ethersproject/web'
const getResult = (payload: {
error?: { code?: number; data?: any; message?: string }
result?: any
}): any => {
if (payload.error) {
const error: any = new Error(payload.error.message)
error.code = payload.error.code
error.data = payload.error.data
throw error
}
return payload.result
}
export interface BlockDescriptor {
hash: string
number: BigNumber
parentHash: string
timestamp: BigNumber
}
export interface L2BlockDescriptor extends BlockDescriptor {
l1Origin: {
hash: string
number: BigNumber
}
sequencerNumber: BigNumber
}
export interface SyncStatusResponse {
currentL1: BlockDescriptor
headL1: BlockDescriptor
unsafeL2: L2BlockDescriptor
safeL2: L2BlockDescriptor
finalizedL2: L2BlockDescriptor
}
export class OpNodeProvider extends EventEmitter {
readonly connection: ConnectionInfo
private _nextId: number = 0
constructor(url?: ConnectionInfo | string) {
super()
if (typeof url === 'string') {
this.connection = { url }
} else {
this.connection = url
}
}
async syncStatus(): Promise<SyncStatusResponse> {
const result = await this.send('optimism_syncStatus', [])
return {
currentL1: {
hash: result.current_l1.hash,
number: BigNumber.from(result.current_l1.number),
parentHash: result.current_l1.parentHash,
timestamp: BigNumber.from(result.current_l1.timestamp),
},
headL1: {
hash: result.head_l1.hash,
number: BigNumber.from(result.head_l1.number),
parentHash: result.head_l1.parentHash,
timestamp: BigNumber.from(result.head_l1.timestamp),
},
unsafeL2: {
hash: result.unsafe_l2.hash,
number: BigNumber.from(result.unsafe_l2.number),
parentHash: result.unsafe_l2.parentHash,
timestamp: BigNumber.from(result.unsafe_l2.timestamp),
l1Origin: {
hash: result.unsafe_l2.l1origin.hash,
number: BigNumber.from(result.unsafe_l2.l1origin.number),
},
sequencerNumber: BigNumber.from(result.unsafe_l2.sequenceNumber),
},
safeL2: {
hash: result.safe_l2.hash,
number: BigNumber.from(result.safe_l2.number),
parentHash: result.safe_l2.parentHash,
timestamp: BigNumber.from(result.safe_l2.timestamp),
l1Origin: {
hash: result.safe_l2.l1origin.hash,
number: BigNumber.from(result.safe_l2.l1origin.number),
},
sequencerNumber: BigNumber.from(result.safe_l2.sequenceNumber),
},
finalizedL2: {
hash: result.finalized_l2.hash,
number: BigNumber.from(result.finalized_l2.number),
parentHash: result.finalized_l2.parentHash,
timestamp: BigNumber.from(result.finalized_l2.timestamp),
l1Origin: {
hash: result.finalized_l2.l1origin.hash,
number: BigNumber.from(result.finalized_l2.l1origin.number),
},
sequencerNumber: BigNumber.from(result.finalized_l2.sequenceNumber),
},
}
}
// TODO(tynes): turn the response into a stronger type
async rollupConfig() {
const result = await this.send('optimism_rollupConfig', [])
return result
}
send(method: string, params: Array<any>): Promise<any> {
const request = {
method,
params,
id: this._nextId++,
jsonrpc: '2.0',
}
this.emit('debug', {
action: 'request',
request: deepCopy(request),
provider: this,
})
const result = fetchJson(
this.connection,
JSON.stringify(request),
getResult
).then(
(res) => {
this.emit('debug', {
action: 'response',
request,
response: res,
provider: this,
})
return res
},
(error) => {
this.emit('debug', {
action: 'response',
error,
request,
provider: this,
})
throw error
}
)
return result
}
}
This diff is collapsed.
/* Imports: Internal */
import { expect } from '../setup'
import { sleep, clone, reqenv, getenv } from '../../src'
describe('sleep', async () => {
it('should return wait input amount of ms', async () => {
const startTime = Date.now()
await sleep(1000)
const endTime = Date.now()
expect(startTime + 1000 <= endTime).to.deep.equal(true)
})
})
describe('clone', async () => {
it('should return a cloned object', async () => {
const exampleObject = { example: 'Example' }
const clonedObject = clone(exampleObject)
expect(clonedObject).to.not.equal(exampleObject)
expect(JSON.stringify(clonedObject)).to.equal(JSON.stringify(exampleObject))
})
})
describe('reqenv', async () => {
let cachedEnvironment: NodeJS.ProcessEnv
const temporaryEnvironmentKey = 'testVariable'
const temporaryEnvironment = {
[temporaryEnvironmentKey]: 'This is an environment variable',
}
before(() => {
cachedEnvironment = process.env
process.env = temporaryEnvironment
})
it('should return an existent environment variable', async () => {
const requiredEnvironmentValue = reqenv(temporaryEnvironmentKey)
expect(requiredEnvironmentValue).to.equal(
temporaryEnvironment[temporaryEnvironmentKey]
)
})
it('should throw an error trying to return a variable that does not exist', async () => {
const undeclaredVariableName = 'undeclaredVariable'
const failedReqenv = () => reqenv(undeclaredVariableName)
expect(failedReqenv).to.throw()
})
after(() => {
process.env = cachedEnvironment
})
})
describe('getenv', async () => {
let cachedEnvironment: NodeJS.ProcessEnv
const temporaryEnvironmentKey = 'testVariable'
const temporaryEnvironment = {
[temporaryEnvironmentKey]: 'This is an environment variable',
}
const fallback = 'fallback'
before(() => {
cachedEnvironment = process.env
process.env = temporaryEnvironment
})
it('should return an existent environment variable', async () => {
const environmentVariable = getenv(temporaryEnvironmentKey)
expect(environmentVariable).to.equal(
temporaryEnvironment[temporaryEnvironmentKey]
)
})
it('should return an existent environment variable even if fallback is passed', async () => {
const environmentVariable = getenv(temporaryEnvironmentKey, fallback)
expect(environmentVariable).to.equal(
temporaryEnvironment[temporaryEnvironmentKey]
)
})
it('should return fallback if variable is not defined', async () => {
const undeclaredVariableName = 'undeclaredVariable'
expect(getenv(undeclaredVariableName, fallback)).to.equal(fallback)
})
it('should return undefined if no fallback is passed and variable is not defined', async () => {
expect(getenv('undeclaredVariable')).to.be.undefined
})
after(() => {
process.env = cachedEnvironment
})
})
This diff is collapsed.
import { expect } from '../setup'
import { applyL1ToL2Alias, undoL1ToL2Alias } from '../../src'
describe('address aliasing utils', () => {
describe('applyL1ToL2Alias', () => {
it('should be able to apply the alias to a valid address', () => {
expect(
applyL1ToL2Alias('0x0000000000000000000000000000000000000000')
).to.equal('0x1111000000000000000000000000000000001111')
})
it('should be able to apply the alias even if the operation overflows', () => {
expect(
applyL1ToL2Alias('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
).to.equal('0x1111000000000000000000000000000000001110')
})
it('should throw if the input is not a valid address', () => {
expect(() => {
applyL1ToL2Alias('0x1234')
}).to.throw('not a valid address: 0x1234')
})
})
describe('undoL1ToL2Alias', () => {
it('should be able to undo the alias from a valid address', () => {
expect(
undoL1ToL2Alias('0x1111000000000000000000000000000000001111')
).to.equal('0x0000000000000000000000000000000000000000')
})
it('should be able to undo the alias even if the operation underflows', () => {
expect(
undoL1ToL2Alias('0x1111000000000000000000000000000000001110')
).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF')
})
it('should throw if the input is not a valid address', () => {
expect(() => {
undoL1ToL2Alias('0x1234')
}).to.throw('not a valid address: 0x1234')
})
})
})
This diff is collapsed.
/* External Imports */
import chai = require('chai')
import Mocha from 'mocha'
const should = chai.should()
const expect = chai.expect
export { should, expect, Mocha }
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"compilerOptions": {
"typeRoots": ["node_modules/@types"]
},
"include": [
"src/**/*"
]
}
This diff is collapsed.
This diff is collapsed.
module.exports = {
...require('../../.prettierrc.js'),
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment