Commit ff823e43 authored by protolambda's avatar protolambda

migration: drop legacy extras / CI

parent 086aa0f0
on: [push, pull_request]
name: Cannon Test
permissions:
contents: read
jobs:
cannon-test:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.20.x
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- uses: actions/checkout@v3
with:
submodules: true
- name: unicorn commit hash
working-directory: ./diffmips/unicorn
run: |
git rev-parse HEAD > /tmp/unicorn-commit-hash.txt
- name: cached libunicorn
uses: actions/cache@v3
with:
path: |
./diffmips/unicorn/build
key:
unicorn-build-{{ hashFiles('/tmp/unicorn-commit-hash.txt') }}
restore-keys: |
unicorn-build-{{ hashFiles('/tmp/unicorn-commit-hash.txt') }}
- name: install libunicorn
working-directory: ./diffmips
run: make libunicorn
- uses: actions/cache@v3
with:
# In order:
# * Module download cache
# * Build cache (Linux)
# * Build cache (Mac)
# * Build cache (Windows)
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
~\AppData\Local\go-build
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-
- name: main golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53.3
working-directory: ./
skip-cache: true # we already have go caching
args: --timeout=3m
- name: unicorntest golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53.3
working-directory: ./diffmips/unicorntest
skip-cache: true # we already have go caching
args: --timeout=3m
- name: Build examples
working-directory: ./example
run: make elf
- name: build contracts
working-directory: ./contracts
run: forge build
- name: mipsevm tests
working-directory: ./mipsevm
run: go test ./...
- name: diffmips tests
working-directory: ./diffmips/unicorntest
run: go test ./...
[submodule "unicorn"]
path = diffmips/unicorn
url = https://github.com/unicorn-engine/unicorn.git
SHELL := /bin/bash
build: submodules libunicorn
.PHONY: build
submodules:
# CI will checkout submodules on its own (and fails on these commands)
if [[ -z "$$GITHUB_ENV" ]]; then \
git submodule init; \
git submodule update; \
fi
.PHONY: submodules
# Approximation, use `make libunicorn_rebuild` to force.
unicorn/build: unicorn/CMakeLists.txt
mkdir -p unicorn/build
cd unicorn/build && cmake .. -DUNICORN_ARCH=mips -DCMAKE_BUILD_TYPE=Release
# Not sure why, but the second invocation is needed for fresh installs on MacOS.
if [ "$(shell uname)" == "Darwin" ]; then \
cd unicorn/build && cmake .. -DUNICORN_ARCH=mips -DCMAKE_BUILD_TYPE=Release; \
fi
# Rebuild whenever anything in the unicorn/ directory changes.
unicorn/build/libunicorn.so: unicorn/build unicorn
cd unicorn/build && make -j8
# The Go linker / runtime expects dynamic libraries in the unicorn/ dir.
find ./unicorn/build -name "libunicorn.*" | xargs -L 1 -I {} cp {} ./unicorn/
# Update timestamp on libunicorn.so to make it more recent than the build/ dir.
# On Mac this will create a new empty file (dyn libraries are .dylib), but works
# fine for the purpose of avoiding recompilation.
touch unicorn/build/libunicorn.so
libunicorn: unicorn/build/libunicorn.so
.PHONY: libunicorn
libunicorn_rebuild:
touch unicorn/CMakeLists.txt
make libunicorn
.PHONY: libunicorn_rebuild
clean:
rm -f unicorn/libunicorn.*
.PHONY: clean
test:
cd unicorntest && go test -v ./...
.PHONY: test
# diffmips
This is a collection of MIPS testing tools.
The Unicorn emulator first backed Cannon directly, but has been separated as a testing-only tool,
and is replaced with a Go implementation of the minimal MIPS functionality.
Directory layout
```
unicorn -- Sub-module, used by mipsevm for offchain MIPS emulation.
unicorntest -- Go module with Go tests, diffed against the Cannon state. [Work in Progress]
```
### `unicorn`
To build unicorn from source (git sub-module), run:
```
make libunicorn
```
### `unicorntest`
This requires `unicorn` to be built, as well as the `contracts` for testing.
To test:
```
make test
```
Subproject commit 7b8c63dfe650b5d4d2bf684526161971925e6350
package unicorntest
import (
"bytes"
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/cannon/mipsevm"
)
// baseAddrStart - baseAddrEnd is used in tests to write the results to
const baseAddrEnd = 0xbf_ff_ff_f0
const baseAddrStart = 0xbf_c0_00_00
// endAddr is used as return-address for tests
const endAddr = 0xa7ef00d0
func TestState(t *testing.T) {
testFiles, err := os.ReadDir("../../mipsevm/open_mips_tests/test/bin")
require.NoError(t, err)
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
if f.Name() == "oracle.bin" {
t.Skip("oracle test needs to be updated to use syscall pre-image oracle")
}
fn := path.Join("../../mipsevm/open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn)
require.NoError(t, err)
state := &mipsevm.State{PC: 0, NextPC: 4, Memory: mipsevm.NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr
mu, err := NewUnicorn()
require.NoError(t, err, "load unicorn")
defer mu.Close()
require.NoError(t, mu.MemMap(baseAddrStart, ((baseAddrEnd-baseAddrStart)&^mipsevm.PageAddrMask)+mipsevm.PageSize))
require.NoError(t, mu.MemMap(endAddr&^mipsevm.PageAddrMask, mipsevm.PageSize))
err = LoadUnicorn(state, mu)
require.NoError(t, err, "load state into unicorn")
us, err := NewUnicornState(mu, state, nil, os.Stdout, os.Stderr)
require.NoError(t, err, "hook unicorn to state")
for i := 0; i < 1000; i++ {
if us.state.PC == endAddr {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
require.Equal(t, uint32(endAddr), us.state.PC, "must reach end")
// inspect test result
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
})
}
}
module unicorntest
go 1.20
require (
github.com/ethereum-optimism/cannon v0.0.0
github.com/stretchr/testify v1.8.2
github.com/unicorn-engine/unicorn v0.0.0-20230207094436-7b8c63dfe650
)
require (
github.com/DataDog/zstd v1.5.2 // indirect
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.9.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect
github.com/cockroachdb/redact v1.1.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/ethereum-optimism/cannon/preimage v0.0.0 // indirect
github.com/ethereum/go-ethereum v1.11.5 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/ethereum-optimism/cannon v0.0.0 => ../../
replace github.com/ethereum-optimism/cannon/preimage v0.0.0 => ../../preimage
// We need to point to our local Unicorn clone for the shared object to be located correctly in all our Go commands.
// See https://github.com/unicorn-engine/unicorn/blob/7b8c63dfe650b5d4d2bf684526161971925e6350/bindings/go/unicorn/unicorn.go#L11
// The -L../../../ -lunicorn points to the unicorn root directory relative to the unicorn Go bindings.
// Run make libunicorn in the Cannon repo root to create this libunicorn.so dependency.
//
// If you are importing this as a library you will need to also point it to a unicorn clone with a `replace`
// in your `go.mod` (or `go.work`), or use `go build -ldflags="-L../path/to/my/unicorn/build -lunicorn`,
// or simply have it installed globally so Go can find it. The `replace` here will be ignored as library-user.
replace github.com/unicorn-engine/unicorn v0.0.0-20230207094436-7b8c63dfe650 => ../unicorn
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8=
github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk=
github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk=
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM=
github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ=
github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
package unicorntest
import (
"encoding/binary"
"fmt"
"io"
"log"
"math"
"github.com/ethereum-optimism/cannon/mipsevm"
uc "github.com/unicorn-engine/unicorn/bindings/go/unicorn"
)
type PreimageOracle interface {
Hint(v []byte)
GetPreimage(k [32]byte) []byte
}
type UnicornState struct {
mu uc.Unicorn
state *mipsevm.State
stdOut io.Writer
stdErr io.Writer
lastMemAccess uint32
memProofEnabled bool
memProof [28 * 32]byte
preimageOracle PreimageOracle
// cached pre-image data, including 8 byte length prefix
lastPreimage []byte
// key for above preimage
lastPreimageKey [32]byte
// offset we last read from, or max uint32 if nothing is read this step
lastPreimageOffset uint32
}
const (
fdStdin = 0
fdStdout = 1
fdStderr = 2
fdHintRead = 3
fdHintWrite = 4
fdPreimageRead = 5
fdPreimageWrite = 6
)
const (
MipsEBADF = 0x9
MipsEINVAL = 0x16
)
func NewUnicornState(mu uc.Unicorn, state *mipsevm.State, po PreimageOracle, stdOut, stdErr io.Writer) (*UnicornState, error) {
m := &UnicornState{
mu: mu,
state: state,
stdOut: stdOut,
stdErr: stdErr,
preimageOracle: po,
}
st := m.state
readPreimage := func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage
if key != m.lastPreimageKey {
m.lastPreimageKey = key
data := po.GetPreimage(key)
// add the length prefix
preimage = make([]byte, 0, 8+len(data))
preimage = binary.BigEndian.AppendUint64(preimage, uint64(len(data)))
preimage = append(preimage, data...)
m.lastPreimage = preimage
}
m.lastPreimageOffset = offset
datLen = uint32(copy(dat[:], preimage[offset:]))
return
}
trackMemAccess := func(effAddr uint32) {
if m.memProofEnabled && m.lastMemAccess != effAddr {
if m.lastMemAccess != ^uint32(0) {
panic(fmt.Errorf("unexpected different mem access at %08x, already have access at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
m.memProof = m.state.Memory.MerkleProof(effAddr)
}
}
var err error
_, err = mu.HookAdd(uc.HOOK_INTR, func(mu uc.Unicorn, intno uint32) {
if intno != 17 {
log.Fatal("invalid interrupt ", intno, " at step ", st.Step)
}
syscallNum := st.Registers[2] // v0
v0 := uint32(0)
v1 := uint32(0)
a0 := st.Registers[4]
a1 := st.Registers[5]
a2 := st.Registers[6]
fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum {
case 4090: // mmap
sz := a1
if sz&mipsevm.PageAddrMask != 0 { // adjust size to align with page size
sz += mipsevm.PageSize - (sz & mipsevm.PageAddrMask)
}
if a0 == 0 {
v0 = st.Heap
fmt.Printf("mmap heap 0x%x size 0x%x\n", v0, sz)
st.Heap += sz
} else {
v0 = a0
fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz)
}
// Go does this thing where it first gets memory with PROT_NONE,
// and then mmaps with a hint with prot=3 (PROT_READ|WRITE).
// We can ignore the NONE case, to avoid duplicate/overlapping mmap calls to unicorn.
prot := a2
if prot != 0 {
if err := mu.MemMap(uint64(v0), uint64(sz)); err != nil {
log.Fatalf("mmap fail: %v", err)
}
}
case 4045: // brk
v0 = 0x40000000
case 4120: // clone (not supported)
v0 = 1
case 4246: // exit_group
st.Exited = true
st.ExitCode = uint8(a0)
return
case 4003: // read
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code
switch a0 {
case fdStdin:
// leave v0 and v1 zero: read nothing, no error
case fdPreimageRead: // pre-image oracle
effAddr := a1 & 0xFFffFFfc
trackMemAccess(effAddr)
mem := st.Memory.GetMemory(effAddr)
dat, datLen := readPreimage(st.PreimageKey, st.PreimageOffset)
fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, st.PreimageOffset, datLen, dat[:datLen], st.PreimageKey, a2)
alignment := a1 & 3
space := 4 - alignment
if space < datLen {
datLen = space
}
if a2 < datLen {
datLen = a2
}
var outMem [4]byte
binary.BigEndian.PutUint32(outMem[:], mem)
copy(outMem[alignment:], dat[:datLen])
st.Memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:]))
if err := mu.MemWrite(uint64(effAddr), outMem[:]); err != nil {
log.Fatalf("failed to write pre-image data to memory: %v", err)
}
st.PreimageOffset += datLen
v0 = datLen
fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, st.PreimageOffset, effAddr, outMem)
case fdHintRead: // hint response
// don't actually read into memory, just say we read it all, we ignore the result anyway
v0 = a2
default:
v0 = 0xFFffFFff
v1 = MipsEBADF
}
case 4004: // write
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code
switch a0 {
case fdStdout:
_, _ = io.Copy(stdOut, st.Memory.ReadMemoryRange(a1, a2))
v0 = a2
case fdStderr:
_, _ = io.Copy(stdErr, st.Memory.ReadMemoryRange(a1, a2))
v0 = a2
case fdHintWrite:
hintData, _ := io.ReadAll(st.Memory.ReadMemoryRange(a1, a2))
st.LastHint = append(st.LastHint, hintData...)
for len(st.LastHint) >= 4 { // process while there is enough data to check if there are any hints
hintLen := binary.BigEndian.Uint32(st.LastHint[:4])
if hintLen >= uint32(len(st.LastHint[4:])) {
hint := st.LastHint[4 : 4+hintLen] // without the length prefix
st.LastHint = st.LastHint[4+hintLen:]
po.Hint(hint)
} else {
break // stop processing hints if there is incomplete data buffered
}
}
v0 = a2
case fdPreimageWrite:
effAddr := a1 & 0xFFffFFfc
trackMemAccess(effAddr)
mem := st.Memory.GetMemory(effAddr)
key := st.PreimageKey
alignment := a1 & 3
space := 4 - alignment
if space < a2 {
a2 = space
}
copy(key[:], key[a2:])
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], mem)
copy(key[32-a2:], tmp[:])
st.PreimageKey = key
st.PreimageOffset = 0
fmt.Printf("updating pre-image key: %s\n", st.PreimageKey)
v0 = a2
default:
v0 = 0xFFffFFff
v1 = MipsEBADF
}
case 4055: // fcntl
// args: a0 = fd, a1 = cmd
if a1 == 3 { // F_GETFL: get file descriptor flags
switch a0 {
case fdStdin, fdPreimageRead, fdHintRead:
v0 = 0 // O_RDONLY
case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite:
v0 = 1 // O_WRONLY
default:
v0 = 0xFFffFFff
v1 = MipsEBADF
}
} else {
v0 = 0xFFffFFff
v1 = MipsEINVAL // cmd not recognized by this kernel
}
}
_ = mu.RegWrite(uc.MIPS_REG_V0, uint64(v0))
_ = mu.RegWrite(uc.MIPS_REG_A3, uint64(v1))
}, 0, ^uint64(0))
if err != nil {
return nil, fmt.Errorf("failed to set up interrupt/syscall hook: %w", err)
}
// Shout if Go mmap calls didn't allocate the memory properly
_, err = mu.HookAdd(uc.HOOK_MEM_UNMAPPED, func(mu uc.Unicorn, typ int, addr uint64, size int, value int64) bool {
fmt.Printf("MEM UNMAPPED typ %d addr %016x size %x value %x\n", typ, addr, size, value)
//return false
// TODO: Unmapped memory access can occur when loading from a snapshot that spans unused pages
// This callback should return false though to handle invalid memory accesses early
if err := mu.MemMap(addr&^4095, 4096); err != nil {
fmt.Printf("failed to mmap addr (%x). reason: %v\n", addr, err)
}
return true
}, 0, ^uint64(0))
if err != nil {
return nil, fmt.Errorf("failed to set up unmapped-mem-write hook: %w", err)
}
_, err = mu.HookAdd(uc.HOOK_MEM_READ, func(mu uc.Unicorn, access int, addr64 uint64, size int, value int64) {
effAddr := uint32(addr64 & 0xFFFFFFFC) // pass effective addr to tracer
trackMemAccess(effAddr)
}, 0, ^uint64(0))
if err != nil {
return nil, fmt.Errorf("failed to set up mem-write hook: %w", err)
}
_, err = mu.HookAdd(uc.HOOK_MEM_WRITE, func(mu uc.Unicorn, access int, addr64 uint64, size int, value int64) {
if addr64 > math.MaxUint32 {
panic("invalid addr")
}
if size < 0 || size > 4 {
panic("invalid mem size")
}
effAddr := uint32(addr64 & 0xFFFFFFFC)
pre := st.Memory.GetMemory(effAddr)
var post uint32
rt := value
rs := addr64 & 3
if size == 1 {
val := uint32((rt & 0xFF) << (24 - (rs&3)*8))
mask := 0xFFFFFFFF ^ uint32(0xFF<<(24-(rs&3)*8))
post = (pre & mask) | val
} else if size == 2 {
val := uint32((rt & 0xFFFF) << (16 - (rs&2)*8))
mask := 0xFFFFFFFF ^ uint32(0xFFFF<<(16-(rs&2)*8))
post = (pre & mask) | val
} else if size == 4 {
post = uint32(rt)
} else {
log.Fatal("bad size write to ram")
}
trackMemAccess(effAddr)
// only set memory after making the proof: we need the pre-state
st.Memory.SetMemory(effAddr, post)
}, 0, ^uint64(0))
if err != nil {
return nil, fmt.Errorf("failed to set up mem-write hook: %w", err)
}
return m, nil
}
func (m *UnicornState) Step(proof bool) (wit *mipsevm.StepWitness, err error) {
defer func() { // pre-image oracle or emulator hooks might panic
if a := recover(); a != nil {
if ae, ok := a.(error); ok {
err = ae
} else {
err = fmt.Errorf("panic: %v", a)
}
}
}()
m.memProofEnabled = proof
m.lastMemAccess = ^uint32(0)
m.lastPreimageOffset = ^uint32(0)
if proof {
insnProof := m.state.Memory.MerkleProof(m.state.PC)
wit = &mipsevm.StepWitness{
State: m.state.EncodeWitness(),
MemProof: insnProof[:],
}
}
insn := m.state.Memory.GetMemory(m.state.PC)
oldNextPC := m.state.NextPC
newNextPC := oldNextPC + 4
opcode := insn >> 26
switch opcode {
case 2, 3: // J/JAL
newNextPC = signExtend(insn&0x03FFFFFF, 25) << 2
case 1, 4, 5, 6, 7: // branching
rs := m.state.Registers[(insn>>21)&0x1F]
shouldBranch := false
switch opcode {
case 4, 5:
rt := m.state.Registers[(insn>>16)&0x1F]
shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5)
case 6:
shouldBranch = int32(rs) <= 0 // blez
case 7:
shouldBranch = int32(rs) > 0 // bgtz
case 1:
rtv := (insn >> 16) & 0x1F
if rtv == 0 {
shouldBranch = int32(rs) < 0
} // bltz
if rtv == 1 {
shouldBranch = int32(rs) >= 0
} // bgez
}
if shouldBranch {
newNextPC = m.state.PC + 4 + (signExtend(insn&0xFFFF, 15) << 2)
}
case 0:
if funcv := insn & 0x3f; funcv == 8 || funcv == 9 { // JR/JALR
rs := m.state.Registers[(insn>>21)&0x1F]
newNextPC = rs
}
}
// Execute only a single instruction.
// The memory and syscall hooks will update the state with any of the dynamic changes.
err = m.mu.StartWithOptions(uint64(m.state.PC), uint64(m.state.NextPC), &uc.UcOptions{
Timeout: 0, // 0 to disable, value is in ms.
Count: 1,
})
if err != nil {
return nil, err
}
if proof {
wit.MemProof = append(wit.MemProof, m.memProof[:]...)
if m.lastPreimageOffset != ^uint32(0) {
wit.PreimageOffset = m.lastPreimageOffset
wit.PreimageKey = m.lastPreimageKey
wit.PreimageValue = m.lastPreimage
}
}
// count it
m.state.Step += 1
// Now do post-processing to keep our state in sync:
// 1) match the registers post-state
batch, err := m.mu.RegReadBatch(regBatchKeys)
if err != nil {
return nil, fmt.Errorf("failed to read register batch: %w", err)
}
for i := 0; i < 32; i++ {
m.state.Registers[i] = uint32(batch[i])
}
_ = uint32(batch[32]) // ignore the PC, we follow oldNextPC instead, to emulate delay-slot behavior
m.state.LO = uint32(batch[33])
m.state.HI = uint32(batch[34])
// 2) adopt the old nextPC as new PC. Unless we just exited.
// This effectively implements delay-slots, even though unicorn immediately loses
// delay-slot information when only executing a single instruction.
if !m.state.Exited {
m.state.PC = oldNextPC
err = m.mu.RegWrite(uc.MIPS_REG_PC, uint64(oldNextPC))
if err != nil {
return nil, fmt.Errorf("failed to write PC register: %w", err)
}
m.state.NextPC = newNextPC
}
return
}
func NewUnicorn() (uc.Unicorn, error) {
return uc.NewUnicorn(uc.ARCH_MIPS, uc.MODE_32|uc.MODE_BIG_ENDIAN)
}
func LoadUnicorn(st *mipsevm.State, mu uc.Unicorn) error {
// mmap and write each page of memory state into unicorn
if err := st.Memory.ForEachPage(func(pageIndex uint32, page *mipsevm.Page) error {
addr := uint64(pageIndex) << mipsevm.PageAddrSize
if err := mu.MemMap(addr, mipsevm.PageSize); err != nil {
return fmt.Errorf("failed to mmap page at addr 0x%x: %w", addr, err)
}
if err := mu.MemWrite(addr, page[:]); err != nil {
return fmt.Errorf("failed to write page at addr 0x%x: %w", addr, err)
}
return nil
}); err != nil {
return err
}
// write all registers into unicorn, including PC, LO, HI
regValues := make([]uint64, 32+3)
// TODO: do we have to sign-extend registers before writing them to unicorn, or are the trailing bits unused?
for i, v := range st.Registers {
regValues[i] = uint64(v)
}
regValues[32] = uint64(st.PC)
regValues[33] = uint64(st.LO)
regValues[34] = uint64(st.HI)
if err := mu.RegWriteBatch(regBatchKeys, regValues); err != nil {
return fmt.Errorf("failed to write registers: %w", err)
}
return nil
}
func signExtend(v uint32, i uint32) uint32 {
mask := ^((uint32(1) << i) - 1)
if v&(1<<i) != 0 {
return v | mask
} else {
return v &^ mask
}
}
var regBatchKeys = func() []int {
var batch []int
for i := 0; i < 32; i++ {
batch = append(batch, uc.MIPS_REG_ZERO+i)
}
batch = append(batch, uc.MIPS_REG_PC, uc.MIPS_REG_LO, uc.MIPS_REG_HI)
return batch
}()
package unicorntest
import (
"testing"
"github.com/stretchr/testify/require"
uc "github.com/unicorn-engine/unicorn/bindings/go/unicorn"
)
// TestUnicornDelaySlot test that unicorn works, and determine exactly how delay slots behave
func TestUnicornDelaySlot(t *testing.T) {
mu, err := NewUnicorn()
require.NoError(t, err)
defer mu.Close()
require.NoError(t, mu.MemMap(0, 4096))
require.NoError(t, mu.RegWrite(uc.MIPS_REG_RA, 420), "set RA to addr that is multiple of 4")
require.NoError(t, mu.MemWrite(0, []byte{0x03, 0xe0, 0x00, 0x08}), "jr $ra")
require.NoError(t, mu.MemWrite(4, []byte{0x20, 0x09, 0x0a, 0xFF}), "addi $t1 $r0 0x0aff")
require.NoError(t, mu.MemWrite(32, []byte{0x20, 0x09, 0x0b, 0xFF}), "addi $t1 $r0 0x0bff")
_, err = mu.HookAdd(uc.HOOK_CODE, func(mu uc.Unicorn, addr uint64, size uint32) {
t.Logf("addr: %08x", addr)
}, uint64(0), ^uint64(0))
require.NoError(t, err)
// stop at instruction in addr=4, the delay slot
require.NoError(t, mu.StartWithOptions(uint64(0), uint64(4), &uc.UcOptions{
Timeout: 0, // 0 to disable, value is in ms.
Count: 2,
}))
t1, err := mu.RegRead(uc.MIPS_REG_T1)
require.NoError(t, err)
require.NotEqual(t, uint64(0x0aff), t1, "delay slot should not execute")
pc, err := mu.RegRead(uc.MIPS_REG_PC)
require.NoError(t, err)
// unicorn is weird here: when entering a delay slot, it does not update the PC register by itself.
require.Equal(t, uint64(0), pc, "delay slot, no jump yet")
// now restart, but run two instructions, to include the delay slot
require.NoError(t, mu.StartWithOptions(uint64(0), ^uint64(0), &uc.UcOptions{
Timeout: 0, // 0 to disable, value is in ms.
Count: 2,
}))
pc, err = mu.RegRead(uc.MIPS_REG_PC)
require.NoError(t, err)
require.Equal(t, uint64(420), pc, "jumped after NOP delay slot")
t1, err = mu.RegRead(uc.MIPS_REG_T1)
require.NoError(t, err)
require.Equal(t, uint64(0x0aff), t1, "delay slot should execute")
require.NoError(t, mu.StartWithOptions(uint64(32), uint64(32+4), &uc.UcOptions{
Timeout: 0, // 0 to disable, value is in ms.
Count: 1,
}))
t1, err = mu.RegRead(uc.MIPS_REG_T1)
require.NoError(t, err)
require.Equal(t, uint64(0x0bff), t1, "regular instruction should work fine")
}
# cannon-extra
This is a collection of extra / legacy scripts. Cannon v0.3 and onwards reduces scope.
- The challenge/response bookkeeping contract part of the fraud proof is in development
separate from Cannon, hence moved into `extra` and deprecated.
- No usage of Merkle Patricia Tries (MPTs) for the VM state anymore.
- No `minigeth` anymore: see [`op-program`](https://github.com/ethereum-optimism/optimism/tree/develop/op-program) instead.
- No `mipigo` anymore: the ELF-loading and startup preparation is now part of the Go `mipsevm` toolset.
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.3;
pragma experimental ABIEncoderV2;
import "./lib/Lib_RLPReader.sol";
/// @notice MIPS virtual machine interface
interface IMIPS {
/// @notice Given a MIPS state hash (includes code & registers), execute the next instruction and returns
/// the update state hash.
function Step(bytes32 stateHash) external returns (bytes32);
/// @notice Returns the associated MIPS memory contract.
function m() external pure returns (IMIPSMemory);
}
/// @notice MIPS memory (really "state", including registers and memory-mapped I/O)
interface IMIPSMemory {
/// @notice Adds a `(hash(anything) => anything)` entry to the mapping that underpins all the
/// Merkle tries that this contract deals with (where "state hash" = Merkle root of such
/// a trie).
/// @param anything node data to add to the trie
function AddTrieNode(bytes calldata anything) external;
function ReadMemory(bytes32 stateHash, uint32 addr) external view returns (uint32);
function ReadBytes32(bytes32 stateHash, uint32 addr) external view returns (bytes32);
/// @notice Write 32 bits at the given address and returns the updated state hash.
function WriteMemory(bytes32 stateHash, uint32 addr, uint32 val) external returns (bytes32);
/// @notice Write 32 bytes at the given address and returns the updated state hash.
function WriteBytes32(bytes32 stateHash, uint32 addr, bytes32 val) external returns (bytes32);
}
/// @notice Implementation of the challenge game, which allows a challenger to challenge an L1 block
/// by asserting a different state root for the transition implied by the block's
/// transactions. The challenger plays against a defender (the owner of this contract),
/// which we assume acts honestly. The challenger and the defender perform a binary search
/// over the execution trace of the fault proof program (in this case minigeth), in order
/// to determine a single execution step that they disagree on, at which point that step
/// can be executed on-chain in order to determine if the challenge is valid.
contract Challenge {
address payable immutable owner;
IMIPS immutable mips;
IMIPSMemory immutable mem;
/// @notice State hash of the fault proof program's initial MIPS state.
bytes32 public immutable globalStartState;
constructor(IMIPS _mips, bytes32 _globalStartState) {
owner = msg.sender;
mips = _mips;
mem = _mips.m();
globalStartState = _globalStartState;
}
struct ChallengeData {
// Left bound of the binary search: challenger & defender agree on all steps <= L.
uint256 L;
// Right bound of the binary search: challenger & defender disagree on all steps >= R.
uint256 R;
// Maps step numbers to asserted state hashes for the challenger.
mapping(uint256 => bytes32) assertedState;
// Maps step numbers to asserted state hashes for the defender.
mapping(uint256 => bytes32) defendedState;
// Address of the challenger.
address payable challenger;
// Block number preceding the challenged block.
uint256 blockNumberN;
}
/// @notice ID if the last created challenged, incremented for new challenge IDs.
uint256 public lastChallengeId = 0;
/// @notice Maps challenge IDs to challenge data.
mapping(uint256 => ChallengeData) public challenges;
/// @notice Emitted when a new challenge is created.
event ChallengeCreated(uint256 challengeId);
/// @notice Challenges the transition from block `blockNumberN` to the next block (N+1), which is
/// the block being challenged.
/// Before calling this, it is necessary to have loaded all the trie node necessary to
/// write the input hash in the Merkleized initial MIPS state, and to read the output hash
/// and machine state from the Merkleized final MIPS state (i.e. `finalSystemState`). Use
/// `MIPSMemory.AddTrieNode` for this purpose. Use `callWithTrieNodes` to figure out
/// which nodes you need.
/// @param blockNumberN The number N of the parent of the block being challenged
/// @param blockHeaderNp1 The RLP-encoded header of the block being challenged (N+1)
/// @param assertionRoot The state root that the challenger claims is the correct one for the
/// given the transactions included in block N+1.
/// @param finalSystemState The state hash of the fault proof program's final MIPS state.
/// @param stepCount The number of steps (MIPS instructions) taken to execute the fault proof
/// program.
/// @return The challenge identifier
function initiateChallenge(
uint blockNumberN, bytes calldata blockHeaderNp1, bytes32 assertionRoot,
bytes32 finalSystemState, uint256 stepCount)
external
returns (uint256)
{
bytes32 computedBlockHash = keccak256(blockHeaderNp1);
// get block hashes, can replace with oracle
bytes32 blockNumberNHash = blockhash(blockNumberN);
bytes32 blockNumberNp1Hash = blockhash(blockNumberN+1);
if (blockNumberNHash == bytes32(0) || blockNumberNp1Hash == bytes32(0)) {
revert("block number too old to challenge");
}
require(blockNumberNp1Hash == computedBlockHash, "incorrect header supplied for block N+1");
// Decode the N+1 block header to construct the fault proof program's input hash.
// Because the input hash is constructed from data proven against on-chain block hashes,
// it is provably correct, and we can consider that both parties agree on it.
bytes32 inputHash;
{
Lib_RLPReader.RLPItem[] memory decodedHeader = Lib_RLPReader.readList(blockHeaderNp1);
bytes32 parentHash = Lib_RLPReader.readBytes32(decodedHeader[0]);
// This should never happen, as we validated the hashes beforehand.
require(blockNumberNHash == parentHash, "parent block hash somehow wrong");
bytes32 newroot = Lib_RLPReader.readBytes32(decodedHeader[3]);
require(assertionRoot != newroot,
"asserting that the real state is correct is not a challenge");
bytes32 txhash = Lib_RLPReader.readBytes32(decodedHeader[4]);
bytes32 coinbase = bytes32(uint256(uint160(Lib_RLPReader.readAddress(decodedHeader[2]))));
bytes32 unclehash = Lib_RLPReader.readBytes32(decodedHeader[1]);
bytes32 gaslimit = Lib_RLPReader.readBytes32(decodedHeader[9]);
bytes32 time = Lib_RLPReader.readBytes32(decodedHeader[11]);
inputHash = keccak256(abi.encodePacked(parentHash, txhash, coinbase, unclehash, gaslimit, time));
}
// Write input hash at predefined memory address.
bytes32 startState = globalStartState;
startState = mem.WriteBytes32(startState, 0x30000000, inputHash);
// Confirm that `finalSystemState` asserts the state you claim and that the machine is stopped.
require(mem.ReadMemory(finalSystemState, 0xC0000080) == 0x5EAD0000,
"the final MIPS machine state is not stopped (PC != 0x5EAD0000)");
require(mem.ReadMemory(finalSystemState, 0x30000800) == 0x1337f00d,
"the final state root has not been written a the predefined MIPS memory location");
require(mem.ReadBytes32(finalSystemState, 0x30000804) == assertionRoot,
"the final MIPS machine state asserts a different state root than your challenge");
uint256 challengeId = lastChallengeId++;
ChallengeData storage c = challenges[challengeId];
// A NEW CHALLENGER APPEARS
c.challenger = msg.sender;
c.blockNumberN = blockNumberN;
c.assertedState[0] = startState;
c.defendedState[0] = startState;
c.assertedState[stepCount] = finalSystemState;
c.L = 0;
c.R = stepCount;
emit ChallengeCreated(challengeId);
return challengeId;
}
/// @notice Calling `initiateChallenge`, `confirmStateTransition` or `denyStateTransition requires
/// some trie nodes to have been supplied beforehand (see these functions for details).
/// This function can be used to figure out which nodes are needed, as memory-accessing
/// functions in MIPSMemory.sol will revert with the missing node ID when a node is
/// missing. Therefore, you can call this function repeatedly via `eth_call`, and
/// iteratively build the list of required node until the call succeeds.
/// @param target The contract to call to (usually this contract)
/// @param dat The data to include in the call (usually the calldata for a call to
/// one of the aforementionned functions)
/// @param nodes The nodes to add the MIPS state trie before making the call
function callWithTrieNodes(address target, bytes calldata dat, bytes[] calldata nodes) public {
for (uint i = 0; i < nodes.length; i++) {
mem.AddTrieNode(nodes[i]);
}
(bool success, bytes memory revertData) = target.call(dat);
if (!success) {
uint256 revertDataLength = revertData.length;
assembly {
let revertDataStart := add(revertData, 32)
revert(revertDataStart, revertDataLength)
}
}
}
/// @notice Indicates whether the given challenge is still searching (true), or if the single step
/// of disagreement has been found (false).
function isSearching(uint256 challengeId) view public returns (bool) {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
return c.L + 1 != c.R;
}
/// @notice Returns the next step number where the challenger and the defender must compared
/// state hash, namely the midpoint between the current left and right bounds of the
/// binary search.
function getStepNumber(uint256 challengeId) view public returns (uint256) {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
return (c.L+c.R)/2;
}
/// @notice Returns the last state hash proposed by the challenger during the binary search.
function getProposedState(uint256 challengeId) view public returns (bytes32) {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
uint256 stepNumber = getStepNumber(challengeId);
return c.assertedState[stepNumber];
}
/// @notice The challenger can call this function to submit the state hash for the next step
/// in the binary search (cf. `getStepNumber`).
function proposeState(uint256 challengeId, bytes32 stateHash) external {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
require(c.challenger == msg.sender, "must be challenger");
require(isSearching(challengeId), "must be searching");
uint256 stepNumber = getStepNumber(challengeId);
require(c.assertedState[stepNumber] == bytes32(0), "state already proposed");
c.assertedState[stepNumber] = stateHash;
}
/// @notice The defender can call this function to submit the state hash for the next step
/// in the binary search (cf. `getStepNumber`). He can only do this after the challenger
/// has submitted his own state hash for this step.
/// If the defender believes there are less steps in the execution of the fault proof
/// program than the current step number, he should submit the final state hash.
function respondState(uint256 challengeId, bytes32 stateHash) external {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
require(owner == msg.sender, "must be owner");
require(isSearching(challengeId), "must be searching");
uint256 stepNumber = getStepNumber(challengeId);
require(c.assertedState[stepNumber] != bytes32(0), "challenger state not proposed");
require(c.defendedState[stepNumber] == bytes32(0), "state already proposed");
// Technically, we don't have to save these states, but we have to if we want to let the
// defender terminate the proof early (and not via a timeout) after the binary search completes.
c.defendedState[stepNumber] = stateHash;
// update binary search bounds
if (c.assertedState[stepNumber] == c.defendedState[stepNumber]) {
c.L = stepNumber; // agree
} else {
c.R = stepNumber; // disagree
}
}
/// @notice Emitted when the challenger can provably be shown to be correct about his assertion.
event ChallengerWins(uint256 challengeId);
/// @notice Emitted when the challenger can provably be shown to be wrong about his assertion.
event ChallengerLoses(uint256 challengeId);
/// @notice Emitted when the challenger should lose if he does not generate a `ChallengerWins`
/// event in a timely manner (TBD). This occurs in a specific scenario when we can't
/// explicitly verify that the defender is right (cf. `denyStateTransition).
event ChallengerLosesByDefault(uint256 challengeId);
/// @notice Anybody can call this function to confirm that the single execution step that the
/// challenger and defender disagree on does indeed yield the result asserted by the
/// challenger, leading to him winning the challenge.
/// Before calling this function, you need to add trie nodes so that the MIPS state can be
/// read/written by the single step execution. Use `MIPSMemory.AddTrieNode` for this
/// purpose. Use `callWithTrieNodes` to figure out which nodes you need.
/// You will also need to supply any preimage that the step tries to access with
/// `MIPSMemory.AddPreimage`. See `scripts/assert.js` for details on how this can be
/// done.
function confirmStateTransition(uint256 challengeId) external {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
require(!isSearching(challengeId), "binary search not finished");
bytes32 stepState = mips.Step(c.assertedState[c.L]);
require(stepState == c.assertedState[c.R], "wrong asserted state for challenger");
// pay out bounty!!
(bool sent, ) = c.challenger.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
emit ChallengerWins(challengeId);
}
/// @notice Anybody can call this function to confirm that the single execution step that the
/// challenger and defender disagree on does indeed yield the result asserted by the
/// defender, leading to the challenger losing the challenge.
/// Before calling this function, you need to add trie nodes so that the MIPS state can be
/// read/written by the single step execution. Use `MIPSMemory.AddTrieNode` for this
/// purpose. Use `callWithTrieNodes` to figure out which nodes you need.
/// You will also need to supply any preimage that the step tries to access with
/// `MIPSMemory.AddPreimage`. See `scripts/assert.js` for details on how this can be
/// done.
function denyStateTransition(uint256 challengeId) external {
ChallengeData storage c = challenges[challengeId];
require(c.challenger != address(0), "invalid challenge");
require(!isSearching(challengeId), "binary search not finished");
// We run this before the next check so that if executing the final step somehow
// causes a revert, then at least we do not emit `ChallengerLosesByDefault` when we know that
// the challenger can't win (even if right) because of the revert.
bytes32 stepState = mips.Step(c.defendedState[c.L]);
// If the challenger always agrees with the defender during the search, we end up with:
// c.L + 1 == c.R == stepCount (from `initiateChallenge`)
// In this case, the defender didn't assert his state hash for c.R, which makes
// `c.defendedState[c.R]` zero. This means we can't verify that the defender right about the
// final execution step.
// The solution is to emit `ChallengerLosesByDefault` to signify the challenger should lose
// if he can't emit `ChallengerWins` in a timely manner.
if (c.defendedState[c.R] == bytes32(0)) {
emit ChallengerLosesByDefault(challengeId);
return;
}
require(stepState == c.defendedState[c.R], "wrong asserted state for defender");
// consider the challenger mocked
emit ChallengerLoses(challengeId);
}
/// @notice Allow sending money to the contract (without calldata).
receive() external payable {}
/// @notice Allows the owner to withdraw funds from the contract.
function withdraw() external {
require(msg.sender == owner);
owner.transfer(address(this).balance);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.3;
import "./lib/Lib_Keccak256.sol";
import "./lib/Lib_MerkleTrie.sol";
import { Lib_BytesUtils } from "./lib/Lib_BytesUtils.sol";
contract MIPSMemory {
function AddTrieNode(bytes calldata anything) public {
Lib_MerkleTrie.GetTrie()[keccak256(anything)] = anything;
}
struct Preimage {
uint64 length;
mapping(uint => uint64) data;
}
mapping(bytes32 => Preimage) public preimage;
function MissingPreimageRevert(bytes32 outhash, uint offset) internal pure {
Lib_BytesUtils.revertWithHex(abi.encodePacked(outhash, offset));
}
function GetPreimageLength(bytes32 outhash) public view returns (uint32) {
uint64 data = preimage[outhash].length;
if (data == 0) {
MissingPreimageRevert(outhash, 0);
}
return uint32(data);
}
function GetPreimage(bytes32 outhash, uint offset) public view returns (uint32) {
uint64 data = preimage[outhash].data[offset];
if (data == 0) {
MissingPreimageRevert(outhash, offset);
}
return uint32(data);
}
function AddPreimage(bytes calldata anything, uint offset) public {
require(offset & 3 == 0, "offset must be 32-bit aligned");
uint len = anything.length;
require(offset < len, "offset can't be longer than input");
Preimage storage p = preimage[keccak256(anything)];
require(p.length == 0 || uint32(p.length) == len, "length is somehow wrong");
p.length = (1 << 32) | uint64(uint32(len));
p.data[offset] = (1 << 32) |
((len <= (offset+0) ? 0 : uint32(uint8(anything[offset+0]))) << 24) |
((len <= (offset+1) ? 0 : uint32(uint8(anything[offset+1]))) << 16) |
((len <= (offset+2) ? 0 : uint32(uint8(anything[offset+2]))) << 8) |
((len <= (offset+3) ? 0 : uint32(uint8(anything[offset+3]))) << 0);
}
// one per owner (at a time)
struct LargePreimage {
uint offset;
uint len;
uint32 data;
}
mapping(address => LargePreimage) public largePreimage;
// sadly due to soldiity limitations this can't be in the LargePreimage struct
mapping(address => uint64[25]) public largePreimageState;
function AddLargePreimageInit(uint offset) public {
require(offset & 3 == 0, "offset must be 32-bit aligned");
Lib_Keccak256.CTX memory c;
Lib_Keccak256.keccak_init(c);
largePreimageState[msg.sender] = c.A;
largePreimage[msg.sender].offset = offset;
largePreimage[msg.sender].len = 0;
}
// input 136 bytes, as many times as you'd like
// Uses about 500k gas, 3435 gas/byte
function AddLargePreimageUpdate(bytes calldata dat) public {
require(dat.length == 136, "update must be in multiples of 136");
// sha3_process_block
Lib_Keccak256.CTX memory c;
c.A = largePreimageState[msg.sender];
int offset = int(largePreimage[msg.sender].offset) - int(largePreimage[msg.sender].len);
if (offset >= 0 && offset < 136) {
largePreimage[msg.sender].data = fbo(dat, uint(offset));
}
Lib_Keccak256.sha3_xor_input(c, dat);
Lib_Keccak256.sha3_permutation(c);
largePreimageState[msg.sender] = c.A;
largePreimage[msg.sender].len += 136;
}
function AddLargePreimageFinal(bytes calldata idat) public view returns (bytes32, uint32, uint32) {
require(idat.length < 136, "final must be less than 136");
int offset = int(largePreimage[msg.sender].offset) - int(largePreimage[msg.sender].len);
require(offset < int(idat.length), "offset must be less than length");
Lib_Keccak256.CTX memory c;
c.A = largePreimageState[msg.sender];
bytes memory dat = new bytes(136);
for (uint i = 0; i < idat.length; i++) {
dat[i] = idat[i];
}
uint len = largePreimage[msg.sender].len + idat.length;
uint32 data = largePreimage[msg.sender].data;
if (offset >= 0) {
data = fbo(dat, uint(offset));
}
dat[135] = bytes1(uint8(0x80));
dat[idat.length] |= bytes1(uint8(0x1));
Lib_Keccak256.sha3_xor_input(c, dat);
Lib_Keccak256.sha3_permutation(c);
bytes32 outhash = Lib_Keccak256.get_hash(c);
require(len < 0x10000000, "max length is 32-bit");
return (outhash, uint32(len), data);
}
function AddLargePreimageFinalSaved(bytes calldata idat) public {
bytes32 outhash;
uint32 len;
uint32 data;
(outhash, len, data) = AddLargePreimageFinal(idat);
Preimage storage p = preimage[outhash];
require(p.length == 0 || uint32(p.length) == len, "length is somehow wrong");
require(largePreimage[msg.sender].offset < len, "offset is somehow beyond length");
p.length = (1 << 32) | uint64(len);
p.data[largePreimage[msg.sender].offset] = (1 << 32) | data;
}
function tb(uint32 dat) internal pure returns (bytes memory) {
bytes memory ret = new bytes(4);
ret[0] = bytes1(uint8(dat >> 24));
ret[1] = bytes1(uint8(dat >> 16));
ret[2] = bytes1(uint8(dat >> 8));
ret[3] = bytes1(uint8(dat >> 0));
return ret;
}
function fb(bytes memory dat) internal pure returns (uint32) {
require(dat.length == 4, "wrong length value");
uint32 ret = uint32(uint8(dat[0])) << 24 |
uint32(uint8(dat[1])) << 16 |
uint32(uint8(dat[2])) << 8 |
uint32(uint8(dat[3]));
return ret;
}
function fbo(bytes memory dat, uint offset) internal pure returns (uint32) {
uint32 ret = uint32(uint8(dat[offset+0])) << 24 |
uint32(uint8(dat[offset+1])) << 16 |
uint32(uint8(dat[offset+2])) << 8 |
uint32(uint8(dat[offset+3]));
return ret;
}
function WriteMemory(bytes32 stateHash, uint32 addr, uint32 value) public returns (bytes32) {
require(addr & 3 == 0, "write memory must be 32-bit aligned");
return Lib_MerkleTrie.update(tb(addr>>2), tb(value), stateHash);
}
function WriteBytes32(bytes32 stateHash, uint32 addr, bytes32 val) public returns (bytes32) {
for (uint32 i = 0; i < 32; i += 4) {
uint256 tv = uint256(val>>(224-(i*8)));
stateHash = WriteMemory(stateHash, addr+i, uint32(tv));
}
return stateHash;
}
// TODO: refactor writeMemory function to not need these
event DidStep(bytes32 stateHash);
function WriteMemoryWithReceipt(bytes32 stateHash, uint32 addr, uint32 value) public {
bytes32 newStateHash = WriteMemory(stateHash, addr, value);
emit DidStep(newStateHash);
}
function WriteBytes32WithReceipt(bytes32 stateHash, uint32 addr, bytes32 value) public {
bytes32 newStateHash = WriteBytes32(stateHash, addr, value);
emit DidStep(newStateHash);
}
// needed for preimage oracle
function ReadBytes32(bytes32 stateHash, uint32 addr) public view returns (bytes32) {
uint256 ret = 0;
for (uint32 i = 0; i < 32; i += 4) {
ret <<= 32;
ret |= uint256(ReadMemory(stateHash, addr+i));
}
return bytes32(ret);
}
function ReadMemory(bytes32 stateHash, uint32 addr) public view returns (uint32) {
require(addr & 3 == 0, "read memory must be 32-bit aligned");
// zero register is always 0
if (addr == 0xc0000000) {
return 0;
}
// MMIO preimage oracle
if (addr >= 0x31000000 && addr < 0x32000000) {
bytes32 pihash = ReadBytes32(stateHash, 0x30001000);
if (pihash == keccak256("")) {
// both the length and any data are 0
return 0;
}
if (addr == 0x31000000) {
return uint32(GetPreimageLength(pihash));
}
return GetPreimage(pihash, addr-0x31000004);
}
bool exists;
bytes memory value;
(exists, value) = Lib_MerkleTrie.get(tb(addr>>2), stateHash);
if (!exists) {
// this is uninitialized memory
return 0;
} else {
return fb(value);
}
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/**
* @title Lib_BytesUtils
*/
library Lib_BytesUtils {
/**********************
* Internal Functions *
**********************/
function concat(
bytes memory _preBytes,
bytes memory _postBytes
)
internal
pure
returns (bytes memory)
{
bytes memory tempBytes;
assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)
// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)
for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}
// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(0x40, and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
))
}
return tempBytes;
}
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
)
internal
pure
returns (bytes memory)
{
require(_length + 31 >= _length, "slice_overflow");
require(_start + _length >= _start, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function slice(
bytes memory _bytes,
uint256 _start
)
internal
pure
returns (bytes memory)
{
if (_bytes.length - _start == 0) {
return bytes('');
}
return slice(_bytes, _start, _bytes.length - _start);
}
function toBytes32PadLeft(
bytes memory _bytes
)
internal
pure
returns (bytes32)
{
bytes32 ret;
uint256 len = _bytes.length <= 32 ? _bytes.length : 32;
assembly {
ret := shr(mul(sub(32, len), 8), mload(add(_bytes, 32)))
}
return ret;
}
function toBytes32(
bytes memory _bytes
)
internal
pure
returns (bytes32)
{
if (_bytes.length < 32) {
bytes32 ret;
assembly {
ret := mload(add(_bytes, 32))
}
return ret;
}
return abi.decode(_bytes,(bytes32)); // will truncate if input length > 32 bytes
}
function toUint256(
bytes memory _bytes
)
internal
pure
returns (uint256)
{
return uint256(toBytes32(_bytes));
}
function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) {
require(_start + 3 >= _start, "toUint24_overflow");
require(_bytes.length >= _start + 3 , "toUint24_outOfBounds");
uint24 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x3), _start))
}
return tempUint;
}
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
require(_start + 1 >= _start, "toUint8_overflow");
require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
uint8 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}
return tempUint;
}
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
require(_start + 20 >= _start, "toAddress_overflow");
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function revertWithHex(
bytes memory _bytes
)
internal
pure
{
bytes memory node = Lib_BytesUtils.toNibbles(_bytes);
for (uint i = 0; i < node.length; i++) {
if (node[i] < bytes1(uint8(10))) {
node[i] = bytes1(uint8(node[i]) + uint8(0x30));
} else {
node[i] = bytes1(uint8(node[i]) + uint8(0x61-10));
}
}
revert(string(node));
}
function toNibbles(
bytes memory _bytes
)
internal
pure
returns (bytes memory)
{
bytes memory nibbles = new bytes(_bytes.length * 2);
for (uint256 i = 0; i < _bytes.length; i++) {
nibbles[i * 2] = _bytes[i] >> 4;
nibbles[i * 2 + 1] = bytes1(uint8(_bytes[i]) % 16);
}
return nibbles;
}
function fromNibbles(
bytes memory _bytes
)
internal
pure
returns (bytes memory)
{
bytes memory ret = new bytes(_bytes.length / 2);
for (uint256 i = 0; i < ret.length; i++) {
ret[i] = (_bytes[i * 2] << 4) | (_bytes[i * 2 + 1]);
}
return ret;
}
function equal(
bytes memory _bytes,
bytes memory _other
)
internal
pure
returns (bool)
{
return keccak256(_bytes) == keccak256(_other);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
// https://chenglongma.com/10/simple-keccak/
// https://github.com/firefly/wallet/blob/master/source/libs/ethers/src/keccak256.c
library Lib_Keccak256 {
struct CTX {
uint64[25] A;
}
function get_round_constant(uint round) internal pure returns (uint64) {
uint64 result = 0;
uint8 roundInfo = uint8(0x7421587966164852535d4f3f26350c0e5579211f705e1a01 >> (round*8));
result |= (uint64(roundInfo) << (63-6)) & (1 << 63);
result |= (uint64(roundInfo) << (31-5)) & (1 << 31);
result |= (uint64(roundInfo) << (15-4)) & (1 << 15);
result |= (uint64(roundInfo) << (7-3)) & (1 << 7);
result |= (uint64(roundInfo) << (3-2)) & (1 << 3);
result |= (uint64(roundInfo) << (1-1)) & (1 << 1);
result |= (uint64(roundInfo) << (0-0)) & (1 << 0);
return result;
}
function keccak_theta_rho_pi(CTX memory c) internal pure {
uint64 C0 = c.A[0] ^ c.A[5] ^ c.A[10] ^ c.A[15] ^ c.A[20];
uint64 C1 = c.A[1] ^ c.A[6] ^ c.A[11] ^ c.A[16] ^ c.A[21];
uint64 C2 = c.A[2] ^ c.A[7] ^ c.A[12] ^ c.A[17] ^ c.A[22];
uint64 C3 = c.A[3] ^ c.A[8] ^ c.A[13] ^ c.A[18] ^ c.A[23];
uint64 C4 = c.A[4] ^ c.A[9] ^ c.A[14] ^ c.A[19] ^ c.A[24];
uint64 D0 = (C1 << 1) ^ (C1 >> 63) ^ C4;
uint64 D1 = (C2 << 1) ^ (C2 >> 63) ^ C0;
uint64 D2 = (C3 << 1) ^ (C3 >> 63) ^ C1;
uint64 D3 = (C4 << 1) ^ (C4 >> 63) ^ C2;
uint64 D4 = (C0 << 1) ^ (C0 >> 63) ^ C3;
c.A[0] ^= D0;
uint64 A1 = ((c.A[1] ^ D1) << 1) ^ ((c.A[1] ^ D1) >> (64-1));
c.A[1] = ((c.A[6] ^ D1) << 44) ^ ((c.A[6] ^ D1) >> (64-44));
c.A[6] = ((c.A[9] ^ D4) << 20) ^ ((c.A[9] ^ D4) >> (64-20));
c.A[9] = ((c.A[22] ^ D2) << 61) ^ ((c.A[22] ^ D2) >> (64-61));
c.A[22] = ((c.A[14] ^ D4) << 39) ^ ((c.A[14] ^ D4) >> (64-39));
c.A[14] = ((c.A[20] ^ D0) << 18) ^ ((c.A[20] ^ D0) >> (64-18));
c.A[20] = ((c.A[2] ^ D2) << 62) ^ ((c.A[2] ^ D2) >> (64-62));
c.A[2] = ((c.A[12] ^ D2) << 43) ^ ((c.A[12] ^ D2) >> (64-43));
c.A[12] = ((c.A[13] ^ D3) << 25) ^ ((c.A[13] ^ D3) >> (64-25));
c.A[13] = ((c.A[19] ^ D4) << 8) ^ ((c.A[19] ^ D4) >> (64-8));
c.A[19] = ((c.A[23] ^ D3) << 56) ^ ((c.A[23] ^ D3) >> (64-56));
c.A[23] = ((c.A[15] ^ D0) << 41) ^ ((c.A[15] ^ D0) >> (64-41));
c.A[15] = ((c.A[4] ^ D4) << 27) ^ ((c.A[4] ^ D4) >> (64-27));
c.A[4] = ((c.A[24] ^ D4) << 14) ^ ((c.A[24] ^ D4) >> (64-14));
c.A[24] = ((c.A[21] ^ D1) << 2) ^ ((c.A[21] ^ D1) >> (64-2));
c.A[21] = ((c.A[8] ^ D3) << 55) ^ ((c.A[8] ^ D3) >> (64-55));
c.A[8] = ((c.A[16] ^ D1) << 45) ^ ((c.A[16] ^ D1) >> (64-45));
c.A[16] = ((c.A[5] ^ D0) << 36) ^ ((c.A[5] ^ D0) >> (64-36));
c.A[5] = ((c.A[3] ^ D3) << 28) ^ ((c.A[3] ^ D3) >> (64-28));
c.A[3] = ((c.A[18] ^ D3) << 21) ^ ((c.A[18] ^ D3) >> (64-21));
c.A[18] = ((c.A[17] ^ D2) << 15) ^ ((c.A[17] ^ D2) >> (64-15));
c.A[17] = ((c.A[11] ^ D1) << 10) ^ ((c.A[11] ^ D1) >> (64-10));
c.A[11] = ((c.A[7] ^ D2) << 6) ^ ((c.A[7] ^ D2) >> (64-6));
c.A[7] = ((c.A[10] ^ D0) << 3) ^ ((c.A[10] ^ D0) >> (64-3));
c.A[10] = A1;
}
function keccak_chi(CTX memory c) internal pure {
uint i;
uint64 A0;
uint64 A1;
uint64 A2;
uint64 A3;
uint64 A4;
for (i = 0; i < 25; i+=5) {
A0 = c.A[0 + i];
A1 = c.A[1 + i];
A2 = c.A[2 + i];
A3 = c.A[3 + i];
A4 = c.A[4 + i];
c.A[0 + i] ^= ~A1 & A2;
c.A[1 + i] ^= ~A2 & A3;
c.A[2 + i] ^= ~A3 & A4;
c.A[3 + i] ^= ~A4 & A0;
c.A[4 + i] ^= ~A0 & A1;
}
}
function keccak_init(CTX memory c) internal pure {
// is this needed?
uint i;
for (i = 0; i < 25; i++) {
c.A[i] = 0;
}
}
function sha3_xor_input(CTX memory c, bytes memory dat) internal pure {
for (uint i = 0; i < 17; i++) {
uint bo = i*8;
c.A[i] ^= uint64(uint8(dat[bo+7])) << 56 |
uint64(uint8(dat[bo+6])) << 48 |
uint64(uint8(dat[bo+5])) << 40 |
uint64(uint8(dat[bo+4])) << 32 |
uint64(uint8(dat[bo+3])) << 24 |
uint64(uint8(dat[bo+2])) << 16 |
uint64(uint8(dat[bo+1])) << 8 |
uint64(uint8(dat[bo+0])) << 0;
}
}
function sha3_permutation(CTX memory c) internal pure {
uint round;
for (round = 0; round < 24; round++) {
keccak_theta_rho_pi(c);
keccak_chi(c);
// keccak_iota
c.A[0] ^= get_round_constant(round);
}
}
// https://stackoverflow.com/questions/2182002/convert-big-endian-to-little-endian-in-c-without-using-provided-func
function flip(uint64 val) internal pure returns (uint64) {
val = ((val << 8) & 0xFF00FF00FF00FF00 ) | ((val >> 8) & 0x00FF00FF00FF00FF );
val = ((val << 16) & 0xFFFF0000FFFF0000 ) | ((val >> 16) & 0x0000FFFF0000FFFF );
return (val << 32) | (val >> 32);
}
function get_hash(CTX memory c) internal pure returns (bytes32) {
return bytes32((uint256(flip(c.A[0])) << 192) |
(uint256(flip(c.A[1])) << 128) |
(uint256(flip(c.A[2])) << 64) |
(uint256(flip(c.A[3])) << 0));
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* Library Imports */
import { Lib_BytesUtils } from "./Lib_BytesUtils.sol";
import { Lib_RLPReader } from "./Lib_RLPReader.sol";
import { Lib_RLPWriter } from "./Lib_RLPWriter.sol";
/**
* @title Lib_MerkleTrie
*/
library Lib_MerkleTrie {
/*******************
* Data Structures *
*******************/
enum NodeType {
BranchNode,
ExtensionNode,
LeafNode
}
struct TrieNode {
bytes encoded;
Lib_RLPReader.RLPItem[] decoded;
}
function GetTrie() internal pure returns (mapping(bytes32 => bytes) storage trie) {
bytes32 position = keccak256("trie.trie.trie.trie");
assembly { trie.slot := position }
}
/**********************
* Contract Constants *
**********************/
// TREE_RADIX determines the number of elements per branch node.
uint256 constant TREE_RADIX = 16;
// Branch nodes have TREE_RADIX elements plus an additional `value` slot.
uint256 constant BRANCH_NODE_LENGTH = TREE_RADIX + 1;
// Leaf nodes and extension nodes always have two elements, a `path` and a `value`.
uint256 constant LEAF_OR_EXTENSION_NODE_LENGTH = 2;
// Prefixes are prepended to the `path` within a leaf or extension node and
// allow us to differentiate between the two node types. `ODD` or `EVEN` is
// determined by the number of nibbles within the unprefixed `path`. If the
// number of nibbles if even, we need to insert an extra padding nibble so
// the resulting prefixed `path` has an even number of nibbles.
uint8 constant PREFIX_EXTENSION_EVEN = 0;
uint8 constant PREFIX_EXTENSION_ODD = 1;
uint8 constant PREFIX_LEAF_EVEN = 2;
uint8 constant PREFIX_LEAF_ODD = 3;
// Just a utility constant. RLP represents `NULL` as 0x80.
bytes1 constant RLP_NULL = bytes1(0x80);
bytes constant RLP_NULL_BYTES = hex'80';
bytes32 constant internal KECCAK256_RLP_NULL_BYTES = keccak256(RLP_NULL_BYTES);
/**********************
* Internal Functions *
**********************/
/**
* @notice Updates a Merkle trie and returns a new root hash.
* @param _key Key of the node to update, as a hex string.
* @param _value Value of the node to update, as a hex string.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return _updatedRoot Root hash of the newly constructed trie.
*/
function update(
bytes memory _key,
bytes memory _value,
bytes32 _root
)
internal
returns (
bytes32 _updatedRoot
)
{
// Special case when inserting the very first node.
if (_root == KECCAK256_RLP_NULL_BYTES) {
return getSingleNodeRootHash(_key, _value);
}
(TrieNode[] memory proof, uint256 pathLength, bytes memory keyRemainder, ) = _walkNodePath(_key, _root);
TrieNode[] memory newPath = _getNewPath(proof, pathLength, _key, keyRemainder, _value);
_updatedRoot = _getUpdatedTrieRoot(newPath, _key);
}
function getRawNode(bytes memory encoded) private pure returns (TrieNode memory) {
return TrieNode({
encoded: encoded,
decoded: Lib_RLPReader.readList(encoded)
});
}
function getTrieNode(bytes32 nodeId) private view returns (TrieNode memory) {
bytes memory encoded = GetTrie()[nodeId];
if (encoded.length == 0) {
Lib_BytesUtils.revertWithHex(abi.encodePacked(nodeId));
}
require(keccak256(encoded) == nodeId, "bad hash in trie lookup");
return getRawNode(encoded);
}
/**
* @notice Retrieves the value associated with a given key.
* @param _key Key to search for, as hex bytes.
* @param _root Known root of the Merkle trie.
* @return _exists Whether or not the key exists.
* @return _value Value of the key if it exists.
*/
function get(
bytes memory _key,
bytes32 _root
)
internal
view
returns (
bool _exists,
bytes memory _value
)
{
(TrieNode[] memory proof, uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = _walkNodePath(_key, _root);
bool exists = keyRemainder.length == 0;
require(
exists || isFinalNode,
"Provided proof is invalid."
);
bytes memory value = exists ? _getNodeValue(proof[pathLength - 1]) : bytes("");
return (
exists,
value
);
}
/**
* Computes the root hash for a trie with a single node.
* @param _key Key for the single node.
* @param _value Value for the single node.
* @return _updatedRoot Hash of the trie.
*/
function getSingleNodeRootHash(
bytes memory _key,
bytes memory _value
)
internal
returns (
bytes32 _updatedRoot
)
{
bytes memory dat = _makeLeafNode(
Lib_BytesUtils.toNibbles(_key),
_value).encoded;
bytes32 ret = keccak256(dat);
GetTrie()[ret] = dat;
return ret;
}
/*********************
* Private Functions *
*********************/
/**
* @notice Walks through a proof using a provided key.
* @param _key Key to use for the walk.
* @param _root Known root of the trie.
* @return _proof The proof
* @return _pathLength Length of the final path
* @return _keyRemainder Portion of the key remaining after the walk.
* @return _isFinalNode Whether or not we've hit a dead end.
*/
function _walkNodePath(
bytes memory _key,
bytes32 _root
)
private
view
returns (
TrieNode[] memory _proof,
uint256 _pathLength,
bytes memory _keyRemainder,
bool _isFinalNode
)
{
// TODO: this is max length
_proof = new TrieNode[](9);
uint256 pathLength = 0;
bytes memory key = Lib_BytesUtils.toNibbles(_key);
bytes32 currentNodeID = _root;
uint256 currentNodeLength = 32;
uint256 currentKeyIndex = 0;
uint256 currentKeyIncrement = 0;
TrieNode memory currentNode;
// Proof is top-down, so we start at the first element (root).
for (uint256 i = 0; i < _proof.length; i++) {
if (currentNodeID == bytes32(RLP_NULL)) {
break;
}
if (currentNodeLength >= 32) {
currentNode = getTrieNode(currentNodeID);
} else {
currentNode = getRawNode(Lib_BytesUtils.slice(abi.encodePacked(currentNodeID), 0, currentNodeLength));
}
_proof[pathLength] = currentNode;
currentKeyIndex += currentKeyIncrement;
// Keep track of the proof elements we actually need.
// It's expensive to resize arrays, so this simply reduces gas costs.
pathLength += 1;
if (currentKeyIndex == 0) {
// First proof element is always the root node.
require(
keccak256(currentNode.encoded) == currentNodeID,
"Invalid root hash"
);
} else if (currentNode.encoded.length >= 32) {
// Nodes 32 bytes or larger are hashed inside branch nodes.
require(
keccak256(currentNode.encoded) == currentNodeID,
"Invalid large internal hash"
);
} else {
// Nodes smaller than 31 bytes aren't hashed.
require(
Lib_BytesUtils.toBytes32(currentNode.encoded) == currentNodeID,
"Invalid internal node hash"
);
}
if (currentNode.decoded.length == BRANCH_NODE_LENGTH) {
if (currentKeyIndex == key.length) {
// We've hit the end of the key
// meaning the value should be within this branch node.
break;
} else {
// We're not at the end of the key yet.
// Figure out what the next node ID should be and continue.
uint8 branchKey = uint8(key[currentKeyIndex]);
Lib_RLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey];
(currentNodeID, currentNodeLength) = _getNodeID(nextNode);
currentKeyIncrement = 1;
continue;
}
} else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) {
bytes memory path = _getNodePath(currentNode);
uint8 prefix = uint8(path[0]);
uint8 offset = 2 - prefix % 2;
bytes memory pathRemainder = Lib_BytesUtils.slice(path, offset);
bytes memory keyRemainder = Lib_BytesUtils.slice(key, currentKeyIndex);
uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder);
if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) {
if (
pathRemainder.length == sharedNibbleLength &&
keyRemainder.length == sharedNibbleLength
) {
// The key within this leaf matches our key exactly.
// Increment the key index to reflect that we have no remainder.
currentKeyIndex += sharedNibbleLength;
}
// We've hit a leaf node, so our next node should be NULL.
currentNodeID = bytes32(RLP_NULL);
break;
} else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) {
if (sharedNibbleLength != pathRemainder.length) {
// Our extension node is not identical to the remainder.
// We've hit the end of this path
// updates will need to modify this extension.
currentNodeID = bytes32(RLP_NULL);
break;
} else {
// Our extension shares some nibbles.
// Carry on to the next node.
(currentNodeID, currentNodeLength) = _getNodeID(currentNode.decoded[1]);
currentKeyIncrement = sharedNibbleLength;
continue;
}
} else {
revert("Received a node with an unknown prefix");
}
} else {
revert("Received an unparseable node.");
}
}
// If our node ID is NULL, then we're at a dead end.
bool isFinalNode = currentNodeID == bytes32(RLP_NULL);
return (_proof, pathLength, Lib_BytesUtils.slice(key, currentKeyIndex), isFinalNode);
}
/**
* @notice Creates new nodes to support a k/v pair insertion into a given Merkle trie path.
* @param _path Path to the node nearest the k/v pair.
* @param _pathLength Length of the path. Necessary because the provided path may include
* additional nodes (e.g., it comes directly from a proof) and we can't resize in-memory
* arrays without costly duplication.
* @param _key Full original key.
* @param _keyRemainder Portion of the initial key that must be inserted into the trie.
* @param _value Value to insert at the given key.
* @return _newPath A new path with the inserted k/v pair and extra supporting nodes.
*/
function _getNewPath(
TrieNode[] memory _path,
uint256 _pathLength,
bytes memory _key,
bytes memory _keyRemainder,
bytes memory _value
)
private
returns (
TrieNode[] memory _newPath
)
{
bytes memory keyRemainder = _keyRemainder;
// Most of our logic depends on the status of the last node in the path.
TrieNode memory lastNode = _path[_pathLength - 1];
NodeType lastNodeType = _getNodeType(lastNode);
// Create an array for newly created nodes.
// We need up to three new nodes, depending on the contents of the last node.
// Since array resizing is expensive, we'll keep track of the size manually.
// We're using an explicit `totalNewNodes += 1` after insertions for clarity.
TrieNode[] memory newNodes = new TrieNode[](3);
uint256 totalNewNodes = 0;
// solhint-disable-next-line max-line-length
// Reference: https://github.com/ethereumjs/merkle-patricia-tree/blob/c0a10395aab37d42c175a47114ebfcbd7efcf059/src/baseTrie.ts#L294-L313
bool matchLeaf = false;
if (lastNodeType == NodeType.LeafNode) {
uint256 l = 0;
if (_pathLength > 0) {
for (uint256 i = 0; i < _pathLength - 1; i++) {
if (_getNodeType(_path[i]) == NodeType.BranchNode) {
l++;
} else {
l += _getNodeKey(_path[i]).length;
}
}
}
if (
_getSharedNibbleLength(
_getNodeKey(lastNode),
Lib_BytesUtils.slice(Lib_BytesUtils.toNibbles(_key), l)
) == _getNodeKey(lastNode).length
&& keyRemainder.length == 0
) {
matchLeaf = true;
}
}
if (matchLeaf) {
// We've found a leaf node with the given key.
// Simply need to update the value of the node to match.
newNodes[totalNewNodes] = _makeLeafNode(_getNodeKey(lastNode), _value);
totalNewNodes += 1;
} else if (lastNodeType == NodeType.BranchNode) {
if (keyRemainder.length == 0) {
// We've found a branch node with the given key.
// Simply need to update the value of the node to match.
newNodes[totalNewNodes] = _editBranchValue(lastNode, _value);
totalNewNodes += 1;
} else {
// We've found a branch node, but it doesn't contain our key.
// Reinsert the old branch for now.
newNodes[totalNewNodes] = lastNode;
totalNewNodes += 1;
// Create a new leaf node, slicing our remainder since the first byte points
// to our branch node.
newNodes[totalNewNodes] =
_makeLeafNode(Lib_BytesUtils.slice(keyRemainder, 1), _value);
totalNewNodes += 1;
}
} else {
// Our last node is either an extension node or a leaf node with a different key.
bytes memory lastNodeKey = _getNodeKey(lastNode);
uint256 sharedNibbleLength = _getSharedNibbleLength(lastNodeKey, keyRemainder);
if (sharedNibbleLength != 0) {
// We've got some shared nibbles between the last node and our key remainder.
// We'll need to insert an extension node that covers these shared nibbles.
bytes memory nextNodeKey = Lib_BytesUtils.slice(lastNodeKey, 0, sharedNibbleLength);
newNodes[totalNewNodes] = _makeExtensionNode(nextNodeKey, _getNodeHash(_value), true);
totalNewNodes += 1;
// Cut down the keys since we've just covered these shared nibbles.
lastNodeKey = Lib_BytesUtils.slice(lastNodeKey, sharedNibbleLength);
keyRemainder = Lib_BytesUtils.slice(keyRemainder, sharedNibbleLength);
}
// Create an empty branch to fill in.
TrieNode memory newBranch = _makeEmptyBranchNode();
if (lastNodeKey.length == 0) {
// Key remainder was larger than the key for our last node.
// The value within our last node is therefore going to be shifted into
// a branch value slot.
newBranch = _editBranchValue(newBranch, _getNodeValue(lastNode));
} else {
// Last node key was larger than the key remainder.
// We're going to modify some index of our branch.
uint8 branchKey = uint8(lastNodeKey[0]);
// Move on to the next nibble.
lastNodeKey = Lib_BytesUtils.slice(lastNodeKey, 1);
if (lastNodeType == NodeType.LeafNode) {
// We're dealing with a leaf node.
// We'll modify the key and insert the old leaf node into the branch index.
TrieNode memory modifiedLastNode =
_makeLeafNode(lastNodeKey, _getNodeValue(lastNode));
newBranch =
_editBranchIndex(
newBranch,
branchKey,
_getNodeHash(modifiedLastNode.encoded));
} else if (lastNodeKey.length != 0) {
// We're dealing with a shrinking extension node.
// We need to modify the node to decrease the size of the key.
TrieNode memory modifiedLastNode =
_makeExtensionNode(lastNodeKey, _getNodeValue(lastNode), false);
newBranch =
_editBranchIndex(
newBranch,
branchKey,
_getNodeHash(modifiedLastNode.encoded));
} else {
// We're dealing with an unnecessary extension node.
// We're going to delete the node entirely.
// Simply insert its current value into the branch index.
newBranch = _editBranchIndex(newBranch, branchKey, _getNodeValue(lastNode));
}
}
if (keyRemainder.length == 0) {
// We've got nothing left in the key remainder.
// Simply insert the value into the branch value slot.
newBranch = _editBranchValue(newBranch, _value);
// Push the branch into the list of new nodes.
newNodes[totalNewNodes] = newBranch;
totalNewNodes += 1;
} else {
// We've got some key remainder to work with.
// We'll be inserting a leaf node into the trie.
// First, move on to the next nibble.
keyRemainder = Lib_BytesUtils.slice(keyRemainder, 1);
// Push the branch into the list of new nodes.
newNodes[totalNewNodes] = newBranch;
totalNewNodes += 1;
// Push a new leaf node for our k/v pair.
newNodes[totalNewNodes] = _makeLeafNode(keyRemainder, _value);
totalNewNodes += 1;
}
}
// Finally, join the old path with our newly created nodes.
// Since we're overwriting the last node in the path, we use `_pathLength - 1`.
return _joinNodeArrays(_path, _pathLength - 1, newNodes, totalNewNodes);
}
/**
* @notice Computes the trie root from a given path.
* @param _nodes Path to some k/v pair.
* @param _key Key for the k/v pair.
* @return _updatedRoot Root hash for the updated trie.
*/
function _getUpdatedTrieRoot(
TrieNode[] memory _nodes,
bytes memory _key
)
private
returns (
bytes32 _updatedRoot
)
{
bytes memory key = Lib_BytesUtils.toNibbles(_key);
// Some variables to keep track of during iteration.
TrieNode memory currentNode;
NodeType currentNodeType;
bytes memory previousNodeHash;
// Run through the path backwards to rebuild our root hash.
for (uint256 i = _nodes.length; i > 0; i--) {
// Pick out the current node.
currentNode = _nodes[i - 1];
currentNodeType = _getNodeType(currentNode);
if (currentNodeType == NodeType.LeafNode) {
// Leaf nodes are already correctly encoded.
// Shift the key over to account for the nodes key.
bytes memory nodeKey = _getNodeKey(currentNode);
key = Lib_BytesUtils.slice(key, 0, key.length - nodeKey.length);
} else if (currentNodeType == NodeType.ExtensionNode) {
// Shift the key over to account for the nodes key.
bytes memory nodeKey = _getNodeKey(currentNode);
key = Lib_BytesUtils.slice(key, 0, key.length - nodeKey.length);
// If this node is the last element in the path, it'll be correctly encoded
// and we can skip this part.
if (previousNodeHash.length > 0) {
// Re-encode the node based on the previous node.
currentNode = _editExtensionNodeValue(currentNode, previousNodeHash);
}
} else if (currentNodeType == NodeType.BranchNode) {
// If this node is the last element in the path, it'll be correctly encoded
// and we can skip this part.
if (previousNodeHash.length > 0) {
// Re-encode the node based on the previous node.
uint8 branchKey = uint8(key[key.length - 1]);
key = Lib_BytesUtils.slice(key, 0, key.length - 1);
currentNode = _editBranchIndex(currentNode, branchKey, previousNodeHash);
}
}
// Compute the node hash for the next iteration.
previousNodeHash = _getNodeHash(currentNode.encoded);
}
// If the root node is < 32 bytes, it won't have a stored hash
bytes32 encodedHash = keccak256(currentNode.encoded);
if (currentNode.encoded.length < 32) {
GetTrie()[encodedHash] = currentNode.encoded;
}
// Current node should be the root at this point.
// Simply return the hash of its encoding.
return encodedHash;
}
/**
* @notice Parses an RLP-encoded proof into something more useful.
* @param _proof RLP-encoded proof to parse.
* @return _parsed Proof parsed into easily accessible structs.
*/
function _parseProof(
bytes memory _proof
)
private
pure
returns (
TrieNode[] memory _parsed
)
{
Lib_RLPReader.RLPItem[] memory nodes = Lib_RLPReader.readList(_proof);
TrieNode[] memory proof = new TrieNode[](nodes.length);
for (uint256 i = 0; i < nodes.length; i++) {
bytes memory encoded = Lib_RLPReader.readBytes(nodes[i]);
proof[i] = TrieNode({
encoded: encoded,
decoded: Lib_RLPReader.readList(encoded)
});
}
return proof;
}
/**
* @notice Picks out the ID for a node. Node ID is referred to as the
* "hash" within the specification, but nodes < 32 bytes are not actually
* hashed.
* @param _node Node to pull an ID for.
* @return _nodeID ID for the node, depending on the size of its contents.
*/
function _getNodeID(
Lib_RLPReader.RLPItem memory _node
)
private
pure
returns (
bytes32 _nodeID,
uint length
)
{
bytes memory nodeID;
if (_node.length < 32) {
// Nodes smaller than 32 bytes are RLP encoded.
nodeID = Lib_RLPReader.readRawBytes(_node);
} else {
// Nodes 32 bytes or larger are hashed.
nodeID = Lib_RLPReader.readBytes(_node);
}
return (Lib_BytesUtils.toBytes32(nodeID), _node.length);
}
/**
* @notice Gets the path for a leaf or extension node.
* @param _node Node to get a path for.
* @return _path Node path, converted to an array of nibbles.
*/
function _getNodePath(
TrieNode memory _node
)
private
pure
returns (
bytes memory _path
)
{
return Lib_BytesUtils.toNibbles(Lib_RLPReader.readBytes(_node.decoded[0]));
}
/**
* @notice Gets the key for a leaf or extension node. Keys are essentially
* just paths without any prefix.
* @param _node Node to get a key for.
* @return _key Node key, converted to an array of nibbles.
*/
function _getNodeKey(
TrieNode memory _node
)
private
pure
returns (
bytes memory _key
)
{
return _removeHexPrefix(_getNodePath(_node));
}
/**
* @notice Gets the path for a node.
* @param _node Node to get a value for.
* @return _value Node value, as hex bytes.
*/
function _getNodeValue(
TrieNode memory _node
)
private
pure
returns (
bytes memory _value
)
{
Lib_RLPReader.RLPItem memory _in = _node.decoded[_node.decoded.length - 1];
// this is bytes only if the length is 32
(
uint256 itemOffset,
uint256 itemLength,
Lib_RLPReader.RLPItemType itemType
) = Lib_RLPReader._decodeLength(_in);
if (itemType == Lib_RLPReader.RLPItemType.DATA_ITEM) {
return Lib_RLPReader._copy(_in.ptr, itemOffset, itemLength);
} else if (itemType == Lib_RLPReader.RLPItemType.LIST_ITEM) {
require(_in.length < 32, "bad _getNodeValue list");
return Lib_RLPReader._copy(_in.ptr, 0, _in.length);
}
revert("bad _getNodeValue");
}
/**
* @notice Computes the node hash for an encoded node. Nodes < 32 bytes
* are not hashed, all others are keccak256 hashed.
* @param _encoded Encoded node to hash.
* @return _hash Hash of the encoded node. Simply the input if < 32 bytes.
*/
function _getNodeHash(
bytes memory _encoded
)
private
returns (
bytes memory _hash
)
{
if (_encoded.length < 32) {
return _encoded;
} else {
bytes32 encodedHash = keccak256(_encoded);
GetTrie()[encodedHash] = _encoded;
return abi.encodePacked(encodedHash);
}
}
/**
* @notice Determines the type for a given node.
* @param _node Node to determine a type for.
* @return _type Type of the node; BranchNode/ExtensionNode/LeafNode.
*/
function _getNodeType(
TrieNode memory _node
)
private
pure
returns (
NodeType _type
)
{
if (_node.decoded.length == BRANCH_NODE_LENGTH) {
return NodeType.BranchNode;
} else if (_node.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) {
bytes memory path = _getNodePath(_node);
uint8 prefix = uint8(path[0]);
if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) {
return NodeType.LeafNode;
} else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) {
return NodeType.ExtensionNode;
}
}
revert("Invalid node type");
}
/**
* @notice Utility; determines the number of nibbles shared between two
* nibble arrays.
* @param _a First nibble array.
* @param _b Second nibble array.
* @return _shared Number of shared nibbles.
*/
function _getSharedNibbleLength(
bytes memory _a,
bytes memory _b
)
private
pure
returns (
uint256 _shared
)
{
uint256 i = 0;
while (_a.length > i && _b.length > i && _a[i] == _b[i]) {
i++;
}
return i;
}
/**
* @notice Utility; converts an RLP-encoded node into our nice struct.
* @param _raw RLP-encoded node to convert.
* @return _node Node as a TrieNode struct.
*/
function _makeNode(
bytes[] memory _raw
)
private
pure
returns (
TrieNode memory _node
)
{
bytes memory encoded = Lib_RLPWriter.writeList(_raw);
return TrieNode({
encoded: encoded,
decoded: Lib_RLPReader.readList(encoded)
});
}
/**
* @notice Utility; converts an RLP-decoded node into our nice struct.
* @param _items RLP-decoded node to convert.
* @return _node Node as a TrieNode struct.
*/
function _makeNode(
Lib_RLPReader.RLPItem[] memory _items
)
private
pure
returns (
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](_items.length);
for (uint256 i = 0; i < _items.length; i++) {
raw[i] = Lib_RLPReader.readRawBytes(_items[i]);
}
return _makeNode(raw);
}
/**
* @notice Creates a new extension node.
* @param _key Key for the extension node, unprefixed.
* @param _value Value for the extension node.
* @return _node New extension node with the given k/v pair.
*/
function _makeExtensionNode(
bytes memory _key,
bytes memory _value,
bool isBytes
)
private
pure
returns (
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](2);
bytes memory key = _addHexPrefix(_key, false);
raw[0] = Lib_RLPWriter.writeBytes(Lib_BytesUtils.fromNibbles(key));
if (_value.length >= 32 || isBytes) {
raw[1] = Lib_RLPWriter.writeBytes(_value);
} else {
raw[1] = _value;
}
return _makeNode(raw);
}
/**
* Creates a new extension node with the same key but a different value.
* @param _node Extension node to copy and modify.
* @param _value New value for the extension node.
* @return New node with the same key and different value.
*/
function _editExtensionNodeValue(
TrieNode memory _node,
bytes memory _value
)
private
pure
returns (
TrieNode memory
)
{
bytes[] memory raw = new bytes[](2);
bytes memory key = _addHexPrefix(_getNodeKey(_node), false);
raw[0] = Lib_RLPWriter.writeBytes(Lib_BytesUtils.fromNibbles(key));
if (_value.length < 32) {
raw[1] = _value;
} else {
raw[1] = Lib_RLPWriter.writeBytes(_value);
}
return _makeNode(raw);
}
/**
* @notice Creates a new leaf node.
* @dev This function is essentially identical to `_makeExtensionNode`.
* Although we could route both to a single method with a flag, it's
* more gas efficient to keep them separate and duplicate the logic.
* @param _key Key for the leaf node, unprefixed.
* @param _value Value for the leaf node.
* @return _node New leaf node with the given k/v pair.
*/
function _makeLeafNode(
bytes memory _key,
bytes memory _value
)
private
pure
returns (
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](2);
bytes memory key = _addHexPrefix(_key, true);
raw[0] = Lib_RLPWriter.writeBytes(Lib_BytesUtils.fromNibbles(key));
raw[1] = Lib_RLPWriter.writeBytes(_value);
return _makeNode(raw);
}
/**
* @notice Creates an empty branch node.
* @return _node Empty branch node as a TrieNode struct.
*/
function _makeEmptyBranchNode()
private
pure
returns (
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](BRANCH_NODE_LENGTH);
for (uint256 i = 0; i < raw.length; i++) {
raw[i] = RLP_NULL_BYTES;
}
return _makeNode(raw);
}
/**
* @notice Modifies the value slot for a given branch.
* @param _branch Branch node to modify.
* @param _value Value to insert into the branch.
* @return _updatedNode Modified branch node.
*/
function _editBranchValue(
TrieNode memory _branch,
bytes memory _value
)
private
pure
returns (
TrieNode memory _updatedNode
)
{
bytes memory encoded = Lib_RLPWriter.writeBytes(_value);
_branch.decoded[_branch.decoded.length - 1] = Lib_RLPReader.toRLPItem(encoded);
return _makeNode(_branch.decoded);
}
/**
* @notice Modifies a slot at an index for a given branch.
* @param _branch Branch node to modify.
* @param _index Slot index to modify.
* @param _value Value to insert into the slot.
* @return _updatedNode Modified branch node.
*/
function _editBranchIndex(
TrieNode memory _branch,
uint8 _index,
bytes memory _value
)
private
pure
returns (
TrieNode memory _updatedNode
)
{
bytes memory encoded = _value.length < 32 ? _value : Lib_RLPWriter.writeBytes(_value);
_branch.decoded[_index] = Lib_RLPReader.toRLPItem(encoded);
return _makeNode(_branch.decoded);
}
/**
* @notice Utility; adds a prefix to a key.
* @param _key Key to prefix.
* @param _isLeaf Whether or not the key belongs to a leaf.
* @return _prefixedKey Prefixed key.
*/
function _addHexPrefix(
bytes memory _key,
bool _isLeaf
)
private
pure
returns (
bytes memory _prefixedKey
)
{
uint8 prefix = _isLeaf ? uint8(0x02) : uint8(0x00);
uint8 offset = uint8(_key.length % 2);
bytes memory prefixed = new bytes(2 - offset);
prefixed[0] = bytes1(prefix + offset);
return abi.encodePacked(prefixed, _key);
}
/**
* @notice Utility; removes a prefix from a path.
* @param _path Path to remove the prefix from.
* @return _unprefixedKey Unprefixed key.
*/
function _removeHexPrefix(
bytes memory _path
)
private
pure
returns (
bytes memory _unprefixedKey
)
{
if (uint8(_path[0]) % 2 == 0) {
return Lib_BytesUtils.slice(_path, 2);
} else {
return Lib_BytesUtils.slice(_path, 1);
}
}
/**
* @notice Utility; combines two node arrays. Array lengths are required
* because the actual lengths may be longer than the filled lengths.
* Array resizing is extremely costly and should be avoided.
* @param _a First array to join.
* @param _aLength Length of the first array.
* @param _b Second array to join.
* @param _bLength Length of the second array.
* @return _joined Combined node array.
*/
function _joinNodeArrays(
TrieNode[] memory _a,
uint256 _aLength,
TrieNode[] memory _b,
uint256 _bLength
)
private
pure
returns (
TrieNode[] memory _joined
)
{
TrieNode[] memory ret = new TrieNode[](_aLength + _bLength);
// Copy elements from the first array.
for (uint256 i = 0; i < _aLength; i++) {
ret[i] = _a[i];
}
// Copy elements from the second array.
for (uint256 i = 0; i < _bLength; i++) {
ret[i + _aLength] = _b[i];
}
return ret;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/**
* @title Lib_RLPReader
* @dev Adapted from "RLPReader" by Hamdi Allam (hamdi.allam97@gmail.com).
*/
library Lib_RLPReader {
/*************
* Constants *
*************/
uint256 constant internal MAX_LIST_LENGTH = 32;
/*********
* Enums *
*********/
enum RLPItemType {
DATA_ITEM,
LIST_ITEM
}
/***********
* Structs *
***********/
struct RLPItem {
uint256 length;
uint256 ptr;
}
/**********************
* Internal Functions *
**********************/
/**
* Converts bytes to a reference to memory position and length.
* @param _in Input bytes to convert.
* @return Output memory reference.
*/
function toRLPItem(
bytes memory _in
)
internal
pure
returns (
RLPItem memory
)
{
uint256 ptr;
assembly {
ptr := add(_in, 32)
}
return RLPItem({
length: _in.length,
ptr: ptr
});
}
/**
* Reads an RLP list value into a list of RLP items.
* @param _in RLP list value.
* @return Decoded RLP list items.
*/
function readList(
RLPItem memory _in
)
internal
pure
returns (
RLPItem[] memory
)
{
(
uint256 listOffset,
,
RLPItemType itemType
) = _decodeLength(_in);
require(
itemType == RLPItemType.LIST_ITEM,
"Invalid RLP list value."
);
// Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by
// writing to the length. Since we can't know the number of RLP items without looping over
// the entire input, we'd have to loop twice to accurately size this array. It's easier to
// simply set a reasonable maximum list length and decrease the size before we finish.
RLPItem[] memory out = new RLPItem[](MAX_LIST_LENGTH);
uint256 itemCount = 0;
uint256 offset = listOffset;
while (offset < _in.length) {
require(
itemCount < MAX_LIST_LENGTH,
"Provided RLP list exceeds max list length."
);
(
uint256 itemOffset,
uint256 itemLength,
) = _decodeLength(RLPItem({
length: _in.length - offset,
ptr: _in.ptr + offset
}));
out[itemCount] = RLPItem({
length: itemLength + itemOffset,
ptr: _in.ptr + offset
});
itemCount += 1;
offset += itemOffset + itemLength;
}
// Decrease the array size to match the actual item count.
assembly {
mstore(out, itemCount)
}
return out;
}
/**
* Reads an RLP list value into a list of RLP items.
* @param _in RLP list value.
* @return Decoded RLP list items.
*/
function readList(
bytes memory _in
)
internal
pure
returns (
RLPItem[] memory
)
{
return readList(
toRLPItem(_in)
);
}
/**
* Reads an RLP bytes value into bytes.
* @param _in RLP bytes value.
* @return Decoded bytes.
*/
function readBytes(
RLPItem memory _in
)
internal
pure
returns (
bytes memory
)
{
(
uint256 itemOffset,
uint256 itemLength,
RLPItemType itemType
) = _decodeLength(_in);
require(
itemType == RLPItemType.DATA_ITEM,
"Invalid RLP bytes value."
);
return _copy(_in.ptr, itemOffset, itemLength);
}
/**
* Reads an RLP bytes value into bytes.
* @param _in RLP bytes value.
* @return Decoded bytes.
*/
function readBytes(
bytes memory _in
)
internal
pure
returns (
bytes memory
)
{
return readBytes(
toRLPItem(_in)
);
}
/**
* Reads an RLP string value into a string.
* @param _in RLP string value.
* @return Decoded string.
*/
function readString(
RLPItem memory _in
)
internal
pure
returns (
string memory
)
{
return string(readBytes(_in));
}
/**
* Reads an RLP string value into a string.
* @param _in RLP string value.
* @return Decoded string.
*/
function readString(
bytes memory _in
)
internal
pure
returns (
string memory
)
{
return readString(
toRLPItem(_in)
);
}
/**
* Reads an RLP bytes32 value into a bytes32.
* @param _in RLP bytes32 value.
* @return Decoded bytes32.
*/
function readBytes32(
RLPItem memory _in
)
internal
pure
returns (
bytes32
)
{
require(
_in.length <= 33,
"Invalid RLP bytes32 value."
);
(
uint256 itemOffset,
uint256 itemLength,
RLPItemType itemType
) = _decodeLength(_in);
require(
itemType == RLPItemType.DATA_ITEM,
"Invalid RLP bytes32 value."
);
uint256 ptr = _in.ptr + itemOffset;
bytes32 out;
assembly {
out := mload(ptr)
// Shift the bytes over to match the item size.
if lt(itemLength, 32) {
out := div(out, exp(256, sub(32, itemLength)))
}
}
return out;
}
/**
* Reads an RLP bytes32 value into a bytes32.
* @param _in RLP bytes32 value.
* @return Decoded bytes32.
*/
function readBytes32(
bytes memory _in
)
internal
pure
returns (
bytes32
)
{
return readBytes32(
toRLPItem(_in)
);
}
/**
* Reads an RLP uint256 value into a uint256.
* @param _in RLP uint256 value.
* @return Decoded uint256.
*/
function readUint256(
RLPItem memory _in
)
internal
pure
returns (
uint256
)
{
return uint256(readBytes32(_in));
}
/**
* Reads an RLP uint256 value into a uint256.
* @param _in RLP uint256 value.
* @return Decoded uint256.
*/
function readUint256(
bytes memory _in
)
internal
pure
returns (
uint256
)
{
return readUint256(
toRLPItem(_in)
);
}
/**
* Reads an RLP bool value into a bool.
* @param _in RLP bool value.
* @return Decoded bool.
*/
function readBool(
RLPItem memory _in
)
internal
pure
returns (
bool
)
{
require(
_in.length == 1,
"Invalid RLP boolean value."
);
uint256 ptr = _in.ptr;
uint256 out;
assembly {
out := byte(0, mload(ptr))
}
require(
out == 0 || out == 1,
"Lib_RLPReader: Invalid RLP boolean value, must be 0 or 1"
);
return out != 0;
}
/**
* Reads an RLP bool value into a bool.
* @param _in RLP bool value.
* @return Decoded bool.
*/
function readBool(
bytes memory _in
)
internal
pure
returns (
bool
)
{
return readBool(
toRLPItem(_in)
);
}
/**
* Reads an RLP address value into a address.
* @param _in RLP address value.
* @return Decoded address.
*/
function readAddress(
RLPItem memory _in
)
internal
pure
returns (
address
)
{
if (_in.length == 1) {
return address(0);
}
require(
_in.length == 21,
"Invalid RLP address value."
);
return address(readUint256(_in));
}
/**
* Reads an RLP address value into a address.
* @param _in RLP address value.
* @return Decoded address.
*/
function readAddress(
bytes memory _in
)
internal
pure
returns (
address
)
{
return readAddress(
toRLPItem(_in)
);
}
/**
* Reads the raw bytes of an RLP item.
* @param _in RLP item to read.
* @return Raw RLP bytes.
*/
function readRawBytes(
RLPItem memory _in
)
internal
pure
returns (
bytes memory
)
{
return _copy(_in);
}
/*********************
* Private Functions *
*********************/
/**
* Decodes the length of an RLP item.
* @param _in RLP item to decode.
* @return Offset of the encoded data.
* @return Length of the encoded data.
* @return RLP item type (LIST_ITEM or DATA_ITEM).
*/
function _decodeLength(
RLPItem memory _in
)
internal
pure
returns (
uint256,
uint256,
RLPItemType
)
{
require(
_in.length > 0,
"RLP item cannot be null."
);
uint256 ptr = _in.ptr;
uint256 prefix;
assembly {
prefix := byte(0, mload(ptr))
}
if (prefix <= 0x7f) {
// Single byte.
return (0, 1, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xb7) {
// Short string.
uint256 strLen = prefix - 0x80;
require(
_in.length > strLen,
"Invalid RLP short string."
);
return (1, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xbf) {
// Long string.
uint256 lenOfStrLen = prefix - 0xb7;
require(
_in.length > lenOfStrLen,
"Invalid RLP long string length."
);
uint256 strLen;
assembly {
// Pick out the string length.
strLen := div(
mload(add(ptr, 1)),
exp(256, sub(32, lenOfStrLen))
)
}
require(
_in.length > lenOfStrLen + strLen,
"Invalid RLP long string."
);
return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xf7) {
// Short list.
uint256 listLen = prefix - 0xc0;
require(
_in.length > listLen,
"Invalid RLP short list."
);
return (1, listLen, RLPItemType.LIST_ITEM);
} else {
// Long list.
uint256 lenOfListLen = prefix - 0xf7;
require(
_in.length > lenOfListLen,
"Invalid RLP long list length."
);
uint256 listLen;
assembly {
// Pick out the list length.
listLen := div(
mload(add(ptr, 1)),
exp(256, sub(32, lenOfListLen))
)
}
require(
_in.length > lenOfListLen + listLen,
"Invalid RLP long list."
);
return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM);
}
}
/**
* Copies the bytes from a memory location.
* @param _src Pointer to the location to read from.
* @param _offset Offset to start reading from.
* @param _length Number of bytes to read.
* @return Copied bytes.
*/
function _copy(
uint256 _src,
uint256 _offset,
uint256 _length
)
internal
pure
returns (
bytes memory
)
{
bytes memory out = new bytes(_length);
if (out.length == 0) {
return out;
}
uint256 src = _src + _offset;
uint256 dest;
assembly {
dest := add(out, 32)
}
// Copy over as many complete words as we can.
for (uint256 i = 0; i < _length / 32; i++) {
assembly {
mstore(dest, mload(src))
}
src += 32;
dest += 32;
}
// Pick out the remaining bytes.
uint256 mask = 256 ** (32 - (_length % 32)) - 1;
assembly {
mstore(
dest,
or(
and(mload(src), not(mask)),
and(mload(dest), mask)
)
)
}
return out;
}
/**
* Copies an RLP item into bytes.
* @param _in RLP item to copy.
* @return Copied bytes.
*/
function _copy(
RLPItem memory _in
)
private
pure
returns (
bytes memory
)
{
return _copy(_in.ptr, 0, _in.length);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/**
* @title Lib_RLPWriter
* @author Bakaoh (with modifications)
*/
library Lib_RLPWriter {
/**********************
* Internal Functions *
**********************/
/**
* RLP encodes a byte string.
* @param _in The byte string to encode.
* @return The RLP encoded string in bytes.
*/
function writeBytes(
bytes memory _in
)
internal
pure
returns (
bytes memory
)
{
bytes memory encoded;
if (_in.length == 1 && uint8(_in[0]) < 128) {
encoded = _in;
} else {
encoded = abi.encodePacked(_writeLength(_in.length, 128), _in);
}
return encoded;
}
/**
* RLP encodes a list of RLP encoded byte byte strings.
* @param _in The list of RLP encoded byte strings.
* @return The RLP encoded list of items in bytes.
*/
function writeList(
bytes[] memory _in
)
internal
pure
returns (
bytes memory
)
{
bytes memory list = _flatten(_in);
return abi.encodePacked(_writeLength(list.length, 192), list);
}
/**
* RLP encodes a string.
* @param _in The string to encode.
* @return The RLP encoded string in bytes.
*/
function writeString(
string memory _in
)
internal
pure
returns (
bytes memory
)
{
return writeBytes(bytes(_in));
}
/**
* RLP encodes an address.
* @param _in The address to encode.
* @return The RLP encoded address in bytes.
*/
function writeAddress(
address _in
)
internal
pure
returns (
bytes memory
)
{
return writeBytes(abi.encodePacked(_in));
}
/**
* RLP encodes a bytes32 value.
* @param _in The bytes32 to encode.
* @return _out The RLP encoded bytes32 in bytes.
*/
function writeBytes32(
bytes32 _in
)
internal
pure
returns (
bytes memory _out
)
{
return writeBytes(abi.encodePacked(_in));
}
/**
* RLP encodes a uint.
* @param _in The uint256 to encode.
* @return The RLP encoded uint256 in bytes.
*/
function writeUint(
uint256 _in
)
internal
pure
returns (
bytes memory
)
{
return writeBytes(_toBinary(_in));
}
/**
* RLP encodes a bool.
* @param _in The bool to encode.
* @return The RLP encoded bool in bytes.
*/
function writeBool(
bool _in
)
internal
pure
returns (
bytes memory
)
{
bytes memory encoded = new bytes(1);
encoded[0] = (_in ? bytes1(0x01) : bytes1(0x80));
return encoded;
}
/*********************
* Private Functions *
*********************/
/**
* Encode the first byte, followed by the `len` in binary form if `length` is more than 55.
* @param _len The length of the string or the payload.
* @param _offset 128 if item is string, 192 if item is list.
* @return RLP encoded bytes.
*/
function _writeLength(
uint256 _len,
uint256 _offset
)
private
pure
returns (
bytes memory
)
{
bytes memory encoded;
if (_len < 56) {
encoded = new bytes(1);
encoded[0] = byte(uint8(_len) + uint8(_offset));
} else {
uint256 lenLen;
uint256 i = 1;
while (_len / i != 0) {
lenLen++;
i *= 256;
}
encoded = new bytes(lenLen + 1);
encoded[0] = byte(uint8(lenLen) + uint8(_offset) + 55);
for(i = 1; i <= lenLen; i++) {
encoded[i] = byte(uint8((_len / (256**(lenLen-i))) % 256));
}
}
return encoded;
}
/**
* Encode integer in big endian binary form with no leading zeroes.
* @notice TODO: This should be optimized with assembly to save gas costs.
* @param _x The integer to encode.
* @return RLP encoded bytes.
*/
function _toBinary(
uint256 _x
)
private
pure
returns (
bytes memory
)
{
bytes memory b = abi.encodePacked(_x);
uint256 i = 0;
for (; i < 32; i++) {
if (b[i] != 0) {
break;
}
}
bytes memory res = new bytes(32 - i);
for (uint256 j = 0; j < res.length; j++) {
res[j] = b[i++];
}
return res;
}
/**
* Copies a piece of memory to another location.
* @notice From: https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol.
* @param _dest Destination location.
* @param _src Source location.
* @param _len Length of memory to copy.
*/
function _memcpy(
uint256 _dest,
uint256 _src,
uint256 _len
)
private
pure
{
uint256 dest = _dest;
uint256 src = _src;
uint256 len = _len;
for(; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
uint256 mask = 256 ** (32 - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
/**
* Flattens a list of byte strings into one byte string.
* @notice From: https://github.com/sammayo/solidity-rlp-encoder/blob/master/RLPEncode.sol.
* @param _list List of byte strings to flatten.
* @return The flattened byte string.
*/
function _flatten(
bytes[] memory _list
)
private
pure
returns (
bytes memory
)
{
if (_list.length == 0) {
return new bytes(0);
}
uint256 len;
uint256 i = 0;
for (; i < _list.length; i++) {
len += _list[i].length;
}
bytes memory flattened = new bytes(len);
uint256 flattenedPtr;
assembly { flattenedPtr := add(flattened, 0x20) }
for(i = 0; i < _list.length; i++) {
bytes memory item = _list[i];
uint256 listPtr;
assembly { listPtr := add(item, 0x20)}
_memcpy(flattenedPtr, listPtr, item.length);
flattenedPtr += _list[i].length;
}
return flattened;
}
}
#!/usr/bin/env bash
# The following variables can be overridden as environment variables:
# * BLOCK (block whose transition will be challenged)
# * SKIP_NODE (skip forking a node, useful if you've already forked a node)
#
# Example usage:
# SKIP_NODE=1 BLOCK=13284469 ./demo/challenge_fault.sh
# --- DOC ----------------------------------------------------------------------
# Unlike the simple scenario (cf. challenge_simple.sh), in this
# challenge-response scenario we use the correct block data (preimages) and
# instead use the `OUTPUTFAULT` environment variable to request a fault in the
# challenger's execution, making his challenge invalid.
#
# The "fault" in question is a behaviour hardcoded in `mipsevm` (Unicorn mode
# only) which triggers when the `OUTPUTFAULT` env var is set: when writing to
# MIPS address 0x30000804 (address where the output hash is written at the end
# of execution), it will write a wrong value instead.
#
# Alternatively, if `REGFAULT` is set, it should contain a MIPS execution step
# number and causes the MIPS register V0 to be set to a bogus value at the given
# execution step. (Just like before, this behaviour is hardcoded in `mipsevm` in
# Unicorn mode and triggers when `REGFAULT` is set.)
#
# This is much slower than the previous scenario because:
#
# - Since we write to the output hash at the end of execution, we will execute ~
# `log(n) * 3/4 * n` MIPS steps (where `n` = number of steps in full
# execution) vs `log(n) * 1/4 * n`in the previous example. (This is the
# difference of having the fault occur in the first vs (one of) the last
# steps.)
#
# - The challenged block contains almost 4x as many transactions as the original
# (8.5M vs 30M gas).
# --- SCRIPT SETUP -------------------------------------------------------------
shout() {
echo ""
echo "----------------------------------------"
echo "$1"
echo "----------------------------------------"
echo ""
}
# Exit if any command fails.
set -e
exit_trap() {
# Print an error if the last command failed
# (in which case the script is exiting because of set -e).
[[ $? == 0 ]] && return
echo "----------------------------------------"
echo "EARLY EXIT: SCRIPT FAILED"
echo "----------------------------------------"
# Kill (send SIGTERM) to the whole process group, also killing
# any background processes.
# I think the trap command resets SIGTERM before resending it to the whole
# group. (cf. https://stackoverflow.com/a/2173421)
trap - SIGTERM && kill -- -$$
}
trap "exit_trap" SIGINT SIGTERM EXIT
# --- BOOT MAINNET FORK --------------------------------------------------------
if [[ ! "$SKIP_NODE" ]]; then
NODE_LOG="challenge_fault_node.log"
shout "BOOTING MAINNET FORK NODE IN BACKGROUND (LOG: $NODE_LOG)"
# get directory containing this file
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
# run a hardhat mainnet fork node
"$SCRIPT_DIR/forked_node.sh" > "$NODE_LOG" 2>&1 &
# give the node some time to boot up
sleep 10
fi
# --- CHALLENGE SETUP ----------------------------------------------------------
# hardhat network to use
NETWORK=${NETWORK:-l1}
export NETWORK
# block whose transition will be challenged
# this variable is read by challenge.js, respond.js and assert.js
BLOCK=${BLOCK:-13284491}
export BLOCK
# challenge ID, read by respond.js and assert.js
export ID=0
# clear data from previous runs
mkdir -p /tmp/cannon /tmp/cannon_fault && rm -rf /tmp/cannon/* /tmp/cannon_fault/*
# stored in /tmp/cannon/golden.json
shout "GENERATING INITIAL MEMORY STATE CHECKPOINT"
mipsevm/mipsevm --outputGolden
shout "DEPLOYING CONTRACTS"
npx hardhat run scripts/deploy.js --network $NETWORK
# challenger will use same initial memory checkpoint and deployed contracts
cp /tmp/cannon/{golden,deployed}.json /tmp/cannon_fault/
shout "FETCHING PREIMAGES FOR REAL BLOCK"
minigeth/go-ethereum $BLOCK
shout "COMPUTING REAL MIPS FINAL MEMORY CHECKPOINT"
mipsevm/mipsevm --blockNumber=$BLOCK
# these are the preimages for the real block (but go into a different basedir)
shout "FETCHING PREIMAGES FOR FAULTY BLOCK"
BASEDIR=/tmp/cannon_fault minigeth/go-ethereum $BLOCK
# since the computation includes a fault, the output file will be different than
# for the real block
shout "COMPUTE FAKE MIPS CHECKPOINT"
OUTPUTFAULT=1 BASEDIR=/tmp/cannon_fault mipsevm/mipsevm --blockNumber=$BLOCK
# alternatively, to inject a fault in registers instead of memory
# REGFAULT=13240000 BASEDIR=/tmp/cannon_fault mipsevm/mipsevm --blockNumber=$BLOCK
# --- BINARY SEARCH ------------------------------------------------------------
shout "STARTING CHALLENGE"
BASEDIR=/tmp/cannon_fault npx hardhat run scripts/challenge.js --network $NETWORK
shout "BINARY SEARCH"
for i in {1..25}; do
echo ""
echo "--- STEP $i / 25 --"
echo ""
OUTPUTFAULT=1 BASEDIR=/tmp/cannon_fault CHALLENGER=1 npx hardhat run scripts/respond.js --network $NETWORK
npx hardhat run scripts/respond.js --network $NETWORK
done
# --- SINGLE STEP EXECUTION ----------------------------------------------------
shout "ASSERTING AS CHALLENGER (should fail)"
set +e # this should fail!
BASEDIR=/tmp/cannon_fault CHALLENGER=1 npx hardhat run scripts/assert.js --network $NETWORK
set -e
shout "ASSERTING AS DEFENDER (should pass)"
npx hardhat run scripts/assert.js --network $NETWORK
#!/usr/bin/env bash
# The following variables can be overridden as environment variables:
# * BLOCK (block whose transition will be challenged)
# * WRONG_BLOCK (block number used by challenger)
# * SKIP_NODE (skip forking a node, useful if you've already forked a node)
#
# Example usage:
# SKIP_NODE=1 BLOCK=13284469 WRONG_BLOCK=13284491 ./demo/challenge_simple.sh
# --- DOC ----------------------------------------------------------------------
# In this example, the challenger will challenge the transition from a block
# (`BLOCK`), but pretends that chain state before another block (`WRONG_BLOCK`)
# is the state before the challenged block. Consequently, the challenger will
# disagree with the defender on every single step of the challenge game, and the
# single step to execute will be the very first MIPS instruction executed. The
# reason is that the initial MIPS state Merkle root is stored on-chain, and
# immediately modified to reflect the fact that the input hash for the block is
# written at address 0x3000000.
#
# (The input hash is automatically validated against the blockhash, so note that
# in this demo the challenger has to provide the correct (`BLOCK`) input hash to
# the `initiateChallenge` function of `Challenge.sol`, but will execute as
# though the input hash was the one derived from `WRONG_BLOCK`.)
#
# Because the challenger uses the wrong inputs, it will assert a post-state
# (Merkle root) for the first MIPS instruction that has the wrong input hash at
# 0x3000000. Hence, the challenge will fail.
# --- SCRIPT SETUP -------------------------------------------------------------
shout() {
echo ""
echo "----------------------------------------"
echo "$1"
echo "----------------------------------------"
echo ""
}
# Exit if any command fails.
set -e
exit_trap() {
# Print an error if the last command failed
# (in which case the script is exiting because of set -e).
[[ $? == 0 ]] && return
echo "----------------------------------------"
echo "EARLY EXIT: SCRIPT FAILED"
echo "----------------------------------------"
# Kill (send SIGTERM) to the whole process group, also killing
# any background processes.
# I think the trap command resets SIGTERM before resending it to the whole
# group. (cf. https://stackoverflow.com/a/2173421)
trap - SIGTERM && kill -- -$$
}
trap "exit_trap" SIGINT SIGTERM EXIT
# --- BOOT MAINNET FORK --------------------------------------------------------
if [[ ! "$SKIP_NODE" ]]; then
NODE_LOG="challenge_simple_node.log"
shout "BOOTING MAINNET FORK NODE IN BACKGROUND (LOG: $NODE_LOG)"
# get directory containing this file
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
# run a hardhat mainnet fork node
"$SCRIPT_DIR/forked_node.sh" > "$NODE_LOG" 2>&1 &
# give the node some time to boot up
sleep 10
fi
# --- CHALLENGE SETUP ----------------------------------------------------------
# hardhat network to use
NETWORK=${NETWORK:-l1}
export NETWORK
# challenge ID, read by respond.js and assert.js
export ID=0
# block whose transition will be challenged
# this variable is read by challenge.js, respond.js and assert.js
BLOCK=${BLOCK:-13284469}
export BLOCK
# block whose pre-state is used by the challenger instead of the challenged block's pre-state
WRONG_BLOCK=${WRONG_BLOCK:-13284491}
# clear data from previous runs
mkdir -p /tmp/cannon /tmp/cannon_fault && rm -rf /tmp/cannon/* /tmp/cannon_fault/*
# stored in /tmp/cannon/golden.json
shout "GENERATING INITIAL MEMORY STATE CHECKPOINT"
mipsevm/mipsevm --outputGolden
shout "DEPLOYING CONTRACTS"
npx hardhat run scripts/deploy.js --network $NETWORK
# challenger will use same initial memory checkpoint and deployed contracts
cp /tmp/cannon/{golden,deployed}.json /tmp/cannon_fault/
shout "FETCHING PREIMAGES FOR REAL BLOCK"
minigeth/go-ethereum $BLOCK
shout "COMPUTING REAL MIPS FINAL MEMORY CHECKPOINT"
mipsevm/mipsevm --blockNumber=$BLOCK
shout "FETCHING PREIMAGES FOR WRONG BLOCK"
BASEDIR=/tmp/cannon_fault minigeth/go-ethereum $WRONG_BLOCK
shout "COMPUTING FAKE MIPS FINAL MEMORY CHECKPOINT"
BASEDIR=/tmp/cannon_fault mipsevm/mipsevm --blockNumber=$WRONG_BLOCK
# pretend the wrong block's input, checkpoints and preimages are the right block's
ln -s /tmp/cannon_fault/0_$WRONG_BLOCK /tmp/cannon_fault/0_$BLOCK
# --- BINARY SEARCH ------------------------------------------------------------
shout "STARTING CHALLENGE"
BASEDIR=/tmp/cannon_fault npx hardhat run scripts/challenge.js --network $NETWORK
shout "BINARY SEARCH"
for i in {1..23}; do
echo ""
echo "--- STEP $i / 23 ---"
echo ""
BASEDIR=/tmp/cannon_fault CHALLENGER=1 npx hardhat run scripts/respond.js --network $NETWORK
npx hardhat run scripts/respond.js --network $NETWORK
done
# --- SINGLE STEP EXECUTION ----------------------------------------------------
shout "ASSERTING AS CHALLENGER (should fail)"
set +e # this should fail!
BASEDIR=/tmp/cannon_fault CHALLENGER=1 npx hardhat run scripts/assert.js --network $NETWORK
set -e
shout "ASSERTING AS DEFENDER (should pass)"
npx hardhat run scripts/assert.js --network $NETWORK
#!/usr/bin/env bash
# This runs a hardhat node forked from mainnet at the specified block.
# You need to run this in a separate terminal (or in the background)
# before running challenge_simple.sh or challenge_fault.sh.
#
# RPC_URL and FORK_BLOCK can be overwritten as environment variables. If not
# provided, defaults are used.
# Uncomment this line if you receive the error:
# Error HH604: Error running JSON-RPC server: error:0308010C:digital envelope routines::unsupported
# export NODE_OPTIONS=--openssl-legacy-provider
RPC_URL=${RPC_URL:-"https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"}
# block at which to fork mainnet
FORK_BLOCK=${FORK_BLOCK:-13284495}
# testing on hardhat (forked mainnet, a few blocks ahead of challenges in
# challenge_simple.sh and challenge_fault.sh)
npx hardhat node --fork $RPC_URL --fork-block-number $FORK_BLOCK
#!/usr/bin/env bash
# Similar to challenge_fault.sh, but runs on L2!
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
export ETH_RPC_URL=http://127.0.0.1:9545/
export BLOCK=$(cast block-number)
SKIP_NODE=1 NETWORK=l2 "$SCRIPT_DIR/challenge_fault.sh"
#!/usr/bin/env bash
# Similar to challenge_simple.sh, but runs on L2!
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
export ETH_RPC_URL=http://127.0.0.1:9545/
export BLOCK=$(cast block-number)
export WRONG_BLOCK=$(expr $BLOCK - 1)
SKIP_NODE=1 NETWORK=l2 "$SCRIPT_DIR/challenge_simple.sh"
const { deployed, getTrieNodesForCall, getTrieAtStep } = require("../scripts/lib")
async function main() {
let [c, m, mm] = await deployed()
const challengeId = parseInt(process.env.ID)
const blockNumberN = parseInt(process.env.BLOCK)
const isChallenger = process.env.CHALLENGER == "1"
let step = (await c.getStepNumber(challengeId)).toNumber()
console.log("searching step", step, "at block", blockNumberN)
if (await c.isSearching(challengeId)) {
console.log("search is NOT done")
return
}
let cdat
if (isChallenger) {
// challenger declare victory
cdat = c.interface.encodeFunctionData("confirmStateTransition", [challengeId])
} else {
// defender declare victory
// note: not always possible
cdat = c.interface.encodeFunctionData("denyStateTransition", [challengeId])
}
let startTrie = getTrieAtStep(blockNumberN, step)
let finalTrie = getTrieAtStep(blockNumberN, step+1)
let preimages = Object.assign({}, startTrie['preimages'], finalTrie['preimages']);
let nodes = await getTrieNodesForCall(c, c.address, cdat, preimages)
for (n of nodes) {
await mm.AddTrieNode(n)
}
let ret
if (isChallenger) {
ret = await c.confirmStateTransition(challengeId)
} else {
ret = await c.denyStateTransition(challengeId)
}
let receipt = await ret.wait()
console.log(receipt.events.map((x) => x.event))
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
const fs = require("fs")
const { basedir, deployed, getBlockRlp, getTrieNodesForCall } = require("../scripts/lib")
async function main() {
let [c, m, mm] = await deployed()
const blockNumberN = parseInt(process.env.BLOCK)
if (isNaN(blockNumberN)) {
throw "usage: BLOCK=<number> npx hardhat run challenge.js"
}
console.log("challenging block number", blockNumberN)
// sadly this doesn't work on hosthat
const blockNp1 = await network.provider.send("eth_getBlockByNumber", ["0x"+(blockNumberN+1).toString(16), false])
console.log(blockNp1)
const blockNp1Rlp = getBlockRlp(blockNp1)
console.log(c.address, m.address, mm.address)
// TODO: move this to lib, it's shared with the test
let startTrie = JSON.parse(fs.readFileSync(basedir+"/golden.json"))
const assertionRootBinary = fs.readFileSync(basedir+"/0_"+blockNumberN.toString()+"/output")
var assertionRoot = "0x"
for (var i=0; i<32; i++) {
hex = assertionRootBinary[i].toString(16);
assertionRoot += ("0"+hex).slice(-2);
}
console.log("asserting root", assertionRoot)
let finalTrie = JSON.parse(fs.readFileSync(basedir+"/0_"+blockNumberN.toString()+"/checkpoint_final.json"))
let preimages = Object.assign({}, startTrie['preimages'], finalTrie['preimages']);
const finalSystemState = finalTrie['root']
let args = [blockNumberN, blockNp1Rlp, assertionRoot, finalSystemState, finalTrie['step']]
let cdat = c.interface.encodeFunctionData("initiateChallenge", args)
let nodes = await getTrieNodesForCall(c, c.address, cdat, preimages)
// run "on chain"
for (n of nodes) {
await mm.AddTrieNode(n)
}
// TODO: Setting the gas limit explicitly here shouldn't be necessary, for some
// weird reason (to be investigated), it is for L2.
// let ret = await c.initiateChallenge(...args)
let ret = await c.initiateChallenge(...args, { gasLimit: 10_000_000 })
let receipt = await ret.wait()
// ChallengeCreated event
let challengeId = receipt.events[0].args['challengeId'].toNumber()
console.log("new challenge with id", challengeId)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
const { deploy } = require("../scripts/lib")
const fs = require("fs")
async function main() {
let [c, m, mm] = await deploy()
let json = {
"Challenge": c.address,
"MIPS": m.address,
"MIPSMemory": mm.address,
}
console.log("deployed", json)
fs.writeFileSync("/tmp/cannon/deployed.json", JSON.stringify(json))
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
const fs = require("fs")
const rlp = require('rlp')
const child_process = require("child_process")
const basedir = process.env.BASEDIR == undefined ? "/tmp/cannon" : process.env.BASEDIR
async function deploy() {
const MIPS = await ethers.getContractFactory("MIPS")
const m = await MIPS.deploy()
const mm = await ethers.getContractAt("MIPSMemory", await m.m())
let startTrie = JSON.parse(fs.readFileSync(basedir+"/golden.json"))
let goldenRoot = startTrie["root"]
console.log("goldenRoot is", goldenRoot)
const Challenge = await ethers.getContractFactory("Challenge")
const c = await Challenge.deploy(m.address, goldenRoot)
return [c,m,mm]
}
function getBlockRlp(block) {
let dat = [
block['parentHash'],
block['sha3Uncles'],
block['miner'],
block['stateRoot'],
block['transactionsRoot'],
block['receiptsRoot'],
block['logsBloom'],
block['difficulty'],
block['number'],
block['gasLimit'],
block['gasUsed'],
block['timestamp'],
block['extraData'],
block['mixHash'],
block['nonce'],
];
// post london
if (block['baseFeePerGas'] !== undefined) {
dat.push(block['baseFeePerGas'])
}
dat = dat.map(x => (x == "0x0") ? "0x" : x)
//console.log(dat)
let rdat = rlp.encode(dat)
if (ethers.utils.keccak256(rdat) != block['hash']) {
throw "block hash doesn't match"
}
return rdat
}
async function deployed() {
let addresses = JSON.parse(fs.readFileSync(basedir+"/deployed.json"))
const c = await ethers.getContractAt("Challenge", addresses["Challenge"])
const m = await ethers.getContractAt("MIPS", addresses["MIPS"])
const mm = await ethers.getContractAt("MIPSMemory", addresses["MIPSMemory"])
return [c,m,mm]
}
class MissingHashError extends Error {
constructor(hash, offset) {
super("hash is missing")
this.hash = hash
this.offset = offset
}
}
async function getTrieNodesForCall(c, caddress, cdat, preimages) {
let nodes = []
while (1) {
try {
// TODO: make this eth call?
// needs something like initiateChallengeWithTrieNodesj
let calldata = c.interface.encodeFunctionData("callWithTrieNodes", [caddress, cdat, nodes])
ret = await ethers.provider.call({
to:c.address,
data:calldata
});
break
} catch(e) {
let missing = e.toString().split("'")[1]
if (missing == undefined) {
// other kind of error from HTTPProvider
missing = e.error.message.toString().split("execution reverted: ")[1]
}
if (missing !== undefined && missing.length == 64) {
console.log("requested node", missing)
let node = preimages["0x"+missing]
if (node === undefined) {
throw("node not found")
}
const bin = Uint8Array.from(Buffer.from(node, 'base64').toString('binary'), c => c.charCodeAt(0))
nodes.push(bin)
continue
} else if (missing !== undefined && missing.length == 128) {
let hash = missing.slice(0, 64)
let offset = parseInt(missing.slice(64, 128), 16)
console.log("requested hash oracle", hash, offset)
throw new MissingHashError(hash, offset)
} else {
console.log(e)
break
}
}
}
return nodes
}
function getTrieAtStep(blockNumberN, step) {
const fn = basedir+"/0_"+blockNumberN.toString()+"/checkpoint_"+step.toString()+".json"
if (!fs.existsSync(fn)) {
console.log("running mipsevm")
child_process.execSync("mipsevm/mipsevm --blockNumber="+blockNumberN.toString()+" --target="+step.toString(), {stdio: 'inherit'})
}
return JSON.parse(fs.readFileSync(fn))
}
async function writeMemory(mm, root, addr, data, bytes32=false) {
if (bytes32) {
ret = await mm.WriteBytes32WithReceipt(root, addr, data)
} else {
ret = await mm.WriteMemoryWithReceipt(root, addr, data)
}
const receipt = await ret.wait()
for (l of receipt.logs) {
if (l.topics[0] == "0x86b89b5c9818dbbf520dd979a5f250d357508fe11b9511d4a43fd9bc6aa1be70") {
root = l.data
}
}
console.log("new hash", root)
return root
}
module.exports = { basedir, deploy, deployed, getTrieNodesForCall, getBlockRlp, getTrieAtStep, writeMemory, MissingHashError }
const fs = require("fs")
const { deployed, getTrieNodesForCall, getTrieAtStep } = require("../scripts/lib")
async function main() {
let [c, m, mm] = await deployed()
const challengeId = parseInt(process.env.ID)
const blockNumberN = parseInt(process.env.BLOCK)
const isChallenger = process.env.CHALLENGER == "1"
let step = (await c.getStepNumber(challengeId)).toNumber()
console.log("searching step", step, "at block", blockNumberN)
if (!(await c.isSearching(challengeId))) {
console.log("search is done")
return
}
// see if it's proposed or not
const proposed = await c.getProposedState(challengeId)
const isProposing = proposed == "0x0000000000000000000000000000000000000000000000000000000000000000"
if (isProposing != isChallenger) {
console.log("bad challenger state")
return
}
console.log("isProposing", isProposing)
let thisTrie = getTrieAtStep(blockNumberN, step)
const root = thisTrie['root']
console.log("new root", root)
let ret
if (isProposing) {
ret = await c.proposeState(challengeId, root)
} else {
ret = await c.respondState(challengeId, root)
}
let receipt = await ret.wait()
console.log("done", receipt.blockNumber)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
\ No newline at end of file
const { expect } = require("chai")
const fs = require("fs")
const { deploy, getTrieNodesForCall } = require("../scripts/lib")
// This test needs preimages to run correctly.
// It is skipped when running `make test_contracts`, but can be run with `make test_challenge`.
describe("Challenge contract", function () {
if (!fs.existsSync("/tmp/cannon/golden.json")) {
console.log("golden file doesn't exist, skipping test")
return
}
beforeEach(async function () {
[c, m, mm] = await deploy()
})
it("challenge contract deploys", async function() {
console.log("Challenge deployed at", c.address)
})
it("initiate challenge", async function() {
// TODO: is there a better way to get the "HardhatNetworkProvider"?
const hardhat = network.provider._wrapped._wrapped._wrapped._wrapped._wrapped
const blockchain = hardhat._node._blockchain
// get data
const blockNumberN = (await ethers.provider.getBlockNumber())-2
const blockNp1 = blockchain._data._blocksByNumber.get(blockNumberN+1)
const blockNp1Rlp = blockNp1.header.serialize()
const assertionRoot = "0x9e0261efe4509912b8862f3d45a0cb8404b99b239247df9c55871bd3844cebbd"
let startTrie = JSON.parse(fs.readFileSync("/tmp/cannon/golden.json"))
let finalTrie = JSON.parse(fs.readFileSync("/tmp/cannon/0_13284469/checkpoint_final.json"))
let preimages = Object.assign({}, startTrie['preimages'], finalTrie['preimages']);
const finalSystemState = finalTrie['root']
let args = [blockNumberN, blockNp1Rlp, assertionRoot, finalSystemState, finalTrie['step']]
let cdat = c.interface.encodeFunctionData("initiateChallenge", args)
let nodes = await getTrieNodesForCall(c, c.address, cdat, preimages)
// run "on chain"
for (n of nodes) {
await mm.AddTrieNode(n)
}
let ret = await c.initiateChallenge(...args)
let receipt = await ret.wait()
// ChallengeCreated event
let challengeId = receipt.events[0].args['challengeId'].toNumber()
console.log("new challenge with id", challengeId)
// the real issue here is from step 0->1 when we write the input hash
// TODO: prove the challenger wrong?
}).timeout(200_000)
})
const { keccak256 } = require("@ethersproject/keccak256");
const { expect } = require("chai");
describe("MIPSMemory contract", function () {
beforeEach(async function () {
const MIPSMemory = await ethers.getContractFactory("MIPSMemory");
mm = await MIPSMemory.deploy();
console.log("deployed at", mm.address);
})
it("Keccak should work", async function () {
await mm.AddLargePreimageInit(0);
console.log("preimage initted");
// empty
async function tl(n) {
const test = new Uint8Array(n)
for (var i = 0; i < n; i++) test[i] = 0x62;
console.log("test size", n)
expect((await mm.AddLargePreimageFinal(test))[0]).to.equal(keccak256(test));
}
await tl(1)
await tl(100)
await tl(134)
await tl(135)
// block size is 136
let dat = new Uint8Array(136)
dat[0] = 0x61
await mm.AddLargePreimageUpdate(dat);
const hash = (await mm.AddLargePreimageFinal([]))[0];
console.log("preimage updated");
const realhash = keccak256(dat);
console.log("comp hash is", hash);
console.log("real hash is", realhash);
expect(hash).to.equal(realhash);
});
it("oracle save should work", async function () {
await mm.AddLargePreimageInit(4)
let dat = new TextEncoder("utf-8").encode("hello world")
let dathash = keccak256(dat)
const tst = await mm.AddLargePreimageFinal(dat)
expect(tst[0]).to.equal(dathash)
expect(tst[1]).to.equal(11)
expect(tst[2]).to.equal(0x6f20776f)
await mm.AddLargePreimageFinalSaved(dat)
await mm.AddPreimage(dat, 0)
let retl = await mm.GetPreimageLength(dathash)
let ret = await mm.GetPreimage(dathash, 4)
expect(retl).to.equal(11)
expect(ret).to.equal(0x6f20776f)
// other type
retl = await mm.GetPreimageLength(dathash)
ret = await mm.GetPreimage(dathash, 0)
expect(retl).to.equal(11)
expect(ret).to.equal(0x68656c6c)
})
});
\ No newline at end of file
const { expect } = require("chai");
const { execPath } = require("process");
const trieAdd = {"root":"0x22ffce7c56d926c2d8d6337d8917fa0e1880e1869e189c15385ead63c6c45b93","preimages":{"0x044371dc86fb8c621bc84b69dce16de366de1126777250888b17416d0bd11279":"+FPGIIQ8EL//xiCENhD/8MYghDQRAAHGIIQ8CP//xiCENQj//cYghDQJAAPGIIQBCVAgxiCELUIAAcYghK4CAAjGIISuEQAExiCEA+AACICAgICAgA==","0x0fdfcc24b1b21d78ef2b7c6503eb9354677743685c2d00a14a8b502a177911b0":"+HGgL4Jb+u0gEWM4e9G4lO/GsyUEY/heVoGOAfiI04qPXfSgLCZprT7WBOLipiwJxxI0vy09rw9iPR+x0p/Xz1p3X5WgaNY/x30waJPsd6PWg76b094l8vUmmL6XB1XdUFy+xfWAgICAgICAgICAgICAgA==","0x11228d4f4a028a9088e6ec0aa6513e0d4731d9dc488e2af1957e46ba80624a69":"5oQAAAAAoARDcdyG+4xiG8hLadzhbeNm3hEmd3JQiIsXQW0L0RJ5","0x22ffce7c56d926c2d8d6337d8917fa0e1880e1869e189c15385ead63c6c45b93":"+FGgESKNT0oCipCI5uwKplE+DUcx2dxIjirxlX5GuoBiSmmAgKBv5gezlmGtxjQAs8Du76D93mAxExw5qWgAZjJQp1xmfICAgICAgICAgICAgIA=","0x2c2669ad3ed604e2e2a62c09c71234bf2d3daf0f623d1fb1d29fd7cf5a775f95":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIRerQAAgA==","0x2f825bfaed201163387bd1b894efc6b3250463f85e56818e01f888d38a8f5df4":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAgA==","0x68d63fc77d306893ec77a3d683be9bd3de25f2f52698be970755dd505cbec5f5":"6cYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAACAgICAgICAgICAgICA","0x6fe607b39661adc63400b3c0eeefa0fdde6031131c39a96800663250a75c667c":"5YMQAACgD9/MJLGyHXjvK3xlA+uTVGd3Q2hcLQChSotQKhd5EbA="}};
const trieOracle = {"root":"0x26595bd4f73f14273d9f43f0c5253eef964f4581d5e293cded54d23f1cf3db5f","step":-1,"preimages":{"0x0fdfcc24b1b21d78ef2b7c6503eb9354677743685c2d00a14a8b502a177911b0":"+HGgL4Jb+u0gEWM4e9G4lO/GsyUEY/heVoGOAfiI04qPXfSgLCZprT7WBOLipiwJxxI0vy09rw9iPR+x0p/Xz1p3X5WgaNY/x30waJPsd6PWg76b094l8vUmmL6XB1XdUFy+xfWAgICAgICAgICAgICAgA==","0x16e7f9821e0a3a2fc92d337f6d269c4c0ddb4d5c10a04b49d368643c9818ec1d":"5YMQAACgoLKCLk7kk3rBB2CtW7YQizz670HG0TNCobpfs/1MJqs=","0x26595bd4f73f14273d9f43f0c5253eef964f4581d5e293cded54d23f1cf3db5f":"+FGgFuf5gh4KOi/JLTN/bSacTA3bTVwQoEtJ02hkPJgY7B2AgKBv5gezlmGtxjQAs8Du76D93mAxExw5qWgAZjJQp1xmfICAgICAgICAgICAgIA=","0x2c2669ad3ed604e2e2a62c09c71234bf2d3daf0f623d1fb1d29fd7cf5a775f95":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIRerQAAgA==","0x2f825bfaed201163387bd1b894efc6b3250463f85e56818e01f888d38a8f5df4":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAgA==","0x65c024ed3b68b3f86a44f6af5179e4ad055ac046ac2e954355d476c611947b16":"+HHGIISuCAAQxiCEPAhCpcYghDUI7F/GIISuCAAUxiCEPAgDu8YghDUI+iXGIISuCAAYxiCEPAhMsMYghDUIH63GIISuCAAcxiCEJAIPtMYghAAAAAzGIIQ8ETEAxiCEjigAAMYghCQMAAvGIIQBDGgjgA==","0x68d63fc77d306893ec77a3d683be9bd3de25f2f52698be970755dd505cbec5f5":"6cYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAACAgICAgICAgICAgICA","0x6fe607b39661adc63400b3c0eeefa0fdde6031131c39a96800663250a75c667c":"5YMQAACgD9/MJLGyHXjvK3xlA+uTVGd3Q2hcLQChSotQKhd5EbA=","0x8cd6ed962850eebdb5bf360b496b5ab3425659a8ba3d115d5bb71055981a6bc2":"+F/GIIQtogABxiCEjigABMYghDwMaGXGIIQ1jGxsxiCEAQxoI8YghC2jAAHGIIQAQxAkxiCEPBC//8YghDYQ//DGIIQ0EQABxiCErgIACMYghK4RAATGIIQD4AAIgICAgA==","0xa0b2822e4ee4937ac10760ad5bb6108b3cfaef41c6d13342a1ba5fb3fd4c26ab":"+HGgt8fQkZe4faINYK98tz2Czvb1LZ/LaDPtw0E2aioQ4lagZcAk7Ttos/hqRPavUXnkrQVawEasLpVDVdR2xhGUexagjNbtlihQ7r21vzYLSWtas0JWWai6PRFdW7cQVZgaa8KAgICAgICAgICAgICAgA==","0xb7c7d09197b87da20d60af7cb73d82cef6f52d9fcb6833edc341366a2a10e256":"+HHGIIQ8EDAAxiCENhAQAMYghDwIRxfGIIQ1CDKFxiCErggAAMYghDwIqNfGIIQ1CDQexiCErggABMYghDwIXpfGIIQ1CC/GxiCErggACMYghDwIdyjGIIQ1CGOExiCErggADMYghDwI+ALGIIQ1CPjvgA=="}};
async function addPreimages(mm, trie) {
for (k in trie['preimages']) {
const bin = Uint8Array.from(Buffer.from(trie['preimages'][k], 'base64').toString('binary'), c => c.charCodeAt(0))
await mm.AddTrieNode(bin)
}
return trie['root']
}
describe("MIPS contract", function () {
beforeEach(async function () {
const MIPS = await ethers.getContractFactory("MIPS")
m = await MIPS.deploy()
mm = await ethers.getContractAt("MIPSMemory", await m.m())
})
it("add should work", async function () {
let root = await addPreimages(mm, trieAdd)
console.log("start", root)
for (let i = 0; i < 12; i++) {
ret = await m.Step(root)
const receipt = await ret.wait()
for (l of receipt.logs) {
if (l.topics[0] == "0x86b89b5c9818dbbf520dd979a5f250d357508fe11b9511d4a43fd9bc6aa1be70") {
root = l.data
}
}
console.log(i, root)
}
}).timeout(40_000);
it("oracle should work", async function () {
let root = await addPreimages(mm, trieOracle)
// "hello world" is the oracle
await mm.AddPreimage(Buffer.from("hello world"), 0)
console.log("start", root)
let pc = 0, out1, out2
while (pc != 0x5ead0000) {
ret = await m.Step(root)
const receipt = await ret.wait()
for (l of receipt.logs) {
if (l.topics[0] == "0x86b89b5c9818dbbf520dd979a5f250d357508fe11b9511d4a43fd9bc6aa1be70") {
root = l.data
}
}
pc = await mm.ReadMemory(root, 0xc0000080)
out1 = await mm.ReadMemory(root, 0xbffffff4)
out2 = await mm.ReadMemory(root, 0xbffffff8)
console.log(root, pc, out1, out2)
}
expect(out1).to.equal(1)
expect(out2).to.equal(1)
}).timeout(200_000);
});
const fs = require("fs")
const { expect } = require("chai")
const { deploy, getTrieNodesForCall, MissingHashError } = require("../scripts/lib")
const trieAdd = {"root":"0x22ffce7c56d926c2d8d6337d8917fa0e1880e1869e189c15385ead63c6c45b93","preimages":{"0x044371dc86fb8c621bc84b69dce16de366de1126777250888b17416d0bd11279":"+FPGIIQ8EL//xiCENhD/8MYghDQRAAHGIIQ8CP//xiCENQj//cYghDQJAAPGIIQBCVAgxiCELUIAAcYghK4CAAjGIISuEQAExiCEA+AACICAgICAgA==","0x0fdfcc24b1b21d78ef2b7c6503eb9354677743685c2d00a14a8b502a177911b0":"+HGgL4Jb+u0gEWM4e9G4lO/GsyUEY/heVoGOAfiI04qPXfSgLCZprT7WBOLipiwJxxI0vy09rw9iPR+x0p/Xz1p3X5WgaNY/x30waJPsd6PWg76b094l8vUmmL6XB1XdUFy+xfWAgICAgICAgICAgICAgA==","0x11228d4f4a028a9088e6ec0aa6513e0d4731d9dc488e2af1957e46ba80624a69":"5oQAAAAAoARDcdyG+4xiG8hLadzhbeNm3hEmd3JQiIsXQW0L0RJ5","0x22ffce7c56d926c2d8d6337d8917fa0e1880e1869e189c15385ead63c6c45b93":"+FGgESKNT0oCipCI5uwKplE+DUcx2dxIjirxlX5GuoBiSmmAgKBv5gezlmGtxjQAs8Du76D93mAxExw5qWgAZjJQp1xmfICAgICAgICAgICAgIA=","0x2c2669ad3ed604e2e2a62c09c71234bf2d3daf0f623d1fb1d29fd7cf5a775f95":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIRerQAAgA==","0x2f825bfaed201163387bd1b894efc6b3250463f85e56818e01f888d38a8f5df4":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAgA==","0x68d63fc77d306893ec77a3d683be9bd3de25f2f52698be970755dd505cbec5f5":"6cYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAACAgICAgICAgICAgICA","0x6fe607b39661adc63400b3c0eeefa0fdde6031131c39a96800663250a75c667c":"5YMQAACgD9/MJLGyHXjvK3xlA+uTVGd3Q2hcLQChSotQKhd5EbA="}};
const trieOracle = {"root":"0x26595bd4f73f14273d9f43f0c5253eef964f4581d5e293cded54d23f1cf3db5f","step":-1,"preimages":{"0x0fdfcc24b1b21d78ef2b7c6503eb9354677743685c2d00a14a8b502a177911b0":"+HGgL4Jb+u0gEWM4e9G4lO/GsyUEY/heVoGOAfiI04qPXfSgLCZprT7WBOLipiwJxxI0vy09rw9iPR+x0p/Xz1p3X5WgaNY/x30waJPsd6PWg76b094l8vUmmL6XB1XdUFy+xfWAgICAgICAgICAgICAgA==","0x16e7f9821e0a3a2fc92d337f6d269c4c0ddb4d5c10a04b49d368643c9818ec1d":"5YMQAACgoLKCLk7kk3rBB2CtW7YQizz670HG0TNCobpfs/1MJqs=","0x26595bd4f73f14273d9f43f0c5253eef964f4581d5e293cded54d23f1cf3db5f":"+FGgFuf5gh4KOi/JLTN/bSacTA3bTVwQoEtJ02hkPJgY7B2AgKBv5gezlmGtxjQAs8Du76D93mAxExw5qWgAZjJQp1xmfICAgICAgICAgICAgIA=","0x2c2669ad3ed604e2e2a62c09c71234bf2d3daf0f623d1fb1d29fd7cf5a775f95":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIRerQAAgA==","0x2f825bfaed201163387bd1b894efc6b3250463f85e56818e01f888d38a8f5df4":"+HHGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAADGIIQAAAAAgA==","0x65c024ed3b68b3f86a44f6af5179e4ad055ac046ac2e954355d476c611947b16":"+HHGIISuCAAQxiCEPAhCpcYghDUI7F/GIISuCAAUxiCEPAgDu8YghDUI+iXGIISuCAAYxiCEPAhMsMYghDUIH63GIISuCAAcxiCEJAIPtMYghAAAAAzGIIQ8ETEAxiCEjigAAMYghCQMAAvGIIQBDGgjgA==","0x68d63fc77d306893ec77a3d683be9bd3de25f2f52698be970755dd505cbec5f5":"6cYghAAAAADGIIQAAAAAxiCEAAAAAMYghAAAAACAgICAgICAgICAgICA","0x6fe607b39661adc63400b3c0eeefa0fdde6031131c39a96800663250a75c667c":"5YMQAACgD9/MJLGyHXjvK3xlA+uTVGd3Q2hcLQChSotQKhd5EbA=","0x8cd6ed962850eebdb5bf360b496b5ab3425659a8ba3d115d5bb71055981a6bc2":"+F/GIIQtogABxiCEjigABMYghDwMaGXGIIQ1jGxsxiCEAQxoI8YghC2jAAHGIIQAQxAkxiCEPBC//8YghDYQ//DGIIQ0EQABxiCErgIACMYghK4RAATGIIQD4AAIgICAgA==","0xa0b2822e4ee4937ac10760ad5bb6108b3cfaef41c6d13342a1ba5fb3fd4c26ab":"+HGgt8fQkZe4faINYK98tz2Czvb1LZ/LaDPtw0E2aioQ4lagZcAk7Ttos/hqRPavUXnkrQVawEasLpVDVdR2xhGUexagjNbtlihQ7r21vzYLSWtas0JWWai6PRFdW7cQVZgaa8KAgICAgICAgICAgICAgA==","0xb7c7d09197b87da20d60af7cb73d82cef6f52d9fcb6833edc341366a2a10e256":"+HHGIIQ8EDAAxiCENhAQAMYghDwIRxfGIIQ1CDKFxiCErggAAMYghDwIqNfGIIQ1CDQexiCErggABMYghDwIXpfGIIQ1CC/GxiCErggACMYghDwIdyjGIIQ1CGOExiCErggADMYghDwI+ALGIIQ1CPjvgA=="}};
async function dynamicExecWithTrie(c, m, mm, root, preimages, rootdir) {
let mdat = m.interface.encodeFunctionData("Step", [root])
let nodes
while (true) {
try {
nodes = await getTrieNodesForCall(c, m.address, mdat, preimages)
} catch(err) {
if (err instanceof MissingHashError) {
let value = fs.readFileSync(rootdir+"0x"+err.hash)
console.log("handling hash oracle request")
await mm.AddPreimage(value, err.offset)
continue
} else {
throw err
}
}
break
}
for (n of nodes) {
await mm.AddTrieNode(n)
}
let ret = await m.Step(root)
const receipt = await ret.wait()
for (l of receipt.logs) {
if (l.topics[0] == "0x86b89b5c9818dbbf520dd979a5f250d357508fe11b9511d4a43fd9bc6aa1be70") {
root = l.data
}
}
return root
}
// really a copy of mips_test_execwtrie
describe("Exec with trie dynamic", function () {
beforeEach(async function () {
const MIPS = await ethers.getContractFactory("MIPS")
m = await MIPS.deploy()
mm = await ethers.getContractAt("MIPSMemory", await m.m())
// fake challenge for use of getTrieNodesForCall
const Challenge = await ethers.getContractFactory("Challenge")
const fakeGoldenHash = '0x0000000000000000000000000000000000000000000000000000000000000000'
c = await Challenge.deploy(m.address, fakeGoldenHash)
})
it("add should work", async function () {
let root = trieAdd['root']
for (let i = 0; i < 12; i++) {
root = await dynamicExecWithTrie(c, m, mm, root, trieAdd['preimages'], null)
console.log(i, root)
}
}).timeout(120000)
it("oracle should work", async function () {
let root = trieOracle['root']
let pc = 0, out1, out2
while (pc != 0x5ead0000) {
root = await dynamicExecWithTrie(c, m, mm, root, trieOracle['preimages'], "mipsevm/testoracle/")
pc = await mm.ReadMemory(root, 0xc0000080)
out1 = await mm.ReadMemory(root, 0xbffffff4)
out2 = await mm.ReadMemory(root, 0xbffffff8)
console.log(root, pc, out1, out2)
}
expect(out1).to.equal(1)
expect(out2).to.equal(1)
}).timeout(300000)
})
const { expect } = require("chai");
const { writeMemory } = require("../scripts/lib")
function randint(n) {
return Math.floor(Math.random() * n)
}
describe("MIPSMemory contract", function () {
beforeEach(async function () {
const MIPSMemory = await ethers.getContractFactory("MIPSMemory")
mm = await MIPSMemory.deploy()
await mm.AddTrieNode(new Uint8Array([0x80]))
})
it("write from new should work", async function() {
let root = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
root = await writeMemory(mm, root, 0, 1)
root = await writeMemory(mm, root, 4, 2)
expect(await mm.ReadMemory(root, 0)).to.equal(1)
expect(await mm.ReadMemory(root, 4)).to.equal(2)
})
it("write three should work", async function() {
await mm.AddTrieNode(new Uint8Array([0x80]))
let root = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
root = await writeMemory(mm, root, 0, 1)
root = await writeMemory(mm, root, 4, 2)
root = await writeMemory(mm, root, 0x40, 3)
expect(await mm.ReadMemory(root, 0)).to.equal(1)
expect(await mm.ReadMemory(root, 4)).to.equal(2)
expect(await mm.ReadMemory(root, 0x40)).to.equal(3)
})
it("write other three should work", async function() {
let root = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
root = await writeMemory(mm, root, 0x7fffd00c, 1)
root = await writeMemory(mm, root, 0x7fffd010, 2)
root = await writeMemory(mm, root, 0x7fffcffc, 3)
expect(await mm.ReadMemory(root, 0x7fffd00c)).to.equal(1)
expect(await mm.ReadMemory(root, 0x7fffd010)).to.equal(2)
expect(await mm.ReadMemory(root, 0x7fffcffc)).to.equal(3)
})
it("bug found fuzzing 1", async function() {
let root = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
root = await writeMemory(mm, root, 0, 0)
root = await writeMemory(mm, root, 0, 1)
root = await writeMemory(mm, root, 0, 2)
})
it("fuzzing should be okay", async function() {
let root = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
let kv = {}
for (var i = 0; i < 100; i++) {
const keys = Object.keys(kv)
const choice = Math.random()
if (choice < 0.3 || keys.length == 0) {
// write new key
const key = randint(0x100)*4
const value = randint(0x100000000)
console.log("writing", key, value)
root = await writeMemory(mm, root, key, value)
kv[key] = value
} else if (choice < 0.5) {
// write new high key
const key = randint(0x100)*4 + 0x10000000
const value = randint(0x100000000)
console.log("writing", key, value)
root = await writeMemory(mm, root, key, value)
kv[key] = value
} else if (choice > 0.7) {
// read old key
const idx = randint(keys.length)
const key = keys[idx]
console.log("reading", key)
expect(await mm.ReadMemory(root, key)).to.equal(kv[key])
} else {
// rewrite old key
const idx = randint(keys.length)
const key = keys[idx]
const value = randint(0x100000000)
console.log("writing", key, value)
root = await writeMemory(mm, root, key, value)
kv[key] = value
}
}
}).timeout(60000)
})
const { keccak256 } = require("@ethersproject/keccak256");
const { expect } = require("chai");
const chai = require("chai");
const { solidity } = require("ethereum-waffle");
chai.use(solidity);
const { writeMemory } = require("../scripts/lib")
async function loadPreimageAndSelect(mm, data, offset) {
// add in the preimage at offset 4
const hash = keccak256(data)
await mm.AddPreimage(data, offset)
// write the oracle selection address
let root = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
root = await writeMemory(mm, root, 0x30001000, hash, true)
return root
}
describe("MIPSMemory oracle", function () {
beforeEach(async function () {
const MIPSMemory = await ethers.getContractFactory("MIPSMemory")
mm = await MIPSMemory.deploy()
await mm.AddTrieNode(new Uint8Array([0x80]))
})
it("simple oracle", async function() {
root = await loadPreimageAndSelect(mm, [0x11,0x22,0x33,0x44,0xaa,0xbb,0xcc,0xdd], 4)
// length is 8
expect(await mm.ReadMemory(root, 0x31000000)).to.equal(8)
// offset 4 is 0xaabbccdd
expect(await mm.ReadMemory(root, 0x31000008)).to.equal(0xaabbccdd)
// offset 0 isn't loaded
await expect(mm.ReadMemory(root, 0x31000004)).to.be.reverted;
})
it("misaligned oracle", async function() {
root = await loadPreimageAndSelect(mm, [0x11,0x22,0x33,0x44,0xaa,0xbb,0xcc], 4)
expect(await mm.ReadMemory(root, 0x31000000)).to.equal(7)
expect(await mm.ReadMemory(root, 0x31000008)).to.equal(0xaabbcc00)
})
})
\ No newline at end of file
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