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

Merge branch 'develop' into willc/module-fix

parents 71727eae 9f0958f7
---
'@eth-optimism/atst': minor
---
Update readAttestations and prepareWriteAttestation to handle keys longer than 32 bytes
---
'@eth-optimism/fault-detector': patch
---
Fixes a bug that would cause the fault detector to error out if no outputs had been proposed yet.
...@@ -163,6 +163,10 @@ jobs: ...@@ -163,6 +163,10 @@ jobs:
description: Docker build context description: Docker build context
type: string type: string
default: "." default: "."
docker_target:
description: "target build stage"
type: string
default: ""
registry: registry:
description: Docker registry description: Docker registry
type: string type: string
...@@ -196,7 +200,7 @@ jobs: ...@@ -196,7 +200,7 @@ jobs:
DOCKER_TAGS=$(echo -ne <<parameters.docker_tags>> | sed "s/,/\n/g" | sed "s/[^a-zA-Z0-9\n]/-/g" | sed -e "s|^|-t ${IMAGE_BASE}:|") DOCKER_TAGS=$(echo -ne <<parameters.docker_tags>> | sed "s/,/\n/g" | sed "s/[^a-zA-Z0-9\n]/-/g" | sed -e "s|^|-t ${IMAGE_BASE}:|")
docker context create buildx-build docker context create buildx-build
docker buildx create --use buildx-build docker buildx create --use buildx-build
docker buildx build --platform=<<parameters.platforms>> --push \ docker buildx build --platform=<<parameters.platforms>> --target "<<parameters.docker_target>>" --push \
$(echo -ne $DOCKER_TAGS | tr '\n' ' ') \ $(echo -ne $DOCKER_TAGS | tr '\n' ' ') \
-f <<parameters.docker_file>> \ -f <<parameters.docker_file>> \
<<parameters.docker_context>> <<parameters.docker_context>>
...@@ -534,7 +538,7 @@ jobs: ...@@ -534,7 +538,7 @@ jobs:
command: | command: |
# Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional # Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional
# constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building. # constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building.
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=true OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \ OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
--format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \ --format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \
-- -timeout=20m ./... -- -timeout=20m ./...
working_directory: <<parameters.module>> working_directory: <<parameters.module>>
...@@ -845,7 +849,7 @@ jobs: ...@@ -845,7 +849,7 @@ jobs:
./hive \ ./hive \
-sim=<<parameters.sim>> \ -sim=<<parameters.sim>> \
-sim.loglevel=5 \ -sim.loglevel=5 \
-client=go-ethereum,op-geth_optimism-history,op-proposer_<<parameters.version>>,op-batcher_<<parameters.version>>,op-node_<<parameters.version>> |& tee /tmp/hive.log || echo "failed." -client=go-ethereum,op-geth_optimism,op-proposer_<<parameters.version>>,op-batcher_<<parameters.version>>,op-node_<<parameters.version>> |& tee /tmp/hive.log || echo "failed."
- run: - run:
command: | command: |
tar -cvf /tmp/workspace.tgz -C /home/circleci/project /home/circleci/project/workspace tar -cvf /tmp/workspace.tgz -C /home/circleci/project /home/circleci/project/workspace
...@@ -1132,6 +1136,14 @@ workflows: ...@@ -1132,6 +1136,14 @@ workflows:
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
context: context:
- oplabs-gcr - oplabs-gcr
- docker-publish:
name: chain-mon-docker-publish
docker_file: ./ops/docker/Dockerfile.packages
docker_name: chain-mon
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
docker_target: wd-mon
context:
- oplabs-gcr
- hive-test: - hive-test:
name: hive-test-rpc name: hive-test-rpc
version: <<pipeline.git.revision>> version: <<pipeline.git.revision>>
......
...@@ -62,8 +62,8 @@ require ( ...@@ -62,8 +62,8 @@ require (
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect github.com/tklauser/numcpus v0.4.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
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
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
......
...@@ -631,8 +631,8 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh ...@@ -631,8 +631,8 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh
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-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.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-20190121172915-509febef88a4/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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
...@@ -708,7 +708,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b ...@@ -708,7 +708,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
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-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-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-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=
...@@ -790,8 +790,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -790,8 +790,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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=
...@@ -799,8 +799,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 ...@@ -799,8 +799,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
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.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=
......
...@@ -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 v1.11.2-aea0402.0.20230301232322-c407b2a217b7 replace github.com/ethereum/go-ethereum v1.11.2 => github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b
//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 v1.11.2-aea0402.0.20230301232322-c407b2a217b7 h1:bkttBXCRDv2Mp4VoGBglr4BjS7icIuN8HS5ZFpeKfvE= github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b h1:7RNzqCwam//7PPieblo8GSIVukwrfoPO+0xT1yMp9Zw=
github.com/ethereum-optimism/op-geth v1.11.2-aea0402.0.20230301232322-c407b2a217b7/go.mod h1:/tjlXxOaovIyuF0l6+wCzr6AtDb3lYWTymmpQAQcqu8= github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b/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=
......
...@@ -4,7 +4,7 @@ go 1.17 ...@@ -4,7 +4,7 @@ go 1.17
replace ( 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.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.20230301232322-c407b2a217b7 github.com/ethereum/go-ethereum v1.11.2 => github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b
) )
require ( require (
......
...@@ -81,6 +81,7 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -81,6 +81,7 @@ func Main(version string, cliCtx *cli.Context) error {
rpcCfg.ListenAddr, rpcCfg.ListenAddr,
rpcCfg.ListenPort, rpcCfg.ListenPort,
version, version,
oprpc.WithLogger(l),
) )
if rpcCfg.EnableAdmin { if rpcCfg.EnableAdmin {
server.AddAPI(gethrpc.API{ server.AddAPI(gethrpc.API{
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -8,6 +8,8 @@ import ( ...@@ -8,6 +8,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/db" "github.com/ethereum-optimism/optimism/op-chain-ops/db"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
...@@ -22,7 +24,6 @@ import ( ...@@ -22,7 +24,6 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/hardhat" "github.com/ethereum-optimism/optimism/op-bindings/hardhat"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -46,11 +47,6 @@ func main() { ...@@ -46,11 +47,6 @@ func main() {
Usage: "Path to ovm-addresses.json", Usage: "Path to ovm-addresses.json",
Required: true, Required: true,
}, },
&cli.StringFlag{
Name: "evm-addresses",
Usage: "Path to evm-addresses.json",
Required: true,
},
&cli.StringFlag{ &cli.StringFlag{
Name: "ovm-allowances", Name: "ovm-allowances",
Usage: "Path to ovm-allowances.json", Usage: "Path to ovm-allowances.json",
...@@ -62,8 +58,8 @@ func main() { ...@@ -62,8 +58,8 @@ func main() {
Required: true, Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "evm-messages", Name: "witness-file",
Usage: "Path to evm-messages.json", Usage: "Path to witness file",
Required: true, Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
...@@ -118,30 +114,35 @@ func main() { ...@@ -118,30 +114,35 @@ func main() {
return err return err
} }
ovmAddresses, err := migration.NewAddresses(ctx.String("ovm-addresses")) ovmAddresses, err := crossdomain.NewAddresses(ctx.String("ovm-addresses"))
if err != nil { if err != nil {
return err return err
} }
evmAddresess, err := migration.NewAddresses(ctx.String("evm-addresses")) ovmAllowances, err := crossdomain.NewAllowances(ctx.String("ovm-allowances"))
if err != nil { if err != nil {
return err return err
} }
ovmAllowances, err := migration.NewAllowances(ctx.String("ovm-allowances")) ovmMessages, err := crossdomain.NewSentMessageFromJSON(ctx.String("ovm-messages"))
if err != nil { if err != nil {
return err return err
} }
ovmMessages, err := migration.NewSentMessage(ctx.String("ovm-messages")) evmMessages, evmAddresses, err := crossdomain.ReadWitnessData(ctx.String("witness-file"))
if err != nil {
return err
}
evmMessages, err := migration.NewSentMessage(ctx.String("evm-messages"))
if err != nil { if err != nil {
return err return err
} }
migrationData := migration.MigrationData{ log.Info(
"Loaded witness data",
"ovmAddresses", len(ovmAddresses),
"evmAddresses", len(evmAddresses),
"ovmAllowances", len(ovmAllowances),
"ovmMessages", len(ovmMessages),
"evmMessages", len(evmMessages),
)
migrationData := crossdomain.MigrationData{
OvmAddresses: ovmAddresses, OvmAddresses: ovmAddresses,
EvmAddresses: evmAddresess, EvmAddresses: evmAddresses,
OvmAllowances: ovmAllowances, OvmAllowances: ovmAllowances,
OvmMessages: ovmMessages, OvmMessages: ovmMessages,
EvmMessages: evmMessages, EvmMessages: evmMessages,
......
...@@ -18,8 +18,6 @@ import ( ...@@ -18,8 +18,6 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -768,7 +766,7 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy ...@@ -768,7 +766,7 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy
evmMsgs := ctx.String("evm-messages") evmMsgs := ctx.String("evm-messages")
log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs) log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs)
ovmMessages, err := migration.NewSentMessage(ovmMsgs) ovmMessages, err := crossdomain.NewSentMessageFromJSON(ovmMsgs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -777,20 +775,20 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy ...@@ -777,20 +775,20 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy
// committed to in git. // committed to in git.
if l1ChainID.Cmp(common.Big1) != 0 { if l1ChainID.Cmp(common.Big1) != 0 {
log.Info("not using ovm messages because its not mainnet") log.Info("not using ovm messages because its not mainnet")
ovmMessages = []*migration.SentMessage{} ovmMessages = []*crossdomain.SentMessage{}
} }
evmMessages, err := migration.NewSentMessage(evmMsgs) evmMessages, err := crossdomain.NewSentMessageFromJSON(evmMsgs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
migrationData := migration.MigrationData{ migrationData := crossdomain.MigrationData{
OvmMessages: ovmMessages, OvmMessages: ovmMessages,
EvmMessages: evmMessages, EvmMessages: evmMessages,
} }
wds, err := migrationData.ToWithdrawals() wds, _, err := migrationData.ToWithdrawals()
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
package migration package crossdomain
import ( import (
"errors" "errors"
......
package migration package crossdomain
import ( import (
"math/big" "math/big"
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -12,28 +13,40 @@ import ( ...@@ -12,28 +13,40 @@ import (
var ( var (
ErrUnknownSlotInMessagePasser = errors.New("unknown slot in legacy message passer") ErrUnknownSlotInMessagePasser = errors.New("unknown slot in legacy message passer")
ErrMissingSlotInWitness = errors.New("missing storage slot in witness data") ErrMissingSlotInWitness = errors.New("missing storage slot in witness data (see logs for details)")
) )
// PreCheckWithdrawals checks that the given list of withdrawals represents all withdrawals made // PreCheckWithdrawals checks that the given list of withdrawals represents all withdrawals made
// in the legacy system and filters out any extra withdrawals not included in the legacy system. // in the legacy system and filters out any extra withdrawals not included in the legacy system.
func PreCheckWithdrawals(db *state.StateDB, withdrawals DangerousUnfilteredWithdrawals) (SafeFilteredWithdrawals, error) { func PreCheckWithdrawals(db *state.StateDB, withdrawals DangerousUnfilteredWithdrawals, invalidMessages []InvalidMessage) (SafeFilteredWithdrawals, error) {
// Convert each withdrawal into a storage slot, and build a map of those slots. // Convert each withdrawal into a storage slot, and build a map of those slots.
slotsInp := make(map[common.Hash]*LegacyWithdrawal) validSlotsInp := make(map[common.Hash]*LegacyWithdrawal)
for _, wd := range withdrawals { for _, wd := range withdrawals {
slot, err := wd.StorageSlot() slot, err := wd.StorageSlot()
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot check withdrawals: %w", err) return nil, fmt.Errorf("cannot check withdrawals: %w", err)
} }
slotsInp[slot] = wd validSlotsInp[slot] = wd
}
// Convert each invalid message into a storage slot, and build a map of those slots.
invalidSlotsInp := make(map[common.Hash]InvalidMessage)
for _, msg := range invalidMessages {
slot, err := msg.StorageSlot()
if err != nil {
return nil, fmt.Errorf("cannot check invalid messages: %w", err)
}
invalidSlotsInp[slot] = msg
} }
// Build a mapping of the slots of all messages actually sent in the legacy system. // Build a mapping of the slots of all messages actually sent in the legacy system.
var count int var count int
var innerErr error var innerErr error
slotsAct := make(map[common.Hash]bool) slotsAct := make(map[common.Hash]bool)
progress := util.ProgressLogger(1000, "Iterating legacy messages")
err := db.ForEachStorage(predeploys.LegacyMessagePasserAddr, func(key, value common.Hash) bool { err := db.ForEachStorage(predeploys.LegacyMessagePasserAddr, func(key, value common.Hash) bool {
progress()
// When a message is inserted into the LegacyMessagePasser, it is stored with the value // When a message is inserted into the LegacyMessagePasser, it is stored with the value
// of the ABI encoding of "true". Although there should not be any other storage slots, we // of the ABI encoding of "true". Although there should not be any other storage slots, we
// can safely ignore anything that is not "true". // can safely ignore anything that is not "true".
...@@ -59,24 +72,32 @@ func PreCheckWithdrawals(db *state.StateDB, withdrawals DangerousUnfilteredWithd ...@@ -59,24 +72,32 @@ func PreCheckWithdrawals(db *state.StateDB, withdrawals DangerousUnfilteredWithd
log.Info("Iterated legacy messages", "count", count) log.Info("Iterated legacy messages", "count", count)
// Iterate over the list of actual slots and check that we have an input message for each one. // Iterate over the list of actual slots and check that we have an input message for each one.
var missing int
for slot := range slotsAct { for slot := range slotsAct {
_, ok := slotsInp[slot] _, okValid := validSlotsInp[slot]
if !ok { _, okInvalid := invalidSlotsInp[slot]
return nil, ErrMissingSlotInWitness if !okValid && !okInvalid {
log.Error("missing storage slot", "slot", slot.String())
missing++
} }
} }
if missing > 0 {
log.Error("missing storage slots in witness data", "count", missing)
return nil, ErrMissingSlotInWitness
}
// Iterate over the list of input messages and check that we have a known slot for each one. // Iterate over the list of input messages and check that we have a known slot for each one.
// We'll filter out any extra messages that are not in the legacy system. // We'll filter out any extra messages that are not in the legacy system.
filtered := make(SafeFilteredWithdrawals, 0) filtered := make(SafeFilteredWithdrawals, 0)
for slot := range slotsInp { for slot := range validSlotsInp {
_, ok := slotsAct[slot] _, ok := slotsAct[slot]
if !ok { if !ok {
log.Info("filtering out unknown input message", "slot", slot.String()) log.Info("filtering out unknown input message", "slot", slot.String())
continue continue
} }
wd := slotsInp[slot] wd := validSlotsInp[slot]
if wd.MessageSender != predeploys.L2CrossDomainMessengerAddr { if wd.MessageSender != predeploys.L2CrossDomainMessengerAddr {
log.Info("filtering out message from sender other than the L2XDM", "sender", wd.MessageSender) log.Info("filtering out message from sender other than the L2XDM", "sender", wd.MessageSender)
continue continue
......
...@@ -71,7 +71,7 @@ func TestPreCheckWithdrawals_InvalidSlotInStorage(t *testing.T) { ...@@ -71,7 +71,7 @@ func TestPreCheckWithdrawals_InvalidSlotInStorage(t *testing.T) {
err = stateDB.Database().TrieDB().Commit(root, true) err = stateDB.Database().TrieDB().Commit(root, true)
require.NoError(t, err) require.NoError(t, err)
_, err = PreCheckWithdrawals(stateDB, nil) _, err = PreCheckWithdrawals(stateDB, nil, nil)
require.ErrorIs(t, err, ErrUnknownSlotInMessagePasser) require.ErrorIs(t, err, ErrUnknownSlotInMessagePasser)
} }
...@@ -130,5 +130,5 @@ func runPrecheck(t *testing.T, dbWds []*LegacyWithdrawal, witnessWds []*LegacyWi ...@@ -130,5 +130,5 @@ func runPrecheck(t *testing.T, dbWds []*LegacyWithdrawal, witnessWds []*LegacyWi
err = stateDB.Database().TrieDB().Commit(root, true) err = stateDB.Database().TrieDB().Commit(root, true)
require.NoError(t, err) require.NoError(t, err)
return PreCheckWithdrawals(stateDB, witnessWds) return PreCheckWithdrawals(stateDB, witnessWds, nil)
} }
MSG|0x4200000000000000000000000000000000000007|cafa81dc000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001a4cbd4ece900000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be1000000000000000000000000420000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000019bd000000000000000000000000000000000000000000000000000000000000000e4a9f9e675000000000000000000000000d533a949740bb3306d119cc777fa900ba034cd520000000000000000000000000994206dfe8de6ec6920ff4d779b0d950605fb53000000000000000000000000e3a44dd2a8c108be56a78635121ec914074da16d000000000000000000000000e3a44dd2a8c108be56a78635121ec914074da16d0000000000000000000000000000000000000000000001b0ac98ab3858d7547800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
MSG|0x8B1d477410344785ff1DF52500032E6D5f532EE4|cafa81dc000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030420690000000000000000000000000000000000000000000000000000000000
ETH|0x6340d44c5174588B312F545eEC4a42f8a514eF50
\ No newline at end of file
package crossdomain package crossdomain
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
// DangerousUnfilteredWithdrawals is a list of raw withdrawal witness // DangerousUnfilteredWithdrawals is a list of raw withdrawal witness
...@@ -30,3 +33,35 @@ type WithdrawalMessage interface { ...@@ -30,3 +33,35 @@ type WithdrawalMessage interface {
Hash() (common.Hash, error) Hash() (common.Hash, error)
StorageSlot() (common.Hash, error) StorageSlot() (common.Hash, error)
} }
// InvalidMessage represents a message to the L1 message passer that
// cannot be decoded as a withdrawal. They are defined as a separate
// type in order to completely disambiguate them from any other
// message.
type InvalidMessage SentMessage
func (msg *InvalidMessage) Encode() ([]byte, error) {
out := make([]byte, len(msg.Msg)+20)
copy(out, msg.Msg)
copy(out[len(msg.Msg):], msg.Who.Bytes())
return out, nil
}
func (msg *InvalidMessage) Hash() (common.Hash, error) {
bytes, err := msg.Encode()
if err != nil {
return common.Hash{}, fmt.Errorf("cannot hash: %w", err)
}
return crypto.Keccak256Hash(bytes), nil
}
func (msg *InvalidMessage) StorageSlot() (common.Hash, error) {
hash, err := msg.Hash()
if err != nil {
return common.Hash{}, fmt.Errorf("cannot compute storage slot: %w", err)
}
preimage := make([]byte, 64)
copy(preimage, hash.Bytes())
return crypto.Keccak256Hash(preimage), nil
}
package crossdomain
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestInvalidMessage(t *testing.T) {
tests := []struct {
name string
msg InvalidMessage
slot common.Hash
}{
{
name: "unparseable x-domain message on mainnet",
msg: InvalidMessage{
Who: common.HexToAddress("0x8b1d477410344785ff1df52500032e6d5f532ee4"),
Msg: common.FromHex("0x042069"),
},
slot: common.HexToHash("0x2a49ae6579c3878f10cf87ecdbebc6c4e2b2159ffe2b1af88af6ca9697fc32cb"),
},
{
name: "valid x-domain message on mainnet for validation",
msg: InvalidMessage{
Who: common.HexToAddress("0x4200000000000000000000000000000000000007"),
Msg: common.FromHex("" +
"0xcbd4ece900000000000000000000000099c9fc46f92e8a1c0dec1b1747d01090" +
"3e884be100000000000000000000000042000000000000000000000000000000" +
"0000001000000000000000000000000000000000000000000000000000000000" +
"0000008000000000000000000000000000000000000000000000000000000000" +
"00019be200000000000000000000000000000000000000000000000000000000" +
"000000e4a9f9e675000000000000000000000000a0b86991c6218b36c1d19d4a" +
"2e9eb0ce3606eb480000000000000000000000007f5c764cbc14f9669b88837c" +
"a1490cca17c31607000000000000000000000000a420b2d1c0841415a695b81e" +
"5b867bcd07dff8c9000000000000000000000000c186fa914353c44b2e33ebe0" +
"5f21846f1048beda000000000000000000000000000000000000000000000000" +
"00000000295d681d000000000000000000000000000000000000000000000000" +
"00000000000000c0000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"00000000",
),
},
slot: common.HexToHash("0x8f8f6be7a4c5048f46ca41897181d17c10c39365ead5ac27c23d1e8e466d0ed5"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// StorageSlot() tests Hash() and Encode() so we don't
// need to test these separately.
slot, err := test.msg.StorageSlot()
require.NoError(t, err)
require.Equal(t, test.slot, slot)
})
}
}
package migration package crossdomain
import ( import (
"bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"strings"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
) )
// SentMessageJSON represents an entry in the JSON file that is created by // SentMessage represents an entry in the JSON file that is created by
// the `migration-data` package. Each entry represents a call to the // the `migration-data` package. Each entry represents a call to the
// `LegacyMessagePasser`. The `who` should always be the // `LegacyMessagePasser`. The `who` should always be the
// `L2CrossDomainMessenger` and the `msg` should be an abi encoded // `L2CrossDomainMessenger` and the `msg` should be an abi encoded
...@@ -20,10 +24,10 @@ type SentMessage struct { ...@@ -20,10 +24,10 @@ type SentMessage struct {
Msg hexutil.Bytes `json:"msg"` Msg hexutil.Bytes `json:"msg"`
} }
// NewSentMessageJSON will read a JSON file from disk given a path to the JSON // NewSentMessageFromJSON will read a JSON file from disk given a path to the JSON
// file. The JSON file this function reads from disk is an output from the // file. The JSON file this function reads from disk is an output from the
// `migration-data` package. // `migration-data` package.
func NewSentMessage(path string) ([]*SentMessage, error) { func NewSentMessageFromJSON(path string) ([]*SentMessage, error) {
file, err := os.ReadFile(path) file, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot find sent message json at %s: %w", path, err) return nil, fmt.Errorf("cannot find sent message json at %s: %w", path, err)
...@@ -37,15 +41,81 @@ func NewSentMessage(path string) ([]*SentMessage, error) { ...@@ -37,15 +41,81 @@ func NewSentMessage(path string) ([]*SentMessage, error) {
return j, nil return j, nil
} }
// ReadWitnessData will read messages and addresses from a raw l2geth state
// dump file.
func ReadWitnessData(path string) ([]*SentMessage, OVMETHAddresses, error) {
f, err := os.Open(path)
if err != nil {
return nil, nil, fmt.Errorf("cannot open witness data file: %w", err)
}
defer f.Close()
scan := bufio.NewScanner(f)
var witnesses []*SentMessage
addresses := make(map[common.Address]bool)
for scan.Scan() {
line := scan.Text()
splits := strings.Split(line, "|")
if len(splits) < 2 {
return nil, nil, fmt.Errorf("invalid line: %s", line)
}
switch splits[0] {
case "MSG":
if len(splits) != 3 {
return nil, nil, fmt.Errorf("invalid line: %s", line)
}
msg := splits[2]
// Make sure that the witness data has a 0x prefix
if !strings.HasPrefix(msg, "0x") {
msg = "0x" + msg
}
abi, err := bindings.LegacyMessagePasserMetaData.GetAbi()
if err != nil {
return nil, nil, fmt.Errorf("failed to get abi: %w", err)
}
msgB := hexutil.MustDecode(msg)
method, err := abi.MethodById(msgB[:4])
if err != nil {
return nil, nil, fmt.Errorf("failed to get method: %w", err)
}
out, err := method.Inputs.Unpack(msgB[4:])
if err != nil {
return nil, nil, fmt.Errorf("failed to unpack: %w", err)
}
cast, ok := out[0].([]byte)
if !ok {
return nil, nil, fmt.Errorf("failed to cast to bytes")
}
witnesses = append(witnesses, &SentMessage{
Who: common.HexToAddress(splits[1]),
Msg: cast,
})
case "ETH":
addresses[common.HexToAddress(splits[1])] = true
default:
return nil, nil, fmt.Errorf("invalid line: %s", line)
}
}
return witnesses, addresses, nil
}
// ToLegacyWithdrawal will convert a SentMessageJSON to a LegacyWithdrawal // ToLegacyWithdrawal will convert a SentMessageJSON to a LegacyWithdrawal
// struct. This is useful because the LegacyWithdrawal struct has helper // struct. This is useful because the LegacyWithdrawal struct has helper
// functions on it that can compute the withdrawal hash and the storage slot. // functions on it that can compute the withdrawal hash and the storage slot.
func (s *SentMessage) ToLegacyWithdrawal() (*crossdomain.LegacyWithdrawal, error) { func (s *SentMessage) ToLegacyWithdrawal() (*LegacyWithdrawal, error) {
data := make([]byte, len(s.Who)+len(s.Msg)) data := make([]byte, len(s.Who)+len(s.Msg))
copy(data, s.Msg) copy(data, s.Msg)
copy(data[len(s.Msg):], s.Who[:]) copy(data[len(s.Msg):], s.Who[:])
var w crossdomain.LegacyWithdrawal var w LegacyWithdrawal
if err := w.Decode(data); err != nil { if err := w.Decode(data); err != nil {
return nil, err return nil, err
} }
...@@ -117,26 +187,26 @@ type MigrationData struct { ...@@ -117,26 +187,26 @@ type MigrationData struct {
EvmMessages []*SentMessage EvmMessages []*SentMessage
} }
func (m *MigrationData) ToWithdrawals() (crossdomain.DangerousUnfilteredWithdrawals, error) { func (m *MigrationData) ToWithdrawals() (DangerousUnfilteredWithdrawals, []InvalidMessage, error) {
messages := make(crossdomain.DangerousUnfilteredWithdrawals, 0) messages := make(DangerousUnfilteredWithdrawals, 0)
invalidMessages := make([]InvalidMessage, 0)
for _, msg := range m.OvmMessages { for _, msg := range m.OvmMessages {
wd, err := msg.ToLegacyWithdrawal() wd, err := msg.ToLegacyWithdrawal()
if err != nil { if err != nil {
return nil, err return nil, nil, fmt.Errorf("error serializing OVM message: %w", err)
} }
messages = append(messages, wd) messages = append(messages, wd)
if err != nil {
return nil, err
}
} }
for _, msg := range m.EvmMessages { for _, msg := range m.EvmMessages {
wd, err := msg.ToLegacyWithdrawal() wd, err := msg.ToLegacyWithdrawal()
if err != nil { if err != nil {
return nil, err log.Warn("Discovered mal-formed withdrawal", "who", msg.Who, "data", msg.Msg)
invalidMessages = append(invalidMessages, InvalidMessage(*msg))
continue
} }
messages = append(messages, wd) messages = append(messages, wd)
} }
return messages, nil return messages, invalidMessages, nil
} }
func (m *MigrationData) Addresses() []common.Address { func (m *MigrationData) Addresses() []common.Address {
......
package crossdomain
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestRead(t *testing.T) {
witnesses, addresses, err := ReadWitnessData("testdata/witness.txt")
require.NoError(t, err)
require.Equal(t, []*SentMessage{
{
Who: common.HexToAddress("0x4200000000000000000000000000000000000007"),
Msg: common.FromHex(
"0xcbd4ece900000000000000000000000099c9fc46f92e8a1c0dec1b1747d01090" +
"3e884be100000000000000000000000042000000000000000000000000000000" +
"0000001000000000000000000000000000000000000000000000000000000000" +
"0000008000000000000000000000000000000000000000000000000000000000" +
"00019bd000000000000000000000000000000000000000000000000000000000" +
"000000e4a9f9e675000000000000000000000000d533a949740bb3306d119cc7" +
"77fa900ba034cd520000000000000000000000000994206dfe8de6ec6920ff4d" +
"779b0d950605fb53000000000000000000000000e3a44dd2a8c108be56a78635" +
"121ec914074da16d000000000000000000000000e3a44dd2a8c108be56a78635" +
"121ec914074da16d0000000000000000000000000000000000000000000001b0" +
"ac98ab3858d75478000000000000000000000000000000000000000000000000" +
"00000000000000c0000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"00000000",
),
},
{
Who: common.HexToAddress("0x8b1d477410344785ff1df52500032e6d5f532ee4"),
Msg: common.FromHex("0x042069"),
},
}, witnesses)
require.Equal(t, OVMETHAddresses{
common.HexToAddress("0x6340d44c5174588B312F545eEC4a42f8a514eF50"): true,
}, addresses)
}
...@@ -8,9 +8,9 @@ import ( ...@@ -8,9 +8,9 @@ import (
"io" "io"
"strings" "strings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
...@@ -105,7 +105,7 @@ func IterateAllowanceList(r io.Reader, cb AllowanceCB) error { ...@@ -105,7 +105,7 @@ func IterateAllowanceList(r io.Reader, cb AllowanceCB) error {
func IterateMintEvents(db ethdb.Database, headNum uint64, cb AddressCBWithHead, progressCb func(uint64)) error { func IterateMintEvents(db ethdb.Database, headNum uint64, cb AddressCBWithHead, progressCb func(uint64)) error {
for headNum > 0 { for headNum > 0 {
hash := rawdb.ReadCanonicalHash(db, headNum) hash := rawdb.ReadCanonicalHash(db, headNum)
receipts, err := migration.ReadLegacyReceipts(db, hash, headNum) receipts, err := crossdomain.ReadLegacyReceipts(db, hash, headNum)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -4,9 +4,10 @@ import ( ...@@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration" "github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -30,7 +31,7 @@ var ( ...@@ -30,7 +31,7 @@ var (
func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int, noCheck bool) error { func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int, noCheck bool) error {
// Chain params to use for integrity checking. // Chain params to use for integrity checking.
params := migration.ParamsByChainID[chainID] params := crossdomain.ParamsByChainID[chainID]
if params == nil { if params == nil {
return fmt.Errorf("no chain params for %d", chainID) return fmt.Errorf("no chain params for %d", chainID)
} }
...@@ -47,7 +48,7 @@ func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int ...@@ -47,7 +48,7 @@ func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int
// Migrate the legacy ETH to ETH. // Migrate the legacy ETH to ETH.
log.Info("Migrating legacy ETH to ETH", "num-accounts", len(addresses)) log.Info("Migrating legacy ETH to ETH", "num-accounts", len(addresses))
totalMigrated := new(big.Int) totalMigrated := new(big.Int)
logAccountProgress := ProgressLogger(1000, "imported accounts") logAccountProgress := util.ProgressLogger(1000, "imported accounts")
for addr := range deduped { for addr := range deduped {
// No accounts should have a balance in state. If they do, bail. // No accounts should have a balance in state. If they do, bail.
if db.GetBalance(addr).Sign() > 0 { if db.GetBalance(addr).Sign() > 0 {
......
package ether package ether
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration" "github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
...@@ -17,9 +19,9 @@ import ( ...@@ -17,9 +19,9 @@ import (
// slots in the LegacyERC20ETH contract. We don't have to filter out extra addresses like we do for // slots in the LegacyERC20ETH contract. We don't have to filter out extra addresses like we do for
// withdrawals because we'll simply carry the balance of a given address to the new system, if the // withdrawals because we'll simply carry the balance of a given address to the new system, if the
// account is extra then it won't have any balance and nothing will happen. // account is extra then it won't have any balance and nothing will happen.
func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.Address, allowances []*migration.Allowance, chainID int, noCheck bool) ([]common.Address, error) { func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) ([]common.Address, error) {
// Chain params to use for integrity checking. // Chain params to use for integrity checking.
params := migration.ParamsByChainID[chainID] params := crossdomain.ParamsByChainID[chainID]
if params == nil { if params == nil {
return nil, fmt.Errorf("no chain params for %d", chainID) return nil, fmt.Errorf("no chain params for %d", chainID)
} }
...@@ -53,7 +55,10 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common. ...@@ -53,7 +55,10 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.
// slots that we know we can ignore (totalSupply, name, symbol). // slots that we know we can ignore (totalSupply, name, symbol).
var count int var count int
slotsAct := make(map[common.Hash]common.Hash) slotsAct := make(map[common.Hash]common.Hash)
progress := util.ProgressLogger(1000, "Read OVM_ETH storage slot")
err := db.ForEachStorage(predeploys.LegacyERC20ETHAddr, func(key, value common.Hash) bool { err := db.ForEachStorage(predeploys.LegacyERC20ETHAddr, func(key, value common.Hash) bool {
progress()
// We can safely ignore specific slots (totalSupply, name, symbol). // We can safely ignore specific slots (totalSupply, name, symbol).
if ignoredSlots[key] { if ignoredSlots[key] {
return true return true
...@@ -75,13 +80,16 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common. ...@@ -75,13 +80,16 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.
// keep track of the total balance to be migrated and throw if the total supply exceeds the // keep track of the total balance to be migrated and throw if the total supply exceeds the
// expected supply delta. // expected supply delta.
totalFound := new(big.Int) totalFound := new(big.Int)
var unknown bool
for slot := range slotsAct { for slot := range slotsAct {
slotType, ok := slotsInp[slot] slotType, ok := slotsInp[slot]
if !ok { if !ok {
if noCheck { if noCheck {
log.Error("ignoring unknown storage slot in state", "slot", slot) log.Error("ignoring unknown storage slot in state", "slot", slot.String())
} else { } else {
log.Crit("unknown storage slot in state: %s", slot) unknown = true
log.Error("unknown storage slot in state", "slot", slot.String())
continue
} }
} }
...@@ -102,6 +110,9 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common. ...@@ -102,6 +110,9 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.
} }
} }
} }
if unknown {
return nil, errors.New("unknown storage slots in state (see logs for details)")
}
// Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher // Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher
// than the actual migrated amount because self-destructs will remove ETH supply in a way that // than the actual migrated amount because self-destructs will remove ETH supply in a way that
......
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
) )
...@@ -89,7 +88,7 @@ var ( ...@@ -89,7 +88,7 @@ var (
// PostCheckMigratedDB will check that the migration was performed correctly // PostCheckMigratedDB will check that the migration was performed correctly
func PostCheckMigratedDB( func PostCheckMigratedDB(
ldb ethdb.Database, ldb ethdb.Database,
migrationData migration.MigrationData, migrationData crossdomain.MigrationData,
l1XDM *common.Address, l1XDM *common.Address,
l1ChainID uint64, l1ChainID uint64,
finalSystemOwner common.Address, finalSystemOwner common.Address,
...@@ -468,8 +467,8 @@ func PostCheckL1Block(db vm.StateDB, info *derive.L1BlockInfo) error { ...@@ -468,8 +467,8 @@ func PostCheckL1Block(db vm.StateDB, info *derive.L1BlockInfo) error {
return nil return nil
} }
func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossDomainMessenger *common.Address) error { func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address) error {
wds, err := data.ToWithdrawals() wds, invalidMessages, err := data.ToWithdrawals()
if err != nil { if err != nil {
return err return err
} }
...@@ -479,6 +478,7 @@ func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossD ...@@ -479,6 +478,7 @@ func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossD
// some witness data may references withdrawals that reverted. // some witness data may references withdrawals that reverted.
oldToNewSlots := make(map[common.Hash]common.Hash) oldToNewSlots := make(map[common.Hash]common.Hash)
wdsByOldSlot := make(map[common.Hash]*crossdomain.LegacyWithdrawal) wdsByOldSlot := make(map[common.Hash]*crossdomain.LegacyWithdrawal)
invalidMessagesByOldSlot := make(map[common.Hash]crossdomain.InvalidMessage)
for _, wd := range wds { for _, wd := range wds {
migrated, err := crossdomain.MigrateWithdrawal(wd, l1CrossDomainMessenger) migrated, err := crossdomain.MigrateWithdrawal(wd, l1CrossDomainMessenger)
if err != nil { if err != nil {
...@@ -497,6 +497,15 @@ func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossD ...@@ -497,6 +497,15 @@ func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossD
oldToNewSlots[legacySlot] = migratedSlot oldToNewSlots[legacySlot] = migratedSlot
wdsByOldSlot[legacySlot] = wd wdsByOldSlot[legacySlot] = wd
} }
for _, im := range invalidMessages {
invalidSlot, err := im.StorageSlot()
if err != nil {
return fmt.Errorf("cannot compute legacy storage slot: %w", err)
}
invalidMessagesByOldSlot[invalidSlot] = im
}
log.Info("computed withdrawal storage slots", "migrated", len(oldToNewSlots), "invalid", len(invalidMessagesByOldSlot))
// Now, iterate over each legacy withdrawal and check if there is a corresponding // Now, iterate over each legacy withdrawal and check if there is a corresponding
// migrated withdrawal. // migrated withdrawal.
...@@ -515,6 +524,17 @@ func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossD ...@@ -515,6 +524,17 @@ func CheckWithdrawalsAfter(db vm.StateDB, data migration.MigrationData, l1CrossD
return false return false
} }
// Make sure invalid slots don't get migrated.
_, isInvalidSlot := invalidMessagesByOldSlot[key]
if isInvalidSlot {
value := db.GetState(predeploys.L2ToL1MessagePasserAddr, key)
if value != abiFalse {
innerErr = fmt.Errorf("expected invalid slot not to be migrated, but got %s", value)
return false
}
return true
}
// Grab the migrated slot. // Grab the migrated slot.
migratedSlot := oldToNewSlots[key] migratedSlot := oldToNewSlots[key]
if migratedSlot == (common.Hash{}) { if migratedSlot == (common.Hash{}) {
......
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/ether" "github.com/ethereum-optimism/optimism/op-chain-ops/ether"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
...@@ -35,7 +34,7 @@ type MigrationResult struct { ...@@ -35,7 +34,7 @@ type MigrationResult struct {
} }
// MigrateDB will migrate an l2geth legacy Optimism database to a Bedrock database. // MigrateDB will migrate an l2geth legacy Optimism database to a Bedrock database.
func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, migrationData *migration.MigrationData, commit, noCheck bool) (*MigrationResult, error) { func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, migrationData *crossdomain.MigrationData, commit, noCheck bool) (*MigrationResult, error) {
// Grab the hash of the tip of the legacy chain. // Grab the hash of the tip of the legacy chain.
hash := rawdb.ReadHeadHeaderHash(ldb) hash := rawdb.ReadHeadHeaderHash(ldb)
log.Info("Reading chain tip from database", "hash", hash) log.Info("Reading chain tip from database", "hash", hash)
...@@ -114,17 +113,19 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m ...@@ -114,17 +113,19 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
// Convert all input messages into legacy messages. Note that this list is not yet filtered and // Convert all input messages into legacy messages. Note that this list is not yet filtered and
// may be missing some messages or have some extra messages. // may be missing some messages or have some extra messages.
unfilteredWithdrawals, err := migrationData.ToWithdrawals() unfilteredWithdrawals, invalidMessages, err := migrationData.ToWithdrawals()
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot serialize withdrawals: %w", err) return nil, fmt.Errorf("cannot serialize withdrawals: %w", err)
} }
log.Info("Read withdrawals from witness data", "unfiltered", len(unfilteredWithdrawals), "invalid", len(invalidMessages))
// We now need to check that we have all of the withdrawals that we expect to have. An error // We now need to check that we have all of the withdrawals that we expect to have. An error
// will be thrown if there are any missing messages, and any extra messages will be removed. // will be thrown if there are any missing messages, and any extra messages will be removed.
var filteredWithdrawals crossdomain.SafeFilteredWithdrawals var filteredWithdrawals crossdomain.SafeFilteredWithdrawals
if !noCheck { if !noCheck {
log.Info("Checking withdrawals...") log.Info("Checking withdrawals...")
filteredWithdrawals, err = crossdomain.PreCheckWithdrawals(db, unfilteredWithdrawals) filteredWithdrawals, err = crossdomain.PreCheckWithdrawals(db, unfilteredWithdrawals, invalidMessages)
if err != nil { if err != nil {
return nil, fmt.Errorf("withdrawals mismatch: %w", err) return nil, fmt.Errorf("withdrawals mismatch: %w", err)
} }
......
...@@ -5,8 +5,9 @@ import ( ...@@ -5,8 +5,9 @@ import (
"math/big" "math/big"
"path/filepath" "path/filepath"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
) )
...@@ -30,28 +31,28 @@ type Config struct { ...@@ -30,28 +31,28 @@ type Config struct {
func Migrate(cfg *Config) (*genesis.MigrationResult, error) { func Migrate(cfg *Config) (*genesis.MigrationResult, error) {
deployConfig := cfg.DeployConfig deployConfig := cfg.DeployConfig
ovmAddresses, err := migration.NewAddresses(cfg.OVMAddressesPath) ovmAddresses, err := crossdomain.NewAddresses(cfg.OVMAddressesPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
evmAddresess, err := migration.NewAddresses(cfg.EVMAddressesPath) evmAddresess, err := crossdomain.NewAddresses(cfg.EVMAddressesPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ovmAllowances, err := migration.NewAllowances(cfg.OVMAllowancesPath) ovmAllowances, err := crossdomain.NewAllowances(cfg.OVMAllowancesPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ovmMessages, err := migration.NewSentMessage(cfg.OVMMessagesPath) ovmMessages, err := crossdomain.NewSentMessageFromJSON(cfg.OVMMessagesPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
evmMessages, err := migration.NewSentMessage(cfg.EVMMessagesPath) evmMessages, err := crossdomain.NewSentMessageFromJSON(cfg.EVMMessagesPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
migrationData := migration.MigrationData{ migrationData := crossdomain.MigrationData{
OvmAddresses: ovmAddresses, OvmAddresses: ovmAddresses,
EvmAddresses: evmAddresess, EvmAddresses: evmAddresess,
OvmAllowances: ovmAllowances, OvmAllowances: ovmAllowances,
......
package ether package util
import ( import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
......
...@@ -4,8 +4,6 @@ import ( ...@@ -4,8 +4,6 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
...@@ -144,24 +142,19 @@ func TestL2Sequencer_SequencerOnlyReorg(gt *testing.T) { ...@@ -144,24 +142,19 @@ func TestL2Sequencer_SequencerOnlyReorg(gt *testing.T) {
// so it'll keep the L2 block with the old L1 origin, since no conflict is detected. // so it'll keep the L2 block with the old L1 origin, since no conflict is detected.
sequencer.ActL1HeadSignal(t) sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
// TODO: CLI-3405 we can detect the inconsistency of the L1 origin of the unsafe L2 head: // Verifier should detect the inconsistency of the L1 origin and reset the pipeline to follow the reorg
// as verifier, there is no need to wait for sequencer to recognize it.
newStatus := sequencer.SyncStatus() newStatus := sequencer.SyncStatus()
require.Equal(t, status.HeadL1.Hash, newStatus.UnsafeL2.L1Origin.Hash, "still have old bad L1 origin") require.Zero(t, newStatus.UnsafeL2.L1Origin.Number, "back to genesis block with good L1 origin, drop old unsafe L2 chain with bad L1 origins")
require.NotEqual(t, status.HeadL1.Hash, newStatus.HeadL1.Hash, "did see the new L1 head change") require.NotEqual(t, status.HeadL1.Hash, newStatus.HeadL1.Hash, "did see the new L1 head change")
require.Equal(t, newStatus.HeadL1.Hash, newStatus.CurrentL1.Hash, "did sync the new L1 head as verifier") require.Equal(t, newStatus.HeadL1.Hash, newStatus.CurrentL1.Hash, "did sync the new L1 head as verifier")
// the block N+1 cannot build on the old N which still refers to the now orphaned L1 origin // the block N+1 cannot build on the old N which still refers to the now orphaned L1 origin
require.Equal(t, status.UnsafeL2.L1Origin.Number, newStatus.HeadL1.Number-1, "seeing N+1 to attempt to build on N") require.Equal(t, status.UnsafeL2.L1Origin.Number, newStatus.HeadL1.Number-1, "seeing N+1 to attempt to build on N")
require.NotEqual(t, status.UnsafeL2.L1Origin.Hash, newStatus.HeadL1.ParentHash, "but N+1 cannot fit on N") require.NotEqual(t, status.UnsafeL2.L1Origin.Hash, newStatus.HeadL1.ParentHash, "but N+1 cannot fit on N")
sequencer.ActL1HeadSignal(t)
// sequence more L2 blocks, until we actually need the next L1 origin // After hitting a reset error, it resets derivation, and drops the old L1 chain
sequencer.ActBuildToL1HeadExclUnsafe(t)
// We expect block building to fail when the next L1 block is not consistent with the existing L1 origin
sequencer.ActL2StartBlockCheckErr(t, derive.ErrReset)
// After hitting a reset error, it reset derivation, and drops the old L1 chain
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
require.Zero(t, sequencer.SyncStatus().UnsafeL2.L1Origin.Number, "back to genesis block with good L1 origin, drop old unsafe L2 chain with bad L1 origins")
// Can build new L2 blocks with good L1 origin // Can build new L2 blocks with good L1 origin
sequencer.ActBuildToL1HeadUnsafe(t) sequencer.ActBuildToL1HeadUnsafe(t)
require.Equal(t, newStatus.HeadL1.Hash, sequencer.SyncStatus().UnsafeL2.L1Origin.Hash, "build L2 chain with new correct L1 origins") require.Equal(t, newStatus.HeadL1.Hash, sequencer.SyncStatus().UnsafeL2.L1Origin.Hash, "build L2 chain with new correct L1 origins")
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"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-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -371,6 +372,12 @@ func TestRegolith(t *testing.T) { ...@@ -371,6 +372,12 @@ func TestRegolith(t *testing.T) {
tx, _, err := opGeth.L2Client.TransactionByHash(ctx, contractCreateTx.Hash()) tx, _, err := opGeth.L2Client.TransactionByHash(ctx, contractCreateTx.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedNonce, *tx.EffectiveNonce(), "should report actual tx nonce") require.Equal(t, expectedNonce, *tx.EffectiveNonce(), "should report actual tx nonce")
// Should be able to search for logs even though there are deposit transactions in blocks.
logs, err := opGeth.L2Client.FilterLogs(ctx, ethereum.FilterQuery{})
require.NoError(t, err)
require.NotNil(t, logs)
require.Empty(t, logs)
}) })
t.Run("ReturnUnusedGasToPool_"+test.name, func(t *testing.T) { t.Run("ReturnUnusedGasToPool_"+test.name, func(t *testing.T) {
......
...@@ -415,6 +415,7 @@ func TestMixedDepositValidity(t *testing.T) { ...@@ -415,6 +415,7 @@ func TestMixedDepositValidity(t *testing.T) {
// TestMixedWithdrawalValidity makes a number of withdrawal transactions and ensures ones with modified parameters are // TestMixedWithdrawalValidity makes a number of withdrawal transactions and ensures ones with modified parameters are
// rejected while unmodified ones are accepted. This runs test cases in different systems. // rejected while unmodified ones are accepted. This runs test cases in different systems.
func TestMixedWithdrawalValidity(t *testing.T) { func TestMixedWithdrawalValidity(t *testing.T) {
parallel(t)
// Setup our logger handler // Setup our logger handler
if !verboseGethNodes { if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler()) log.Root().SetHandler(log.DiscardHandler())
...@@ -424,7 +425,6 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -424,7 +425,6 @@ func TestMixedWithdrawalValidity(t *testing.T) {
for i := 0; i <= 8; i++ { for i := 0; i <= 8; i++ {
i := i // avoid loop var capture i := i // avoid loop var capture
t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) { t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) {
parallel(t)
// Create our system configuration, funding all accounts we created for L1/L2, and start it // Create our system configuration, funding all accounts we created for L1/L2, and start it
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 6 cfg.DeployConfig.FinalizationPeriodSeconds = 6
......
package fetch
import (
"context"
"encoding/json"
"fmt"
"log"
"math/big"
"os"
"path"
"time"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
type TransactionWithMeta struct {
TxIndex uint64 `json:"tx_index"`
InboxAddr common.Address `json:"inbox_address"`
BlockNumber uint64 `json:"block_number"`
BlockHash common.Hash `json:"block_hash"`
ChainId uint64 `json:"chain_id"`
Sender common.Address `json:"sender"`
ValidSender bool `json:"valid_sender"`
Frames []derive.Frame `json:"frames"`
FrameErr string `json:"frame_parse_error"`
ValidFrames bool `json:"valid_data"`
Tx *types.Transaction `json:"tx"`
}
type Config struct {
Start, End uint64
ChainID *big.Int
BatchInbox common.Address
BatchSenders map[common.Address]struct{}
OutDirectory string
}
func Batches(client *ethclient.Client, config Config) (totalValid, totalInvalid int) {
if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
log.Fatal(err)
}
number := new(big.Int).SetUint64(config.Start)
signer := types.LatestSignerForChainID(config.ChainID)
for i := config.Start; i < config.End; i++ {
valid, invalid := fetchBatchesPerBlock(client, number, signer, config)
totalValid += valid
totalInvalid += invalid
number = number.Add(number, common.Big1)
}
return
}
func fetchBatchesPerBlock(client *ethclient.Client, number *big.Int, signer types.Signer, config Config) (validBatchCount, invalidBatchCount int) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
block, err := client.BlockByNumber(ctx, number)
if err != nil {
log.Fatal(err)
}
for i, tx := range block.Transactions() {
if tx.To() != nil && *tx.To() == config.BatchInbox {
sender, err := signer.Sender(tx)
if err != nil {
log.Fatal(err)
}
validSender := true
if _, ok := config.BatchSenders[sender]; !ok {
fmt.Printf("Found a transaction (%s) from an invalid sender (%s)\n", tx.Hash().String(), sender.String())
invalidBatchCount += 1
validSender = false
}
validFrames := true
frameError := ""
frames, err := derive.ParseFrames(tx.Data())
if err != nil {
fmt.Printf("Found a transaction (%s) with invalid data: %v\n", tx.Hash().String(), err)
validFrames = false
frameError = err.Error()
}
if validSender && validFrames {
validBatchCount += 1
} else {
invalidBatchCount += 1
}
txm := &TransactionWithMeta{
Tx: tx,
Sender: sender,
ValidSender: validSender,
TxIndex: uint64(i),
BlockNumber: block.NumberU64(),
BlockHash: block.Hash(),
ChainId: config.ChainID.Uint64(),
InboxAddr: config.BatchInbox,
Frames: frames,
FrameErr: frameError,
ValidFrames: validFrames,
}
filename := path.Join(config.OutDirectory, fmt.Sprintf("%s.json", tx.Hash().String()))
file, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
enc := json.NewEncoder(file)
if err := enc.Encode(txm); err != nil {
log.Fatal(err)
}
}
}
return
}
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "batch-decoder"
app.Usage = "Optimism Batch Decoding Utility"
app.Commands = []cli.Command{
{
Name: "fetch",
Usage: "Fetches batches in the specified range",
Flags: []cli.Flag{
cli.IntFlag{
Name: "start",
Required: true,
Usage: "First block (inclusive) to fetch",
},
cli.IntFlag{
Name: "end",
Required: true,
Usage: "Last block (exclusive) to fetch",
},
cli.StringFlag{
Name: "inbox",
Required: true,
Usage: "Batch Inbox Address",
},
cli.StringFlag{
Name: "sender",
Required: true,
Usage: "Batch Sender Address",
},
cli.StringFlag{
Name: "out",
Value: "/tmp/batch_decoder/transactions_cache",
Usage: "Cache directory for the found transactions",
},
cli.StringFlag{
Name: "l1",
Required: true,
Usage: "L1 RPC URL",
EnvVar: "L1_RPC",
},
},
Action: func(cliCtx *cli.Context) error {
client, err := ethclient.Dial(cliCtx.String("l1"))
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
chainID, err := client.ChainID(ctx)
if err != nil {
log.Fatal(err)
}
config := fetch.Config{
Start: uint64(cliCtx.Int("start")),
End: uint64(cliCtx.Int("end")),
ChainID: chainID,
BatchSenders: map[common.Address]struct{}{
common.HexToAddress(cliCtx.String("sender")): struct{}{},
},
BatchInbox: common.HexToAddress(cliCtx.String("inbox")),
OutDirectory: cliCtx.String("out"),
}
totalValid, totalInvalid := fetch.Batches(client, config)
fmt.Printf("Fetched batches in range [%v,%v). Found %v valid & %v invalid batches\n", config.Start, config.End, totalValid, totalInvalid)
fmt.Printf("Fetch Config: Chain ID: %v. Inbox Address: %v. Valid Senders: %v.\n", config.ChainID, config.BatchInbox, config.BatchSenders)
fmt.Printf("Wrote transactions with batches to %v\n", config.OutDirectory)
return nil
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
...@@ -208,13 +208,6 @@ func (eq *EngineQueue) SafeL2Head() eth.L2BlockRef { ...@@ -208,13 +208,6 @@ func (eq *EngineQueue) SafeL2Head() eth.L2BlockRef {
return eq.safeHead return eq.safeHead
} }
func (eq *EngineQueue) LastL2Time() uint64 {
if len(eq.safeAttributes) == 0 {
return eq.safeHead.Time
}
return uint64(eq.safeAttributes[len(eq.safeAttributes)-1].Timestamp)
}
func (eq *EngineQueue) Step(ctx context.Context) error { func (eq *EngineQueue) Step(ctx context.Context) error {
if eq.needForkchoiceUpdate { if eq.needForkchoiceUpdate {
return eq.tryUpdateEngine(ctx) return eq.tryUpdateEngine(ctx)
...@@ -224,7 +217,12 @@ func (eq *EngineQueue) Step(ctx context.Context) error { ...@@ -224,7 +217,12 @@ func (eq *EngineQueue) Step(ctx context.Context) error {
} }
outOfData := false outOfData := false
if len(eq.safeAttributes) == 0 { if len(eq.safeAttributes) == 0 {
eq.origin = eq.prev.Origin() newOrigin := eq.prev.Origin()
// Check if the L2 unsafe head origin is consistent with the new origin
if err := eq.verifyNewL1Origin(ctx, newOrigin); err != nil {
return err
}
eq.origin = newOrigin
eq.postProcessSafeL2() // make sure we track the last L2 safe head for every new L1 block eq.postProcessSafeL2() // make sure we track the last L2 safe head for every new L1 block
if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF { if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF {
outOfData = true outOfData = true
...@@ -246,6 +244,38 @@ func (eq *EngineQueue) Step(ctx context.Context) error { ...@@ -246,6 +244,38 @@ func (eq *EngineQueue) Step(ctx context.Context) error {
} }
} }
// verifyNewL1Origin checks that the L2 unsafe head still has a L1 origin that is on the canonical chain.
// If the unsafe head origin is after the new L1 origin it is assumed to still be canonical.
// The check is only required when moving to a new L1 origin.
func (eq *EngineQueue) verifyNewL1Origin(ctx context.Context, newOrigin eth.L1BlockRef) error {
if newOrigin == eq.origin {
return nil
}
unsafeOrigin := eq.unsafeHead.L1Origin
if newOrigin.Number == unsafeOrigin.Number && newOrigin.ID() != unsafeOrigin {
return NewResetError(fmt.Errorf("l1 origin was inconsistent with l2 unsafe head origin, need reset to resolve: l1 origin: %v; unsafe origin: %v",
newOrigin.ID(), unsafeOrigin))
}
// Avoid requesting an older block by checking against the parent hash
if newOrigin.Number == unsafeOrigin.Number+1 && newOrigin.ParentHash != unsafeOrigin.Hash {
return NewResetError(fmt.Errorf("l2 unsafe head origin is no longer canonical, need reset to resolve: canonical hash: %v; unsafe origin hash: %v",
newOrigin.ParentHash, unsafeOrigin.Hash))
}
if newOrigin.Number > unsafeOrigin.Number+1 {
// If unsafe origin is further behind new origin, check it's still on the canonical chain.
canonical, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, unsafeOrigin.Number)
if err != nil {
return NewTemporaryError(fmt.Errorf("failed to fetch canonical L1 block at slot: %v; err: %w", unsafeOrigin.Number, err))
}
if canonical.ID() != unsafeOrigin {
eq.log.Error("Resetting due to origin mismatch")
return NewResetError(fmt.Errorf("l2 unsafe head origin is no longer canonical, need reset to resolve: canonical: %v; unsafe origin: %v",
canonical, unsafeOrigin))
}
}
return nil
}
// tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized, // tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized,
// and then marks the latest fully derived L2 block from this as finalized, // and then marks the latest fully derived L2 block from this as finalized,
// or defaults to the current finalized L2 block. // or defaults to the current finalized L2 block.
......
This diff is collapsed.
...@@ -24,10 +24,10 @@ const MaxFrameLen = 1_000_000 ...@@ -24,10 +24,10 @@ const MaxFrameLen = 1_000_000
// is_last = bool // is_last = bool
type Frame struct { type Frame struct {
ID ChannelID ID ChannelID `json:"id"`
FrameNumber uint16 FrameNumber uint16 `json:"frame_number"`
Data []byte Data []byte `json:"data"`
IsLast bool IsLast bool `'json:"is_last"`
} }
// MarshalBinary writes the frame to `w`. // MarshalBinary writes the frame to `w`.
......
package derive package derive
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
) )
...@@ -44,3 +45,19 @@ func (id ChannelID) String() string { ...@@ -44,3 +45,19 @@ func (id ChannelID) String() string {
func (id ChannelID) TerminalString() string { func (id ChannelID) TerminalString() string {
return fmt.Sprintf("%x..%x", id[:3], id[13:]) return fmt.Sprintf("%x..%x", id[:3], id[13:])
} }
func (id ChannelID) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
func (id *ChannelID) UnmarshalText(text []byte) error {
h, err := hex.DecodeString(string(text))
if err != nil {
return err
}
if len(h) != ChannelIDLength {
return errors.New("invalid length")
}
copy(id[:], h)
return nil
}
...@@ -92,7 +92,7 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -92,7 +92,7 @@ func Main(version string, cliCtx *cli.Context) error {
} }
rpcCfg := cfg.RPCConfig rpcCfg := cfg.RPCConfig
server := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, version) server := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, version, oprpc.WithLogger(l))
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)
......
...@@ -65,6 +65,9 @@ func NewLogger(cfg CLIConfig) log.Logger { ...@@ -65,6 +65,9 @@ func NewLogger(cfg CLIConfig) log.Logger {
handler := log.StreamHandler(os.Stdout, Format(cfg.Format, cfg.Color)) handler := log.StreamHandler(os.Stdout, Format(cfg.Format, cfg.Color))
handler = log.SyncHandler(handler) handler = log.SyncHandler(handler)
handler = log.LvlFilterHandler(Level(cfg.Level), handler) handler = log.LvlFilterHandler(Level(cfg.Level), handler)
// Set the root handle to what we have configured. Some components like go-ethereum's RPC
// server use log.Root() instead of being able to pass in a log.
log.Root().SetHandler(handler)
logger := log.New() logger := log.New()
logger.SetHandler(handler) logger.SetHandler(handler)
return logger return logger
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
...@@ -316,6 +317,7 @@ func StoragePatch(patch io.Reader, address common.Address) HeadFn { ...@@ -316,6 +317,7 @@ func StoragePatch(patch io.Reader, address common.Address) HeadFn {
} }
type OvmOwnersConfig struct { type OvmOwnersConfig struct {
Network string `json:"network"`
Owner common.Address `json:"owner"` Owner common.Address `json:"owner"`
Sequencer common.Address `json:"sequencer"` Sequencer common.Address `json:"sequencer"`
Proposer common.Address `json:"proposer"` Proposer common.Address `json:"proposer"`
...@@ -323,17 +325,42 @@ type OvmOwnersConfig struct { ...@@ -323,17 +325,42 @@ type OvmOwnersConfig struct {
func OvmOwners(conf *OvmOwnersConfig) HeadFn { func OvmOwners(conf *OvmOwnersConfig) HeadFn {
return func(headState *state.StateDB) error { return func(headState *state.StateDB) error {
var addressManager common.Address // Lib_AddressManager
var l1SBProxy common.Address // Proxy__OVM_L1StandardBridge
var l1XDMProxy common.Address // Proxy__OVM_L1CrossDomainMessenger
var l1ERC721BridgeProxy common.Address
switch conf.Network {
case "mainnet":
addressManager = common.HexToAddress("0xdE1FCfB0851916CA5101820A69b13a4E276bd81F")
l1SBProxy = common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1")
l1XDMProxy = common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1")
l1ERC721BridgeProxy = common.HexToAddress("0x5a7749f83b81B301cAb5f48EB8516B986DAef23D")
case "goerli":
addressManager = common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111")
l1SBProxy = common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8")
l1XDMProxy = common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294")
l1ERC721BridgeProxy = common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9")
default:
return fmt.Errorf("unknown network: %q", conf.Network)
}
// See Proxy.sol OWNER_KEY: https://eips.ethereum.org/EIPS/eip-1967#admin-address
ownerSlot := common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
// Address manager owner // Address manager owner
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.Hash{}, conf.Owner.Hash()) // Ownable, first storage slot
headState.SetState(addressManager, common.Hash{}, conf.Owner.Hash())
// L1SB proxy owner // L1SB proxy owner
headState.SetState(common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"), common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"), conf.Owner.Hash()) headState.SetState(l1SBProxy, ownerSlot, conf.Owner.Hash())
// L1XDM owner // L1XDM owner
headState.SetState(common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"), common.Hash{31: 0x33}, conf.Owner.Hash()) // 0x33 = 51. L1CrossDomainMessenger is L1CrossDomainMessenger (0) Lib_AddressResolver (1) OwnableUpgradeable (1, but covered by gap) + ContextUpgradeable (special gap of 50) and then _owner
headState.SetState(l1XDMProxy, common.Hash{31: 0x33}, conf.Owner.Hash())
// L1 ERC721 bridge owner // L1 ERC721 bridge owner
headState.SetState(common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"), common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"), conf.Owner.Hash()) headState.SetState(l1ERC721BridgeProxy, ownerSlot, conf.Owner.Hash())
// Legacy sequencer/proposer addresses // Legacy sequencer/proposer addresses
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.HexToHash("0x2e0dfce60e9e27f035ce28f63c1bdd77cff6b13d8909da4d81d623ff9123fbdc"), conf.Sequencer.Hash()) // See AddressManager.sol "addresses" mapping(bytes32 => address), at slot position 1
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.HexToHash("0x9776dbdebd0d5eedaea450b21da9901ecd5254e5136a3a9b7b0ecd532734d5b5"), conf.Proposer.Hash()) addressesSlot := common.BigToHash(big.NewInt(1))
headState.SetState(addressManager, crypto.Keccak256Hash(crypto.Keccak256([]byte("OVM_Sequencer")), addressesSlot.Bytes()), conf.Sequencer.Hash())
headState.SetState(addressManager, crypto.Keccak256Hash(crypto.Keccak256([]byte("OVM_Proposer")), addressesSlot.Bytes()), conf.Proposer.Hash())
// Fund sequencer and proposer with 100 ETH // Fund sequencer and proposer with 100 ETH
headState.SetBalance(conf.Sequencer, HundredETH) headState.SetBalance(conf.Sequencer, HundredETH)
headState.SetBalance(conf.Proposer, HundredETH) headState.SetBalance(conf.Proposer, HundredETH)
......
FROM ethereumoptimism/op-geth:optimism FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:optimism
RUN apk add --no-cache jq RUN apk add --no-cache jq
......
...@@ -14,10 +14,10 @@ npm install @eth-optimism/atst --global ...@@ -14,10 +14,10 @@ npm install @eth-optimism/atst --global
npx atst <command> [options] npx atst <command> [options]
``` ```
## Commands ### Commands
read read an attestation - `read` read an attestation
write write an attestation - `write` write an attestation
For more info, run any command with the `--help` flag: For more info, run any command with the `--help` flag:
...@@ -26,55 +26,46 @@ npx atst read --help ...@@ -26,55 +26,46 @@ npx atst read --help
npx atst write --help npx atst write --help
``` ```
## Options: ### General options
-h, --help Display this message - `-h`, `--help` Display help message
-v, --version Display version number - `-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 ### Read
`--creator <string> Address of the creator of the attestation` - `--creator <address>` Address of the creator of the attestation
`--about <string> Address of the subject of the attestation` - `--about <address>` Address of the subject of the attestation
`--key <string> Key of the attestation either as string or hex number` - `--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)` - `[--data-type <string>]` The DataType type `string` | `bytes` | `number` | `bool` | `address` (default: `string`)
`--rpc-url [url] Rpc url to use (default: https://mainnet.optimism.io)` - `[--rpc-url <url>]` Rpc url to use (default: `https://mainnet.optimism.io`)
`--contract [address] Contract address to read from (default: 0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77)` - `[--contract <address>]` Contract address to read from (default: `0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77`)
`-h, --help Display this message` - `-h`, `--help` Display help message
Example: Example:
```bash ```bash
npx atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3 npx atst read --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 \
--creator 0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3
``` ```
### write ### Write
- `--private-key <string>` Private key of the creator of the attestation
- `[--data-type <string>]` The DataType type `string` | `bytes` | `number` | `bool` | `address` (default: `string`)
- `--about <address>` Address of the subject of the attestation
- `--key <address>` 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
`--private-key <string> Address of the creator of the attestation` Example:
`--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 ```bash
atst write --key "optimist.base-uri" --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 --value "my attestation" --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545 atst write --key "optimist.base-uri" \
atst/0.0.0 --about 0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5 \
--value "my attestation" \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--rpc-url http://localhost:8545
``` ```
This diff is collapsed.
...@@ -76,6 +76,35 @@ describe(getEvents.name, () => { ...@@ -76,6 +76,35 @@ describe(getEvents.name, () => {
"transactionHash": "0x61f59bd4dfe54272d9369effe3ae57a0ef2584161fcf2bbd55f5596002e759bd", "transactionHash": "0x61f59bd4dfe54272d9369effe3ae57a0ef2584161fcf2bbd55f5596002e759bd",
"transactionIndex": 1, "transactionIndex": 1,
}, },
{
"address": "0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77",
"args": [
"0xBCf86Fd70a0183433763ab0c14E7a760194f3a9F",
"0x00000000000000000000000000000000000060A7",
"0x616e696d616c6661726d2e7363686f6f6c2e617474656e646564000000000000",
"0x01",
],
"blockHash": "0x4870baaac6d7195952dc25e5dc0109ea324f819f8152d2889c7b4ad64040a9bf",
"blockNumber": 6278428,
"data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000",
"decode": [Function],
"event": "AttestationCreated",
"eventSignature": "AttestationCreated(address,address,bytes32,bytes)",
"getBlock": [Function],
"getTransaction": [Function],
"getTransactionReceipt": [Function],
"logIndex": 0,
"removeListener": [Function],
"removed": false,
"topics": [
"0x28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d85",
"0x000000000000000000000000bcf86fd70a0183433763ab0c14e7a760194f3a9f",
"0x00000000000000000000000000000000000000000000000000000000000060a7",
"0x616e696d616c6661726d2e7363686f6f6c2e617474656e646564000000000000",
],
"transactionHash": "0x4e836b74c51a370375efa374297524d9b0f6eacdd699c30556680ae7dc9a14ea",
"transactionIndex": 1,
},
] ]
`) `)
}) })
......
...@@ -43,7 +43,7 @@ describe(prepareWriteAttestation.name, () => { ...@@ -43,7 +43,7 @@ describe(prepareWriteAttestation.name, () => {
expect(result.address).toMatchInlineSnapshot( expect(result.address).toMatchInlineSnapshot(
'"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"' '"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"'
) )
expect(result.chainId).toMatchInlineSnapshot('10') expect(result.chainId).toMatchInlineSnapshot('undefined')
expect(result.functionName).toMatchInlineSnapshot('"attest"') expect(result.functionName).toMatchInlineSnapshot('"attest"')
expect(result.mode).toMatchInlineSnapshot('"prepared"') expect(result.mode).toMatchInlineSnapshot('"prepared"')
expect(result.request.gasLimit).toMatchInlineSnapshot(` expect(result.request.gasLimit).toMatchInlineSnapshot(`
...@@ -54,18 +54,16 @@ describe(prepareWriteAttestation.name, () => { ...@@ -54,18 +54,16 @@ describe(prepareWriteAttestation.name, () => {
`) `)
}) })
it('should throw an error if key is longer than 32 bytes', async () => { it('should work for key longer than 32 bytes', async () => {
const dataType = 'string' const dataType = 'string'
await expect( expect(
readAttestation( await readAttestation(
creator, creator,
about, about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot', 'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType dataType
) )
).rejects.toThrowErrorMatchingInlineSnapshot( ).toMatchInlineSnapshot('""')
'"Key is longer than the max length of 32 for attestation keys"'
)
}) })
}) })
import { Address, prepareWriteContract } from '@wagmi/core' import { Address, prepareWriteContract } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress' import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { WagmiBytes } from '../types/WagmiBytes' import { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi' import { abi } from './abi'
import { createKey } from './createKey'
import { createValue } from './createValue' import { createValue } from './createValue'
export const prepareWriteAttestation = async ( export const prepareWriteAttestation = async (
...@@ -13,15 +13,7 @@ export const prepareWriteAttestation = async ( ...@@ -13,15 +13,7 @@ export const prepareWriteAttestation = async (
chainId: number | undefined = undefined, chainId: number | undefined = undefined,
contractAddress: Address = ATTESTATION_STATION_ADDRESS contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => { ) => {
let formattedKey: WagmiBytes const formattedKey = createKey(key) as WagmiBytes
try {
formattedKey = formatBytes32String(key) as WagmiBytes
} catch (e) {
console.error(e)
throw new Error(
`key is longer than 32 bytes: ${key}. Try using a shorter key or using 'encodeRawKey' to encode the key into 32 bytes first`
)
}
return prepareWriteContract({ return prepareWriteContract({
address: contractAddress, address: contractAddress,
abi, abi,
......
...@@ -49,7 +49,7 @@ describe(prepareWriteAttestations.name, () => { ...@@ -49,7 +49,7 @@ describe(prepareWriteAttestations.name, () => {
expect(result.address).toMatchInlineSnapshot( expect(result.address).toMatchInlineSnapshot(
'"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"' '"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"'
) )
expect(result.chainId).toMatchInlineSnapshot('10') expect(result.chainId).toMatchInlineSnapshot('undefined')
expect(result.functionName).toMatchInlineSnapshot('"attest"') expect(result.functionName).toMatchInlineSnapshot('"attest"')
expect(result.mode).toMatchInlineSnapshot('"prepared"') expect(result.mode).toMatchInlineSnapshot('"prepared"')
expect(result.request.gasLimit).toMatchInlineSnapshot(` expect(result.request.gasLimit).toMatchInlineSnapshot(`
...@@ -60,18 +60,16 @@ describe(prepareWriteAttestations.name, () => { ...@@ -60,18 +60,16 @@ describe(prepareWriteAttestations.name, () => {
`) `)
}) })
it('should throw an error if key is longer than 32 bytes', async () => { it('should work if key is longer than 32 bytes', async () => {
const dataType = 'string' const dataType = 'string'
await expect( expect(
readAttestation( await readAttestation(
creator, creator,
about, about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot', 'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType dataType
) )
).rejects.toThrowErrorMatchingInlineSnapshot( ).toMatchInlineSnapshot('""')
'"Key is longer than the max length of 32 for attestation keys"'
)
}) })
}) })
...@@ -26,16 +26,14 @@ describe(readAttestation.name, () => { ...@@ -26,16 +26,14 @@ describe(readAttestation.name, () => {
) )
}) })
it('should throw an error if key is longer than 32 bytes', async () => { it('should work if key is longer than 32 bytes', async () => {
await expect( expect(
readAttestation( await readAttestation(
creator, creator,
about, about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot', 'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType dataType
) )
).rejects.toThrowErrorMatchingInlineSnapshot( ).toMatchInlineSnapshot('""')
'"Key is longer than the max length of 32 for attestation keys"'
)
}) })
}) })
import { readContracts } from '@wagmi/core' import { readContracts } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress' import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import type { AttestationReadParams } from '../types/AttestationReadParams' import type { AttestationReadParams } from '../types/AttestationReadParams'
import { DEFAULT_DATA_TYPE } from '../types/DataTypeOption' import { DEFAULT_DATA_TYPE } from '../types/DataTypeOption'
import type { WagmiBytes } from '../types/WagmiBytes' import type { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi' import { abi } from './abi'
import { createKey } from './createKey'
import { parseAttestationBytes } from './parseAttestationBytes' import { parseAttestationBytes } from './parseAttestationBytes'
/** /**
...@@ -39,16 +39,11 @@ export const readAttestations = async ( ...@@ -39,16 +39,11 @@ export const readAttestations = async (
key, key,
contractAddress = ATTESTATION_STATION_ADDRESS, contractAddress = ATTESTATION_STATION_ADDRESS,
} = attestation } = attestation
if (key.length > 32) {
throw new Error(
'Key is longer than the max length of 32 for attestation keys'
)
}
return { return {
address: contractAddress, address: contractAddress,
abi, abi,
functionName: 'attestations', functionName: 'attestations',
args: [creator, about, formatBytes32String(key) as WagmiBytes], args: [creator, about, createKey(key) as WagmiBytes],
} as const } as const
}) })
......
...@@ -174,16 +174,18 @@ L2OutputOracleUpgradeable_Test:test_initValuesOnProxy_succeeds() (gas: 26208) ...@@ -174,16 +174,18 @@ L2OutputOracleUpgradeable_Test:test_initValuesOnProxy_succeeds() (gas: 26208)
L2OutputOracleUpgradeable_Test:test_initializeImpl_alreadyInitialized_reverts() (gas: 15149) L2OutputOracleUpgradeable_Test:test_initializeImpl_alreadyInitialized_reverts() (gas: 15149)
L2OutputOracleUpgradeable_Test:test_initializeProxy_alreadyInitialized_reverts() (gas: 20175) L2OutputOracleUpgradeable_Test:test_initializeProxy_alreadyInitialized_reverts() (gas: 20175)
L2OutputOracleUpgradeable_Test:test_upgrading_succeeds() (gas: 180481) L2OutputOracleUpgradeable_Test:test_upgrading_succeeds() (gas: 180481)
L2StandardBridge_BridgeERC20To_Test:test_bridgeERC20To_succeeds() (gas: 387833) L2StandardBridge_BridgeERC20To_Test:test_bridgeERC20To_succeeds() (gas: 389773)
L2StandardBridge_BridgeERC20To_Test:test_withdrawTo_withdrawingERC20_succeeds() (gas: 388064) L2StandardBridge_BridgeERC20To_Test:test_withdrawTo_withdrawingERC20_succeeds() (gas: 390006)
L2StandardBridge_BridgeERC20_Test:test_bridgeERC20_succeeds() (gas: 383536) L2StandardBridge_BridgeERC20_Test:test_bridgeERC20_succeeds() (gas: 385280)
L2StandardBridge_BridgeERC20_Test:test_bridgeLegacyERC20_succeeds() (gas: 393552)
L2StandardBridge_BridgeERC20_Test:test_withdrawLegacyERC20_succeeds() (gas: 393878)
L2StandardBridge_BridgeERC20_Test:test_withdraw_notEOA_reverts() (gas: 251758) L2StandardBridge_BridgeERC20_Test:test_withdraw_notEOA_reverts() (gas: 251758)
L2StandardBridge_BridgeERC20_Test:test_withdraw_withdrawingERC20_succeeds() (gas: 383722) L2StandardBridge_BridgeERC20_Test:test_withdraw_withdrawingERC20_succeeds() (gas: 385508)
L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_incorrectValue_reverts() (gas: 23843) L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_incorrectValue_reverts() (gas: 23843)
L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToMessenger_reverts() (gas: 23982) L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToMessenger_reverts() (gas: 23982)
L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToSelf_reverts() (gas: 23870) L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToSelf_reverts() (gas: 23870)
L2StandardBridge_Bridge_Test:test_finalizeDeposit_depositingERC20_succeeds() (gas: 91013) L2StandardBridge_Bridge_Test:test_finalizeDeposit_depositingERC20_succeeds() (gas: 93824)
L2StandardBridge_Bridge_Test:test_finalizeDeposit_depositingETH_succeeds() (gas: 89889) L2StandardBridge_Bridge_Test:test_finalizeDeposit_depositingETH_succeeds() (gas: 92700)
L2StandardBridge_FinalizeBridgeETH_Test:test_finalizeBridgeETH_succeeds() (gas: 43155) L2StandardBridge_FinalizeBridgeETH_Test:test_finalizeBridgeETH_succeeds() (gas: 43155)
L2StandardBridge_Test:test_initialize_succeeds() (gas: 24292) L2StandardBridge_Test:test_initialize_succeeds() (gas: 24292)
L2StandardBridge_Test:test_receive_succeeds() (gas: 174011) L2StandardBridge_Test:test_receive_succeeds() (gas: 174011)
...@@ -264,13 +266,13 @@ OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutp ...@@ -264,13 +266,13 @@ OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutp
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 207520) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 207520)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41753) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41753)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 199464) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 199464)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 203388) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 206360)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 180229) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 180229)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 244483) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 244377)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 245634) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 245528)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 53555) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 53555)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 235047) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 234941)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_targetFails_fails() (gas: 8797746687696163866) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_targetFails_fails() (gas: 8797746687696163864)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_timestampLessThanL2OracleStart_reverts() (gas: 197042) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_timestampLessThanL2OracleStart_reverts() (gas: 197042)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() (gas: 85690) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() (gas: 85690)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() (gas: 137350) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() (gas: 137350)
...@@ -403,6 +405,8 @@ ResourceMetering_Test:test_meter_updateTenEmptyBlocks_succeeds() (gas: 21161) ...@@ -403,6 +405,8 @@ ResourceMetering_Test:test_meter_updateTenEmptyBlocks_succeeds() (gas: 21161)
ResourceMetering_Test:test_meter_updateTwoEmptyBlocks_succeeds() (gas: 21117) ResourceMetering_Test:test_meter_updateTwoEmptyBlocks_succeeds() (gas: 21117)
ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 8017416) ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 8017416)
ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 16045) ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 16045)
SafeCall_call_Test:test_callWithMinGas_noLeakageHigh_succeeds() (gas: 2075873614)
SafeCall_call_Test:test_callWithMinGas_noLeakageLow_succeeds() (gas: 753665282)
Semver_Test:test_behindProxy_succeeds() (gas: 506748) Semver_Test:test_behindProxy_succeeds() (gas: 506748)
Semver_Test:test_version_succeeds() (gas: 9418) Semver_Test:test_version_succeeds() (gas: 9418)
SequencerFeeVault_Test:test_constructor_succeeds() (gas: 5526) SequencerFeeVault_Test:test_constructor_succeeds() (gas: 5526)
...@@ -410,6 +414,8 @@ SequencerFeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 5442) ...@@ -410,6 +414,8 @@ SequencerFeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 5442)
SequencerFeeVault_Test:test_receive_succeeds() (gas: 17373) SequencerFeeVault_Test:test_receive_succeeds() (gas: 17373)
SequencerFeeVault_Test:test_withdraw_notEnough_reverts() (gas: 9331) SequencerFeeVault_Test:test_withdraw_notEnough_reverts() (gas: 9331)
SequencerFeeVault_Test:test_withdraw_succeeds() (gas: 163228) SequencerFeeVault_Test:test_withdraw_succeeds() (gas: 163228)
StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 49936)
StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33072)
SystemConfig_Initialize_TestFail:test_initialize_lowGasLimit_reverts() (gas: 62012) SystemConfig_Initialize_TestFail:test_initialize_lowGasLimit_reverts() (gas: 62012)
SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10612) SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10612)
SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10555) SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10555)
......
...@@ -195,6 +195,11 @@ After the initial Bedrock upgrade, contracts MUST use the following versioning s ...@@ -195,6 +195,11 @@ After the initial Bedrock upgrade, contracts MUST use the following versioning s
We have made an exception to the `Semver` rule for the `WETH` contract to avoid making changes to a well-known, simple, and recognizable contract. We have made an exception to the `Semver` rule for the `WETH` contract to avoid making changes to a well-known, simple, and recognizable contract.
### Dependencies
Where basic functionality is already supported by an existing contract in the OpenZeppelin library,
we should default to using the Upgradeable version of that contract.
### Tests ### Tests
Tests are written using Foundry. Tests are written using Foundry.
...@@ -207,7 +212,7 @@ These guidelines are also encoded in a script which can be run with: ...@@ -207,7 +212,7 @@ These guidelines are also encoded in a script which can be run with:
ts-node scripts/forge-test-names.ts ts-node scripts/forge-test-names.ts
``` ```
*Note: This is a work in progress, not all test files are compliant with these guidelines.* _Note: This is a work in progress, not all test files are compliant with these guidelines._
#### Organizing Principles #### Organizing Principles
......
...@@ -43,11 +43,6 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -43,11 +43,6 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*/ */
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/**
* @notice Additional gas reserved for clean up after finalizing a transaction withdrawal.
*/
uint256 internal constant FINALIZE_GAS_BUFFER = 20_000;
/** /**
* @notice Address of the L2OutputOracle. * @notice Address of the L2OutputOracle.
*/ */
...@@ -363,26 +358,19 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -363,26 +358,19 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
// Mark the withdrawal as finalized so it can't be replayed. // Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true; finalizedWithdrawals[withdrawalHash] = true;
// We want to maintain the property that the amount of gas supplied to the call to the
// target contract is at least the gas limit specified by the user. We can do this by
// enforcing that, at this point in time, we still have gaslimit + buffer gas available.
require(
gasleft() >= _tx.gasLimit + FINALIZE_GAS_BUFFER,
"OptimismPortal: insufficient gas to finalize withdrawal"
);
// Set the l2Sender so contracts know who triggered this withdrawal on L2. // Set the l2Sender so contracts know who triggered this withdrawal on L2.
l2Sender = _tx.sender; l2Sender = _tx.sender;
// Trigger the call to the target contract. We use SafeCall because we don't // Trigger the call to the target contract. We use a custom low level method
// care about the returndata and we don't want target contracts to be able to force this // SafeCall.callWithMinGas to ensure two key properties
// call to run out of gas via a returndata bomb. // 1. Target contracts cannot force this call to run out of gas by returning a very large
bool success = SafeCall.call( // amount of data (and this is OK because we don't care about the returndata here).
_tx.target, // 2. The amount of gas provided to the call to the target contract is at least the gas
gasleft() - FINALIZE_GAS_BUFFER, // limit specified by the user. If there is not enough gas in the callframe to
_tx.value, // accomplish this, `callWithMinGas` will revert.
_tx.data // Additionally, if there is not enough gas remaining to complete the execution after the
); // call returns, this function will revert.
bool success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
// Reset the l2Sender back to the default value. // Reset the l2Sender back to the default value.
l2Sender = Constants.DEFAULT_L2_SENDER; l2Sender = Constants.DEFAULT_L2_SENDER;
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ILegacyMintableERC20 } from "../universal/OptimismMintableERC20.sol";
/**
* @title LegacyMintableERC20
* @notice The legacy implementation of the OptimismMintableERC20. This
* contract is deprecated and should no longer be used.
*/
contract LegacyMintableERC20 is ILegacyMintableERC20, ERC20 {
/**
* @notice Emitted when the token is minted by the bridge.
*/
event Mint(address indexed _account, uint256 _amount);
/**
* @notice Emitted when a token is burned by the bridge.
*/
event Burn(address indexed _account, uint256 _amount);
/**
* @notice The token on the remote domain.
*/
address public l1Token;
/**
* @notice The local bridge.
*/
address public l2Bridge;
/**
* @param _l2Bridge Address of the L2 standard bridge.
* @param _l1Token Address of the corresponding L1 token.
* @param _name ERC20 name.
* @param _symbol ERC20 symbol.
*/
constructor(
address _l2Bridge,
address _l1Token,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) {
l1Token = _l1Token;
l2Bridge = _l2Bridge;
}
/**
* @notice Modifier that requires the contract was called by the bridge.
*/
modifier onlyL2Bridge() {
require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");
_;
}
/**
* @notice EIP165 implementation.
*/
function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {
bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165
bytes4 secondSupportedInterface = ILegacyMintableERC20.l1Token.selector ^
ILegacyMintableERC20.mint.selector ^
ILegacyMintableERC20.burn.selector;
return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;
}
/**
* @notice Only the bridge can mint tokens.
* @param _to The account receiving tokens.
* @param _amount The amount of tokens to receive.
*/
function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {
_mint(_to, _amount);
emit Mint(_to, _amount);
}
/**
* @notice Only the bridge can burn tokens.
* @param _from The account having tokens burnt.
* @param _amount The amount of tokens being burnt.
*/
function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {
_burn(_from, _amount);
emit Burn(_from, _amount);
}
}
...@@ -26,7 +26,7 @@ library SafeCall { ...@@ -26,7 +26,7 @@ library SafeCall {
_gas, // gas _gas, // gas
_target, // recipient _target, // recipient
_value, // ether value _value, // ether value
add(_calldata, 0x20), // inloc add(_calldata, 32), // inloc
mload(_calldata), // inlen mload(_calldata), // inlen
0, // outloc 0, // outloc
0 // outlen 0 // outlen
...@@ -34,4 +34,71 @@ library SafeCall { ...@@ -34,4 +34,71 @@ library SafeCall {
} }
return _success; return _success;
} }
/**
* @notice Perform a low level call without copying any returndata. This function
* will revert if the call cannot be performed with the specified minimum
* gas.
*
* @param _target Address to call
* @param _minGas The minimum amount of gas that may be passed to the call
* @param _value Amount of value to pass to the call
* @param _calldata Calldata to pass to the call
*/
function callWithMinGas(
address _target,
uint256 _minGas,
uint256 _value,
bytes memory _calldata
) internal returns (bool) {
bool _success;
assembly {
// Assertion: gasleft() >= ((_minGas + 200) * 64) / 63
//
// Because EIP-150 ensures that, a maximum of 63/64ths of the remaining gas in the call
// frame may be passed to a subcontext, we need to ensure that the gas will not be
// truncated to hold this function's invariant: "If a call is performed by
// `callWithMinGas`, it must receive at least the specified minimum gas limit." In
// addition, exactly 51 gas is consumed between the below `GAS` opcode and the `CALL`
// opcode, so it is factored in with some extra room for error.
if lt(gas(), div(mul(64, add(_minGas, 200)), 63)) {
// Store the "Error(string)" selector in scratch space.
mstore(0, 0x08c379a0)
// Store the pointer to the string length in scratch space.
mstore(32, 32)
// Store the string.
//
// SAFETY:
// - We pad the beginning of the string with two zero bytes as well as the
// length (24) to ensure that we override the free memory pointer at offset
// 0x40. This is necessary because the free memory pointer is likely to
// be greater than 1 byte when this function is called, but it is incredibly
// unlikely that it will be greater than 3 bytes. As for the data within
// 0x60, it is ensured that it is 0 due to 0x60 being the zero offset.
// - It's fine to clobber the free memory pointer, we're reverting.
mstore(88, 0x0000185361666543616c6c3a204e6f7420656e6f75676820676173)
// Revert with 'Error("SafeCall: Not enough gas")'
revert(28, 100)
}
// The call will be supplied at least (((_minGas + 200) * 64) / 63) - 49 gas due to the
// above assertion. This ensures that, in all circumstances, the call will
// receive at least the minimum amount of gas specified.
// We can prove this property by solving the inequalities:
// ((((_minGas + 200) * 64) / 63) - 49) >= _minGas
// ((((_minGas + 200) * 64) / 63) - 51) * (63 / 64) >= _minGas
// Both inequalities hold true for all possible values of `_minGas`.
_success := call(
gas(), // gas
_target, // recipient
_value, // ether value
add(_calldata, 32), // inloc
mload(_calldata), // inlen
0x00, // outloc
0x00 // outlen
)
}
return _success;
}
} }
...@@ -27,6 +27,7 @@ import { AddressManager } from "../legacy/AddressManager.sol"; ...@@ -27,6 +27,7 @@ import { AddressManager } from "../legacy/AddressManager.sol";
import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol"; import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol"; import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol";
contract CommonTest is Test { contract CommonTest is Test {
address alice = address(128); address alice = address(128);
...@@ -276,6 +277,7 @@ contract Bridge_Initializer is Messenger_Initializer { ...@@ -276,6 +277,7 @@ contract Bridge_Initializer is Messenger_Initializer {
ERC20 L1Token; ERC20 L1Token;
ERC20 BadL1Token; ERC20 BadL1Token;
OptimismMintableERC20 L2Token; OptimismMintableERC20 L2Token;
LegacyMintableERC20 LegacyL2Token;
ERC20 NativeL2Token; ERC20 NativeL2Token;
ERC20 BadL2Token; ERC20 BadL2Token;
OptimismMintableERC20 RemoteL1Token; OptimismMintableERC20 RemoteL1Token;
...@@ -398,6 +400,14 @@ contract Bridge_Initializer is Messenger_Initializer { ...@@ -398,6 +400,14 @@ contract Bridge_Initializer is Messenger_Initializer {
L1Token = new ERC20("Native L1 Token", "L1T"); L1Token = new ERC20("Native L1 Token", "L1T");
LegacyL2Token = new LegacyMintableERC20({
_l2Bridge: address(L2Bridge),
_l1Token: address(L1Token),
_name: string.concat("LegacyL2-", L1Token.name()),
_symbol: string.concat("LegacyL2-", L1Token.symbol())
});
vm.label(address(LegacyL2Token), "LegacyMintableERC20");
// Deploy the L2 ERC20 now // Deploy the L2 ERC20 now
L2Token = OptimismMintableERC20( L2Token = OptimismMintableERC20(
L2TokenFactory.createStandardL2Token( L2TokenFactory.createStandardL2Token(
......
...@@ -155,15 +155,15 @@ contract L2StandardBridge_Test is Bridge_Initializer { ...@@ -155,15 +155,15 @@ contract L2StandardBridge_Test is Bridge_Initializer {
contract PreBridgeERC20 is Bridge_Initializer { contract PreBridgeERC20 is Bridge_Initializer {
// withdraw and BridgeERC20 should behave the same when transferring ERC20 tokens // withdraw and BridgeERC20 should behave the same when transferring ERC20 tokens
// so they should share the same setup and expectEmit calls // so they should share the same setup and expectEmit calls
function _preBridgeERC20(bool isLegacy) internal { function _preBridgeERC20(bool _isLegacy, address _l2Token) internal {
// Alice has 100 L2Token // Alice has 100 L2Token
deal(address(L2Token), alice, 100, true); deal(_l2Token, alice, 100, true);
assertEq(L2Token.balanceOf(alice), 100); assertEq(ERC20(_l2Token).balanceOf(alice), 100);
uint256 nonce = L2Messenger.messageNonce(); uint256 nonce = L2Messenger.messageNonce();
bytes memory message = abi.encodeWithSelector( bytes memory message = abi.encodeWithSelector(
StandardBridge.finalizeBridgeERC20.selector, StandardBridge.finalizeBridgeERC20.selector,
address(L1Token), address(L1Token),
address(L2Token), _l2Token,
alice, alice,
alice, alice,
100, 100,
...@@ -190,23 +190,17 @@ contract PreBridgeERC20 is Bridge_Initializer { ...@@ -190,23 +190,17 @@ contract PreBridgeERC20 is Bridge_Initializer {
}) })
); );
if (isLegacy) { if (_isLegacy) {
vm.expectCall( vm.expectCall(
address(L2Bridge), address(L2Bridge),
abi.encodeWithSelector( abi.encodeWithSelector(L2Bridge.withdraw.selector, _l2Token, 100, 1000, hex"")
L2Bridge.withdraw.selector,
address(L2Token),
100,
1000,
hex""
)
); );
} else { } else {
vm.expectCall( vm.expectCall(
address(L2Bridge), address(L2Bridge),
abi.encodeWithSelector( abi.encodeWithSelector(
L2Bridge.bridgeERC20.selector, L2Bridge.bridgeERC20.selector,
address(L2Token), _l2Token,
address(L1Token), address(L1Token),
100, 100,
1000, 1000,
...@@ -237,15 +231,15 @@ contract PreBridgeERC20 is Bridge_Initializer { ...@@ -237,15 +231,15 @@ contract PreBridgeERC20 is Bridge_Initializer {
// The L2Bridge should burn the tokens // The L2Bridge should burn the tokens
vm.expectCall( vm.expectCall(
address(L2Token), _l2Token,
abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100) abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100)
); );
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
emit WithdrawalInitiated(address(L1Token), address(L2Token), alice, alice, 100, hex""); emit WithdrawalInitiated(address(L1Token), _l2Token, alice, alice, 100, hex"");
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
emit ERC20BridgeInitiated(address(L2Token), address(L1Token), alice, alice, 100, hex""); emit ERC20BridgeInitiated(_l2Token, address(L1Token), alice, alice, 100, hex"");
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
emit MessagePassed( emit MessagePassed(
...@@ -276,7 +270,7 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 { ...@@ -276,7 +270,7 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 {
// - emits WithdrawalInitiated // - emits WithdrawalInitiated
// - calls Withdrawer.initiateWithdrawal // - calls Withdrawer.initiateWithdrawal
function test_withdraw_withdrawingERC20_succeeds() external { function test_withdraw_withdrawingERC20_succeeds() external {
_preBridgeERC20({ isLegacy: true }); _preBridgeERC20({ _isLegacy: true, _l2Token: address(L2Token) });
L2Bridge.withdraw(address(L2Token), 100, 1000, hex""); L2Bridge.withdraw(address(L2Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0); assertEq(L2Token.balanceOf(alice), 0);
...@@ -287,12 +281,26 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 { ...@@ -287,12 +281,26 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 {
// - emits WithdrawalInitiated // - emits WithdrawalInitiated
// - calls Withdrawer.initiateWithdrawal // - calls Withdrawer.initiateWithdrawal
function test_bridgeERC20_succeeds() external { function test_bridgeERC20_succeeds() external {
_preBridgeERC20({ isLegacy: false }); _preBridgeERC20({ _isLegacy: false, _l2Token: address(L2Token) });
L2Bridge.bridgeERC20(address(L2Token), address(L1Token), 100, 1000, hex""); L2Bridge.bridgeERC20(address(L2Token), address(L1Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0); assertEq(L2Token.balanceOf(alice), 0);
} }
function test_withdrawLegacyERC20_succeeds() external {
_preBridgeERC20({ _isLegacy: true, _l2Token: address(LegacyL2Token) });
L2Bridge.withdraw(address(LegacyL2Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
}
function test_bridgeLegacyERC20_succeeds() external {
_preBridgeERC20({ _isLegacy: false, _l2Token: address(LegacyL2Token) });
L2Bridge.bridgeERC20(address(LegacyL2Token), address(L1Token), 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0);
}
function test_withdraw_notEOA_reverts() external { function test_withdraw_notEOA_reverts() external {
// This contract has 100 L2Token // This contract has 100 L2Token
deal(address(L2Token), address(this), 100, true); deal(address(L2Token), address(this), 100, true);
...@@ -305,14 +313,14 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 { ...@@ -305,14 +313,14 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 {
contract PreBridgeERC20To is Bridge_Initializer { contract PreBridgeERC20To is Bridge_Initializer {
// withdrawTo and BridgeERC20To should behave the same when transferring ERC20 tokens // withdrawTo and BridgeERC20To should behave the same when transferring ERC20 tokens
// so they should share the same setup and expectEmit calls // so they should share the same setup and expectEmit calls
function _preBridgeERC20To(bool isLegacy) internal { function _preBridgeERC20To(bool _isLegacy, address _l2Token) internal {
deal(address(L2Token), alice, 100, true); deal(_l2Token, alice, 100, true);
assertEq(L2Token.balanceOf(alice), 100); assertEq(ERC20(L2Token).balanceOf(alice), 100);
uint256 nonce = L2Messenger.messageNonce(); uint256 nonce = L2Messenger.messageNonce();
bytes memory message = abi.encodeWithSelector( bytes memory message = abi.encodeWithSelector(
StandardBridge.finalizeBridgeERC20.selector, StandardBridge.finalizeBridgeERC20.selector,
address(L1Token), address(L1Token),
address(L2Token), _l2Token,
alice, alice,
bob, bob,
100, 100,
...@@ -340,10 +348,10 @@ contract PreBridgeERC20To is Bridge_Initializer { ...@@ -340,10 +348,10 @@ contract PreBridgeERC20To is Bridge_Initializer {
); );
vm.expectEmit(true, true, true, true, address(L2Bridge)); vm.expectEmit(true, true, true, true, address(L2Bridge));
emit WithdrawalInitiated(address(L1Token), address(L2Token), alice, bob, 100, hex""); emit WithdrawalInitiated(address(L1Token), _l2Token, alice, bob, 100, hex"");
vm.expectEmit(true, true, true, true, address(L2Bridge)); vm.expectEmit(true, true, true, true, address(L2Bridge));
emit ERC20BridgeInitiated(address(L2Token), address(L1Token), alice, bob, 100, hex""); emit ERC20BridgeInitiated(_l2Token, address(L1Token), alice, bob, 100, hex"");
vm.expectEmit(true, true, true, true, address(messagePasser)); vm.expectEmit(true, true, true, true, address(messagePasser));
emit MessagePassed( emit MessagePassed(
...@@ -364,12 +372,12 @@ contract PreBridgeERC20To is Bridge_Initializer { ...@@ -364,12 +372,12 @@ contract PreBridgeERC20To is Bridge_Initializer {
vm.expectEmit(true, true, true, true, address(L2Messenger)); vm.expectEmit(true, true, true, true, address(L2Messenger));
emit SentMessageExtension1(address(L2Bridge), 0); emit SentMessageExtension1(address(L2Bridge), 0);
if (isLegacy) { if (_isLegacy) {
vm.expectCall( vm.expectCall(
address(L2Bridge), address(L2Bridge),
abi.encodeWithSelector( abi.encodeWithSelector(
L2Bridge.withdrawTo.selector, L2Bridge.withdrawTo.selector,
address(L2Token), _l2Token,
bob, bob,
100, 100,
1000, 1000,
...@@ -381,7 +389,7 @@ contract PreBridgeERC20To is Bridge_Initializer { ...@@ -381,7 +389,7 @@ contract PreBridgeERC20To is Bridge_Initializer {
address(L2Bridge), address(L2Bridge),
abi.encodeWithSelector( abi.encodeWithSelector(
L2Bridge.bridgeERC20To.selector, L2Bridge.bridgeERC20To.selector,
address(L2Token), _l2Token,
address(L1Token), address(L1Token),
bob, bob,
100, 100,
...@@ -427,7 +435,7 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To { ...@@ -427,7 +435,7 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To {
// - emits WithdrawalInitiated w/ correct recipient // - emits WithdrawalInitiated w/ correct recipient
// - calls Withdrawer.initiateWithdrawal // - calls Withdrawer.initiateWithdrawal
function test_withdrawTo_withdrawingERC20_succeeds() external { function test_withdrawTo_withdrawingERC20_succeeds() external {
_preBridgeERC20To({ isLegacy: true }); _preBridgeERC20To({ _isLegacy: true, _l2Token: address(L2Token) });
L2Bridge.withdrawTo(address(L2Token), bob, 100, 1000, hex""); L2Bridge.withdrawTo(address(L2Token), bob, 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0); assertEq(L2Token.balanceOf(alice), 0);
...@@ -438,7 +446,7 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To { ...@@ -438,7 +446,7 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To {
// - emits WithdrawalInitiated w/ correct recipient // - emits WithdrawalInitiated w/ correct recipient
// - calls Withdrawer.initiateWithdrawal // - calls Withdrawer.initiateWithdrawal
function test_bridgeERC20To_succeeds() external { function test_bridgeERC20To_succeeds() external {
_preBridgeERC20To({ isLegacy: false }); _preBridgeERC20To({ _isLegacy: false, _l2Token: address(L2Token) });
L2Bridge.bridgeERC20To(address(L2Token), address(L1Token), bob, 100, 1000, hex""); L2Bridge.bridgeERC20To(address(L2Token), address(L1Token), bob, 100, 1000, hex"");
assertEq(L2Token.balanceOf(alice), 0); assertEq(L2Token.balanceOf(alice), 0);
} }
......
...@@ -905,7 +905,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is Portal_Initializer { ...@@ -905,7 +905,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is Portal_Initializer {
); );
vm.warp(block.timestamp + oracle.FINALIZATION_PERIOD_SECONDS() + 1); vm.warp(block.timestamp + oracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectRevert("OptimismPortal: insufficient gas to finalize withdrawal"); vm.expectRevert("SafeCall: Not enough gas");
op.finalizeWithdrawalTransaction{ gas: gasLimit }(insufficientGasTx); op.finalizeWithdrawalTransaction{ gas: gasLimit }(insufficientGasTx);
} }
......
...@@ -14,8 +14,8 @@ contract SafeCall_call_Test is CommonTest { ...@@ -14,8 +14,8 @@ contract SafeCall_call_Test is CommonTest {
) external { ) external {
vm.assume(from.balance == 0); vm.assume(from.balance == 0);
vm.assume(to.balance == 0); vm.assume(to.balance == 0);
// no precompiles // no precompiles (mainnet)
vm.assume(uint160(to) > 10); assumeNoPrecompiles(to, 1);
// don't call the vm // don't call the vm
vm.assume(to != address(vm)); vm.assume(to != address(vm));
vm.assume(from != address(vm)); vm.assume(from != address(vm));
...@@ -23,20 +23,140 @@ contract SafeCall_call_Test is CommonTest { ...@@ -23,20 +23,140 @@ contract SafeCall_call_Test is CommonTest {
vm.assume(to != address(0x000000000000000000636F6e736F6c652e6c6f67)); vm.assume(to != address(0x000000000000000000636F6e736F6c652e6c6f67));
// don't call the create2 deployer // don't call the create2 deployer
vm.assume(to != address(0x4e59b44847b379578588920cA78FbF26c0B4956C)); vm.assume(to != address(0x4e59b44847b379578588920cA78FbF26c0B4956C));
// don't send funds to self
vm.assume(from != to);
assertEq(from.balance, 0, "from balance is 0"); assertEq(from.balance, 0, "from balance is 0");
vm.deal(from, value); vm.deal(from, value);
assertEq(from.balance, value, "from balance not dealt"); assertEq(from.balance, value, "from balance not dealt");
vm.expectCall(to, value, data); uint256[2] memory balancesBefore = [from.balance, to.balance];
vm.expectCall(to, value, data);
vm.prank(from); vm.prank(from);
bool success = SafeCall.call(to, gas, value, data); bool success = SafeCall.call(to, gas, value, data);
assertEq(success, true, "call not successful"); assertTrue(success, "call not successful");
assertEq(to.balance, value, "to balance received"); if (from == to) {
assertEq(from.balance, 0, "from balance not drained"); assertEq(from.balance, balancesBefore[0], "Self-send did not change balance");
} else {
assertEq(from.balance, balancesBefore[0] - value, "from balance not drained");
assertEq(to.balance, balancesBefore[1] + value, "to balance received");
}
}
function testFuzz_callWithMinGas_hasEnough_succeeds(
address from,
address to,
uint64 minGas,
uint64 value,
bytes memory data
) external {
vm.assume(from.balance == 0);
vm.assume(to.balance == 0);
// no precompiles (mainnet)
assumeNoPrecompiles(to, 1);
// don't call the vm
vm.assume(to != address(vm));
vm.assume(from != address(vm));
// don't call the console
vm.assume(to != address(0x000000000000000000636F6e736F6c652e6c6f67));
// don't call the create2 deployer
vm.assume(to != address(0x4e59b44847b379578588920cA78FbF26c0B4956C));
assertEq(from.balance, 0, "from balance is 0");
vm.deal(from, value);
assertEq(from.balance, value, "from balance not dealt");
// Bound minGas to [0, l1_block_gas_limit]
minGas = uint64(bound(minGas, 0, 30_000_000));
uint256[2] memory balancesBefore = [from.balance, to.balance];
vm.expectCallMinGas(to, value, minGas, data);
vm.prank(from);
bool success = SafeCall.callWithMinGas(to, minGas, value, data);
assertTrue(success, "call not successful");
if (from == to) {
assertEq(from.balance, balancesBefore[0], "Self-send did not change balance");
} else {
assertEq(from.balance, balancesBefore[0] - value, "from balance not drained");
assertEq(to.balance, balancesBefore[1] + value, "to balance received");
}
}
function test_callWithMinGas_noLeakageLow_succeeds() external {
SimpleSafeCaller caller = new SimpleSafeCaller();
for (uint64 i = 5000; i < 50_000; i++) {
uint256 snapshot = vm.snapshot();
// 26,071 is the exact amount of gas required to make the safe call
// successfully.
if (i < 26_071) {
assertFalse(caller.makeSafeCall(i, 25_000));
} else {
vm.expectCallMinGas(
address(caller),
0,
25_000,
abi.encodeWithSelector(caller.setA.selector, 1)
);
assertTrue(caller.makeSafeCall(i, 25_000));
}
assertTrue(vm.revertTo(snapshot));
}
}
function test_callWithMinGas_noLeakageHigh_succeeds() external {
SimpleSafeCaller caller = new SimpleSafeCaller();
for (uint64 i = 15_200_000; i < 15_300_000; i++) {
uint256 snapshot = vm.snapshot();
// 15,238,769 is the exact amount of gas required to make the safe call
// successfully.
if (i < 15_238_769) {
assertFalse(caller.makeSafeCall(i, 15_000_000));
} else {
vm.expectCallMinGas(
address(caller),
0,
15_000_000,
abi.encodeWithSelector(caller.setA.selector, 1)
);
assertTrue(caller.makeSafeCall(i, 15_000_000));
}
assertTrue(vm.revertTo(snapshot));
}
}
}
contract SimpleSafeCaller {
uint256 public a;
function makeSafeCall(uint64 gas, uint64 minGas) external returns (bool) {
return
SafeCall.call(
address(this),
gas,
0,
abi.encodeWithSelector(this.makeSafeCallMinGas.selector, minGas)
);
}
function makeSafeCallMinGas(uint64 minGas) external returns (bool) {
return
SafeCall.callWithMinGas(
address(this),
minGas,
0,
abi.encodeWithSelector(this.setA.selector, 1)
);
}
function setA(uint256 _a) external {
a = _a;
} }
} }
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { StandardBridge } from "../universal/StandardBridge.sol";
import { CommonTest } from "./CommonTest.t.sol";
import {
OptimismMintableERC20,
ILegacyMintableERC20
} from "../universal/OptimismMintableERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title StandardBridgeTester
* @notice Simple wrapper around the StandardBridge contract that exposes
* internal functions so they can be more easily tested directly.
*/
contract StandardBridgeTester is StandardBridge {
constructor(address payable _messenger, address payable _otherBridge)
StandardBridge(_messenger, _otherBridge)
{}
function isOptimismMintableERC20(address _token) external view returns (bool) {
return _isOptimismMintableERC20(_token);
}
function isCorrectTokenPair(address _mintableToken, address _otherToken)
external
view
returns (bool)
{
return _isCorrectTokenPair(_mintableToken, _otherToken);
}
receive() external payable override {}
}
/**
* @title LegacyMintable
* @notice Simple implementation of the legacy OptimismMintableERC20.
*/
contract LegacyMintable is ERC20, ILegacyMintableERC20 {
constructor(string memory _name, string memory _ticker) ERC20(_name, _ticker) {}
function l1Token() external view returns (address) {
return address(0);
}
function mint(address _to, uint256 _amount) external pure {}
function burn(address _from, uint256 _amount) external pure {}
/**
* @notice Implements ERC165. This implementation should not be changed as
* it is how the actual legacy optimism mintable token does the
* check. Allows for testing against code that is has been deployed,
* assuming different compiler version is no problem.
*/
function supportsInterface(bytes4 _interfaceId) external view returns (bool) {
bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165
bytes4 secondSupportedInterface = ILegacyMintableERC20.l1Token.selector ^
ILegacyMintableERC20.mint.selector ^
ILegacyMintableERC20.burn.selector;
return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;
}
}
/**
* @title StandardBridge_Stateless_Test
* @notice Tests internal functions that require no existing state or contract
* interactions with the messenger.
*/
contract StandardBridge_Stateless_Test is CommonTest {
StandardBridgeTester internal bridge;
OptimismMintableERC20 internal mintable;
ERC20 internal erc20;
LegacyMintable internal legacy;
function setUp() public override {
super.setUp();
bridge = new StandardBridgeTester({
_messenger: payable(address(0)),
_otherBridge: payable(address(0))
});
mintable = new OptimismMintableERC20({
_bridge: address(0),
_remoteToken: address(0),
_name: "Stonks",
_symbol: "STONK"
});
erc20 = new ERC20("Altcoin", "ALT");
legacy = new LegacyMintable("Legacy", "LEG");
}
/**
* @notice Test coverage for identifying OptimismMintableERC20 tokens.
* This function should return true for both modern and legacy
* OptimismMintableERC20 tokens and false for any accounts that
* do not implement the interface.
*/
function test_isOptimismMintableERC20_succeeds() external {
// Both the modern and legacy mintable tokens should return true
assertTrue(bridge.isOptimismMintableERC20(address(mintable)));
assertTrue(bridge.isOptimismMintableERC20(address(legacy)));
// A regular ERC20 should return false
assertFalse(bridge.isOptimismMintableERC20(address(erc20)));
// Non existent contracts should return false and not revert
assertEq(address(0x20).code.length, 0);
assertFalse(bridge.isOptimismMintableERC20(address(0x20)));
}
/**
* @notice Test coverage of isCorrectTokenPair under different types of
* tokens.
*/
function test_isCorrectTokenPair_succeeds() external {
// Modern + known to be correct remote token
assertTrue(bridge.isCorrectTokenPair(address(mintable), mintable.remoteToken()));
// Modern + known to be correct l1Token (legacy interface)
assertTrue(bridge.isCorrectTokenPair(address(mintable), mintable.l1Token()));
// Modern + known to be incorrect remote token
assertTrue(mintable.remoteToken() != address(0x20));
assertFalse(bridge.isCorrectTokenPair(address(mintable), address(0x20)));
// Legacy + known to be correct l1Token
assertTrue(bridge.isCorrectTokenPair(address(legacy), legacy.l1Token()));
// Legacy + known to be incorrect l1Token
assertTrue(legacy.l1Token() != address(0x20));
assertFalse(bridge.isCorrectTokenPair(address(legacy), address(0x20)));
// A token that doesn't support either modern or legacy interface
// will revert
vm.expectRevert();
bridge.isCorrectTokenPair(address(erc20), address(1));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { StdUtils } from "forge-std/StdUtils.sol";
import { Vm } from "forge-std/Vm.sol";
import { SafeCall } from "../../libraries/SafeCall.sol";
contract SafeCall_Succeeds_Invariants is Test {
SafeCaller_Actor actor;
function setUp() public {
// Create a new safe caller actor.
actor = new SafeCaller_Actor(vm, false);
// Set the caller to this contract
targetSender(address(this));
// Target the safe caller actor.
targetContract(address(actor));
}
/**
* @custom:invariant If `callWithMinGas` performs a call, then it must always
* provide at least the specified minimum gas limit to the subcontext.
*
* If the check for remaining gas in `SafeCall.callWithMinGas` passes, the
* subcontext of the call below it must be provided at least `minGas` gas.
*/
function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public {
assertEq(actor.numCalls(), 0, "no failed calls allowed");
}
function performSafeCallMinGas(uint64 minGas) external {
SafeCall.callWithMinGas(address(0), minGas, 0, hex"");
}
}
contract SafeCall_Fails_Invariants is Test {
SafeCaller_Actor actor;
function setUp() public {
// Create a new safe caller actor.
actor = new SafeCaller_Actor(vm, true);
// Set the caller to this contract
targetSender(address(this));
// Target the safe caller actor.
targetContract(address(actor));
}
/**
* @custom:invariant `callWithMinGas` reverts if there is not enough gas to pass
* to the subcontext.
*
* If there is not enough gas in the callframe to ensure that `callWithMinGas`
* can provide the specified minimum gas limit to the subcontext of the call,
* then `callWithMinGas` must revert.
*/
function invariant_callWithMinGas_neverForwardsMinGas_reverts() public {
assertEq(actor.numCalls(), 0, "no successful calls allowed");
}
function performSafeCallMinGas(uint64 minGas) external {
SafeCall.callWithMinGas(address(0), minGas, 0, hex"");
}
}
contract SafeCaller_Actor is StdUtils {
bool internal immutable FAILS;
Vm internal vm;
uint256 public numCalls;
constructor(Vm _vm, bool _fails) {
vm = _vm;
FAILS = _fails;
}
function performSafeCallMinGas(uint64 gas, uint64 minGas) external {
if (FAILS) {
// Bound the minimum gas amount to [2500, type(uint48).max]
minGas = uint64(bound(minGas, 2500, type(uint48).max));
// Bound the gas passed to [minGas, (((minGas + 200) * 64) / 63)]
gas = uint64(bound(gas, minGas, (((minGas + 200) * 64) / 63)));
} else {
// Bound the minimum gas amount to [2500, type(uint48).max]
minGas = uint64(bound(minGas, 2500, type(uint48).max));
// Bound the gas passed to [(((minGas + 200) * 64) / 63) + 500, type(uint64).max]
gas = uint64(bound(gas, (((minGas + 200) * 64) / 63) + 500, type(uint64).max));
}
vm.expectCallMinGas(address(0x00), 0, minGas, hex"");
bool success = SafeCall.call(
msg.sender,
gas,
0,
abi.encodeWithSelector(0x2ae57a41, minGas)
);
if (success && FAILS) numCalls++;
if (!FAILS && !success) numCalls++;
}
}
...@@ -10,7 +10,7 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol ...@@ -10,7 +10,7 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol
* OptimismMintableERC20. * OptimismMintableERC20.
*/ */
interface IOptimismMintableERC20 is IERC165 { interface IOptimismMintableERC20 is IERC165 {
function remoteToken() external returns (address); function remoteToken() external view returns (address);
function bridge() external returns (address); function bridge() external returns (address);
...@@ -26,7 +26,7 @@ interface IOptimismMintableERC20 is IERC165 { ...@@ -26,7 +26,7 @@ interface IOptimismMintableERC20 is IERC165 {
* on the OptimismMintableERC20 contract for backwards compatibility. * on the OptimismMintableERC20 contract for backwards compatibility.
*/ */
interface ILegacyMintableERC20 is IERC165 { interface ILegacyMintableERC20 is IERC165 {
function l1Token() external returns (address); function l1Token() external view returns (address);
function mint(address _to, uint256 _amount) external; function mint(address _to, uint256 _amount) external;
......
...@@ -458,6 +458,8 @@ abstract contract StandardBridge { ...@@ -458,6 +458,8 @@ abstract contract StandardBridge {
/** /**
* @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. * @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20.
* Calls can be saved in the future by combining this logic with
* `_isOptimismMintableERC20`.
* *
* @param _mintableToken OptimismMintableERC20 to check against. * @param _mintableToken OptimismMintableERC20 to check against.
* @param _otherToken Pair token to check. * @param _otherToken Pair token to check.
...@@ -469,7 +471,13 @@ abstract contract StandardBridge { ...@@ -469,7 +471,13 @@ abstract contract StandardBridge {
view view
returns (bool) returns (bool)
{ {
return _otherToken == OptimismMintableERC20(_mintableToken).l1Token(); if (
ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)
) {
return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token();
} else {
return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken();
}
} }
/** @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event /** @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event
......
...@@ -33,5 +33,6 @@ ...@@ -33,5 +33,6 @@
"eip1559Denominator": 8, "eip1559Denominator": 8,
"eip1559Elasticity": 2, "eip1559Elasticity": 2,
"l1GenesisBlockTimestamp": "0x638a4554", "l1GenesisBlockTimestamp": "0x638a4554",
"l1StartingBlockTag": "earliest" "l1StartingBlockTag": "earliest",
"l2GenesisRegolithTimeOffset": "0x0"
} }
...@@ -23,7 +23,6 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -23,7 +23,6 @@ const deployFn: DeployFunction = async (hre) => {
SystemDictator, SystemDictator,
ProxyAdmin, ProxyAdmin,
AddressManager, AddressManager,
L1CrossDomainMessenger,
L1StandardBridgeProxy, L1StandardBridgeProxy,
L1StandardBridgeProxyWithSigner, L1StandardBridgeProxyWithSigner,
L1ERC721BridgeProxy, L1ERC721BridgeProxy,
...@@ -43,11 +42,6 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -43,11 +42,6 @@ const deployFn: DeployFunction = async (hre) => {
name: 'Lib_AddressManager', name: 'Lib_AddressManager',
signerOrProvider: deployer, signerOrProvider: deployer,
}, },
{
name: 'Proxy__OVM_L1CrossDomainMessenger',
iface: 'L1CrossDomainMessenger',
signerOrProvider: deployer,
},
{ {
name: 'Proxy__OVM_L1StandardBridge', name: 'Proxy__OVM_L1StandardBridge',
}, },
...@@ -118,41 +112,6 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -118,41 +112,6 @@ const deployFn: DeployFunction = async (hre) => {
console.log(`AddressManager already owned by the SystemDictator`) console.log(`AddressManager already owned by the SystemDictator`)
} }
// Transfer ownership of the L1CrossDomainMessenger to SystemDictator.
if (
needsProxyTransfer &&
(await AddressManager.getAddress('OVM_L1CrossDomainMessenger')) !==
ethers.constants.AddressZero &&
(await L1CrossDomainMessenger.owner()) !== SystemDictator.address
) {
if (isLiveDeployer) {
console.log(`Setting L1CrossDomainMessenger owner to MSD`)
await L1CrossDomainMessenger.transferOwnership(SystemDictator.address)
} else {
const tx =
await L1CrossDomainMessenger.populateTransaction.transferOwnership(
SystemDictator.address
)
console.log(`Please transfer L1CrossDomainMessenger owner to MSD`)
console.log(`L1XDM address: ${L1CrossDomainMessenger.address}`)
console.log(`MSD address: ${SystemDictator.address}`)
console.log(`JSON:`)
console.log(jsonifyTransaction(tx))
}
// Wait for the ownership transfer to complete.
await awaitCondition(
async () => {
const owner = await L1CrossDomainMessenger.owner()
return owner === SystemDictator.address
},
30000,
1000
)
} else {
console.log(`L1CrossDomainMessenger already owned by MSD`)
}
// Transfer ownership of the L1StandardBridge (proxy) to SystemDictator. // Transfer ownership of the L1StandardBridge (proxy) to SystemDictator.
if ( if (
needsProxyTransfer && needsProxyTransfer &&
......
...@@ -14,6 +14,7 @@ This directory contains documentation for all defined invariant tests within `co ...@@ -14,6 +14,7 @@ This directory contains documentation for all defined invariant tests within `co
- [L2OutputOracle](./L2OutputOracle.md) - [L2OutputOracle](./L2OutputOracle.md)
- [OptimismPortal](./OptimismPortal.md) - [OptimismPortal](./OptimismPortal.md)
- [ResourceMetering](./ResourceMetering.md) - [ResourceMetering](./ResourceMetering.md)
- [SafeCall](./SafeCall.md)
- [SystemConfig](./SystemConfig.md) - [SystemConfig](./SystemConfig.md)
<!-- END autoTOC --> <!-- END autoTOC -->
......
# `SafeCall` Invariants
## If `callWithMinGas` performs a call, then it must always provide at least the specified minimum gas limit to the subcontext.
**Test:** [`SafeCall.t.sol#L30`](../contracts/test/invariants/SafeCall.t.sol#L30)
If the check for remaining gas in `SafeCall.callWithMinGas` passes, the subcontext of the call below it must be provided at least `minGas` gas.
## `callWithMinGas` reverts if there is not enough gas to pass to the subcontext.
**Test:** [`SafeCall.t.sol#L61`](../contracts/test/invariants/SafeCall.t.sol#L61)
If there is not enough gas in the callframe to ensure that `callWithMinGas` can provide the specified minimum gas limit to the subcontext of the call, then `callWithMinGas` must revert.
...@@ -54,10 +54,16 @@ export const deploy = async ({ ...@@ -54,10 +54,16 @@ export const deploy = async ({
waitConfirmations: hre.deployConfig.numDeployConfirmations, waitConfirmations: hre.deployConfig.numDeployConfirmations,
}) })
console.log(`Deployed ${name} at ${result.address}`) console.log(`Deployed ${name} at ${result.address}`)
// Only wait for the transaction if it was recently deployed in case the
// result was deployed a long time ago and was pruned from the backend.
await hre.ethers.provider.waitForTransaction(result.transactionHash)
} }
// Always wait for the transaction to be mined, just in case. // Check to make sure there is code
await hre.ethers.provider.waitForTransaction(result.transactionHash) const code = await hre.ethers.provider.getCode(result.address)
if (code === '0x') {
throw new Error(`no code for ${result.address}`)
}
// Create the contract object to return. // Create the contract object to return.
const created = asAdvancedContract({ const created = asAdvancedContract({
......
# TeleportrDeposit
> TeleportrDeposit Shout out to 0xclem for providing the inspiration for this contract: https://github.com/0xclem/teleportr/blob/main/contracts/BridgeDeposit.sol
## Methods
### maxBalance
```solidity
function maxBalance() external view returns (uint256)
```
The maximum balance the contract can hold after a receive.
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined
### maxDepositAmount
```solidity
function maxDepositAmount() external view returns (uint256)
```
The maximum amount that be deposited in a receive.
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined
### minDepositAmount
```solidity
function minDepositAmount() external view returns (uint256)
```
The minimum amount that be deposited in a receive.
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined
### owner
```solidity
function owner() external view returns (address)
```
*Returns the address of the current owner.*
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | address | undefined
### renounceOwnership
```solidity
function renounceOwnership() external nonpayable
```
*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
### setMaxAmount
```solidity
function setMaxAmount(uint256 _maxDepositAmount) external nonpayable
```
Sets the maximum amount that can be deposited in a receive.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _maxDepositAmount | uint256 | The new maximum deposit amount.
### setMaxBalance
```solidity
function setMaxBalance(uint256 _maxBalance) external nonpayable
```
Sets the maximum balance the contract can hold after a receive.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _maxBalance | uint256 | The new maximum contract balance.
### setMinAmount
```solidity
function setMinAmount(uint256 _minDepositAmount) external nonpayable
```
Sets the minimum amount that can be deposited in a receive.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _minDepositAmount | uint256 | The new minimum deposit amount.
### totalDeposits
```solidity
function totalDeposits() external view returns (uint256)
```
The total number of successful deposits received.
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined
### transferOwnership
```solidity
function transferOwnership(address newOwner) external nonpayable
```
*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
#### Parameters
| Name | Type | Description |
|---|---|---|
| newOwner | address | undefined
### withdrawBalance
```solidity
function withdrawBalance() external nonpayable
```
Sends the contract&#39;s current balance to the owner.
## Events
### BalanceWithdrawn
```solidity
event BalanceWithdrawn(address indexed owner, uint256 balance)
```
Emitted any time the balance is withdrawn by the owner.
#### Parameters
| Name | Type | Description |
|---|---|---|
| owner `indexed` | address | The current owner and recipient of the funds. |
| balance | uint256 | The current contract balance paid to the owner. |
### EtherReceived
```solidity
event EtherReceived(uint256 indexed depositId, address indexed emitter, uint256 indexed amount)
```
Emitted any time a successful deposit is received.
#### Parameters
| Name | Type | Description |
|---|---|---|
| depositId `indexed` | uint256 | A unique sequencer number identifying the deposit. |
| emitter `indexed` | address | The sending address of the payer. |
| amount `indexed` | uint256 | The amount deposited by the payer. |
### MaxBalanceSet
```solidity
event MaxBalanceSet(uint256 previousBalance, uint256 newBalance)
```
Emitted any time the contract maximum balance is set.
#### Parameters
| Name | Type | Description |
|---|---|---|
| previousBalance | uint256 | The previous maximum contract balance. |
| newBalance | uint256 | The new maximum contract balance. |
### MaxDepositAmountSet
```solidity
event MaxDepositAmountSet(uint256 previousAmount, uint256 newAmount)
```
Emitted any time the maximum deposit amount is set.
#### Parameters
| Name | Type | Description |
|---|---|---|
| previousAmount | uint256 | The previous maximum deposit amount. |
| newAmount | uint256 | The new maximum deposit amount. |
### MinDepositAmountSet
```solidity
event MinDepositAmountSet(uint256 previousAmount, uint256 newAmount)
```
Emitted any time the minimum deposit amount is set.
#### Parameters
| Name | Type | Description |
|---|---|---|
| previousAmount | uint256 | The previous minimum deposit amount. |
| newAmount | uint256 | The new minimum deposit amount. |
### OwnershipTransferred
```solidity
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
```
#### Parameters
| Name | Type | Description |
|---|---|---|
| previousOwner `indexed` | address | undefined |
| newOwner `indexed` | address | undefined |
# TeleportrDisburser
> TeleportrDisburser
## Methods
### disburse
```solidity
function disburse(uint256 _nextDepositId, TeleportrDisburser.Disbursement[] _disbursements) external payable
```
Accepts a list of Disbursements and forwards the amount paid to the contract to each recipient. The method reverts if there are zero disbursements, the total amount to forward differs from the amount sent in the transaction, or the _nextDepositId is unexpected. Failed disbursements will not cause the method to revert, but will instead be held by the contract and availabe for the owner to withdraw.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _nextDepositId | uint256 | The depositId of the first Dispursement.
| _disbursements | TeleportrDisburser.Disbursement[] | A list of Disbursements to process.
### owner
```solidity
function owner() external view returns (address)
```
*Returns the address of the current owner.*
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | address | undefined
### renounceOwnership
```solidity
function renounceOwnership() external nonpayable
```
*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
### totalDisbursements
```solidity
function totalDisbursements() external view returns (uint256)
```
The total number of disbursements processed.
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined
### transferOwnership
```solidity
function transferOwnership(address newOwner) external nonpayable
```
*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
#### Parameters
| Name | Type | Description |
|---|---|---|
| newOwner | address | undefined
### withdrawBalance
```solidity
function withdrawBalance() external nonpayable
```
Sends the contract&#39;s current balance to the owner.
## Events
### BalanceWithdrawn
```solidity
event BalanceWithdrawn(address indexed owner, uint256 balance)
```
Emitted any time the balance is withdrawn by the owner.
#### Parameters
| Name | Type | Description |
|---|---|---|
| owner `indexed` | address | The current owner and recipient of the funds. |
| balance | uint256 | The current contract balance paid to the owner. |
### DisbursementFailed
```solidity
event DisbursementFailed(uint256 indexed depositId, address indexed to, uint256 amount)
```
Emitted any time a disbursement fails to send.
#### Parameters
| Name | Type | Description |
|---|---|---|
| depositId `indexed` | uint256 | The unique sequence number identifying the deposit. |
| to `indexed` | address | The intended recipient of the disbursement. |
| amount | uint256 | The amount intended to be sent to the recipient. |
### DisbursementSuccess
```solidity
event DisbursementSuccess(uint256 indexed depositId, address indexed to, uint256 amount)
```
Emitted any time a disbursement is successfuly sent.
#### Parameters
| Name | Type | Description |
|---|---|---|
| depositId `indexed` | uint256 | The unique sequence number identifying the deposit. |
| to `indexed` | address | The recipient of the disbursement. |
| amount | uint256 | The amount sent to the recipient. |
### OwnershipTransferred
```solidity
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
```
#### Parameters
| Name | Type | Description |
|---|---|---|
| previousOwner `indexed` | address | undefined |
| newOwner `indexed` | address | undefined |
...@@ -124,7 +124,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -124,7 +124,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
this.state.oo = { this.state.oo = {
contract: oo, contract: oo,
filter: oo.filters.OutputProposed(), filter: oo.filters.OutputProposed(),
getTotalElements: async () => oo.latestOutputIndex(), getTotalElements: async () => oo.nextOutputIndex(),
getEventIndex: (args) => args.l2OutputIndex, getEventIndex: (args) => args.l2OutputIndex,
} }
} else { } else {
......
...@@ -605,7 +605,7 @@ The safe L2 head is the highest [safe L2 block][safe-l2-block] that a [rollup no ...@@ -605,7 +605,7 @@ The safe L2 head is the highest [safe L2 block][safe-l2-block] that a [rollup no
[unsafe-l2-block]: glossary.md#unsafe-l2-block [unsafe-l2-block]: glossary.md#unsafe-l2-block
An unsafe L2 block is an L2 block that a [rollup node][rollup-node] knows about, but which was not derived from the L1 An unsafe L2 block is an L2 block that a [rollup node][rollup-node] knows about, but which was not derived from the L1
chian. In sequencer mode, this will be a block sequenced by the sequencer itself. In validator mode, this will be a chain. In sequencer mode, this will be a block sequenced by the sequencer itself. In validator mode, this will be a
block acquired from the sequencer via [unsafe sync][unsafe-sync]. block acquired from the sequencer via [unsafe sync][unsafe-sync].
## Unsafe L2 Head ## Unsafe L2 Head
......
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