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

Merge branch 'develop' into jm/release-wd-mon

parents 670ecb11 c2f0e52f
---
"@eth-optimism/l2geth-exporter": patch
---
build(deps): bump golang.org/x/crypto from 0.0.0-20220307211146-efcb8507fb70 to 0.1.0 in /l2geth-exporter
---
"@eth-optimism/gas-oracle": patch
---
build(deps): bump golang.org/x/net from 0.0.0-20211112202133-69e39bad7dc2 to 0.7.0 in /gas-oracle
---
'@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
---
'@eth-optimism/atst': patch
---
Release ATST
...@@ -39,6 +39,9 @@ packages/contracts-bedrock/deployments/anvil ...@@ -39,6 +39,9 @@ packages/contracts-bedrock/deployments/anvil
# vim # vim
*.sw* *.sw*
# jetbrains
.idea/
.env .env
.env* .env*
!.env.example !.env.example
......
...@@ -70,22 +70,27 @@ You'll need the following: ...@@ -70,22 +70,27 @@ You'll need the following:
### Setup ### Setup
Clone the repository, open it, and install nodejs packages with `yarn`: Clone the repository and open it:
```bash ```bash
git clone git@github.com:ethereum-optimism/optimism.git git clone git@github.com:ethereum-optimism/optimism.git
cd optimism cd optimism
yarn install
``` ```
### Install the Correct Version of NodeJS ### Install the Correct Version of NodeJS
Using `nvm`, install the correct version of NodeJS. Install node v16.16.0 with [nvm](https://github.com/nvm-sh/nvm)
``` ```bash
nvm use nvm use
``` ```
### Install node modules with Yarn
```bash
yarn install
```
### Building the TypeScript packages ### Building the TypeScript packages
[foundry](https://github.com/foundry-rs/foundry) is used for some smart contract [foundry](https://github.com/foundry-rs/foundry) is used for some smart contract
......
...@@ -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=
......
...@@ -56,8 +56,8 @@ require ( ...@@ -56,8 +56,8 @@ require (
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.0.0-20220307211146-efcb8507fb70 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // 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.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
......
...@@ -658,8 +658,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v ...@@ -658,8 +658,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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=
...@@ -724,8 +724,8 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -724,8 +724,8 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
...@@ -735,8 +735,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ...@@ -735,8 +735,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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
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=
......
...@@ -191,6 +191,6 @@ require ( ...@@ -191,6 +191,6 @@ require (
nhooyr.io/websocket v1.8.7 // indirect nhooyr.io/websocket v1.8.7 // indirect
) )
replace github.com/ethereum/go-ethereum v1.11.2 => github.com/ethereum-optimism/op-geth v0.0.0-20230222154945-12d211246519 replace github.com/ethereum/go-ethereum v1.11.2 => github.com/ethereum-optimism/op-geth v1.11.2-aea0402.0.20230227230209-0705cf1b7df9
//replace github.com/ethereum/go-ethereum v1.11.2 => ../go-ethereum //replace github.com/ethereum/go-ethereum v1.11.2 => ../go-ethereum
...@@ -217,8 +217,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 ...@@ -217,8 +217,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20230222154945-12d211246519 h1:6ha+V+b2sHGSgJHLvrrrGjJM27Vr51g381Vq4Jj0Qfc= github.com/ethereum-optimism/op-geth v1.11.2-aea0402.0.20230227230209-0705cf1b7df9 h1:O13fqCZYW+HiGVs+UFKtMUHnCMpWR7XcyTPijm9IAiY=
github.com/ethereum-optimism/op-geth v0.0.0-20230222154945-12d211246519/go.mod h1:/tjlXxOaovIyuF0l6+wCzr6AtDb3lYWTymmpQAQcqu8= github.com/ethereum-optimism/op-geth v1.11.2-aea0402.0.20230227230209-0705cf1b7df9/go.mod h1:/tjlXxOaovIyuF0l6+wCzr6AtDb3lYWTymmpQAQcqu8=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
......
...@@ -2,7 +2,10 @@ module github.com/ethereum-optimism/optimism/indexer ...@@ -2,7 +2,10 @@ module github.com/ethereum-optimism/optimism/indexer
go 1.17 go 1.17
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/ethereum-optimism/op-geth v0.0.0-20230214215134-401b7fd3309b replace (
github.com/ethereum/go-ethereum v1.10.26 => github.com/ethereum-optimism/op-geth v0.0.0-20230214215134-401b7fd3309b
github.com/ethereum/go-ethereum v1.11.2 => github.com/ethereum-optimism/op-geth v1.11.2-aea0402.0.20230227230209-0705cf1b7df9
)
require ( require (
github.com/ethereum-optimism/optimism/op-bindings v0.10.14 github.com/ethereum-optimism/optimism/op-bindings v0.10.14
......
...@@ -43,7 +43,7 @@ require ( ...@@ -43,7 +43,7 @@ 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.1.0 // indirect golang.org/x/sys v0.1.0 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
......
...@@ -499,8 +499,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh ...@@ -499,8 +499,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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=
...@@ -666,8 +666,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ...@@ -666,8 +666,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-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=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
...@@ -9,8 +9,10 @@ import ( ...@@ -9,8 +9,10 @@ import (
"syscall" "syscall"
"time" "time"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
...@@ -42,17 +44,16 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -42,17 +44,16 @@ func Main(version string, cliCtx *cli.Context) error {
return err return err
} }
l.Info("Starting Batch Submitter") if !cfg.Stopped {
if err := batchSubmitter.Start(); err != nil { if err := batchSubmitter.Start(); err != nil {
l.Error("Unable to start Batch Submitter", "error", err) l.Error("Unable to start Batch Submitter", "error", err)
return err return err
} }
defer batchSubmitter.Stop() }
defer batchSubmitter.StopIfRunning()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
l.Info("Batch Submitter started")
pprofConfig := cfg.PprofConfig pprofConfig := cfg.PprofConfig
if pprofConfig.Enabled { if pprofConfig.Enabled {
l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort) l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
...@@ -81,6 +82,13 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -81,6 +82,13 @@ func Main(version string, cliCtx *cli.Context) error {
rpcCfg.ListenPort, rpcCfg.ListenPort,
version, version,
) )
if rpcCfg.EnableAdmin {
server.AddAPI(gethrpc.API{
Namespace: "admin",
Service: rpc.NewAdminAPI(batchSubmitter),
})
l.Info("Admin RPC enabled")
}
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
cancel() cancel()
return fmt.Errorf("error starting RPC server: %w", err) return fmt.Errorf("error starting RPC server: %w", err)
...@@ -97,5 +105,4 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -97,5 +105,4 @@ func Main(version string, cliCtx *cli.Context) error {
cancel() cancel()
_ = server.Stop() _ = server.Stop()
return nil return nil
} }
...@@ -9,12 +9,12 @@ import ( ...@@ -9,12 +9,12 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client" opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
) )
...@@ -81,7 +81,7 @@ type CLIConfig struct { ...@@ -81,7 +81,7 @@ type CLIConfig struct {
// PrivateKey is the private key used to submit sequencer transactions. // PrivateKey is the private key used to submit sequencer transactions.
PrivateKey string PrivateKey string
RPCConfig oprpc.CLIConfig RPCConfig rpc.CLIConfig
/* Optional Params */ /* Optional Params */
...@@ -98,6 +98,8 @@ type CLIConfig struct { ...@@ -98,6 +98,8 @@ type CLIConfig struct {
// compression algorithm. // compression algorithm.
ApproxComprRatio float64 ApproxComprRatio float64
Stopped bool
LogConfig oplog.CLIConfig LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
...@@ -145,10 +147,11 @@ func NewConfig(ctx *cli.Context) CLIConfig { ...@@ -145,10 +147,11 @@ func NewConfig(ctx *cli.Context) CLIConfig {
TargetL1TxSize: ctx.GlobalUint64(flags.TargetL1TxSizeBytesFlag.Name), TargetL1TxSize: ctx.GlobalUint64(flags.TargetL1TxSizeBytesFlag.Name),
TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name), TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name),
ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name), ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name),
Stopped: ctx.GlobalBool(flags.StoppedFlag.Name),
Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name), Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name),
SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name), SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name),
PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name), PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name),
RPCConfig: oprpc.ReadCLIConfig(ctx), RPCConfig: rpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx), PprofConfig: oppprof.ReadCLIConfig(ctx),
......
...@@ -29,6 +29,9 @@ type BatchSubmitter struct { ...@@ -29,6 +29,9 @@ type BatchSubmitter struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
mutex sync.Mutex
running bool
// lastStoredBlock is the last block loaded into `state`. If it is empty it should be set to the l2 safe head. // lastStoredBlock is the last block loaded into `state`. If it is empty it should be set to the l2 safe head.
lastStoredBlock eth.BlockID lastStoredBlock eth.BlockID
...@@ -95,17 +98,14 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitte ...@@ -95,17 +98,14 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitte
}, },
} }
return NewBatchSubmitter(batcherCfg, l) return NewBatchSubmitter(ctx, batcherCfg, l)
} }
// NewBatchSubmitter initializes the BatchSubmitter, gathering any resources // NewBatchSubmitter initializes the BatchSubmitter, gathering any resources
// that will be needed during operation. // that will be needed during operation.
func NewBatchSubmitter(cfg Config, l log.Logger) (*BatchSubmitter, error) { func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger) (*BatchSubmitter, error) {
ctx, cancel := context.WithCancel(context.Background())
balance, err := cfg.L1Client.BalanceAt(ctx, cfg.From, nil) balance, err := cfg.L1Client.BalanceAt(ctx, cfg.From, nil)
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
...@@ -117,26 +117,59 @@ func NewBatchSubmitter(cfg Config, l log.Logger) (*BatchSubmitter, error) { ...@@ -117,26 +117,59 @@ func NewBatchSubmitter(cfg Config, l log.Logger) (*BatchSubmitter, error) {
txMgr: NewTransactionManager(l, txMgr: NewTransactionManager(l,
cfg.TxManagerConfig, cfg.Rollup.BatchInboxAddress, cfg.Rollup.L1ChainID, cfg.TxManagerConfig, cfg.Rollup.BatchInboxAddress, cfg.Rollup.L1ChainID,
cfg.From, cfg.L1Client), cfg.From, cfg.L1Client),
done: make(chan struct{}),
// TODO: this context only exists because the event loop doesn't reach done
// if the tx manager is blocking forever due to e.g. insufficient balance.
ctx: ctx,
cancel: cancel,
state: NewChannelManager(l, cfg.Channel), state: NewChannelManager(l, cfg.Channel),
}, nil }, nil
} }
func (l *BatchSubmitter) Start() error { func (l *BatchSubmitter) Start() error {
l.log.Info("Starting Batch Submitter")
l.mutex.Lock()
defer l.mutex.Unlock()
if l.running {
return errors.New("batcher is already running")
}
l.running = true
l.done = make(chan struct{})
// TODO: this context only exists because the event loop doesn't reach done
// if the tx manager is blocking forever due to e.g. insufficient balance.
l.ctx, l.cancel = context.WithCancel(context.Background())
l.state.Clear()
l.lastStoredBlock = eth.BlockID{}
l.wg.Add(1) l.wg.Add(1)
go l.loop() go l.loop()
l.log.Info("Batch Submitter started")
return nil return nil
} }
func (l *BatchSubmitter) Stop() { func (l *BatchSubmitter) StopIfRunning() {
_ = l.Stop()
}
func (l *BatchSubmitter) Stop() error {
l.log.Info("Stopping Batch Submitter")
l.mutex.Lock()
defer l.mutex.Unlock()
if !l.running {
return errors.New("batcher is not running")
}
l.running = false
l.cancel() l.cancel()
close(l.done) close(l.done)
l.wg.Wait() l.wg.Wait()
l.log.Info("Batch Submitter stopped")
return nil
} }
// loadBlocksIntoState loads all blocks since the previous stored block // loadBlocksIntoState loads all blocks since the previous stored block
...@@ -199,7 +232,7 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth. ...@@ -199,7 +232,7 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.
} }
// Check last stored to see if it needs to be set on startup OR set if is lagged behind. // Check last stored to see if it needs to be set on startup OR set if is lagged behind.
// It lagging implies that the op-node processed some batches that where submitted prior to the current instance of the batcher being alive. // It lagging implies that the op-node processed some batches that were submitted prior to the current instance of the batcher being alive.
if l.lastStoredBlock == (eth.BlockID{}) { if l.lastStoredBlock == (eth.BlockID{}) {
l.log.Info("Starting batch-submitter work at safe-head", "safe", syncStatus.SafeL2) l.log.Info("Starting batch-submitter work at safe-head", "safe", syncStatus.SafeL2)
l.lastStoredBlock = syncStatus.SafeL2.ID() l.lastStoredBlock = syncStatus.SafeL2.ID()
...@@ -263,7 +296,7 @@ func (l *BatchSubmitter) loop() { ...@@ -263,7 +296,7 @@ func (l *BatchSubmitter) loop() {
// hack to exit this loop. Proper fix is to do request another send tx or parallel tx sending // hack to exit this loop. Proper fix is to do request another send tx or parallel tx sending
// from the channel manager rather than sending the channel in a loop. This stalls b/c if the // from the channel manager rather than sending the channel in a loop. This stalls b/c if the
// context is cancelled while sending, it will never fuilly clearing the pending txns. // context is cancelled while sending, it will never fully clear the pending txns.
select { select {
case <-l.ctx.Done(): case <-l.ctx.Done():
break blockLoop break blockLoop
......
...@@ -3,6 +3,7 @@ package flags ...@@ -3,6 +3,7 @@ package flags
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
...@@ -98,6 +99,11 @@ var ( ...@@ -98,6 +99,11 @@ var (
Value: 1.0, Value: 1.0,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "APPROX_COMPR_RATIO"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "APPROX_COMPR_RATIO"),
} }
StoppedFlag = cli.BoolFlag{
Name: "stopped",
Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "STOPPED"),
}
MnemonicFlag = cli.StringFlag{ MnemonicFlag = cli.StringFlag{
Name: "mnemonic", Name: "mnemonic",
Usage: "The mnemonic used to derive the wallets for either the " + Usage: "The mnemonic used to derive the wallets for either the " +
...@@ -133,6 +139,7 @@ var optionalFlags = []cli.Flag{ ...@@ -133,6 +139,7 @@ var optionalFlags = []cli.Flag{
TargetL1TxSizeBytesFlag, TargetL1TxSizeBytesFlag,
TargetNumFramesFlag, TargetNumFramesFlag,
ApproxComprRatioFlag, ApproxComprRatioFlag,
StoppedFlag,
MnemonicFlag, MnemonicFlag,
SequencerHDPathFlag, SequencerHDPathFlag,
PrivateKeyFlag, PrivateKeyFlag,
...@@ -145,6 +152,7 @@ func init() { ...@@ -145,6 +152,7 @@ func init() {
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opsigner.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opsigner.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, rpc.CLIFlags(envVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...) Flags = append(requiredFlags, optionalFlags...)
} }
......
package rpc
import (
"context"
)
type batcherClient interface {
Start() error
Stop() error
}
type adminAPI struct {
b batcherClient
}
func NewAdminAPI(dr batcherClient) *adminAPI {
return &adminAPI{
b: dr,
}
}
func (a *adminAPI) StartBatcher(_ context.Context) error {
return a.b.Start()
}
func (a *adminAPI) StopBatcher(_ context.Context) error {
return a.b.Stop()
}
package rpc
import (
"github.com/urfave/cli"
opservice "github.com/ethereum-optimism/optimism/op-service"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
const (
EnableAdminFlagName = "rpc.enable-admin"
)
func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: EnableAdminFlagName,
Usage: "Enable the admin API (experimental)",
EnvVar: opservice.PrefixEnvVar(envPrefix, "RPC_ENABLE_ADMIN"),
},
}
}
type CLIConfig struct {
oprpc.CLIConfig
EnableAdmin bool
}
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{
CLIConfig: oprpc.ReadCLIConfig(ctx),
EnableAdmin: ctx.GlobalBool(EnableAdminFlagName),
}
}
...@@ -69,6 +69,9 @@ type DeployConfig struct { ...@@ -69,6 +69,9 @@ type DeployConfig struct {
L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"` L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"`
L2GenesisBlockBaseFeePerGas *hexutil.Big `json:"l2GenesisBlockBaseFeePerGas"` L2GenesisBlockBaseFeePerGas *hexutil.Big `json:"l2GenesisBlockBaseFeePerGas"`
// Seconds after genesis block that Regolith hard fork activates. 0 to activate at genesis. Nil to disable regolith
L2GenesisRegolithTimeOffset *hexutil.Uint64 `json:"l2GenesisRegolithTimeOffset,omitempty"`
// Owner of the ProxyAdmin predeploy // Owner of the ProxyAdmin predeploy
ProxyAdminOwner common.Address `json:"proxyAdminOwner"` ProxyAdminOwner common.Address `json:"proxyAdminOwner"`
// Owner of the system on L1 // Owner of the system on L1
...@@ -284,6 +287,17 @@ func (d *DeployConfig) InitDeveloperDeployedAddresses() error { ...@@ -284,6 +287,17 @@ func (d *DeployConfig) InitDeveloperDeployedAddresses() error {
return nil return nil
} }
func (d *DeployConfig) RegolithTime(genesisTime uint64) *uint64 {
if d.L2GenesisRegolithTimeOffset == nil {
return nil
}
v := uint64(0)
if offset := *d.L2GenesisRegolithTimeOffset; offset > 0 {
v = genesisTime + uint64(offset)
}
return &v
}
// RollupConfig converts a DeployConfig to a rollup.Config // RollupConfig converts a DeployConfig to a rollup.Config
func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHash common.Hash, l2GenesisBlockNumber uint64) (*rollup.Config, error) { func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHash common.Hash, l2GenesisBlockNumber uint64) (*rollup.Config, error) {
if d.OptimismPortalProxy == (common.Address{}) { if d.OptimismPortalProxy == (common.Address{}) {
...@@ -320,6 +334,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas ...@@ -320,6 +334,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas
BatchInboxAddress: d.BatchInboxAddress, BatchInboxAddress: d.BatchInboxAddress,
DepositContractAddress: d.OptimismPortalProxy, DepositContractAddress: d.OptimismPortalProxy,
L1SystemConfigAddress: d.SystemConfigProxy, L1SystemConfigAddress: d.SystemConfigProxy,
RegolithTime: d.RegolithTime(l1StartBlock.Time()),
}, nil }, nil
} }
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -33,3 +34,15 @@ func TestUnmarshalL1StartingBlockTag(t *testing.T) { ...@@ -33,3 +34,15 @@ func TestUnmarshalL1StartingBlockTag(t *testing.T) {
require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf(`{"l1StartingBlockTag": "%s"}`, h)), decoded)) require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf(`{"l1StartingBlockTag": "%s"}`, h)), decoded))
require.EqualValues(t, common.HexToHash(h), *decoded.L1StartingBlockTag.BlockHash) require.EqualValues(t, common.HexToHash(h), *decoded.L1StartingBlockTag.BlockHash)
} }
func TestRegolithTimeZero(t *testing.T) {
regolithOffset := hexutil.Uint64(0)
config := &DeployConfig{L2GenesisRegolithTimeOffset: &regolithOffset}
require.Equal(t, uint64(0), *config.RegolithTime(1234))
}
func TestRegolithTimeAsOffset(t *testing.T) {
regolithOffset := hexutil.Uint64(1500)
config := &DeployConfig{L2GenesisRegolithTimeOffset: &regolithOffset}
require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000))
}
...@@ -289,6 +289,8 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m ...@@ -289,6 +289,8 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
// Set the Optimism options. // Set the Optimism options.
cfg.BedrockBlock = bedrockBlock.Number() cfg.BedrockBlock = bedrockBlock.Number()
// Enable Regolith from the start of Bedrock
cfg.RegolithTime = new(uint64)
cfg.Optimism = &params.OptimismConfig{ cfg.Optimism = &params.OptimismConfig{
EIP1559Denominator: config.EIP1559Denominator, EIP1559Denominator: config.EIP1559Denominator,
EIP1559Elasticity: config.EIP1559Elasticity, EIP1559Elasticity: config.EIP1559Elasticity,
......
...@@ -54,6 +54,7 @@ func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, erro ...@@ -54,6 +54,7 @@ func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, erro
TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true, TerminalTotalDifficultyPassed: true,
BedrockBlock: new(big.Int).SetUint64(uint64(config.L2GenesisBlockNumber)), BedrockBlock: new(big.Int).SetUint64(uint64(config.L2GenesisBlockNumber)),
RegolithTime: config.RegolithTime(block.Time()),
Optimism: &params.OptimismConfig{ Optimism: &params.OptimismConfig{
EIP1559Denominator: eip1559Denom, EIP1559Denominator: eip1559Denom,
EIP1559Elasticity: eip1559Elasticity, EIP1559Elasticity: eip1559Elasticity,
......
...@@ -155,3 +155,11 @@ func (s *L2Sequencer) ActBuildToL1HeadExclUnsafe(t Testing) { ...@@ -155,3 +155,11 @@ func (s *L2Sequencer) ActBuildToL1HeadExclUnsafe(t Testing) {
s.ActL2EndBlock(t) s.ActL2EndBlock(t)
} }
} }
func (s *L2Sequencer) ActBuildL2ToRegolith(t Testing) {
require.NotNil(t, s.rollupCfg.RegolithTime, "cannot activate Regolith when it is not scheduled")
for s.L2Unsafe().Time < *s.rollupCfg.RegolithTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
}
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"math/rand" "math/rand"
"testing" "testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -12,7 +13,13 @@ import ( ...@@ -12,7 +13,13 @@ import (
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
// TestCrossLayerUser tests that common actions of the CrossLayerUser actor work: type regolithScheduledTest struct {
name string
regolithTime *hexutil.Uint64
activateRegolith bool
}
// TestCrossLayerUser tests that common actions of the CrossLayerUser actor work in various regolith configurations:
// - transact on L1 // - transact on L1
// - transact on L2 // - transact on L2
// - deposit on L1 // - deposit on L1
...@@ -20,9 +27,28 @@ import ( ...@@ -20,9 +27,28 @@ import (
// - prove tx on L1 // - prove tx on L1
// - wait 1 week + 1 second // - wait 1 week + 1 second
// - finalize withdrawal on L1 // - finalize withdrawal on L1
func TestCrossLayerUser(gt *testing.T) { func TestCrossLayerUser(t *testing.T) {
zeroTime := hexutil.Uint64(0)
futureTime := hexutil.Uint64(20)
farFutureTime := hexutil.Uint64(2000)
tests := []regolithScheduledTest{
{name: "NoRegolith", regolithTime: nil, activateRegolith: false},
{name: "NotYetRegolith", regolithTime: &farFutureTime, activateRegolith: false},
{name: "RegolithAtGenesis", regolithTime: &zeroTime, activateRegolith: true},
{name: "RegolithAfterGenesis", regolithTime: &futureTime, activateRegolith: true},
}
for _, test := range tests {
test := test // Use a fixed reference as the tests run in parallel
t.Run(test.name, func(gt *testing.T) {
runCrossLayerUserTest(gt, test)
})
}
}
func runCrossLayerUserTest(gt *testing.T, test regolithScheduledTest) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
dp.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
...@@ -64,6 +90,21 @@ func TestCrossLayerUser(gt *testing.T) { ...@@ -64,6 +90,21 @@ func TestCrossLayerUser(gt *testing.T) {
alice.L1.SetUserEnv(l1UserEnv) alice.L1.SetUserEnv(l1UserEnv)
alice.L2.SetUserEnv(l2UserEnv) alice.L2.SetUserEnv(l2UserEnv)
// Build at least one l2 block so we have an unsafe head with a deposit info tx (genesis block doesn't)
seq.ActL2StartBlock(t)
seq.ActL2EndBlock(t)
if test.activateRegolith {
// advance L2 enough to activate regolith fork
seq.ActBuildL2ToRegolith(t)
}
// Check Regolith is active or not by confirming the system info tx is not a system tx
infoTx, err := l2Cl.TransactionInBlock(t.Ctx(), seq.L2Unsafe().Hash, 0)
require.NoError(t, err)
require.True(t, infoTx.IsDepositTx())
// Should only be a system tx if regolith is not enabled
require.Equal(t, !test.activateRegolith, infoTx.IsSystemTx())
// regular L2 tx, in new L2 block // regular L2 tx, in new L2 block
alice.L2.ActResetTxOpts(t) alice.L2.ActResetTxOpts(t)
alice.L2.ActSetTxToAddr(&dp.Addresses.Bob)(t) alice.L2.ActSetTxToAddr(&dp.Addresses.Bob)(t)
...@@ -158,4 +199,11 @@ func TestCrossLayerUser(gt *testing.T) { ...@@ -158,4 +199,11 @@ func TestCrossLayerUser(gt *testing.T) {
miner.ActL1EndBlock(t) miner.ActL1EndBlock(t)
// check withdrawal succeeded // check withdrawal succeeded
alice.L1.ActCheckReceiptStatusOfLastTx(true)(t) alice.L1.ActCheckReceiptStatusOfLastTx(true)(t)
// Check Regolith wasn't activated during the test unintentionally
infoTx, err = l2Cl.TransactionInBlock(t.Ctx(), seq.L2Unsafe().Hash, 0)
require.NoError(t, err)
require.True(t, infoTx.IsDepositTx())
// Should only be a system tx if regolith is not enabled
require.Equal(t, !test.activateRegolith, infoTx.IsSystemTx())
} }
...@@ -214,6 +214,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * ...@@ -214,6 +214,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
BatchInboxAddress: deployConf.BatchInboxAddress, BatchInboxAddress: deployConf.BatchInboxAddress,
DepositContractAddress: predeploys.DevOptimismPortalAddr, DepositContractAddress: predeploys.DevOptimismPortalAddr,
L1SystemConfigAddress: predeploys.DevSystemConfigAddr, L1SystemConfigAddress: predeploys.DevSystemConfigAddr,
RegolithTime: deployConf.RegolithTime(uint64(deployConf.L1GenesisBlockTimestamp)),
} }
deploymentsL1 := DeploymentsL1{ deploymentsL1 := DeploymentsL1{
......
...@@ -340,7 +340,7 @@ func TestMigration(t *testing.T) { ...@@ -340,7 +340,7 @@ func TestMigration(t *testing.T) {
}, lgr.New("module", "batcher")) }, lgr.New("module", "batcher"))
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { t.Cleanup(func() {
batcher.Stop() batcher.StopIfRunning()
}) })
proposer, err := l2os.NewL2OutputSubmitterFromCLIConfig(l2os.CLIConfig{ proposer, err := l2os.NewL2OutputSubmitterFromCLIConfig(l2os.CLIConfig{
......
...@@ -179,7 +179,8 @@ func (d *OpGeth) StartBlockBuilding(ctx context.Context, attrs *eth.PayloadAttri ...@@ -179,7 +179,8 @@ func (d *OpGeth) StartBlockBuilding(ctx context.Context, attrs *eth.PayloadAttri
// CreatePayloadAttributes creates a valid PayloadAttributes containing a L1Info deposit transaction followed by the supplied transactions. // CreatePayloadAttributes creates a valid PayloadAttributes containing a L1Info deposit transaction followed by the supplied transactions.
func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.PayloadAttributes, error) { func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.PayloadAttributes, error) {
timestamp := d.L2Head.Timestamp + 2 timestamp := d.L2Head.Timestamp + 2
l1Info, err := derive.L1InfoDepositBytes(d.sequenceNum, d.L1Head, d.SystemConfig, false) regolith := d.L2ChainConfig.IsRegolith(uint64(timestamp))
l1Info, err := derive.L1InfoDepositBytes(d.sequenceNum, d.L1Head, d.SystemConfig, regolith)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -7,8 +7,11 @@ import ( ...@@ -7,8 +7,11 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -71,104 +74,486 @@ func TestInvalidDepositInFCU(t *testing.T) { ...@@ -71,104 +74,486 @@ func TestInvalidDepositInFCU(t *testing.T) {
require.Equal(t, 0, balance.Cmp(common.Big0)) require.Equal(t, 0, balance.Cmp(common.Big0))
} }
func TestBedrockSystemTxUsesZeroGas(t *testing.T) { func TestPreregolith(t *testing.T) {
futureTimestamp := hexutil.Uint64(4)
tests := []struct {
name string
regolithTime *hexutil.Uint64
}{
{name: "RegolithNotScheduled"},
{name: "RegolithNotYetActive", regolithTime: &futureTimestamp},
}
for _, test := range tests {
test := test
t.Run("GasUsed_"+test.name, func(t *testing.T) {
// Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg) opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err) require.NoError(t, err)
defer opGeth.Close() defer opGeth.Close()
block, err := opGeth.AddL2Block(ctx) fromAddr := cfg.Secrets.Addresses().Alice
oldBalance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.NoError(t, err) require.NoError(t, err)
// Simple transfer deposit tx
depositTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &fromAddr, // send it to ourselves
Value: big.NewInt(params.Ether),
Gas: 25000,
IsSystemTransaction: false,
})
block, err := opGeth.AddL2Block(ctx, depositTx)
require.NoError(t, err)
// L1Info tx should report 0 gas used
infoTx, err := opGeth.L2Client.TransactionInBlock(ctx, block.BlockHash, 0) infoTx, err := opGeth.L2Client.TransactionInBlock(ctx, block.BlockHash, 0)
require.NoError(t, err) require.NoError(t, err)
require.True(t, infoTx.IsSystemTx()) infoRcpt, err := opGeth.L2Client.TransactionReceipt(ctx, infoTx.Hash())
receipt, err := opGeth.L2Client.TransactionReceipt(ctx, infoTx.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, receipt.GasUsed) require.Zero(t, infoRcpt.GasUsed, "should use 0 gas for system tx")
}
func TestBedrockDepositTx(t *testing.T) { // Deposit tx should report all gas used
receipt, err := opGeth.L2Client.TransactionReceipt(ctx, depositTx.Hash())
require.NoError(t, err)
require.Equal(t, depositTx.Gas(), receipt.GasUsed, "should report all gas used")
// Should not refund ETH for unused gas
newBalance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.NoError(t, err)
require.Equal(t, oldBalance, newBalance, "should not repay sender for unused gas")
})
t.Run("DepositNonce_"+test.name, func(t *testing.T) {
// Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg) opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err) require.NoError(t, err)
defer opGeth.Close() defer opGeth.Close()
aliceAddr := cfg.Secrets.Addresses().Alice fromAddr := cfg.Secrets.Addresses().Alice
// Include a tx just to ensure Alice's nonce isn't 0
// Deposit TX with a higher gas limit than required incrementNonceTx := types.NewTx(&types.DepositTx{
depositTx := types.NewTx(&types.DepositTx{ From: fromAddr,
From: aliceAddr, To: &fromAddr,
To: &aliceAddr,
Value: big.NewInt(0), Value: big.NewInt(0),
Gas: 50_000, // Simple transfer only requires 21,000 Gas: 21_000,
IsSystemTransaction: false, IsSystemTransaction: false,
}) })
// Contract creation deposit tx // Contract creation deposit tx
contractCreateTx := types.NewTx(&types.DepositTx{ contractCreateTx := types.NewTx(&types.DepositTx{
From: aliceAddr, From: fromAddr,
Value: big.NewInt(params.Ether), Value: big.NewInt(params.Ether),
Gas: 1000001, Gas: 1000001,
Data: []byte{}, Data: []byte{},
IsSystemTransaction: false, IsSystemTransaction: false,
}) })
_, err = opGeth.AddL2Block(ctx, depositTx, contractCreateTx) _, err = opGeth.AddL2Block(ctx, incrementNonceTx, contractCreateTx)
require.NoError(t, err) require.NoError(t, err)
receipt, err := opGeth.L2Client.TransactionReceipt(ctx, depositTx.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "tx should succeed")
require.Equal(t, depositTx.Gas(), receipt.GasUsed, "should use all gas")
incorrectContractAddress := crypto.CreateAddress(aliceAddr, uint64(0)) // Expected to be wrong expectedNonce := uint64(1)
correctContractAddress := crypto.CreateAddress(aliceAddr, uint64(1)) incorrectContractAddress := crypto.CreateAddress(fromAddr, uint64(0))
correctContractAddress := crypto.CreateAddress(fromAddr, expectedNonce)
createRcpt, err := opGeth.L2Client.TransactionReceipt(ctx, contractCreateTx.Hash()) createRcpt, err := opGeth.L2Client.TransactionReceipt(ctx, contractCreateTx.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, createRcpt.Status, "create should succeed") require.Equal(t, types.ReceiptStatusSuccessful, createRcpt.Status, "create should succeed")
require.Equal(t, incorrectContractAddress, createRcpt.ContractAddress, "should report incorrect contract address") require.Nil(t, createRcpt.DepositNonce, "should not report deposit nonce")
require.Equal(t, incorrectContractAddress, createRcpt.ContractAddress, "should report correct contract address")
contractBalance, err := opGeth.L2Client.BalanceAt(ctx, createRcpt.ContractAddress, nil) contractBalance, err := opGeth.L2Client.BalanceAt(ctx, incorrectContractAddress, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(0), contractBalance.Uint64(), "balance unchanged on incorrect contract address") require.Equal(t, uint64(0), contractBalance.Uint64(), "balance unchanged on incorrect contract address")
contractBalance, err = opGeth.L2Client.BalanceAt(ctx, correctContractAddress, nil) contractBalance, err = opGeth.L2Client.BalanceAt(ctx, correctContractAddress, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(params.Ether), contractBalance.Uint64(), "balance changed on correct contract address") require.Equal(t, uint64(params.Ether), contractBalance.Uint64(), "balance changed on correct contract address")
// Check the actual transaction nonce is reported correctly when retrieving the tx from the API.
tx, _, err := opGeth.L2Client.TransactionByHash(ctx, contractCreateTx.Hash())
require.NoError(t, err)
require.Zero(t, *tx.EffectiveNonce(), "should report 0 as tx nonce")
})
t.Run("UnusedGasConsumed_"+test.name, func(t *testing.T) {
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
fromAddr := cfg.Secrets.Addresses().Alice
// Deposit TX with a high gas limit but using very little actual gas
depositTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &fromAddr, // send it to ourselves
Value: big.NewInt(params.Ether),
// SystemTx is assigned 1M gas limit
Gas: uint64(cfg.DeployConfig.L2GenesisBlockGasLimit) - 1_000_000,
IsSystemTransaction: false,
})
signer := types.LatestSigner(opGeth.L2ChainConfig)
// Second tx with a gas limit that will fit in regolith but not bedrock
tx := types.MustSignNewTx(cfg.Secrets.Bob, signer, &types.DynamicFeeTx{
ChainID: big.NewInt(int64(cfg.DeployConfig.L2ChainID)),
Nonce: 0,
GasTipCap: big.NewInt(100),
GasFeeCap: big.NewInt(100000),
Gas: 1_000_001,
To: &cfg.Secrets.Addresses().Alice,
Value: big.NewInt(0),
Data: nil,
})
_, err = opGeth.AddL2Block(ctx, depositTx, tx)
// Geth checks the gas limit usage of transactions as part of validating the payload attributes and refuses to even start building the block
require.ErrorContains(t, err, "Invalid payload attributes", "block should be invalid due to using too much gas")
})
t.Run("AllowSystemTx_"+test.name, func(t *testing.T) {
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
systemTx, err := derive.L1InfoDeposit(1, opGeth.L1Head, opGeth.SystemConfig, false)
systemTx.IsSystemTransaction = true
require.NoError(t, err)
_, err = opGeth.AddL2Block(ctx, types.NewTx(systemTx))
require.NoError(t, err, "should allow blocks containing system tx")
})
}
} }
func TestBedrockShouldNotRefundDepositTxUnusedGas(t *testing.T) { func TestRegolith(t *testing.T) {
tests := []struct {
name string
regolithTime hexutil.Uint64
activateRegolith func(ctx context.Context, opGeth *OpGeth)
}{
{name: "ActivateAtGenesis", regolithTime: 0, activateRegolith: func(ctx context.Context, opGeth *OpGeth) {}},
{name: "ActivateAfterGenesis", regolithTime: 2, activateRegolith: func(ctx context.Context, opGeth *OpGeth) {
_, err := opGeth.AddL2Block(ctx)
require.NoError(t, err)
}},
}
for _, test := range tests {
test := test
t.Run("GasUsedIsAccurate_"+test.name, func(t *testing.T) {
// Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg) opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err) require.NoError(t, err)
defer opGeth.Close() defer opGeth.Close()
aliceAddr := cfg.Secrets.Addresses().Alice test.activateRegolith(ctx, opGeth)
origBalance, err := opGeth.L2Client.BalanceAt(ctx, aliceAddr, nil)
fromAddr := cfg.Secrets.Addresses().Alice
oldBalance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.NoError(t, err) require.NoError(t, err)
// Deposit TX with a higher gas limit than required // Simple transfer deposit tx
depositTx := types.NewTx(&types.DepositTx{ depositTx := types.NewTx(&types.DepositTx{
From: aliceAddr, From: fromAddr,
To: &aliceAddr, To: &fromAddr, // send it to ourselves
Value: big.NewInt(0), Value: big.NewInt(params.Ether),
Gas: 50_000, // Simple transfer only requires 21,000 Gas: 25000,
IsSystemTransaction: false, IsSystemTransaction: false,
}) })
_, err = opGeth.AddL2Block(ctx, depositTx) block, err := opGeth.AddL2Block(ctx, depositTx)
require.NoError(t, err)
// L1Info tx should report actual gas used, not 0 or the tx gas limit
infoTx, err := opGeth.L2Client.TransactionInBlock(ctx, block.BlockHash, 0)
require.NoError(t, err) require.NoError(t, err)
infoRcpt, err := opGeth.L2Client.TransactionReceipt(ctx, infoTx.Hash())
require.NoError(t, err)
require.NotZero(t, infoRcpt.GasUsed)
require.NotEqual(t, infoTx.Gas(), infoRcpt.GasUsed)
// Deposit tx should report actual gas used (21,000 for a normal transfer)
receipt, err := opGeth.L2Client.TransactionReceipt(ctx, depositTx.Hash()) receipt, err := opGeth.L2Client.TransactionReceipt(ctx, depositTx.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "tx should succeed") require.Equal(t, uint64(21_000), receipt.GasUsed, "should report actual gas used")
// Should not refund ETH for unused gas
newBalance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.NoError(t, err)
require.Equal(t, oldBalance, newBalance, "should not repay sender for unused gas")
})
t.Run("DepositNonceCorrect_"+test.name, func(t *testing.T) {
// Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
test.activateRegolith(ctx, opGeth)
fromAddr := cfg.Secrets.Addresses().Alice
// Include a tx just to ensure Alice's nonce isn't 0
incrementNonceTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &fromAddr,
Value: big.NewInt(0),
Gas: 21_000,
IsSystemTransaction: false,
})
// Contract creation deposit tx
contractCreateTx := types.NewTx(&types.DepositTx{
From: fromAddr,
Value: big.NewInt(params.Ether),
Gas: 1000001,
Data: []byte{},
IsSystemTransaction: false,
})
_, err = opGeth.AddL2Block(ctx, incrementNonceTx, contractCreateTx)
require.NoError(t, err)
newBalance, err := opGeth.L2Client.BalanceAt(ctx, aliceAddr, nil) expectedNonce := uint64(1)
correctContractAddress := crypto.CreateAddress(fromAddr, expectedNonce)
createRcpt, err := opGeth.L2Client.TransactionReceipt(ctx, contractCreateTx.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, origBalance, newBalance, "should not refund cost of unused gas") require.Equal(t, types.ReceiptStatusSuccessful, createRcpt.Status, "create should succeed")
require.Equal(t, &expectedNonce, createRcpt.DepositNonce, "should report correct deposit nonce")
require.Equal(t, correctContractAddress, createRcpt.ContractAddress, "should report correct contract address")
contractBalance, err := opGeth.L2Client.BalanceAt(ctx, createRcpt.ContractAddress, nil)
require.NoError(t, err)
require.Equal(t, uint64(params.Ether), contractBalance.Uint64(), "balance changed on correct contract address")
// Check the actual transaction nonce is reported correctly when retrieving the tx from the API.
tx, _, err := opGeth.L2Client.TransactionByHash(ctx, contractCreateTx.Hash())
require.NoError(t, err)
require.Equal(t, expectedNonce, *tx.EffectiveNonce(), "should report actual tx nonce")
})
t.Run("ReturnUnusedGasToPool_"+test.name, func(t *testing.T) {
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
test.activateRegolith(ctx, opGeth)
fromAddr := cfg.Secrets.Addresses().Alice
// Deposit TX with a high gas limit but using very little actual gas
depositTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &fromAddr, // send it to ourselves
Value: big.NewInt(params.Ether),
// SystemTx is assigned 1M gas limit
Gas: uint64(cfg.DeployConfig.L2GenesisBlockGasLimit) - 1_000_000,
IsSystemTransaction: false,
})
signer := types.LatestSigner(opGeth.L2ChainConfig)
// Second tx with a gas limit that will fit in regolith but not bedrock
tx := types.MustSignNewTx(cfg.Secrets.Bob, signer, &types.DynamicFeeTx{
ChainID: big.NewInt(int64(cfg.DeployConfig.L2ChainID)),
Nonce: 0,
GasTipCap: big.NewInt(100),
GasFeeCap: big.NewInt(100000),
Gas: 1_000_001,
To: &cfg.Secrets.Addresses().Alice,
Value: big.NewInt(0),
Data: nil,
})
_, err = opGeth.AddL2Block(ctx, depositTx, tx)
require.NoError(t, err, "block should be valid as cumulativeGasUsed only tracks actual usage now")
})
t.Run("RejectSystemTx_"+test.name, func(t *testing.T) {
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
test.activateRegolith(ctx, opGeth)
systemTx, err := derive.L1InfoDeposit(1, opGeth.L1Head, opGeth.SystemConfig, false)
systemTx.IsSystemTransaction = true
require.NoError(t, err)
_, err = opGeth.AddL2Block(ctx, types.NewTx(systemTx))
require.ErrorIs(t, err, ErrNewPayloadNotValid, "should reject blocks containing system tx")
})
t.Run("IncludeGasRefunds_"+test.name, func(t *testing.T) {
// Simple constructor that is prefixed to the actual contract code
// Results in the contract code being returned as the code for the new contract
deployPrefixSize := byte(16)
deployPrefix := []byte{
// Copy input data after this prefix into memory starting at address 0x00
// CODECOPY arg size
byte(vm.PUSH1), deployPrefixSize,
byte(vm.CODESIZE),
byte(vm.SUB),
// CODECOPY arg offset
byte(vm.PUSH1), deployPrefixSize,
// CODECOPY arg destOffset
byte(vm.PUSH1), 0x00,
byte(vm.CODECOPY),
// Return code from memory
// RETURN arg size
byte(vm.PUSH1), deployPrefixSize,
byte(vm.CODESIZE),
byte(vm.SUB),
// RETURN arg offset
byte(vm.PUSH1), 0x00,
byte(vm.RETURN),
}
// Stores the first word from call data code to storage slot 0
sstoreContract := []byte{
// Load first word from call data
byte(vm.PUSH1), 0x00,
byte(vm.CALLDATALOAD),
// Store it to slot 0
byte(vm.PUSH1), 0x00,
byte(vm.SSTORE),
}
deployData := append(deployPrefix, sstoreContract...)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
test.activateRegolith(ctx, opGeth)
fromAddr := cfg.Secrets.Addresses().Alice
storeContractAddr := crypto.CreateAddress(fromAddr, 0)
// Deposit TX to deploy a contract that lets us store an arbitrary value
deployTx := types.NewTx(&types.DepositTx{
From: fromAddr,
Value: common.Big0,
Data: deployData,
Gas: 1_000_000,
IsSystemTransaction: false,
})
// Store a non-zero value
storeTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &storeContractAddr,
Value: common.Big0,
Data: []byte{0x06},
Gas: 1_000_000,
IsSystemTransaction: false,
})
// Store a non-zero value
zeroTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &storeContractAddr,
Value: common.Big0,
Data: []byte{0x00},
Gas: 1_000_000,
IsSystemTransaction: false,
})
// Store a non-zero value again
// Has same gas cost as zeroTx, except the first tx gets a gas refund for clearing the storage slot
rezeroTx := types.NewTx(&types.DepositTx{
From: fromAddr,
To: &storeContractAddr,
Value: common.Big0,
Data: []byte{0x00},
Gas: 1_000_001,
IsSystemTransaction: false,
})
_, err = opGeth.AddL2Block(ctx, deployTx, storeTx, zeroTx, rezeroTx)
require.NoError(t, err)
// Sanity check the contract code deployed correctly
code, err := opGeth.L2Client.CodeAt(ctx, storeContractAddr, nil)
require.NoError(t, err)
require.Equal(t, sstoreContract, code, "should create contract with expected code")
deployReceipt, err := opGeth.L2Client.TransactionReceipt(ctx, deployTx.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, deployReceipt.Status)
require.Equal(t, storeContractAddr, deployReceipt.ContractAddress, "should create contract at expected address")
storeReceipt, err := opGeth.L2Client.TransactionReceipt(ctx, storeTx.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, storeReceipt.Status, "setting storage value should succeed")
zeroReceipt, err := opGeth.L2Client.TransactionReceipt(ctx, zeroTx.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, zeroReceipt.Status, "zeroing storage value should succeed")
rezeroReceipt, err := opGeth.L2Client.TransactionReceipt(ctx, rezeroTx.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, rezeroReceipt.Status, "rezeroing storage value should succeed")
require.Greater(t, rezeroReceipt.GasUsed, zeroReceipt.GasUsed, "rezero should use more gas due to not getting gas refund for clearing slot")
})
}
} }
...@@ -212,7 +212,7 @@ func (sys *System) Close() { ...@@ -212,7 +212,7 @@ func (sys *System) Close() {
sys.L2OutputSubmitter.Stop() sys.L2OutputSubmitter.Stop()
} }
if sys.BatchSubmitter != nil { if sys.BatchSubmitter != nil {
sys.BatchSubmitter.Stop() sys.BatchSubmitter.StopIfRunning()
} }
for _, node := range sys.RollupNodes { for _, node := range sys.RollupNodes {
...@@ -309,6 +309,7 @@ func (cfg SystemConfig) Start() (*System, error) { ...@@ -309,6 +309,7 @@ func (cfg SystemConfig) Start() (*System, error) {
BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress, BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress,
DepositContractAddress: predeploys.DevOptimismPortalAddr, DepositContractAddress: predeploys.DevOptimismPortalAddr,
L1SystemConfigAddress: predeploys.DevSystemConfigAddr, L1SystemConfigAddress: predeploys.DevSystemConfigAddr,
RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
} }
} }
defaultConfig := makeRollupConfig() defaultConfig := makeRollupConfig()
......
...@@ -1173,6 +1173,101 @@ func TestStopStartSequencer(t *testing.T) { ...@@ -1173,6 +1173,101 @@ func TestStopStartSequencer(t *testing.T) {
require.Greater(t, blockAfter, blockBefore, "Chain did not advance after starting sequencer") require.Greater(t, blockAfter, blockBefore, "Chain did not advance after starting sequencer")
} }
func TestStopStartBatcher(t *testing.T) {
parallel(t)
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
cfg := DefaultSystemConfig(t)
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
defer sys.Close()
rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["verifier"].HTTPEndpoint())
require.Nil(t, err)
rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient))
l2Seq := sys.Clients["sequencer"]
l2Verif := sys.Clients["verifier"]
// retrieve the initial sync status
seqStatus, err := rollupClient.SyncStatus(context.Background())
require.Nil(t, err)
nonce := uint64(0)
sendTx := func() *types.Receipt {
// Submit TX to L2 sequencer node
tx := types.MustSignNewTx(cfg.Secrets.Alice, types.LatestSignerForChainID(cfg.L2ChainIDBig()), &types.DynamicFeeTx{
ChainID: cfg.L2ChainIDBig(),
Nonce: nonce,
To: &common.Address{0xff, 0xff},
Value: big.NewInt(1_000_000_000),
GasTipCap: big.NewInt(10),
GasFeeCap: big.NewInt(200),
Gas: 21000,
})
nonce++
err = l2Seq.SendTransaction(context.Background(), tx)
require.Nil(t, err, "Sending L2 tx to sequencer")
// Let it show up on the unsafe chain
receipt, err := waitForTransaction(tx.Hash(), l2Seq, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "Waiting for L2 tx on sequencer")
return receipt
}
// send a transaction
receipt := sendTx()
// wait until the block the tx was first included in shows up in the safe chain on the verifier
safeBlockInclusionDuration := time.Duration(3*cfg.DeployConfig.L1BlockTime) * time.Second
_, err = waitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration)
require.Nil(t, err, "Waiting for block on verifier")
// ensure the safe chain advances
newSeqStatus, err := rollupClient.SyncStatus(context.Background())
require.Nil(t, err)
require.Greater(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain did not advance")
// stop the batch submission
err = sys.BatchSubmitter.Stop()
require.Nil(t, err)
// wait for any old safe blocks being submitted / derived
time.Sleep(safeBlockInclusionDuration)
// get the initial sync status
seqStatus, err = rollupClient.SyncStatus(context.Background())
require.Nil(t, err)
// send another tx
sendTx()
time.Sleep(safeBlockInclusionDuration)
// ensure that the safe chain does not advance while the batcher is stopped
newSeqStatus, err = rollupClient.SyncStatus(context.Background())
require.Nil(t, err)
require.Equal(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain advanced while batcher was stopped")
// start the batch submission
err = sys.BatchSubmitter.Start()
require.Nil(t, err)
time.Sleep(safeBlockInclusionDuration)
// send a third tx
receipt = sendTx()
// wait until the block the tx was first included in shows up in the safe chain on the verifier
_, err = waitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration)
require.Nil(t, err, "Waiting for block on verifier")
// ensure that the safe chain advances after restarting the batcher
newSeqStatus, err = rollupClient.SyncStatus(context.Background())
require.Nil(t, err)
require.Greater(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain did not advance after batcher was restarted")
}
func safeAddBig(a *big.Int, b *big.Int) *big.Int { func safeAddBig(a *big.Int, b *big.Int) *big.Int {
return new(big.Int).Add(a, b) return new(big.Int).Add(a, b)
} }
......
...@@ -32,11 +32,11 @@ require ( ...@@ -32,11 +32,11 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.3.8 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/appengine v1.6.6 // indirect google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
......
...@@ -558,8 +558,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT ...@@ -558,8 +558,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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=
...@@ -635,13 +635,13 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w ...@@ -635,13 +635,13 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.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-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
...@@ -651,8 +651,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ...@@ -651,8 +651,8 @@ 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/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.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
...@@ -215,6 +215,7 @@ func (n *OpNode) initRPCServer(ctx context.Context, cfg *Config) error { ...@@ -215,6 +215,7 @@ func (n *OpNode) initRPCServer(ctx context.Context, cfg *Config) error {
} }
if cfg.RPC.EnableAdmin { if cfg.RPC.EnableAdmin {
server.EnableAdminAPI(NewAdminAPI(n.l2Driver, n.metrics)) server.EnableAdminAPI(NewAdminAPI(n.l2Driver, n.metrics))
n.log.Info("Admin RPC enabled")
} }
n.log.Info("Starting JSON-RPC server") n.log.Info("Starting JSON-RPC server")
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
......
...@@ -115,6 +115,7 @@ services: ...@@ -115,6 +115,7 @@ services:
ports: ports:
- "6061:6060" - "6061:6060"
- "7301:7300" - "7301:7300"
- "6545:8545"
environment: environment:
OP_BATCHER_L1_ETH_RPC: http://l1:8545 OP_BATCHER_L1_ETH_RPC: http://l1:8545
OP_BATCHER_L2_ETH_RPC: http://l2:8545 OP_BATCHER_L2_ETH_RPC: http://l2:8545
...@@ -130,10 +131,10 @@ services: ...@@ -130,10 +131,10 @@ services:
OP_BATCHER_RESUBMISSION_TIMEOUT: 30s OP_BATCHER_RESUBMISSION_TIMEOUT: 30s
OP_BATCHER_MNEMONIC: test test test test test test test test test test test junk OP_BATCHER_MNEMONIC: test test test test test test test test test test test junk
OP_BATCHER_SEQUENCER_HD_PATH: "m/44'/60'/0'/0/2" OP_BATCHER_SEQUENCER_HD_PATH: "m/44'/60'/0'/0/2"
OP_BATCHER_SEQUENCER_BATCH_INBOX_ADDRESS: "${SEQUENCER_BATCH_INBOX_ADDRESS}"
OP_BATCHER_LOG_TERMINAL: "true" OP_BATCHER_LOG_TERMINAL: "true"
OP_BATCHER_PPROF_ENABLED: "true" OP_BATCHER_PPROF_ENABLED: "true"
OP_BATCHER_METRICS_ENABLED: "true" OP_BATCHER_METRICS_ENABLED: "true"
OP_BATCHER_RPC_ENABLE_ADMIN: "true"
stateviz: stateviz:
build: build:
......
...@@ -6,11 +6,11 @@ import subprocess ...@@ -6,11 +6,11 @@ import subprocess
import os import os
GETH_VERSION='v1.10.26' GETH_VERSION='v1.11.2'
def main(): def main():
for project in ('.', 'op-wheel', 'indexer'): for project in ('.', 'indexer'):
print(f'Updating {project}...') print(f'Updating {project}...')
update_mod(project) update_mod(project)
...@@ -22,7 +22,7 @@ def update_mod(project): ...@@ -22,7 +22,7 @@ def update_mod(project):
'mod', 'mod',
'edit', 'edit',
'-replace', '-replace',
f'github.com/ethereum/go-ethereum@{GETH_VERSION}=github.com/ethereum-optimism/op-geth@optimism-history' f'github.com/ethereum/go-ethereum@{GETH_VERSION}=github.com/ethereum-optimism/op-geth@optimism'
], cwd=os.path.join(project), check=True) ], cwd=os.path.join(project), check=True)
print('Tidying...') print('Tidying...')
subprocess.run([ subprocess.run([
......
{
"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.
<div align="center">
<br />
<br />
<a href="https://optimism.io"><img alt="Optimism" src="https://raw.githubusercontent.com/ethereum-optimism/brand-kit/main/assets/svg/OPTIMISM-R.svg" width=600></a>
<br />
<h3>@eth-optimism/atst</h3> The official SDK and cli for Optimism's attestation Station
<br />
</div>
<p align="center">
<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
### Visit [Docs](https://community.optimism.io/docs/governance/attestation-station/) for general documentation on the attestation station!
## Getting started
Install
```bash
npm install @eth-optimism/atst wagmi @wagmi/core ethers@5.7.0
```
## atst typescript sdk
The typescript sdk provides a clean [wagmi](https://wagmi.sh/) based interface for reading and writing to the attestation station
### See [sdk docs]() for usage instructions
## atst cli
The cli provides a convenient cli for interacting with the attestation station contract
TODO put a gif here of using it
## React instructions
For react hooks we recomend using the [wagmi cli](https://wagmi.sh/cli/getting-started) with the [etherscan plugin](https://wagmi.sh/cli/plugins/etherscan) and [react plugin](https://wagmi.sh/cli/plugins/react) to automatically generate react hooks around the attestation station. See [example/react](http://todo.todo.todo) for an example.
Use `parseAttestationBytes` and `stringifyAttestationBytes` to parse and stringify attestations before passing them into wagmi hooks.
## Contributing
Please see our [contributing.md](/docs/contributing.md). No contribution is too small.
Having your contribution denied feels bad. Please consider opening an issue before adding any new features or apis
## Check [Awesome ATST](https://todo.todo.todo) for awesome tools and examples around the attestation station
# atst cli docs
## Installation
```bash
npm install @eth-optimism/atst --global
```
## Usage
```bash
npx atst <command> [options]
```
## Commands
read read an attestation
write write an attestation
For more info, run any command with the `--help` flag:
```bash
npx atst read --help
npx atst write --help
```
## Options:
-h, --help Display this message
-v, --version Display version number
## Usage:
```bash
npx atst <command> [options]
```
Commands:
read read an attestation
write write an attestation
For more info, run any command with the `--help` flag:
```bash
npx atst read --help
npx atst write --help
```
### Read
`--creator <string> Address of the creator of the attestation`
`--about <string> Address of the subject of the attestation`
`--key <string> Key of the attestation either as string or hex number`
`--data-type [string] Zod validator for the DataType type string | bytes | number | bool | address (default: string)`
`--rpc-url [url] Rpc url to use (default: https://mainnet.optimism.io)`
`--contract [address] Contract address to read from (default: 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)`
`-h, --help Display this message`
Example:
```bash
npx atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3
```
### write
`--private-key <string> Address of the creator of the attestation`
`--data-type [string] Zod validator for the DataType type string | bytes | number | bool | address (default: string)`
`--about <string> Address of the subject of the attestation`
`--key <string> Key of the attestation either as string or hex number`
`--value <string> undefined`
`--rpc-url [url] Rpc url to use (default: https://mainnet.optimism.io)`
`--contract [address] Contract address to read from (default: 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77) -h, --help Display this message`
```bash
atst write --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --value "my attestation" --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545
atst/0.0.0
```
# atst sdk docs
Typescript sdk for interacting with the ATST based on [@wagmi/core](https://wagmi.sh/core/getting-started)
TODO add a table of contents like [zod](https://github.com/colinhacks/zod/blob/master/README.md)
## Installation
Install atst and it's peer dependencies.
npm
```bash
npm i @eth-optimism/atst @wagmi/core ethers@5.7.0
```
pnpm
```bash
pnpm i @eth-optimism/atst @wagmi/core ethers@5.7.0
```
yarn
```bash
yarn add @eth-optimism/atst @wagmi/core ethers@5.7.0
```
**Note** as ethers v6 is not yet stable we only support ethers v5 at this time
## Basic usage
### Basic Setup
ATST uses `@wagmi/core` under the hood. See their documentation for more information.
```typescript
import { connect, createClient } from '@wagmi/core'
import { providers, Wallet } from 'ethers'
const provider = new providers.JsonRpcProvider({
url: parsedOptions.rpcUrl,
headers: {
'User-Agent': '@eth-optimism/atst',
},
})
createClient({
provider,
})
```
### Reading an attestation
Here is an example of reading an attestation used by the optimist nft
```typescript
import { readAttestation } from '@eth-optimism/atst'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const dataType = 'string' // note string is default
const attestation = await readAttestation(creator, about, key, dataType)
console.log(attestation) // https://assets.optimism.io/4a609661-6774-441f-9fdb-453fdbb89931-bucket/optimist-nft/attributes
```
If reading more than one attestation you can use readAttestations to read them with multicall
### Writing an attestation
To write to an attestation you must [connect](https://wagmi.sh/core/connectors/metaMask) your wagmi client if not already connected. If using Node.js use the [mock connector](https://wagmi.sh/core/connectors/mock)
```typescript
import { prepareWriteAttestation, writeAttestation } from '@eth-optimism/sdk'
const preparedTx = await prepareWriteAttestation(about, key, 'hello world')
console.log(preparedTx.gasLimit)
await writeAttestation(preparedTx)
```
## API
### ATTESTATION_STATION_ADDRESS
The deterministic deployment address for the attestation station currently deployed with create2 on Optimism and Optimism Goerli `0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77`
```typescript
import { ATTESTATION_STATION_ADDRESS } from '@eth-optimism/atst'
```
### abi
The abi of the attestation station
```typescript
import { abi } from '@eth-optimism/atst'
```
### readAttestation
[Reads](https://wagmi.sh/core/actions/readContract) and parses an attestation based on it's data type.
```typescript
const attestation = await readAttestation(
/**
* Address: The creator of the attestation
*/
creator,
/**
* Address: The about topic of the attestation
*/
about,
/**
* string: The key of the attestation
*/
key,
/**
* 'string' | 'bytes' | 'number' | 'bool' | 'address'
* The data type of the attestation
* @defaults defaults to 'string'
*/
dataType,
/**
* Address: the contract address of the attestation station
* @defaults defaults to the create2 address
*/
contractAddress
)
```
`Return Value` The data returned from invoking the contract method.
### readAttestations
Similar to read attestation but reads multiple attestations at once. Pass in a variadic amount of attestations to read.
```typescript
const attestation = await readAttestations({
/**
* Address: The creator of the attestation
*/
creator,
/**
* Address: The about topic of the attestation
*/
about,
/**
* string: The key of the attestation
*/
key,
/**
* 'string' | 'bytes' | 'number' | 'bool' | 'address'
* The data type of the attestation
* @defaults defaults to 'string'
*/
dataType,
/**
* Address: the contract address of the attestation station
* @defaults defaults to the create2 address
*/
contractAddress,
/**
* Boolean: Whether to allow some of the calls to fail
* Defaults to false
*/
allowFailures,
})
```
### parseAttestationBytes
Parses raw bytes from the attestation station based on the type.
Note: `readAttestation` and `readAttestations` already parse the bytes so this is only necessary if reading attestations directly from chain instead of through this sdkA
```typescript
const attestation = parseAttestationBytes(
/**
* HexString: The raw bytes returned from reading an attestation
*/
bytes,
/**
* 'string' | 'bytes' | 'number' | 'bool' | 'address'
* The data type of the attestation
* @defaults defaults to 'string'
*/
dataType
)
```
### prepareWriteAttestation
[Prepares](https://wagmi.sh/core/actions/prepareWriteContract) an attestation to be written.
```typescript
const preparedTx = await prepareWriteAttestation(about, key, 'hello world')
console.log(preparedTx.gasFee)
```
### stringifyAttestationBytes
Stringifys an attestation into raw bytes.
Note: `writeAttestation` already does this for you so this is only needed if using a library other than the attestation station
```typescript
const stringAttestatoin = stringifyAttestationBytes('hello world')
const numberAttestation = stringifyAttestationBytes(500)
const hexAttestation = stringifyAttestationBytes('0x1')
const bigNumberAttestation = stringifyAttestationBytes(
BigNumber.from('9999999999999999999999999')
)
```
### writeAttestation
[Writes the prepared tx](https://wagmi.sh/core/actions/writeContract)
```typescript
const preparedTx = await prepareWriteAttestation(about, key, 'hello world')
await writeAttestation(preparedTx)
```
{
"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",
"generate": "wagmi generate",
"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",
"picocolors": "^1.0.0",
"ora": "^6.1.2",
"zod": "^3.11.6"
},
"peerDependencies": {
"@wagmi/core": ">0.9.0",
"wagmi": ">0.11.0",
"ethers": "^5.0.0"
},
"devDependencies": {
"@testing-library/react-hooks": "^8.0.1",
"ethers": "^5.7.0",
"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",
"@wagmi/cli": "~0.1.5",
"wagmi": "~0.11.0"
}
}
# 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 | BigNumber
) => {
bytes = bytes === '0x' ? '0x0' : bytes
if (BigNumber.isBigNumber(bytes)) {
return bytes.toHexString()
}
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
// Generated by @wagmi/cli@0.1.10 on 2/26/2023 at 11:08:05 AM
import {
useNetwork,
useContract,
UseContractConfig,
useContractRead,
UseContractReadConfig,
useContractWrite,
UseContractWriteConfig,
usePrepareContractWrite,
UsePrepareContractWriteConfig,
useContractEvent,
UseContractEventConfig,
} from 'wagmi'
import {
ReadContractResult,
WriteContractMode,
PrepareWriteContractResult,
} from 'wagmi/actions'
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AttestationStation
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export const attestationStationABI = [
{ stateMutability: 'nonpayable', type: 'constructor', inputs: [] },
{
type: 'event',
anonymous: false,
inputs: [
{
name: 'creator',
internalType: 'address',
type: 'address',
indexed: true,
},
{
name: 'about',
internalType: 'address',
type: 'address',
indexed: true,
},
{ name: 'key', internalType: 'bytes32', type: 'bytes32', indexed: true },
{ name: 'val', internalType: 'bytes', type: 'bytes', indexed: false },
],
name: 'AttestationCreated',
},
{
stateMutability: 'nonpayable',
type: 'function',
inputs: [
{
name: '_attestations',
internalType: 'struct AttestationStation.AttestationData[]',
type: 'tuple[]',
components: [
{ name: 'about', internalType: 'address', type: 'address' },
{ name: 'key', internalType: 'bytes32', type: 'bytes32' },
{ name: 'val', internalType: 'bytes', type: 'bytes' },
],
},
],
name: 'attest',
outputs: [],
},
{
stateMutability: 'nonpayable',
type: 'function',
inputs: [
{ name: '_about', internalType: 'address', type: 'address' },
{ name: '_key', internalType: 'bytes32', type: 'bytes32' },
{ name: '_val', internalType: 'bytes', type: 'bytes' },
],
name: 'attest',
outputs: [],
},
{
stateMutability: 'view',
type: 'function',
inputs: [
{ name: '', internalType: 'address', type: 'address' },
{ name: '', internalType: 'address', type: 'address' },
{ name: '', internalType: 'bytes32', type: 'bytes32' },
],
name: 'attestations',
outputs: [{ name: '', internalType: 'bytes', type: 'bytes' }],
},
{
stateMutability: 'view',
type: 'function',
inputs: [],
name: 'version',
outputs: [{ name: '', internalType: 'string', type: 'string' }],
},
] as const
/**
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export const attestationStationAddress = {
10: '0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77',
420: '0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77',
31337: '0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77',
} as const
/**
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export const attestationStationConfig = {
address: attestationStationAddress,
abi: attestationStationABI,
} as const
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// React
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Wraps __{@link useContract}__ with `abi` set to __{@link attestationStationABI}__.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStation(
config: Omit<UseContractConfig, 'abi' | 'address'> & {
chainId?: keyof typeof attestationStationAddress
} = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContract({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
...config,
})
}
/**
* Wraps __{@link useContractRead}__ with `abi` set to __{@link attestationStationABI}__.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationRead<
TFunctionName extends string,
TSelectData = ReadContractResult<typeof attestationStationABI, TFunctionName>
>(
config: Omit<
UseContractReadConfig<
typeof attestationStationABI,
TFunctionName,
TSelectData
>,
'abi' | 'address'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractRead({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
...config,
} as UseContractReadConfig<
typeof attestationStationABI,
TFunctionName,
TSelectData
>)
}
/**
* Wraps __{@link useContractRead}__ with `abi` set to __{@link attestationStationABI}__ and `functionName` set to `"attestations"`.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationAttestations<
TSelectData = ReadContractResult<typeof attestationStationABI, 'attestations'>
>(
config: Omit<
UseContractReadConfig<
typeof attestationStationABI,
'attestations',
TSelectData
>,
'abi' | 'address' | 'functionName'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractRead({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
functionName: 'attestations',
...config,
} as UseContractReadConfig<
typeof attestationStationABI,
'attestations',
TSelectData
>)
}
/**
* Wraps __{@link useContractRead}__ with `abi` set to __{@link attestationStationABI}__ and `functionName` set to `"version"`.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationVersion<
TSelectData = ReadContractResult<typeof attestationStationABI, 'version'>
>(
config: Omit<
UseContractReadConfig<typeof attestationStationABI, 'version', TSelectData>,
'abi' | 'address' | 'functionName'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractRead({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
functionName: 'version',
...config,
} as UseContractReadConfig<
typeof attestationStationABI,
'version',
TSelectData
>)
}
/**
* Wraps __{@link useContractWrite}__ with `abi` set to __{@link attestationStationABI}__.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationWrite<
TMode extends WriteContractMode,
TFunctionName extends string,
TChainId extends number = keyof typeof attestationStationAddress
>(
config: TMode extends 'prepared'
? UseContractWriteConfig<
TMode,
PrepareWriteContractResult<typeof attestationStationABI, string>['abi'],
TFunctionName
> & { address?: `0x${string}`; chainId?: TChainId }
: UseContractWriteConfig<
TMode,
typeof attestationStationABI,
TFunctionName
> & {
abi?: never
address?: never
chainId?: TChainId
} = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractWrite<TMode, typeof attestationStationABI, TFunctionName>({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
...config,
} as any)
}
/**
* Wraps __{@link useContractWrite}__ with `abi` set to __{@link attestationStationABI}__ and `functionName` set to `"attest"`.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationAttest<
TMode extends WriteContractMode,
TChainId extends number = keyof typeof attestationStationAddress
>(
config: TMode extends 'prepared'
? UseContractWriteConfig<
TMode,
PrepareWriteContractResult<
typeof attestationStationABI,
'attest'
>['abi'],
'attest'
> & {
address?: `0x${string}`
chainId?: TChainId
functionName?: 'attest'
}
: UseContractWriteConfig<TMode, typeof attestationStationABI, 'attest'> & {
abi?: never
address?: never
chainId?: TChainId
functionName?: 'attest'
} = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractWrite<TMode, typeof attestationStationABI, 'attest'>({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
functionName: 'attest',
...config,
} as any)
}
/**
* Wraps __{@link usePrepareContractWrite}__ with `abi` set to __{@link attestationStationABI}__.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function usePrepareAttestationStationWrite<TFunctionName extends string>(
config: Omit<
UsePrepareContractWriteConfig<typeof attestationStationABI, TFunctionName>,
'abi' | 'address'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return usePrepareContractWrite({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
...config,
} as UsePrepareContractWriteConfig<
typeof attestationStationABI,
TFunctionName
>)
}
/**
* Wraps __{@link usePrepareContractWrite}__ with `abi` set to __{@link attestationStationABI}__ and `functionName` set to `"attest"`.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function usePrepareAttestationStationAttest(
config: Omit<
UsePrepareContractWriteConfig<typeof attestationStationABI, 'attest'>,
'abi' | 'address' | 'functionName'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return usePrepareContractWrite({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
functionName: 'attest',
...config,
} as UsePrepareContractWriteConfig<typeof attestationStationABI, 'attest'>)
}
/**
* Wraps __{@link useContractEvent}__ with `abi` set to __{@link attestationStationABI}__.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationEvent<TEventName extends string>(
config: Omit<
UseContractEventConfig<typeof attestationStationABI, TEventName>,
'abi' | 'address'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractEvent({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
...config,
} as UseContractEventConfig<typeof attestationStationABI, TEventName>)
}
/**
* Wraps __{@link useContractEvent}__ with `abi` set to __{@link attestationStationABI}__ and `eventName` set to `"AttestationCreated"`.
*
* - [__View Contract on Optimism Optimism Explorer__](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* - [__View Contract on Optimism Goerli Etherscan__](https://goerli-optimism.etherscan.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)
* -
*/
export function useAttestationStationAttestationCreatedEvent(
config: Omit<
UseContractEventConfig<typeof attestationStationABI, 'AttestationCreated'>,
'abi' | 'address' | 'eventName'
> & { chainId?: keyof typeof attestationStationAddress } = {} as any
) {
const { chain } = useNetwork()
const chainId = config.chainId ?? chain?.id
return useContractEvent({
abi: attestationStationABI,
address:
attestationStationAddress[
chainId as keyof typeof attestationStationAddress
],
eventName: 'AttestationCreated',
...config,
} as UseContractEventConfig<
typeof attestationStationABI,
'AttestationCreated'
>)
}
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,
},
})
import { defineConfig } from '@wagmi/cli'
import { hardhat, react } from '@wagmi/cli/plugins'
import * as chains from 'wagmi/chains'
import {ATTESTATION_STATION_ADDRESS} from '@eth-optimism/atst'
export default defineConfig({
out: 'src/react.ts',
plugins: [
hardhat({
project: '../contracts-periphery',
include: ['AttestationStation.json'],
deployments: {
AttestationStation: {
[chains.optimism.id]: ATTESTATION_STATION_ADDRESS,
[chains.optimismGoerli.id]: ATTESTATION_STATION_ADDRESS,
[chains.foundry.id]: ATTESTATION_STATION_ADDRESS,
}
},
}),
react(),
],
})
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_acrossMultipleWords_works() (gas: 9423)
Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1418) Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1418)
Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17154) Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17154)
...@@ -17,9 +6,6 @@ Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 129874) ...@@ -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_expectedResult5Bytes_works() (gas: 6132)
Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 944) Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 944)
CrossDomainMessenger_BaseGas_Test:test_baseGas_succeeds() (gas: 20097) 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_notMessenger_reverts() (gas: 8416)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner2_reverts() (gas: 62010) CrossDomainOwnable2_Test:test_onlyOwner_notOwner2_reverts() (gas: 62010)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner_reverts() (gas: 16566) CrossDomainOwnable2_Test:test_onlyOwner_notOwner_reverts() (gas: 16566)
...@@ -36,10 +22,24 @@ CrossDomainOwnable3_Test:test_transferOwnershipNoLocal_succeeds() (gas: 48610) ...@@ -36,10 +22,24 @@ CrossDomainOwnable3_Test:test_transferOwnershipNoLocal_succeeds() (gas: 48610)
CrossDomainOwnable3_Test:test_transferOwnership_noLocalZeroAddress_reverts() (gas: 12015) CrossDomainOwnable3_Test:test_transferOwnership_noLocalZeroAddress_reverts() (gas: 12015)
CrossDomainOwnable3_Test:test_transferOwnership_notOwner_reverts() (gas: 13437) CrossDomainOwnable3_Test:test_transferOwnership_notOwner_reverts() (gas: 13437)
CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 12081) 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_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395) DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
FeeVault_Test:test_constructor_succeeds() (gas: 10736) FeeVault_Test:test_constructor_succeeds() (gas: 10736)
FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713) 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_baseFee_succeeds() (gas: 8325)
GasPriceOracle_Test:test_decimals_succeeds() (gas: 6167) GasPriceOracle_Test:test_decimals_succeeds() (gas: 6167)
GasPriceOracle_Test:test_gasPrice_succeeds() (gas: 8294) GasPriceOracle_Test:test_gasPrice_succeeds() (gas: 8294)
...@@ -59,15 +59,15 @@ GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 108592) ...@@ -59,15 +59,15 @@ GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 108592)
GovernanceToken_Test:test_transferFrom_succeeds() (gas: 146273) GovernanceToken_Test:test_transferFrom_succeeds() (gas: 146273)
GovernanceToken_Test:test_transfer_succeeds() (gas: 138108) GovernanceToken_Test:test_transfer_succeeds() (gas: 138108)
Hashing_hashDepositSource_Test:test_hashDepositSource_succeeds() (gas: 633) 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_basefee_succeeds() (gas: 7554)
L1BlockTest:test_hash_succeeds() (gas: 7576) L1BlockTest:test_hash_succeeds() (gas: 7576)
L1BlockTest:test_number_succeeds() (gas: 7629) L1BlockTest:test_number_succeeds() (gas: 7629)
L1BlockTest:test_sequenceNumber_succeeds() (gas: 7630) L1BlockTest:test_sequenceNumber_succeeds() (gas: 7630)
L1BlockTest:test_timestamp_succeeds() (gas: 7640) L1BlockTest:test_timestamp_succeeds() (gas: 7640)
L1BlockTest:test_updateValues_succeeds() (gas: 60482) 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_messageVersion_succeeds() (gas: 24715)
L1CrossDomainMessenger_Test:test_relayMessage_legacyOldReplay_reverts() (gas: 49394) L1CrossDomainMessenger_Test:test_relayMessage_legacyOldReplay_reverts() (gas: 49394)
L1CrossDomainMessenger_Test:test_relayMessage_legacyRetryAfterFailureThenSuccess_reverts() (gas: 228293) L1CrossDomainMessenger_Test:test_relayMessage_legacyRetryAfterFailureThenSuccess_reverts() (gas: 228293)
...@@ -242,19 +242,19 @@ OptimismMintableERC20_Test:test_mint_notBridge_reverts() (gas: 11121) ...@@ -242,19 +242,19 @@ OptimismMintableERC20_Test:test_mint_notBridge_reverts() (gas: 11121)
OptimismMintableERC20_Test:test_mint_succeeds() (gas: 63566) OptimismMintableERC20_Test:test_mint_succeeds() (gas: 63566)
OptimismMintableERC20_Test:test_remoteToken_succeeds() (gas: 7689) OptimismMintableERC20_Test:test_remoteToken_succeeds() (gas: 7689)
OptimismMintableERC20_Test:test_semver_succeeds() (gas: 8812) OptimismMintableERC20_Test:test_semver_succeeds() (gas: 8812)
OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580) OptimismMintableERC721Factory_Test:test_constructor_succeeds() (gas: 8285)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390) OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_succeeds() (gas: 2336687)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_succeeds() (gas: 2523203) OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_zeroRemoteToken_reverts() (gas: 9418)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1268564)
OptimismMintableERC721_Test:test_burn_notBridge_reverts() (gas: 136966) OptimismMintableERC721_Test:test_burn_notBridge_reverts() (gas: 136966)
OptimismMintableERC721_Test:test_burn_succeeds() (gas: 118832) OptimismMintableERC721_Test:test_burn_succeeds() (gas: 118832)
OptimismMintableERC721_Test:test_constructor_succeeds() (gas: 28279) OptimismMintableERC721_Test:test_constructor_succeeds() (gas: 28279)
OptimismMintableERC721_Test:test_safeMint_notBridge_reverts() (gas: 11098) OptimismMintableERC721_Test:test_safeMint_notBridge_reverts() (gas: 11098)
OptimismMintableERC721_Test:test_safeMint_succeeds() (gas: 140524) OptimismMintableERC721_Test:test_safeMint_succeeds() (gas: 140524)
OptimismMintableERC721_Test:test_tokenURI_succeeds() (gas: 163442) OptimismMintableERC721_Test:test_tokenURI_succeeds() (gas: 163442)
OptimismMintableERC721Factory_Test:test_constructor_succeeds() (gas: 8285) OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_succeeds() (gas: 2336687) OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390)
OptimismMintableERC721Factory_Test:test_createOptimismMintableERC721_zeroRemoteToken_reverts() (gas: 9418) 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_cannotInitImpl_reverts() (gas: 11016)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 15940) OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 15940)
OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 16056) OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 16056)
...@@ -297,17 +297,6 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127554) ...@@ -297,17 +297,6 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127554)
OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 24232) OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 24232)
OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46151) OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46151)
OptimismPortal_Test:test_unpause_succeeds() (gas: 31780) 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_chugsplashChangeProxyAdmin_succeeds() (gas: 35586)
ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675) ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675)
ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084) ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084)
...@@ -331,6 +320,17 @@ ProxyAdmin_Test:test_setAddressManager_notOwner_reverts() (gas: 10578) ...@@ -331,6 +320,17 @@ ProxyAdmin_Test:test_setAddressManager_notOwner_reverts() (gas: 10578)
ProxyAdmin_Test:test_setImplementationName_notOwner_reverts() (gas: 11111) ProxyAdmin_Test:test_setImplementationName_notOwner_reverts() (gas: 11111)
ProxyAdmin_Test:test_setImplementationName_succeeds() (gas: 38945) ProxyAdmin_Test:test_setImplementationName_succeeds() (gas: 38945)
ProxyAdmin_Test:test_setProxyType_notOwner_reverts() (gas: 10814) 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_bytestring00_succeeds() (gas: 1878)
RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1855) RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1855)
RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1876) RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1876)
...@@ -392,6 +392,8 @@ RLPWriter_writeUint_Test:test_writeUint_smallint3_succeeds() (gas: 7301) ...@@ -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_smallint4_succeeds() (gas: 7302)
RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280) RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280)
RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7749) 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_initialResourceParams_succeeds() (gas: 8983)
ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008142) ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008142)
ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18369) ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18369)
......
...@@ -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");
}
}
...@@ -21,6 +21,6 @@ require ( ...@@ -21,6 +21,6 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect github.com/tklauser/numcpus v0.2.2 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // 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
) )
...@@ -93,13 +93,13 @@ github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefld ...@@ -93,13 +93,13 @@ github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefld
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
...@@ -114,12 +114,12 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w ...@@ -114,12 +114,12 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
......
...@@ -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;
......
...@@ -57,7 +57,7 @@ require ( ...@@ -57,7 +57,7 @@ require (
github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // 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.1.0 // indirect golang.org/x/sys v0.1.0 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
......
...@@ -524,8 +524,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh ...@@ -524,8 +524,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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=
...@@ -602,7 +602,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT ...@@ -602,7 +602,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 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=
...@@ -695,8 +695,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= ...@@ -695,8 +695,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-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=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
...@@ -28,6 +28,7 @@ with the authorization and validation conditions on L2. ...@@ -28,6 +28,7 @@ with the authorization and validation conditions on L2.
- [Validation and Authorization of Deposited Transactions](#validation-and-authorization-of-deposited-transactions) - [Validation and Authorization of Deposited Transactions](#validation-and-authorization-of-deposited-transactions)
- [Execution](#execution) - [Execution](#execution)
- [Nonce Handling](#nonce-handling) - [Nonce Handling](#nonce-handling)
- [Deposit Receipt](#deposit-receipt)
- [L1 Attributes Deposited Transaction](#l1-attributes-deposited-transaction) - [L1 Attributes Deposited Transaction](#l1-attributes-deposited-transaction)
- [Special Accounts on L2](#special-accounts-on-l2) - [Special Accounts on L2](#special-accounts-on-l2)
- [L1 Attributes Depositor Account](#l1-attributes-depositor-account) - [L1 Attributes Depositor Account](#l1-attributes-depositor-account)
...@@ -52,8 +53,9 @@ transaction types: ...@@ -52,8 +53,9 @@ transaction types:
for the rationale). for the rationale).
3. They buy their L2 gas on L1 and, as such, the L2 gas is not refundable. 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 We define a new [EIP-2718] compatible transaction type with the prefix `0x7E` to represent a deposit transaction.
byte sequence. The first version has `0x00` as the version byte and then as the following fields
A deposit has the following fields
(rlp encoded in the order they appear here): (rlp encoded in the order they appear here):
[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 [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 ...@@ -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 mint`: The ETH value to mint on L2.
- `uint256 value`: The ETH value to send to the recipient account. - `uint256 value`: The ETH value to send to the recipient account.
- `bytes data`: The input data. - `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. - `uint64 gasLimit`: The gasLimit for the L2 transaction.
In contrast to [EIP-155] transactions, this transaction type does not include signature information, In contrast to [EIP-155] transactions, this transaction type:
and makes the `from` address explicit.
- 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 [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 ...@@ -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 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. 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 ### Source hash computation
The `sourceHash` of a deposit transaction is computed based on the origin: The `sourceHash` of a deposit transaction is computed based on the origin:
...@@ -129,27 +138,47 @@ deposit contract][deposit-contract]. ...@@ -129,27 +138,47 @@ deposit contract][deposit-contract].
In order to execute a deposited transaction: In order to execute a deposited transaction:
First, the balance of the `from` account MUST be increased by the amount of `mint`. 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 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. 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 The deposit transaction is processed exactly like a type-3 (EIP-1559) transaction, with the exception of:
follows:
- No fee fields are verified: the deposit does not have any, as it pays for gas on L1.
- `CALLER` and `ORIGIN` set to `from` - No `nonce` field is verified: the deposit does not have any, it's uniquely identified by its `sourceHash`.
- `from` is unchanged from the deposit feed contract's logs (though the address may have been - No access-list is processed: the deposit has no access-list, and it is thus processed as if the access-list is empty.
[aliased][address-aliasing] by the deposit feed contract). - No check if `from` is an Externally Owner Account (EOA): the deposit is ensured not to be an EAO through L1 address
- `context.calldata` set to `data` masking, this may change in future L1 contract-deployments to e.g. enable an account-abstraction like mechanism.
- `context.gas` set to `gasLimit` - Before the Regolith upgrade:
- `context.value` set to `sendValue` - The execution output states a non-standard gas usage:
- If `isSystemTx` is false: execution output states it uses `gasLimit` gas.
No gas is bought on L2 and no refund is provided. The gas used for the deposit is subtracted from - If `isSystemTx` is true: execution output states it uses `0` gas.
the gas pool on L2. Gas usage exactly matches other transaction types (including intrinsic gas). - No gas is refunded as ETH. (either by not refunding or utilizing the fact the gas-price of the deposit is `0`)
If a deposit runs out of gas or has some other failure, the mint will succeed and the nonce of the - No transaction priority fee is charged. No payment is made to the block fee-recipient.
account will be increased, but no other state transition will occur. - 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.
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. 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 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 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). ...@@ -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 [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 Attributes Deposited Transaction
[l1-attr-deposit]: #l1-attributes-deposited-transaction [l1-attr-deposit]: #l1-attributes-deposited-transaction
...@@ -184,12 +240,18 @@ This transaction MUST have the following values: ...@@ -184,12 +240,18 @@ This transaction MUST have the following values:
3. `mint` is `0` 3. `mint` is `0`
4. `value` is `0` 4. `value` is `0`
5. `gasLimit` is set to 150,000,000. 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 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. `setL1BlockValues()` function with correct values associated with the corresponding L1 block (cf.
[reference implementation][l1-attr-ref-implem]). [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 ## Special Accounts on L2
...@@ -268,7 +330,7 @@ transaction are determined by the corresponding `TransactionDeposited` event emi ...@@ -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. 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 7. `data` is unchanged from the emitted value. Depending on the value of `isCreation` it is handled
as either calldata or contract initialization code. 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 It is `false` for user deposited transactions
### Deposit Contract ### 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