Commit ad330fcc authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into aj/update-op-geth

parents 9e2c80dd 33e63455
---
'@eth-optimism/atst': patch
---
Add new atst package
---
"@eth-optimism/batch-submitter-service": patch
---
build(deps): bump golang.org/x/crypto from 0.0.0-20220307211146-efcb8507fb70 to 0.1.0 in /batch-submitter
...@@ -64,8 +64,8 @@ require ( ...@@ -64,8 +64,8 @@ require (
github.com/tklauser/numcpus v0.4.0 // indirect github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/sys v0.1.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
......
...@@ -819,8 +819,9 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh ...@@ -819,8 +819,9 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
...@@ -908,7 +909,7 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx ...@@ -908,7 +909,7 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
...@@ -1002,8 +1003,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -1002,8 +1003,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
...@@ -1015,8 +1016,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ...@@ -1015,8 +1016,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
{
"extends": ["../../.eslintrc.js"]
}
node_modules/
dist/
coverage/
(The MIT License)
Copyright 2020-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.
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/35039927/218812217-92f0f784-cb85-43b9-9ca6-e2b9effd9eb2.png">
<img alt="wagmi logo" src="https://user-images.githubusercontent.com/35039927/218812217-92f0f784-cb85-43b9-9ca6-e2b9effd9eb2.png" width="auto" height="300">
</picture>
</p>
<p align="center">
I need a tagline here
<p>
<a href="https://www.npmjs.com/package/@eth-optimism/atst" target="\_parent">
<img alt="" src="https://img.shields.io/npm/dm/@eth-optimism/atst.svg" />
</a>
# atst
atst is a typescript sdk and cli around the attestation station (TODO link to attstation station)
Maybe put a gif of the cli here?
## Visit [Docs](https://evmts-docs-fx6udvub5-evmts.vercel.app/en/getting-started) for more documentation on teh attestation station!
TODO link to oris docs here
Maybe link to cli docs and sdk docs seperately?
## Getting started
Install
```bash
npm install @eth-optimism/atst
```
## ATST SDK
TODO
## ATST CLI
TODO
## Contributing
Please see our [contributing.md](/docs/contributing.md).
## 🚧 WARNING: UNDER CONSTRUCTION 🚧
**This sdk is not yet released**
## Check out these other awesome attestation station tools
TODO
\ No newline at end of file
# @eth-optimism/atst (WIP)
TODO
WIP sdk and cli tool for the attestation station. Currently in design phase. Any code here is purely experimental proof of concepts.
## Dev setup
To be able to run the cli commands with npx you must first link the node module so npm treats it like an installed package
```
npm link
```
Alternatively `yarn dev` will run the cli
```bash
# these are the same
npx atst --help
yarn dev --help
```
Example of how to read an attestation
```bash
npx atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3
```
{
"name": "@eth-optimism/atst",
"version": "0.0.0",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"module": "dist/index.cjs",
"license": "MIT",
"bin": {
"atst": "./dist/cli.js"
},
"scripts": {
"dev": "tsx src/cli.ts",
"clean": "rm -rf ./node_modules && rm -rf ./dist && rm -rf ./coverage",
"build": "yarn tsup",
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "eslint . --max-warnings=0",
"lint:fix": "yarn lint:check --fix",
"test": "vitest",
"typecheck": "yarn tsc --noEmit"
},
"dependencies": {
"cac": "^6.7.14",
"ethers": "^5.7.0",
"picocolors": "^1.0.0",
"ora": "^6.1.2",
"zod": "^3.11.6"
},
"peerDependencies": {
"@wagmi/core": ">0.9.0"
},
"devDependencies": {
"@testing-library/react-hooks": "^8.0.1",
"jsdom": "^21.1.0",
"react-dom": "^18.2.0",
"react": "^18.2.0",
"execa": "^1.0.0",
"vitest": "^0.28.3",
"tsup": "^6.5.0",
"tsx": "^3.12.2",
"typescript": "^4.9.3",
"@wagmi/core": "^0.9.2"
}
}
# src
Source code for atst
## index.ts
Entrypoint for sdk
## cli.ts
Entrypoint for the cli
## __snapshots__
Vitest snapshots for the vitest tests
## commands
CLI implementations of atst read and write
## contants
Internal and external constants
## lib
All library code for the sdk lives here
## test
Test helpers
## types
Zod and typscript types
\ No newline at end of file
// Vitest Snapshot v1
exports[`logger > \${level}() > logs message "error" 1`] = `"error"`;
exports[`logger > \${level}() > logs message "info" 1`] = `"info"`;
exports[`logger > \${level}() > logs message "log" 1`] = `"log"`;
exports[`logger > \${level}() > logs message "success" 1`] = `"success"`;
exports[`logger > \${level}() > logs message "warn" 1`] = `"warn"`;
#!/usr/bin/env node
import { cac } from 'cac'
import type { Address } from '@wagmi/core'
import { readOptionsValidators, ReadOptions } from './commands/read'
import * as logger from './lib/logger'
// @ts-ignore it's mad about me importing something not in tsconfig.includes
import packageJson from '../package.json'
import { WriteOptions, writeOptionsValidators } from './commands/write'
const cli = cac('atst')
cli
.command('read', 'read an attestation')
.option('--creator <string>', readOptionsValidators.creator.description!)
.option('--about <string>', readOptionsValidators.about.description!)
.option('--key <string>', readOptionsValidators.key.description!)
.option('--data-type [string]', readOptionsValidators.dataType.description!, {
default: readOptionsValidators.dataType.parse(undefined),
})
.option('--rpc-url [url]', readOptionsValidators.rpcUrl.description!, {
default: readOptionsValidators.rpcUrl.parse(undefined),
})
.option('--contract [address]', readOptionsValidators.contract.description!, {
default: readOptionsValidators.contract.parse(undefined),
})
.example(
() =>
`atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3`
)
.action(async (options: ReadOptions) => {
const { read } = await import('./commands/read')
// TODO use the native api to do this instead of parsing the raw args
// by default options parses addresses as numbers without precision
// we should use the args parsing library to do this directly
// but for now I didn't bother to figure out how to do that
const { rawArgs } = cli
const about = rawArgs[rawArgs.indexOf('--about') + 1] as Address
const creator = rawArgs[rawArgs.indexOf('--creator') + 1] as Address
const contract = rawArgs.includes('--contract')
? (rawArgs[rawArgs.indexOf('--contract') + 1] as Address)
: options.contract
await read({ ...options, about, creator, contract })
})
cli
.command('write', 'write an attestation')
.option(
'--private-key <string>',
writeOptionsValidators.privateKey.description!
)
.option('--data-type [string]', readOptionsValidators.dataType.description!, {
default: writeOptionsValidators.dataType.parse(undefined),
})
.option('--about <string>', writeOptionsValidators.about.description!)
.option('--key <string>', writeOptionsValidators.key.description!)
.option('--value <string>', writeOptionsValidators.value.description!)
.option('--rpc-url [url]', writeOptionsValidators.rpcUrl.description!, {
default: writeOptionsValidators.rpcUrl.parse(undefined),
})
.option(
'--contract [address]',
writeOptionsValidators.contract.description!,
{
default: writeOptionsValidators.contract.parse(undefined),
}
)
.example(
() =>
`atst write --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --value "my attestation" --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545`
)
.action(async (options: WriteOptions) => {
const { write } = await import('./commands/write')
// TODO use the native api to do this instead of parsing the raw args
// by default options parses addresses as numbers without precision
// we should use the args parsing library to do this directly
// but for now I didn't bother to figure out how to do that
const { rawArgs } = cli
const privateKey = rawArgs[rawArgs.indexOf('--private-key') + 1] as Address
const about = rawArgs[rawArgs.indexOf('--about') + 1] as Address
const contract = rawArgs.includes('--contract')
? (rawArgs[rawArgs.indexOf('--contract') + 1] as Address)
: options.contract
await write({ ...options, about, privateKey, contract })
})
cli.help()
cli.version(packageJson.version)
void (async () => {
try {
// Parse CLI args without running command
cli.parse(process.argv, { run: false })
if (!cli.matchedCommand && cli.args.length === 0) {
cli.outputHelp()
}
await cli.runMatchedCommand()
} catch (error) {
logger.error(`\n${(error as Error).message}`)
process.exit(1)
}
})()
import { describe, expect, it } from 'vitest'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { watchConsole } from '../test/watchConsole'
import { read } from './read'
describe(`cli:${read.name}`, () => {
it('should read attestation', async () => {
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const dataType = 'string'
const consoleUtil = watchConsole()
await read({
creator,
about,
key,
dataType,
contract: ATTESTATION_STATION_ADDRESS,
rpcUrl: 'http://localhost:8545',
})
expect(consoleUtil.formatted).toMatchInlineSnapshot(
'"https://assets.optimism.io/4a609661-6774-441f-9fdb-453fdbb89931-bucket/optimist-nft/attributes"'
)
})
})
import { Address, createClient } from '@wagmi/core'
import { isAddress } from 'ethers/lib/utils.js'
import { z } from 'zod'
import { providers } from 'ethers'
import * as logger from '../lib/logger'
import { dataTypeOptionValidator } from '../types/DataTypeOption'
import type { WagmiBytes } from '../types/WagmiBytes'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { DEFAULT_RPC_URL } from '../constants/defaultRpcUrl'
import { readAttestation } from '../lib/readAttestation'
const zodAddress = () =>
z
.string()
.transform((addr) => addr as Address)
.refine(isAddress, { message: 'Invalid address' })
export const readOptionsValidators = {
creator: zodAddress().describe('Address of the creator of the attestation'),
about: zodAddress().describe('Address of the subject of the attestation'),
key: z
.string()
.describe('Key of the attestation either as string or hex number'),
dataType: dataTypeOptionValidator,
rpcUrl: z
.string()
.url()
.optional()
.default(DEFAULT_RPC_URL)
.describe('Rpc url to use'),
contract: zodAddress()
.optional()
.default(ATTESTATION_STATION_ADDRESS)
.describe('Contract address to read from'),
}
const validators = z.object(readOptionsValidators)
export type ReadOptions = z.infer<typeof validators>
export const read = async (options: ReadOptions) => {
// TODO make these errors more user friendly
const parsedOptions = await validators.parseAsync(options).catch((e) => {
logger.error(e)
process.exit(1)
})
const provider = new providers.JsonRpcProvider({
url: parsedOptions.rpcUrl,
headers: {
'User-Agent': '@eth-optimism/atst',
},
})
createClient({
provider,
})
try {
const result = await readAttestation(
parsedOptions.creator,
parsedOptions.about,
parsedOptions.key as WagmiBytes,
parsedOptions.dataType,
parsedOptions.contract
)
logger.log(result?.toString())
return result?.toString()
} catch (e) {
logger.error('Unable to read attestation', e)
process.exit(1)
}
}
import { Address } from '@wagmi/core'
import { Wallet } from 'ethers'
import { describe, expect, it } from 'vitest'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { read } from './read'
import { write } from './write'
describe(`cli:${write.name}`, () => {
it('should write attestation', async () => {
// Anvil account[0]
const privateKey =
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
const publicKey = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
const about = Wallet.createRandom().address as Address
const key = 'key'
const value = 'value'
const rpcUrl = 'http://localhost:8545'
const txHash = await write({
privateKey,
about,
key,
value,
contract: ATTESTATION_STATION_ADDRESS,
rpcUrl,
})
expect(txHash.startsWith('0x')).toBe(true)
// check that attestation was written
const attestation = await read({
creator: publicKey,
about,
key,
dataType: 'string',
contract: ATTESTATION_STATION_ADDRESS,
rpcUrl,
})
expect(attestation).toBe(value)
})
})
import { Address, connect, createClient } from '@wagmi/core'
import { isAddress } from 'ethers/lib/utils.js'
import { z } from 'zod'
import { providers, Wallet } from 'ethers'
import { MockConnector } from '@wagmi/core/connectors/mock'
import * as logger from '../lib/logger'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { DEFAULT_RPC_URL } from '../constants/defaultRpcUrl'
import { prepareWriteAttestation } from '../lib/prepareWriteAttestation'
import { writeAttestation } from '../lib/writeAttestation'
import { castAsDataType } from '../lib/castAsDataType'
import { dataTypeOptionValidator } from '../types/DataTypeOption'
const zodAddress = () =>
z
.string()
.transform((addr) => addr as Address)
.refine(isAddress, { message: 'Invalid address' })
const zodWallet = () => z.string().refine((key) => new Wallet(key))
const zodAttestation = () => z.union([z.string(), z.number(), z.boolean()])
export const writeOptionsValidators = {
privateKey: zodWallet().describe('Address of the creator of the attestation'),
about: zodAddress().describe('Address of the subject of the attestation'),
key: z
.string()
.describe('Key of the attestation either as string or hex number'),
value: zodAttestation().describe('Attestation value').default(''),
dataType: dataTypeOptionValidator,
rpcUrl: z
.string()
.url()
.optional()
.default(DEFAULT_RPC_URL)
.describe('Rpc url to use'),
contract: zodAddress()
.optional()
.default(ATTESTATION_STATION_ADDRESS)
.describe('Contract address to read from'),
}
const validators = z.object(writeOptionsValidators)
export type WriteOptions = z.infer<typeof validators>
export const write = async (options: WriteOptions) => {
// TODO make these errors more user friendly
const parsedOptions = await validators.parseAsync(options).catch((e) => {
logger.error(e)
process.exit(1)
})
const provider = new providers.JsonRpcProvider({
url: parsedOptions.rpcUrl,
headers: {
'User-Agent': '@eth-optimism/atst',
},
})
createClient({
provider,
})
const network = await provider.getNetwork()
if (!network) {
logger.error('Unable to detect chainId')
process.exit(1)
}
await connect({
// MockConnector is actually a vanilla connector
// it's called mockConnector because normally they
// expect us to connect with metamask or something
// but we're just using a private key
connector: new MockConnector({
options: {
chainId: network.chainId,
signer: new Wallet(parsedOptions.privateKey, provider),
},
}),
})
try {
const preparedTx = await prepareWriteAttestation(
parsedOptions.about,
parsedOptions.key,
castAsDataType(parsedOptions.value, parsedOptions.dataType),
network.chainId
)
const result = await writeAttestation(preparedTx)
await result.wait()
logger.log(`txHash: ${result.hash}`)
return result.hash
} catch (e) {
logger.error('Unable to read attestation', e)
process.exit(1)
}
}
/**
* The attestation station contract address
* The attestation station contract is deterministically deployed
* to 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77
*/
export const ATTESTATION_STATION_ADDRESS =
'0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77'
/**
* @internal
* Default RPC URL for Optimism
*/
export const DEFAULT_RPC_URL = 'https://mainnet.optimism.io'
// constants
export { ATTESTATION_STATION_ADDRESS } from './constants/attestationStationAddress'
// lib
export { readAttestation } from './lib/readAttestation'
export { readAttestations } from './lib/readAttestations'
export { prepareWriteAttestation } from './lib/prepareWriteAttestation'
export { prepareWriteAttestations } from './lib/prepareWriteAttestations'
export { writeAttestation } from './lib/writeAttestation'
export { abi } from './lib/abi'
export { parseAttestationBytes } from './lib/parseAttestationBytes'
export { stringifyAttestationBytes } from './lib/stringifyAttestationBytes'
// types
export type { AttestationReadParams } from './types/AttestationReadParams'
export type { WagmiBytes } from './types/WagmiBytes'
export type { DataTypeOption } from './types/DataTypeOption'
// Vitest Snapshot v1
exports[`logger > \${level}() > logs message "error" 1`] = `"error"`;
exports[`logger > \${level}() > logs message "info" 1`] = `"info"`;
exports[`logger > \${level}() > logs message "log" 1`] = `"log"`;
exports[`logger > \${level}() > logs message "success" 1`] = `"success"`;
exports[`logger > \${level}() > logs message "warn" 1`] = `"warn"`;
import { describe, expect, it } from 'vitest'
import { abi } from './abi'
/**
* This is a low value test that I made only because
* it makes for a good final check that indeed are
* exporting the correct abi
*/
describe('abi', () => {
it('is the correct abi', () => {
const methodNames = abi.map((obj) => (obj as { name: string }).name)
expect(methodNames).toMatchInlineSnapshot(`
[
undefined,
"AttestationCreated",
"attest",
"attest",
"attestations",
"version",
]
`)
})
})
/**
* The attestation station abi
*/
export const abi = [
{
inputs: [],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'creator',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'about',
type: 'address',
},
{
indexed: true,
internalType: 'bytes32',
name: 'key',
type: 'bytes32',
},
{
indexed: false,
internalType: 'bytes',
name: 'val',
type: 'bytes',
},
],
name: 'AttestationCreated',
type: 'event',
},
{
inputs: [
{
components: [
{
internalType: 'address',
name: 'about',
type: 'address',
},
{
internalType: 'bytes32',
name: 'key',
type: 'bytes32',
},
{
internalType: 'bytes',
name: 'val',
type: 'bytes',
},
],
internalType: 'struct AttestationStation.AttestationData[]',
name: '_attestations',
type: 'tuple[]',
},
],
name: 'attest',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '_about',
type: 'address',
},
{
internalType: 'bytes32',
name: '_key',
type: 'bytes32',
},
{
internalType: 'bytes',
name: '_val',
type: 'bytes',
},
],
name: 'attest',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
{
internalType: 'address',
name: '',
type: 'address',
},
{
internalType: 'bytes32',
name: '',
type: 'bytes32',
},
],
name: 'attestations',
outputs: [
{
internalType: 'bytes',
name: '',
type: 'bytes',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'version',
outputs: [
{
internalType: 'string',
name: '',
type: 'string',
},
],
stateMutability: 'view',
type: 'function',
},
] as const
import { DataTypeOption } from '../types/DataTypeOption'
/**
* @internal
* Takes a datatype and returns the value casted to that type
*/
export const castAsDataType = (value: any, dataType: DataTypeOption) => {
if (dataType === 'string') {
return value
} else if (dataType === 'number') {
return Number(value)
} else if (dataType === 'bool') {
return Boolean(value)
} else if (dataType === 'bytes') {
return value
} else if (dataType === 'address') {
return value
} else {
throw new Error(`Unrecognized data type ${dataType satisfies never}`)
}
}
import { afterEach, describe, expect, it, vi } from 'vitest'
import * as logger from './logger'
import { watchConsole } from '../test/watchConsole'
describe('logger', () => {
afterEach(() => {
vi.restoreAllMocks()
})
describe.each([
{ level: 'success' },
{ level: 'info' },
{ level: 'log' },
{ level: 'warn' },
{ level: 'error' },
// eslint-disable-next-line no-template-curly-in-string
])('${level}()', ({ level }) => {
it(`logs message "${level}"`, () => {
const spy = vi.spyOn(logger, level as 'info')
const consoleUtil = watchConsole()
const loggerFn = logger[level]
loggerFn(level)
expect(spy).toHaveBeenCalledWith(level)
expect(consoleUtil.formatted).toMatchSnapshot()
})
})
})
import util from 'util'
import ora from 'ora'
import pc from 'picocolors'
const format = (args: any[]) => {
return util
.format(...args)
.split('\n')
.join('\n')
}
export const success = (...args: Array<any>) => {
console.log(pc.green(format(args)))
}
export const info = (...args: Array<any>) => {
console.info(pc.blue(format(args)))
}
export const log = (...args: Array<any>) => {
console.log(pc.white(format(args)))
}
export const warn = (...args: Array<any>) => {
console.warn(pc.yellow(format(args)))
}
export const error = (...args: Array<any>) => {
console.error(pc.red(format(args)))
}
export const spinner = () => {
return ora({
color: 'gray',
spinner: 'dots8Bit',
})
}
import { BigNumber } from 'ethers'
import { toUtf8Bytes } from 'ethers/lib/utils.js'
import { expect, describe, it } from 'vitest'
import { WagmiBytes } from '../types/WagmiBytes'
import { parseAttestationBytes } from './parseAttestationBytes'
describe(parseAttestationBytes.name, () => {
it('works for strings', () => {
const str = 'Hello World'
const bytes = BigNumber.from(toUtf8Bytes(str)).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'string')).toBe(str)
})
it('works for numbers', () => {
const num = 123
const bytes = BigNumber.from(num).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'number')).toBe(num.toString())
})
it('works for addresses', () => {
const addr = '0x1234567890123456789012345678901234567890'
const bytes = BigNumber.from(addr).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'address')).toBe(addr)
})
it('works for booleans', () => {
const bytes = BigNumber.from(1).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'bool')).toBe('true')
})
it('should work for raw bytes', () => {
const bytes = '0x420'
expect(parseAttestationBytes(bytes, 'bytes')).toBe(bytes)
})
it('should return raw bytes for invalid type', () => {
const bytes = '0x420'
// @ts-expect-error - this is a test for an error case
expect(parseAttestationBytes(bytes, 'foo')).toBe(bytes)
})
})
import { BigNumber } from 'ethers'
import { toUtf8String } from 'ethers/lib/utils.js'
import type { DataTypeOption } from '../types/DataTypeOption'
import type { WagmiBytes } from '../types/WagmiBytes'
export const parseAttestationBytes = (
attestationBytes: WagmiBytes,
dataType: DataTypeOption
) => {
if (dataType === 'bytes') {
return attestationBytes
}
if (dataType === 'number') {
return BigNumber.from(attestationBytes).toString()
}
if (dataType === 'address') {
return BigNumber.from(attestationBytes).toHexString()
}
if (dataType === 'bool') {
return BigNumber.from(attestationBytes).gt(0) ? 'true' : 'false'
}
if (dataType === 'string') {
return attestationBytes && toUtf8String(attestationBytes)
}
console.warn(`unrecognized dataType ${dataType satisfies never}`)
return attestationBytes
}
import { connect, createClient } from '@wagmi/core'
import { providers, Wallet } from 'ethers'
import { expect, describe, it, beforeAll } from 'vitest'
import { MockConnector } from '@wagmi/core/connectors/mock'
import { prepareWriteAttestation } from './prepareWriteAttestation'
import { readAttestation } from './readAttestation'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const chainId = 10
const provider = new providers.JsonRpcProvider(
{
url: 'http://localhost:8545',
},
chainId
)
const wallet = Wallet.createRandom({ provider })
createClient({
provider,
})
beforeAll(async () => {
await connect({
connector: new MockConnector({
options: {
chainId,
signer: new Wallet(wallet.privateKey, provider),
},
}),
})
})
describe(prepareWriteAttestation.name, () => {
it('Should correctly prepare an attestation', async () => {
const result = await prepareWriteAttestation(about, key, 'hello world')
expect(result.address).toMatchInlineSnapshot(
'"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"'
)
expect(result.chainId).toMatchInlineSnapshot('10')
expect(result.functionName).toMatchInlineSnapshot('"attest"')
expect(result.mode).toMatchInlineSnapshot('"prepared"')
expect(result.request.gasLimit).toMatchInlineSnapshot(`
{
"hex": "0xd6c9",
"type": "BigNumber",
}
`)
})
it('should throw an error if key is longer than 32 bytes', async () => {
const dataType = 'string'
await expect(
readAttestation(
creator,
about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType
)
).rejects.toThrowErrorMatchingInlineSnapshot(
'"Key is longer than the max length of 32 for attestation keys"'
)
})
})
import { Address, prepareWriteContract } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi'
import { stringifyAttestationBytes } from './stringifyAttestationBytes'
export const prepareWriteAttestation = async (
about: Address,
key: string,
value: string | WagmiBytes | number | boolean,
chainId = 10,
contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => {
const formattedKey = formatBytes32String(key) as WagmiBytes
return prepareWriteContract({
address: contractAddress,
abi,
functionName: 'attest',
chainId,
args: [about, formattedKey, stringifyAttestationBytes(value) as WagmiBytes],
})
}
import { connect, createClient } from '@wagmi/core'
import { providers, Wallet } from 'ethers'
import { expect, describe, it, beforeAll } from 'vitest'
import { MockConnector } from '@wagmi/core/connectors/mock'
import { readAttestation } from './readAttestation'
import { prepareWriteAttestations } from './prepareWriteAttestations'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const chainId = 10
const provider = new providers.JsonRpcProvider(
{
url: 'http://localhost:8545',
},
chainId
)
const wallet = Wallet.createRandom({ provider })
createClient({
provider,
})
beforeAll(async () => {
await connect({
connector: new MockConnector({
options: {
chainId,
signer: new Wallet(wallet.privateKey, provider),
},
}),
})
})
describe(prepareWriteAttestations.name, () => {
it('Should correctly prepare an attestation', async () => {
const result = await prepareWriteAttestations([
{
about,
key,
value: 'hello world',
},
])
expect(result.address).toMatchInlineSnapshot(
'"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"'
)
expect(result.chainId).toMatchInlineSnapshot('10')
expect(result.functionName).toMatchInlineSnapshot('"attest"')
expect(result.mode).toMatchInlineSnapshot('"prepared"')
expect(result.request.gasLimit).toMatchInlineSnapshot(`
{
"hex": "0xd9ce",
"type": "BigNumber",
}
`)
})
it('should throw an error if key is longer than 32 bytes', async () => {
const dataType = 'string'
await expect(
readAttestation(
creator,
about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType
)
).rejects.toThrowErrorMatchingInlineSnapshot(
'"Key is longer than the max length of 32 for attestation keys"'
)
})
})
import { Address, prepareWriteContract } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi'
import { stringifyAttestationBytes } from './stringifyAttestationBytes'
type Attestation = {
about: Address
key: string
value: string | WagmiBytes | number | boolean
}
export const prepareWriteAttestations = async (
attestations: Attestation[],
chainId = 10,
contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => {
const formattedAttestations = attestations.map((attestation) => {
const formattedKey = formatBytes32String(attestation.key) as WagmiBytes
const formattedValue = stringifyAttestationBytes(
attestation.value
) as WagmiBytes
return {
about: attestation.about,
key: formattedKey,
val: formattedValue,
} as const
})
return prepareWriteContract({
address: contractAddress,
abi,
functionName: 'attest',
chainId,
args: [formattedAttestations],
})
}
import { createClient } from '@wagmi/core'
import { providers } from 'ethers'
import { expect, describe, it } from 'vitest'
import { readAttestation } from './readAttestation'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const dataType = 'string'
const provider = new providers.JsonRpcProvider({
url: 'http://localhost:8545',
})
createClient({
provider,
})
describe(readAttestation.name, () => {
it('should return the attestation from attestation station', async () => {
const result = await readAttestation(creator, about, key, dataType)
expect(result).toMatchInlineSnapshot(
'"https://assets.optimism.io/4a609661-6774-441f-9fdb-453fdbb89931-bucket/optimist-nft/attributes"'
)
})
it('should throw an error if key is longer than 32 bytes', async () => {
await expect(
readAttestation(
creator,
about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType
)
).rejects.toThrowErrorMatchingInlineSnapshot(
'"Key is longer than the max length of 32 for attestation keys"'
)
})
})
import type { Address } from '@wagmi/core'
import { DataTypeOption, DEFAULT_DATA_TYPE } from '../types/DataTypeOption'
import { readAttestations } from './readAttestations'
/**
* reads attestation from the attestation station contract
*
* @param attestationRead - the parameters for reading an attestation
* @returns attestation result
* @throws Error if key is longer than 32 bytes
* @example
* const attestation = await readAttestation(
* {
* creator: creatorAddress,
* about: aboutAddress,
* key: 'my_key',
* },
*/
export const readAttestation = async (
creator: Address,
about: Address,
key: string,
dataType: DataTypeOption = DEFAULT_DATA_TYPE,
contractAddress: Address = '0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77'
) => {
const [result] = await readAttestations({
creator,
about,
key,
dataType,
contractAddress,
})
return result
}
import { createClient } from '@wagmi/core'
import { providers } from 'ethers'
import { expect, describe, it } from 'vitest'
import { readAttestation } from './readAttestation'
import { readAttestations } from './readAttestations'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const provider = new providers.JsonRpcProvider({
url: 'http://localhost:8545',
})
createClient({
provider,
})
describe(readAttestation.name, () => {
it('should return attestations from attestation station', async () => {
const dataType = 'string'
const result = await readAttestations(
{
creator,
about,
key,
dataType,
},
{
creator,
about,
key,
dataType: 'bool',
},
{
creator,
about,
key,
dataType: 'bytes',
},
{
creator,
about,
key,
dataType: 'number',
}
)
expect(result).toMatchInlineSnapshot(
`
[
"https://assets.optimism.io/4a609661-6774-441f-9fdb-453fdbb89931-bucket/optimist-nft/attributes",
"true",
"0x68747470733a2f2f6173736574732e6f7074696d69736d2e696f2f34613630393636312d363737342d343431662d396664622d3435336664626238393933312d6275636b65742f6f7074696d6973742d6e66742f61747472696275746573",
"9665973469795080068873111198635018086067645613429821071805084917303478255842407465257371959707311987533859075426222329066766033171696373249109388415320911537042272090516917683029511016473045453921068327933733922308146003731827",
]
`
)
})
})
import { readContracts } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import type { AttestationReadParams } from '../types/AttestationReadParams'
import { DEFAULT_DATA_TYPE } from '../types/DataTypeOption'
import type { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi'
import { parseAttestationBytes } from './parseAttestationBytes'
/**
* reads attestations from the attestation station contract
*
* @returns an array of attestation values
* @throws Error if key is longer than 32 bytes
* @example
* const attestations = await readAttestations(
* {
* creator: creatorAddress,
* about: aboutAddress,
* key: 'my_key',
* allowFailure: false,
* },
* {
* creator: creatorAddress2,
* about: aboutAddress2,
* key: 'my_key',
* dataType: 'number',
* contractAddress: '0x1234',
* allowFailure: false,
* },
* )
*/
export const readAttestations = async (
...attestationReads: Array<AttestationReadParams>
) => {
const calls = attestationReads.map((attestation) => {
const {
creator,
about,
key,
contractAddress = ATTESTATION_STATION_ADDRESS,
allowFailure = false,
} = attestation
if (key.length > 32) {
throw new Error(
'Key is longer than the max length of 32 for attestation keys'
)
}
return {
address: contractAddress,
abi,
functionName: 'attestations',
args: [creator, about, formatBytes32String(key) as WagmiBytes],
allowFailure,
} as const
})
const results = await readContracts({
contracts: calls,
})
return results.map((dataBytes, i) => {
const dataType = attestationReads[i].dataType ?? DEFAULT_DATA_TYPE
return parseAttestationBytes(dataBytes, dataType)
})
}
import { Address } from '@wagmi/core'
import { BigNumber } from 'ethers'
import { isAddress, isHexString, toUtf8Bytes } from 'ethers/lib/utils.js'
import { WagmiBytes } from '../types/WagmiBytes'
export const stringifyAttestationBytes = (
bytes: WagmiBytes | string | Address | number | boolean
) => {
if (typeof bytes === 'number') {
return BigNumber.from(bytes).toHexString()
}
if (typeof bytes === 'boolean') {
return bytes ? '0x1' : '0x0'
}
if (isAddress(bytes)) {
return bytes
}
if (isHexString(bytes)) {
return bytes
}
if (typeof bytes === 'string') {
return toUtf8Bytes(bytes)
}
throw new Error(`unrecognized bytes type ${bytes satisfies never}`)
}
import { writeContract } from '@wagmi/core'
import { describe, expect, it } from 'vitest'
import { writeAttestation } from './writeAttestation'
describe(writeAttestation.name, () => {
it('rexports writeContract from @wagmi/core', () => {
expect(writeAttestation).toBe(writeContract)
})
})
import { writeContract } from '@wagmi/core'
export { prepareWriteAttestation } from './prepareWriteAttestation'
export { abi } from './abi'
/**
* Writes an attestation to the blockchain
* Same function as `writeContract` from @wagmi/core
* To use first use prepareWriteContract
*
* @example
* const config = await prepareAttestation(about, key, value)
* const tx = await writeAttestation(config)
*/
export const writeAttestation = writeContract
import { vi } from 'vitest'
/**
* A test util for watching console output
*/
export const watchConsole = () => {
type Console = 'info' | 'log' | 'warn' | 'error'
const output: { [_ in Console | 'all']: string[] } = {
info: [],
log: [],
warn: [],
error: [],
all: [],
}
const handleOutput = (method: Console) => {
return (message: string) => {
output[method].push(message)
output.all.push(message)
}
}
return {
debug: console.debug,
info: vi.spyOn(console, 'info').mockImplementation(handleOutput('info')),
log: vi.spyOn(console, 'log').mockImplementation(handleOutput('log')),
warn: vi.spyOn(console, 'warn').mockImplementation(handleOutput('warn')),
error: vi.spyOn(console, 'error').mockImplementation(handleOutput('error')),
output,
get formatted() {
return output.all.join('\n')
},
}
}
import { Address } from '@wagmi/core'
import { DataTypeOption } from './DataTypeOption'
/**
* The parameters for reading bulk attestations
*/
export interface AttestationReadParams {
creator: Address
about: Address
key: string
dataType?: DataTypeOption
contractAddress?: Address
allowFailure?: boolean
}
import { z } from 'zod'
/**
* @internal
* Default data type for attestations
*/
export const DEFAULT_DATA_TYPE = 'string' as const
/**
* Zod validator for the DataType type
* string | bytes | number | bool | address
*/
export const dataTypeOptionValidator = z
.union([
z.literal('string'),
z.literal('bytes'),
z.literal('number'),
z.literal('bool'),
z.literal('address'),
])
.optional()
.default('string').describe(`Zod validator for the DataType type
string | bytes | number | bool | address`)
/**
* Options for attestation data type
*/
export type DataTypeOption = z.infer<typeof dataTypeOptionValidator>
/**
* @internal
* WagmiBytes is a type that represents a hex string with a length of 32 bytes.
*/
export type WagmiBytes = `0x${string}`
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"composite": true,
"target": "es2017",
"lib": ["esnext"],
"skipLibCheck": true,
"strict": true,
"module": "commonjs",
"outDir": "build"
},
"include": ["src", "src/**/*.json"]
}
import { defineConfig } from 'tsup'
/**
* @see https://tsup.egoist.dev/
*/
export default defineConfig({
name: '@eth-optimism/atst',
/**
* This is also a cli app and tsup will automatically make the cli entrypoint executable
*
* @see https://tsup.egoist.dev/#building-cli-app
*/
entry: ['src/index.ts', 'src/cli.ts'],
outDir: 'dist',
target: 'es2021',
// will create a .js file for commonjs and a .cjs file for esm
format: ['esm', 'cjs'],
// don't generate .d.ts files. This is default but being explicit
dts: false,
splitting: false,
sourcemap: true,
// remove dist folder before building
clean: true,
})
import { defineConfig } from 'vitest/config'
/**
* @see https://vitejs.dev/config/
*/
export default defineConfig({
test: {
environment: 'jsdom',
testTimeout: 10000,
},
})
This diff is collapsed.
...@@ -96,11 +96,11 @@ abstract contract ResourceMetering is Initializable { ...@@ -96,11 +96,11 @@ abstract contract ResourceMetering is Initializable {
// Update base fee by adding the base fee delta and clamp the resulting value between // Update base fee by adding the base fee delta and clamp the resulting value between
// min and max. // min and max.
int256 newBaseFee = Arithmetic.clamp( int256 newBaseFee = Arithmetic.clamp({
int256(uint256(params.prevBaseFee)) + baseFeeDelta, _value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
MINIMUM_BASE_FEE, _min: MINIMUM_BASE_FEE,
MAXIMUM_BASE_FEE _max: MAXIMUM_BASE_FEE
); });
// If we skipped more than one block, we also need to account for every empty block. // If we skipped more than one block, we also need to account for every empty block.
// Empty block means there was no demand for deposits in that block, so we should // Empty block means there was no demand for deposits in that block, so we should
...@@ -109,15 +109,15 @@ abstract contract ResourceMetering is Initializable { ...@@ -109,15 +109,15 @@ abstract contract ResourceMetering is Initializable {
// Update the base fee by repeatedly applying the exponent 1-(1/change_denominator) // Update the base fee by repeatedly applying the exponent 1-(1/change_denominator)
// blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value // blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value
// between min and max. // between min and max.
newBaseFee = Arithmetic.clamp( newBaseFee = Arithmetic.clamp({
Arithmetic.cdexp( _value: Arithmetic.cdexp({
newBaseFee, _coefficient: newBaseFee,
BASE_FEE_MAX_CHANGE_DENOMINATOR, _denominator: BASE_FEE_MAX_CHANGE_DENOMINATOR,
int256(blockDiff - 1) _exponent: int256(blockDiff - 1)
), }),
MINIMUM_BASE_FEE, _min: MINIMUM_BASE_FEE,
MAXIMUM_BASE_FEE _max: MAXIMUM_BASE_FEE
); });
} }
// Update new base fee, reset bought gas, and update block number. // Update new base fee, reset bought gas, and update block number.
...@@ -141,7 +141,7 @@ abstract contract ResourceMetering is Initializable { ...@@ -141,7 +141,7 @@ abstract contract ResourceMetering is Initializable {
// division by zero for L1s that don't support 1559 or to avoid excessive gas burns during // division by zero for L1s that don't support 1559 or to avoid excessive gas burns during
// periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei // periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei
// during any 1 day period in the last 5 years, so should be fine. // during any 1 day period in the last 5 years, so should be fine.
uint256 gasCost = resourceCost / Math.max(block.basefee, 1000000000); uint256 gasCost = resourceCost / Math.max(block.basefee, 1 gwei);
// Give the user a refund based on the amount of gas they used to do all of the work up to // Give the user a refund based on the amount of gas they used to do all of the work up to
// this point. Since we're at the end of the modifier, this should be pretty accurate. Acts // this point. Since we're at the end of the modifier, this should be pretty accurate. Acts
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { AddressManager } from "../legacy/AddressManager.sol";
import { ResolvedDelegateProxy } from "../legacy/ResolvedDelegateProxy.sol";
contract ResolvedDelegateProxy_Test is Test {
AddressManager internal addressManager;
SimpleImplementation internal impl;
SimpleImplementation internal proxy;
function setUp() public {
// Set up the address manager.
addressManager = new AddressManager();
impl = new SimpleImplementation();
addressManager.setAddress("SimpleImplementation", address(impl));
// Set up the proxy.
proxy = SimpleImplementation(
address(new ResolvedDelegateProxy(addressManager, "SimpleImplementation"))
);
}
// Tests that the proxy properly bubbles up returndata when the delegatecall succeeds.
function testFuzz_fallback_delegateCallFoo_succeeds(uint256 x) public {
vm.expectCall(address(impl), abi.encodeWithSelector(impl.foo.selector, x));
assertEq(proxy.foo(x), x);
}
// Tests that the proxy properly bubbles up returndata when the delegatecall reverts.
function test_fallback_delegateCallBar_reverts() public {
vm.expectRevert("SimpleImplementation: revert");
vm.expectCall(address(impl), abi.encodeWithSelector(impl.bar.selector));
proxy.bar();
}
// Tests that the proxy fallback reverts as expected if the implementation within the
// address manager is not set.
function test_fallback_addressManagerNotSet_reverts() public {
AddressManager am = new AddressManager();
SimpleImplementation p = SimpleImplementation(
address(new ResolvedDelegateProxy(am, "SimpleImplementation"))
);
vm.expectRevert("ResolvedDelegateProxy: target address must be initialized");
p.foo(0);
}
}
contract SimpleImplementation {
function foo(uint256 _x) public pure returns (uint256) {
return _x;
}
function bar() public pure {
revert("SimpleImplementation: revert");
}
}
...@@ -3,10 +3,9 @@ pragma solidity 0.8.15; ...@@ -3,10 +3,9 @@ pragma solidity 0.8.15;
/* Testing utilities */ /* Testing utilities */
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { AssetReceiver } from "../universal/AssetReceiver.sol";
import { AttestationStation } from "../universal/op-nft/AttestationStation.sol"; import { AttestationStation } from "../universal/op-nft/AttestationStation.sol";
contract AssetReceiver_Initializer is Test { contract AttestationStation_Initializer is Test {
address alice_attestor = address(128); address alice_attestor = address(128);
address bob = address(256); address bob = address(256);
address sally = address(512); address sally = address(512);
...@@ -21,7 +20,7 @@ contract AssetReceiver_Initializer is Test { ...@@ -21,7 +20,7 @@ contract AssetReceiver_Initializer is Test {
} }
} }
contract AssetReceiverTest is AssetReceiver_Initializer { contract AttestationStationTest is AttestationStation_Initializer {
event AttestationCreated( event AttestationCreated(
address indexed creator, address indexed creator,
address indexed about, address indexed about,
......
...@@ -68,7 +68,7 @@ contract AttestationStation is Semver { ...@@ -68,7 +68,7 @@ contract AttestationStation is Semver {
/** /**
* @notice Allows anyone to create attestations. * @notice Allows anyone to create attestations.
* *
* @param _attestations An array of attestation data. * @param _attestations An array of AttestationData structs.
*/ */
function attest(AttestationData[] calldata _attestations) external { function attest(AttestationData[] calldata _attestations) external {
uint256 length = _attestations.length; uint256 length = _attestations.length;
......
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