Commit c0ab34f3 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

cannon: remove final dep on bindings (#10408)

* cannon: remove final dep on bindings

Removes the last dependency that cannon has on `op-bindings/bindings`.
This is done my creating reusable code for reading foundry artifacts
from disk. This code should be reusable between any service that wants
to read the foundry artifacts from disk. This includes roundtrip
tests for JSON serialization of the foundry artifacts.

* op-chain-ops: address semgrep

https://github.com/golang/go/issues/22967
parent 57211c6d
......@@ -2,10 +2,10 @@ package mipsevm
import (
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
"os"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -19,51 +19,27 @@ import (
"github.com/ethereum/go-ethereum/params"
)
// LoadContracts loads the Cannon contracts, from the contracts package.
func LoadContracts() (*Contracts, error) {
mips, err := loadContract("../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
// LoadArtifacts loads the Cannon contracts, from the contracts package.
func LoadArtifacts() (*Artifacts, error) {
mips, err := foundry.ReadArtifact("../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
if err != nil {
return nil, fmt.Errorf("failed to load MIPS contract: %w", err)
}
oracle, err := loadContract("../../packages/contracts-bedrock/forge-artifacts/PreimageOracle.sol/PreimageOracle.json")
oracle, err := foundry.ReadArtifact("../../packages/contracts-bedrock/forge-artifacts/PreimageOracle.sol/PreimageOracle.json")
if err != nil {
return nil, fmt.Errorf("failed to load Oracle contract: %w", err)
}
return &Contracts{
return &Artifacts{
MIPS: mips,
Oracle: oracle,
}, nil
}
func loadContract(path string) (*Contract, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("artifact at %s not found: %w", path, err)
}
contract := Contract{}
if err := json.Unmarshal(file, &contract); err != nil {
return nil, err
}
return &contract, nil
}
type Contract struct {
DeployedBytecode struct {
Object hexutil.Bytes `json:"object"`
SourceMap string `json:"sourceMap"`
} `json:"deployedBytecode"`
Bytecode struct {
Object hexutil.Bytes `json:"object"`
} `json:"bytecode"`
// ignore abi,etc.
}
type Contracts struct {
MIPS *Contract
Oracle *Contract
type Artifacts struct {
MIPS *foundry.Artifact
Oracle *foundry.Artifact
}
type Addresses struct {
......@@ -73,7 +49,7 @@ type Addresses struct {
FeeRecipient common.Address
}
func NewEVMEnv(contracts *Contracts, addrs *Addresses) (*vm.EVM, *state.StateDB) {
func NewEVMEnv(artifacts *Artifacts, addrs *Addresses) (*vm.EVM, *state.StateDB) {
// Temporary hack until Cancun is activated on mainnet
cpy := *params.MainnetChainConfig
chainCfg := &cpy // don't modify the global chain config
......@@ -94,11 +70,11 @@ func NewEVMEnv(contracts *Contracts, addrs *Addresses) (*vm.EVM, *state.StateDB)
env := vm.NewEVM(blockContext, vm.TxContext{}, state, chainCfg, vmCfg)
// pre-deploy the contracts
env.StateDB.SetCode(addrs.Oracle, contracts.Oracle.DeployedBytecode.Object)
env.StateDB.SetCode(addrs.Oracle, artifacts.Oracle.DeployedBytecode.Object)
var mipsCtorArgs [32]byte
copy(mipsCtorArgs[12:], addrs.Oracle[:])
mipsDeploy := append(hexutil.MustDecode(contracts.MIPS.Bytecode.Object.String()), mipsCtorArgs[:]...)
mipsDeploy := append(hexutil.MustDecode(artifacts.MIPS.Bytecode.Object.String()), mipsCtorArgs[:]...)
startingGas := uint64(30_000_000)
_, deployedMipsAddr, leftOverGas, err := env.Create(vm.AccountRef(addrs.Sender), mipsDeploy, startingGas, common.U2560)
if err != nil {
......
......@@ -12,7 +12,7 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -22,8 +22,8 @@ import (
"github.com/stretchr/testify/require"
)
func testContractsSetup(t require.TestingT) (*Contracts, *Addresses) {
contracts, err := LoadContracts()
func testContractsSetup(t require.TestingT) (*Artifacts, *Addresses) {
artifacts, err := LoadArtifacts()
require.NoError(t, err)
addrs := &Addresses{
......@@ -33,7 +33,7 @@ func testContractsSetup(t require.TestingT) (*Contracts, *Addresses) {
FeeRecipient: common.Address{0xaa},
}
return contracts, addrs
return artifacts, addrs
}
func MarkdownTracer() vm.EVMLogger {
......@@ -45,11 +45,12 @@ type MIPSEVM struct {
evmState *state.StateDB
addrs *Addresses
localOracle PreimageOracle
artifacts *Artifacts
}
func NewMIPSEVM(contracts *Contracts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(contracts, addrs)
return &MIPSEVM{env, evmState, addrs, nil}
func NewMIPSEVM(artifacts *Artifacts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(artifacts, addrs)
return &MIPSEVM{env, evmState, addrs, nil, artifacts}
}
func (m *MIPSEVM) SetTracer(tracer vm.EVMLogger) {
......@@ -70,13 +71,13 @@ func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
if stepWitness.HasPreimage() {
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
poInput, err := encodePreimageOracleInput(t, stepWitness, LocalContext{}, m.localOracle)
poInput, err := encodePreimageOracleInput(t, stepWitness, LocalContext{}, m.localOracle, m.artifacts.Oracle)
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.Oracle, poInput, startingGas, common.U2560)
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
input := encodeStepInput(t, stepWitness, LocalContext{})
input := encodeStepInput(t, stepWitness, LocalContext{}, m.artifacts.MIPS)
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, common.U2560)
require.NoError(t, err, "evm should not fail")
require.Len(t, ret, 32, "expecting 32-byte state hash")
......@@ -95,23 +96,17 @@ func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
return evmPost
}
func encodeStepInput(t *testing.T, wit *StepWitness, localContext LocalContext) []byte {
mipsAbi, err := bindings.MIPSMetaData.GetAbi()
require.NoError(t, err)
input, err := mipsAbi.Pack("step", wit.State, wit.MemProof, localContext)
func encodeStepInput(t *testing.T, wit *StepWitness, localContext LocalContext, mips *foundry.Artifact) []byte {
input, err := mips.ABI.Pack("step", wit.State, wit.MemProof, localContext)
require.NoError(t, err)
return input
}
func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext LocalContext, localOracle PreimageOracle) ([]byte, error) {
func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext LocalContext, localOracle PreimageOracle, oracle *foundry.Artifact) ([]byte, error) {
if wit.PreimageKey == ([32]byte{}) {
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
}
preimageAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err, "failed to load pre-image oracle ABI")
switch preimage.KeyType(wit.PreimageKey[0]) {
case preimage.LocalKeyType:
if len(wit.PreimageValue) > 32+8 {
......@@ -120,7 +115,7 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca
preimagePart := wit.PreimageValue[8:]
var tmp [32]byte
copy(tmp[:], preimagePart)
input, err := preimageAbi.Pack("loadLocalData",
input, err := oracle.ABI.Pack("loadLocalData",
new(big.Int).SetBytes(wit.PreimageKey[1:]),
localContext,
tmp,
......@@ -130,7 +125,7 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca
require.NoError(t, err)
return input, nil
case preimage.Keccak256KeyType:
input, err := preimageAbi.Pack(
input, err := oracle.ABI.Pack(
"loadKeccak256PreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
wit.PreimageValue[8:])
......@@ -143,7 +138,7 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca
preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey())
precompile := common.BytesToAddress(preimage[:20])
callInput := preimage[20:]
input, err := preimageAbi.Pack(
input, err := oracle.ABI.Pack(
"loadPrecompilePreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
precompile,
......@@ -290,7 +285,7 @@ func TestEVMFault(t *testing.T) {
State: initialState.EncodeWitness(),
MemProof: insnProof[:],
}
input := encodeStepInput(t, stepWitness, LocalContext{})
input := encodeStepInput(t, stepWitness, LocalContext{}, contracts.MIPS)
startingGas := uint64(30_000_000)
_, _, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, common.U2560)
......
......@@ -2,31 +2,87 @@ package foundry
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/solc"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// Artifact represents a foundry compilation artifact.
// The Abi is specifically left as a json.RawMessage because
// round trip marshaling/unmarshalling of the abi.ABI type
// causes issues.
// JSON marshaling logic is implemented to maintain the ability
// to roundtrip serialize an artifact
type Artifact struct {
Abi json.RawMessage `json:"abi"`
ABI abi.ABI
abi json.RawMessage
StorageLayout solc.StorageLayout
DeployedBytecode DeployedBytecode
Bytecode Bytecode
}
func (a *Artifact) UnmarshalJSON(data []byte) error {
artifact := artifactMarshaling{}
if err := json.Unmarshal(data, &artifact); err != nil {
return err
}
parsed, err := abi.JSON(strings.NewReader(string(artifact.ABI)))
if err != nil {
return err
}
a.ABI = parsed
a.abi = artifact.ABI
a.StorageLayout = artifact.StorageLayout
a.DeployedBytecode = artifact.DeployedBytecode
a.Bytecode = artifact.Bytecode
return nil
}
func (a Artifact) MarshalJSON() ([]byte, error) {
artifact := artifactMarshaling{
ABI: a.abi,
StorageLayout: a.StorageLayout,
DeployedBytecode: a.DeployedBytecode,
Bytecode: a.Bytecode,
}
return json.Marshal(artifact)
}
// artifactMarshaling is a helper struct for marshaling and unmarshaling
// foundry artifacts.
type artifactMarshaling struct {
ABI json.RawMessage `json:"abi"`
StorageLayout solc.StorageLayout `json:"storageLayout"`
DeployedBytecode DeployedBytecode `json:"deployedBytecode"`
Bytecode Bytecode `json:"bytecode"`
}
// DeployedBytecode represents the deployed bytecode section of the solc compiler output.
type DeployedBytecode struct {
SourceMap string `json:"sourceMap"`
Object hexutil.Bytes `json:"object"`
LinkReferences json.RawMessage `json:"linkReferences"`
ImmutableReferences json.RawMessage `json:"immutableReferences"`
ImmutableReferences json.RawMessage `json:"immutableReferences,omitempty"`
}
// DeployedBytecode represents the bytecode section of the solc compiler output.
type Bytecode struct {
SourceMap string `json:"sourceMap"`
Object hexutil.Bytes `json:"object"`
LinkReferences json.RawMessage `json:"linkReferences"`
SourceMap string `json:"sourceMap"`
Object hexutil.Bytes `json:"object"`
LinkReferences json.RawMessage `json:"linkReferences"`
ImmutableReferences json.RawMessage `json:"immutableReferences,omitempty"`
}
// ReadArtifact will read an artifact from disk given a path.
func ReadArtifact(path string) (*Artifact, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("artifact at %s not found: %w", path, err)
}
artifact := Artifact{}
if err := json.Unmarshal(file, &artifact); err != nil {
return nil, err
}
return &artifact, nil
}
package foundry
import (
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/require"
)
// TestArtifactJSON tests roundtrip serialization of a foundry artifact for commonly used fields.
func TestArtifactJSON(t *testing.T) {
artifact, err := ReadArtifact("testdata/OptimismPortal.json")
require.NoError(t, err)
data, err := json.Marshal(artifact)
require.NoError(t, err)
file, err := os.ReadFile("testdata/OptimismPortal.json")
require.NoError(t, err)
got := unmarshalIntoMap(t, data)
expected := unmarshalIntoMap(t, file)
require.JSONEq(t, marshal(t, got["bytecode"]), marshal(t, expected["bytecode"]))
require.JSONEq(t, marshal(t, got["deployedBytecode"]), marshal(t, expected["deployedBytecode"]))
require.JSONEq(t, marshal(t, got["abi"]), marshal(t, expected["abi"]))
require.JSONEq(t, marshal(t, got["storageLayout"]), marshal(t, expected["storageLayout"]))
}
func unmarshalIntoMap(t *testing.T, file []byte) map[string]any {
var result map[string]any
err := json.Unmarshal(file, &result)
require.NoError(t, err)
return result
}
func marshal(t *testing.T, a any) string {
result, err := json.Marshal(a)
require.NoError(t, err)
return string(result)
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -83,12 +83,13 @@ type StorageLayoutEntry struct {
}
type StorageLayoutType struct {
Encoding string `json:"encoding"`
Label string `json:"label"`
NumberOfBytes uint `json:"numberOfBytes,string"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
Base string `json:"base,omitempty"`
Encoding string `json:"encoding"`
Label string `json:"label"`
NumberOfBytes uint `json:"numberOfBytes,string"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
Base string `json:"base,omitempty"`
Members []StorageLayoutEntry `json:"members,omitempty"`
}
type CompilerOutputEvm struct {
......
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