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 (
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // 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
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-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.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-20180807140117-3d87b88a115f/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
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-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-20190226205417-e64efc72b421/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
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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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=
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.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.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-20181108054448-85acf8d2951c/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,
},
})
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 261340)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 76067)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 348292)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 112945)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 348314)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 112965)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 40409)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 88535)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 75075)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 36386)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 169229)
Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9423)
Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1418)
Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17154)
......@@ -17,9 +6,6 @@ Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 129874)
Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 6132)
Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 944)
CrossDomainMessenger_BaseGas_Test:test_baseGas_succeeds() (gas: 20097)
CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 72494)
CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
CrossDomainOwnable2_Test:test_onlyOwner_notMessenger_reverts() (gas: 8416)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner2_reverts() (gas: 62010)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner_reverts() (gas: 16566)
......@@ -36,10 +22,24 @@ CrossDomainOwnable3_Test:test_transferOwnershipNoLocal_succeeds() (gas: 48610)
CrossDomainOwnable3_Test:test_transferOwnership_noLocalZeroAddress_reverts() (gas: 12015)
CrossDomainOwnable3_Test:test_transferOwnership_notOwner_reverts() (gas: 13437)
CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 12081)
CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 72494)
CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
FeeVault_Test:test_constructor_succeeds() (gas: 10736)
FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 261340)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 76067)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 348292)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 112945)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 348314)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 112965)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 40409)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 88535)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 75075)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 36386)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 169229)
GasPriceOracle_Test:test_baseFee_succeeds() (gas: 8325)
GasPriceOracle_Test:test_decimals_succeeds() (gas: 6167)
GasPriceOracle_Test:test_gasPrice_succeeds() (gas: 8294)
......@@ -59,15 +59,15 @@ GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 108592)
GovernanceToken_Test:test_transferFrom_succeeds() (gas: 146273)
GovernanceToken_Test:test_transfer_succeeds() (gas: 138108)
Hashing_hashDepositSource_Test:test_hashDepositSource_succeeds() (gas: 633)
L1BlockNumberTest:test_fallback_succeeds() (gas: 18655)
L1BlockNumberTest:test_getL1BlockNumber_succeeds() (gas: 10625)
L1BlockNumberTest:test_receive_succeeds() (gas: 25384)
L1BlockTest:test_basefee_succeeds() (gas: 7554)
L1BlockTest:test_hash_succeeds() (gas: 7576)
L1BlockTest:test_number_succeeds() (gas: 7629)
L1BlockTest:test_sequenceNumber_succeeds() (gas: 7630)
L1BlockTest:test_timestamp_succeeds() (gas: 7640)
L1BlockTest:test_updateValues_succeeds() (gas: 60482)
L1BlockNumberTest:test_fallback_succeeds() (gas: 18655)
L1BlockNumberTest:test_getL1BlockNumber_succeeds() (gas: 10625)
L1BlockNumberTest:test_receive_succeeds() (gas: 25384)
L1CrossDomainMessenger_Test:test_messageVersion_succeeds() (gas: 24715)
L1CrossDomainMessenger_Test:test_relayMessage_legacyOldReplay_reverts() (gas: 49394)
L1CrossDomainMessenger_Test:test_relayMessage_legacyRetryAfterFailureThenSuccess_reverts() (gas: 228293)
......@@ -242,19 +242,19 @@ OptimismMintableERC20_Test:test_mint_notBridge_reverts() (gas: 11121)
OptimismMintableERC20_Test:test_mint_succeeds() (gas: 63566)
OptimismMintableERC20_Test:test_remoteToken_succeeds() (gas: 7689)
OptimismMintableERC20_Test:test_semver_succeeds() (gas: 8812)
OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_succeeds() (gas: 2523203)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1268564)
OptimismMintableERC721Factory_Test:test_constructor_succeeds() (gas: 8285)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_succeeds() (gas: 2336687)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_zeroRemoteToken_reverts() (gas: 9418)
OptimismMintableERC721_Test:test_burn_notBridge_reverts() (gas: 136966)
OptimismMintableERC721_Test:test_burn_succeeds() (gas: 118832)
OptimismMintableERC721_Test:test_constructor_succeeds() (gas: 28279)
OptimismMintableERC721_Test:test_safeMint_notBridge_reverts() (gas: 11098)
OptimismMintableERC721_Test:test_safeMint_succeeds() (gas: 140524)
OptimismMintableERC721_Test:test_tokenURI_succeeds() (gas: 163442)
OptimismMintableERC721Factory_Test:test_constructor_succeeds() (gas: 8285)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_succeeds() (gas: 2336687)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_zeroRemoteToken_reverts() (gas: 9418)
OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_succeeds() (gas: 2523203)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1268564)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitImpl_reverts() (gas: 11016)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 15940)
OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 16056)
......@@ -297,17 +297,6 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127554)
OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 24232)
OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46151)
OptimismPortal_Test:test_unpause_succeeds() (gas: 31780)
Proxy_Test:test_delegatesToImpl_succeeds() (gas: 45207)
Proxy_Test:test_implementationKey_succeeds() (gas: 20909)
Proxy_Test:test_implementation_isZeroAddress_reverts() (gas: 47626)
Proxy_Test:test_implementation_zeroAddressCaller_succeeds() (gas: 14752)
Proxy_Test:test_ownerKey_succeeds() (gas: 19059)
Proxy_Test:test_ownerProxyCall_notAdmin_succeeds() (gas: 34615)
Proxy_Test:test_proxyCallToImp_notAdmin_succeeds() (gas: 30008)
Proxy_Test:test_upgradeToAndCall_functionDoesNotExist_reverts() (gas: 104565)
Proxy_Test:test_upgradeToAndCall_isPayable_succeeds() (gas: 53742)
Proxy_Test:test_upgradeToAndCall_succeeds() (gas: 125190)
Proxy_Test:test_upgradeTo_clashingFunctionSignatures_succeeds() (gas: 101359)
ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586)
ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675)
ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084)
......@@ -331,6 +320,17 @@ ProxyAdmin_Test:test_setAddressManager_notOwner_reverts() (gas: 10578)
ProxyAdmin_Test:test_setImplementationName_notOwner_reverts() (gas: 11111)
ProxyAdmin_Test:test_setImplementationName_succeeds() (gas: 38945)
ProxyAdmin_Test:test_setProxyType_notOwner_reverts() (gas: 10814)
Proxy_Test:test_delegatesToImpl_succeeds() (gas: 45207)
Proxy_Test:test_implementationKey_succeeds() (gas: 20909)
Proxy_Test:test_implementation_isZeroAddress_reverts() (gas: 47626)
Proxy_Test:test_implementation_zeroAddressCaller_succeeds() (gas: 14752)
Proxy_Test:test_ownerKey_succeeds() (gas: 19059)
Proxy_Test:test_ownerProxyCall_notAdmin_succeeds() (gas: 34615)
Proxy_Test:test_proxyCallToImp_notAdmin_succeeds() (gas: 30008)
Proxy_Test:test_upgradeToAndCall_functionDoesNotExist_reverts() (gas: 104565)
Proxy_Test:test_upgradeToAndCall_isPayable_succeeds() (gas: 53742)
Proxy_Test:test_upgradeToAndCall_succeeds() (gas: 125190)
Proxy_Test:test_upgradeTo_clashingFunctionSignatures_succeeds() (gas: 101359)
RLPReader_readBytes_Test:test_readBytes_bytestring00_succeeds() (gas: 1878)
RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1855)
RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1876)
......@@ -392,6 +392,8 @@ RLPWriter_writeUint_Test:test_writeUint_smallint3_succeeds() (gas: 7301)
RLPWriter_writeUint_Test:test_writeUint_smallint4_succeeds() (gas: 7302)
RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280)
RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7749)
ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906)
ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783)
ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 8983)
ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008142)
ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18369)
......
......@@ -96,11 +96,11 @@ abstract contract ResourceMetering is Initializable {
// Update base fee by adding the base fee delta and clamp the resulting value between
// min and max.
int256 newBaseFee = Arithmetic.clamp(
int256(uint256(params.prevBaseFee)) + baseFeeDelta,
MINIMUM_BASE_FEE,
MAXIMUM_BASE_FEE
);
int256 newBaseFee = Arithmetic.clamp({
_value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
_min: MINIMUM_BASE_FEE,
_max: MAXIMUM_BASE_FEE
});
// 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
......@@ -109,15 +109,15 @@ abstract contract ResourceMetering is Initializable {
// Update the base fee by repeatedly applying the exponent 1-(1/change_denominator)
// blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value
// between min and max.
newBaseFee = Arithmetic.clamp(
Arithmetic.cdexp(
newBaseFee,
BASE_FEE_MAX_CHANGE_DENOMINATOR,
int256(blockDiff - 1)
),
MINIMUM_BASE_FEE,
MAXIMUM_BASE_FEE
);
newBaseFee = Arithmetic.clamp({
_value: Arithmetic.cdexp({
_coefficient: newBaseFee,
_denominator: BASE_FEE_MAX_CHANGE_DENOMINATOR,
_exponent: int256(blockDiff - 1)
}),
_min: MINIMUM_BASE_FEE,
_max: MAXIMUM_BASE_FEE
});
}
// Update new base fee, reset bought gas, and update block number.
......@@ -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
// 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.
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
// 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;
/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { AssetReceiver } from "../universal/AssetReceiver.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 bob = address(256);
address sally = address(512);
......@@ -21,7 +20,7 @@ contract AssetReceiver_Initializer is Test {
}
}
contract AssetReceiverTest is AssetReceiver_Initializer {
contract AttestationStationTest is AttestationStation_Initializer {
event AttestationCreated(
address indexed creator,
address indexed about,
......
......@@ -68,7 +68,7 @@ contract AttestationStation is Semver {
/**
* @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 {
uint256 length = _attestations.length;
......
......@@ -28,6 +28,7 @@ with the authorization and validation conditions on L2.
- [Validation and Authorization of Deposited Transactions](#validation-and-authorization-of-deposited-transactions)
- [Execution](#execution)
- [Nonce Handling](#nonce-handling)
- [Deposit Receipt](#deposit-receipt)
- [L1 Attributes Deposited Transaction](#l1-attributes-deposited-transaction)
- [Special Accounts on L2](#special-accounts-on-l2)
- [L1 Attributes Depositor Account](#l1-attributes-depositor-account)
......@@ -52,8 +53,9 @@ transaction types:
for the rationale).
3. They buy their L2 gas on L1 and, as such, the L2 gas is not refundable.
We define a new [EIP-2718] compatible transaction type with the prefix `0x7E`, and then a versioned
byte sequence. The first version has `0x00` as the version byte and then as the following fields
We define a new [EIP-2718] compatible transaction type with the prefix `0x7E` to represent a deposit transaction.
A deposit has the following fields
(rlp encoded in the order they appear here):
[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
......@@ -65,10 +67,20 @@ byte sequence. The first version has `0x00` as the version byte and then as the
- `uint256 mint`: The ETH value to mint on L2.
- `uint256 value`: The ETH value to send to the recipient account.
- `bytes data`: The input data.
- `bool isSystemTx`: If true, the transaction does not interact with the L2 block gas pool.
- Note: boolean is disabled (enforced to be `false`) starting from the Regolith upgrade.
- `uint64 gasLimit`: The gasLimit for the L2 transaction.
In contrast to [EIP-155] transactions, this transaction type does not include signature information,
and makes the `from` address explicit.
In contrast to [EIP-155] transactions, this transaction type:
- Does not include a `nonce`, since it is identified by the `sourceHash`.
API responses still include a `nonce` attribute:
- Before Regolith: the `nonce` is always `0`
- With Regolith: the `nonce` is set to the `depositNonce` attribute of the corresponding transaction receipt.
- Does not include signature information, and makes the `from` address explicit.
API responses contain zeroed signature `v`, `r`, `s` values for backwards compatibility.
- Includes new `sourceHash`, `from`, `mint`, and `isSystemTx` attributes.
API responses contain these as additional fields.
[EIP-155]:https://eips.ethereum.org/EIPS/eip-155
......@@ -77,9 +89,6 @@ Picking a high identifier minimizes the risk that the identifier will be used be
transaction type on the L1 chain in the future. We don't pick `0x7F` itself in case it becomes used
for a variable-length encoding scheme.
We chose to add a version field to the deposit transaction to enable the protocol to upgrade the deposit
transaction type without having to take another [EIP-2718] transaction type selector.
### Source hash computation
The `sourceHash` of a deposit transaction is computed based on the origin:
......@@ -129,27 +138,47 @@ deposit contract][deposit-contract].
In order to execute a deposited transaction:
First, the balance of the `from` account MUST be increased by the amount of `mint`.
This is unconditional, and does not revert on deposit failure.
Then, the execution environment for a deposited transaction is initialized based on the
transaction's attributes, in exactly the same manner as it would be for an EIP-155 transaction.
Specifically, a new EVM call frame targeting the `to` address is created with values initialized as
follows:
- `CALLER` and `ORIGIN` set to `from`
- `from` is unchanged from the deposit feed contract's logs (though the address may have been
[aliased][address-aliasing] by the deposit feed contract).
- `context.calldata` set to `data`
- `context.gas` set to `gasLimit`
- `context.value` set to `sendValue`
No gas is bought on L2 and no refund is provided. The gas used for the deposit is subtracted from
the gas pool on L2. Gas usage exactly matches other transaction types (including intrinsic gas).
If a deposit runs out of gas or has some other failure, the mint will succeed and the nonce of the
account will be increased, but no other state transition will occur.
If `isSystemTransaction` in the deposit is set to `true`, the gas used by the deposit is unmetered.
It must not be subtracted from the gas pool and the `usedGas` field of the receipt must be set to 0.
The deposit transaction is processed exactly like a type-3 (EIP-1559) transaction, with the exception of:
- No fee fields are verified: the deposit does not have any, as it pays for gas on L1.
- No `nonce` field is verified: the deposit does not have any, it's uniquely identified by its `sourceHash`.
- No access-list is processed: the deposit has no access-list, and it is thus processed as if the access-list is empty.
- No check if `from` is an Externally Owner Account (EOA): the deposit is ensured not to be an EAO through L1 address
masking, this may change in future L1 contract-deployments to e.g. enable an account-abstraction like mechanism.
- Before the Regolith upgrade:
- The execution output states a non-standard gas usage:
- If `isSystemTx` is false: execution output states it uses `gasLimit` gas.
- If `isSystemTx` is true: execution output states it uses `0` gas.
- No gas is refunded as ETH. (either by not refunding or utilizing the fact the gas-price of the deposit is `0`)
- No transaction priority fee is charged. No payment is made to the block fee-recipient.
- No L1-cost fee is charged, as deposits are derived from L1 and do not have to be submitted as data back to it.
- No base fee is charged. The total base fee accounting does not change.
Note that this includes contract-deployment behavior like with regular transactions,
and gas metering is the same (with the exception of fee related changes above), including metering of intrinsic gas.
Any non-EVM state-transition error emitted by the EVM execution is processed in a special way:
- It is transformed into an EVM-error:
i.e. the deposit will always be included, but its receipt will indicate a failure
if it runs into a non-EVM state-transition error, e.g. failure to transfer the specified
`value` amount of ETH due to insufficient account-balance.
- The world state is rolled back to the start of the EVM processing, after the minting part of the deposit.
- The `nonce` of `from` in the world state is incremented by 1, making the error equivalent to a native EVM failure.
Note that a previous `nonce` increment may have happened during EVM processing, but this would be rolled back first.
Finally, after the above processing, the execution post-processing runs the same:
i.e. the gas pool and receipt are processed identical to a regular transaction.
Starting with the Regolith upgrade however, the receipt of deposit transactions is extended with an additional
`depositNonce` value, storing the `nonce` value of the `from` sender as registered *before* the EVM processing.
Note that the gas used as stated by the execution output is subtracted from the gas pool,
but this execution output value has special edge cases before the Regolith upgrade.
Note for application developers: because `CALLER` and `ORIGIN` are set to `from`, the
semantics of using the `tx.origin == msg.sender` check will not work to determine whether
......@@ -168,6 +197,33 @@ tooling (such as wallets and block explorers).
[create-nonce]: https://github.com/ethereum/execution-specs/blob/617903a8f8d7b50cf71bf1aa733c37897c8d75c1/src/ethereum/frontier/utils/address.py#L40
## Deposit Receipt
Transaction receipts use standard typing as per [EIP-2718].
The Deposit transaction receipt type is equal to a regular receipt,
but extended with an optional `depositNonce` field.
The RLP-encoded consensus-enforced fields are:
- `postStateOrStatus` (standard): this contains the transaction status, see [EIP-658].
- `cumulativeGasUsed` (standard): gas used in the block thus far, including this transaction.
- The actual gas used is derived from the difference in `CumulativeGasUsed` with the previous transaction.
- Starting with Regolith, this accounts for the actual gas usage by the deposit, like regular transactions.
- `bloom` (standard): bloom filter of the transaction logs.
- `logs` (standard): log events emitted by the EVM processing.
- `depositNonce` (unique extension): Optional field. The deposit transaction persists the nonce used during execution.
- Before Regolith, this `depositNonce` field must always be omitted.
- With Regolith, this `depositNonce` field must always be included.
Starting with Regolith, the receipt API responses utilize the receipt changes for more accurate response data:
- The `depositNonce` is included in the receipt JSON data in API responses
- For contract-deployments (when `to == null`), the `depositNonce` helps derive the correct `contractAddress` meta-data,
instead of assuming the nonce was zero.
- The `cumulativeGasUsed` accounts for the actual gas usage, as metered in the EVM processing.
[EIP-658]: https://eips.ethereum.org/EIPS/eip-658
## L1 Attributes Deposited Transaction
[l1-attr-deposit]: #l1-attributes-deposited-transaction
......@@ -184,12 +240,18 @@ This transaction MUST have the following values:
3. `mint` is `0`
4. `value` is `0`
5. `gasLimit` is set to 150,000,000.
6. `isSystemTransaction` is set to `true`.
6. `isSystemTx` is set to `true`.
7. `data` is an [ABI] encoded call to the [L1 attributes predeployed contract][predeploy]'s
`setL1BlockValues()` function with correct values associated with the corresponding L1 block (cf.
[reference implementation][l1-attr-ref-implem]).
No gas is paid for L1 attributes deposited transactions.
If the Regolith upgrade is active, some fields are overridden:
1. `gasLimit` is set to 1,000,000
2. `isSystemTx` is set to `false`
This system-initiated transaction for L1 attributes is not charged any ETH for its allocated `gasLimit`,
as it is effectively part of the state-transition processing.
## Special Accounts on L2
......@@ -268,7 +330,7 @@ transaction are determined by the corresponding `TransactionDeposited` event emi
6. `isCreation` is set to `true` if the transaction is a contract creation, `false` otherwise.
7. `data` is unchanged from the emitted value. Depending on the value of `isCreation` it is handled
as either calldata or contract initialization code.
8. `isSystemTransaction` is set by the rollup node for certain transactions that have unmetered execution.
8. `isSystemTx` is set by the rollup node for certain transactions that have unmetered execution.
It is `false` for user deposited transactions
### Deposit Contract
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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