Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

Commit

Permalink
unicorn: separate unicorn into test-only module
Browse files Browse the repository at this point in the history
  • Loading branch information
protolambda committed May 3, 2023
1 parent bc29480 commit cf43ae7
Show file tree
Hide file tree
Showing 29 changed files with 896 additions and 319 deletions.
17 changes: 13 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ jobs:
uses: actions/cache@v3
with:
path: |
./unicorn/build
./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: .
working-directory: ./diffmips
run: make libunicorn
- uses: actions/cache@v3
with:
Expand All @@ -45,11 +45,17 @@ jobs:
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-
- name: golangci-lint
- name: main golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52
working-directory: mipsevm
working-directory: ./
skip-cache: true # we already have go caching
- name: diffmips golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52
working-directory: ./diffmips
skip-cache: true # we already have go caching
- name: Build examples
working-directory: ./example
Expand All @@ -60,3 +66,6 @@ jobs:
- name: mipsevm tests
working-directory: ./mipsevm
run: go test ./...
- name: diffmips tests
working-directory: ./diffmips
run: go test ./...
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "unicorn"]
path = unicorn
path = diffmips/unicorn
url = https://github.com/unicorn-engine/unicorn.git
54 changes: 2 additions & 52 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,62 +1,12 @@
SHELL := /bin/bash

build: submodules libunicorn contracts
build: contracts
.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

# Must be a definition and not a rule, otherwise it gets only called once and
# not before each test as we wish.
define clear_cache
rm -rf /tmp/cannon
mkdir -p /tmp/cannon
endef

clear_cache:
$(call clear_cache)
.PHONY: clear_cache

clean:
rm -f unicorn/libunicorn.*
.PHONY: clean

contracts:
cd contracts && forge build
.PHONY: contracts

test: libunicorn
test:
cd mipsevm && go test -v ./...
.PHONY: test
20 changes: 1 addition & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,18 @@ contracts -- A MIPS emulator implementation, using merkleized state and a pre-im
example -- Example programs that can be run and proven with Cannon.
extra -- Extra scripts and legacy contracts, deprecated.
mipsevm -- Go tooling to test the onchain MIPS implementation, and generate proof data.
unicorn -- Sub-module, used by mipsevm for offchain MIPS emulation.
diffmips -- MIPS diff testing, to ensure correctness of the main Cannon implementation, with isolated dependencies.
```

## Building

### `unicorn`

To build unicorn from source (git sub-module), run:
```
make libunicorn
```

### `contracts`

The contracts are compiled with [`forge`](https://github.com/foundry-rs/foundry).
```
make contracts
```

### `mipsevm`

This requires `unicorn` to be built, as well as the `contracts` for testing.

To test:
```
make test
```

Also see notes in `mipsevm/go.mod` about the Unicorn dependency, if you wish to use Cannon as a Go library.

## License

MIT, see [`LICENSE`](./LICENSE) file.
Expand Down
2 changes: 1 addition & 1 deletion cmd/load_elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

"github.com/urfave/cli/v2"

"cannon/mipsevm"
"github.com/ethereum-optimism/cannon/mipsevm"
)

var (
Expand Down
2 changes: 1 addition & 1 deletion cmd/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strconv"
"strings"

"cannon/mipsevm"
"github.com/ethereum-optimism/cannon/mipsevm"
)

type StepMatcher func(st *mipsevm.State) bool
Expand Down
18 changes: 4 additions & 14 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"

"cannon/mipsevm"
"github.com/ethereum-optimism/cannon/mipsevm"
"github.com/ethereum-optimism/cannon/preimage"
)

Expand Down Expand Up @@ -182,13 +182,7 @@ func Run(ctx *cli.Context) error {
if err != nil {
return err
}
//mu, err := mipsevm.NewUnicorn()
//if err != nil {
// return fmt.Errorf("failed to create unicorn emulator: %w", err)
//}
//if err := mipsevm.LoadUnicorn(state, mu); err != nil {
// return fmt.Errorf("failed to load state into unicorn emulator: %w", err)
//}

l := Logger(os.Stderr, log.LvlInfo)
outLog := &mipsevm.LoggingWriter{Name: "program std-out", Log: l}
errLog := &mipsevm.LoggingWriter{Name: "program std-err", Log: l}
Expand Down Expand Up @@ -232,15 +226,11 @@ func Run(ctx *cli.Context) error {
}
}

//us, err := mipsevm.NewUnicornState(mu, state, po, outLog, errLog)
//if err != nil {
// return fmt.Errorf("failed to setup instrumented VM state: %w", err)
//}
us := mipsevm.NewNonUnicornState(state, po, outLog, errLog)
us := mipsevm.NewInstrumentedState(state, po, outLog, errLog)
proofFmt := ctx.String(RunProofFmtFlag.Name)
snapshotFmt := ctx.String(RunSnapshotFmtFlag.Name)

stepFn := us.NonUnicornStep
stepFn := us.Step
if po.cmd != nil {
stepFn = Guard(po.cmd.ProcessState, stepFn)
}
Expand Down
47 changes: 47 additions & 0 deletions diffmips/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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
28 changes: 28 additions & 0 deletions diffmips/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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
```

68 changes: 68 additions & 0 deletions diffmips/unicorntest/diff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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")
})
}
}
Loading

0 comments on commit cf43ae7

Please sign in to comment.