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