Skip to content

Commit c0ab34f

Browse files
authored
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 golang/go#22967
1 parent 57211c6 commit c0ab34f

File tree

6 files changed

+10734
-73
lines changed

6 files changed

+10734
-73
lines changed

cannon/mipsevm/evm.go

+13-37
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package mipsevm
22

33
import (
44
"encoding/binary"
5-
"encoding/json"
65
"fmt"
76
"math/big"
8-
"os"
7+
8+
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
99

1010
"github.com/ethereum/go-ethereum/common"
1111
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -19,51 +19,27 @@ import (
1919
"github.com/ethereum/go-ethereum/params"
2020
)
2121

22-
// LoadContracts loads the Cannon contracts, from the contracts package.
23-
func LoadContracts() (*Contracts, error) {
24-
mips, err := loadContract("../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
22+
// LoadArtifacts loads the Cannon contracts, from the contracts package.
23+
func LoadArtifacts() (*Artifacts, error) {
24+
mips, err := foundry.ReadArtifact("../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
2525
if err != nil {
2626
return nil, fmt.Errorf("failed to load MIPS contract: %w", err)
2727
}
2828

29-
oracle, err := loadContract("../../packages/contracts-bedrock/forge-artifacts/PreimageOracle.sol/PreimageOracle.json")
29+
oracle, err := foundry.ReadArtifact("../../packages/contracts-bedrock/forge-artifacts/PreimageOracle.sol/PreimageOracle.json")
3030
if err != nil {
3131
return nil, fmt.Errorf("failed to load Oracle contract: %w", err)
3232
}
3333

34-
return &Contracts{
34+
return &Artifacts{
3535
MIPS: mips,
3636
Oracle: oracle,
3737
}, nil
3838
}
3939

40-
func loadContract(path string) (*Contract, error) {
41-
file, err := os.ReadFile(path)
42-
if err != nil {
43-
return nil, fmt.Errorf("artifact at %s not found: %w", path, err)
44-
}
45-
46-
contract := Contract{}
47-
if err := json.Unmarshal(file, &contract); err != nil {
48-
return nil, err
49-
}
50-
return &contract, nil
51-
}
52-
53-
type Contract struct {
54-
DeployedBytecode struct {
55-
Object hexutil.Bytes `json:"object"`
56-
SourceMap string `json:"sourceMap"`
57-
} `json:"deployedBytecode"`
58-
Bytecode struct {
59-
Object hexutil.Bytes `json:"object"`
60-
} `json:"bytecode"`
61-
// ignore abi,etc.
62-
}
63-
64-
type Contracts struct {
65-
MIPS *Contract
66-
Oracle *Contract
40+
type Artifacts struct {
41+
MIPS *foundry.Artifact
42+
Oracle *foundry.Artifact
6743
}
6844

6945
type Addresses struct {
@@ -73,7 +49,7 @@ type Addresses struct {
7349
FeeRecipient common.Address
7450
}
7551

76-
func NewEVMEnv(contracts *Contracts, addrs *Addresses) (*vm.EVM, *state.StateDB) {
52+
func NewEVMEnv(artifacts *Artifacts, addrs *Addresses) (*vm.EVM, *state.StateDB) {
7753
// Temporary hack until Cancun is activated on mainnet
7854
cpy := *params.MainnetChainConfig
7955
chainCfg := &cpy // don't modify the global chain config
@@ -94,11 +70,11 @@ func NewEVMEnv(contracts *Contracts, addrs *Addresses) (*vm.EVM, *state.StateDB)
9470

9571
env := vm.NewEVM(blockContext, vm.TxContext{}, state, chainCfg, vmCfg)
9672
// pre-deploy the contracts
97-
env.StateDB.SetCode(addrs.Oracle, contracts.Oracle.DeployedBytecode.Object)
73+
env.StateDB.SetCode(addrs.Oracle, artifacts.Oracle.DeployedBytecode.Object)
9874

9975
var mipsCtorArgs [32]byte
10076
copy(mipsCtorArgs[12:], addrs.Oracle[:])
101-
mipsDeploy := append(hexutil.MustDecode(contracts.MIPS.Bytecode.Object.String()), mipsCtorArgs[:]...)
77+
mipsDeploy := append(hexutil.MustDecode(artifacts.MIPS.Bytecode.Object.String()), mipsCtorArgs[:]...)
10278
startingGas := uint64(30_000_000)
10379
_, deployedMipsAddr, leftOverGas, err := env.Create(vm.AccountRef(addrs.Sender), mipsDeploy, startingGas, common.U2560)
10480
if err != nil {

cannon/mipsevm/evm_test.go

+17-22
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"testing"
1313
"time"
1414

15-
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
15+
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
1616
preimage "github.com/ethereum-optimism/optimism/op-preimage"
1717
"github.com/ethereum/go-ethereum/common"
1818
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -22,8 +22,8 @@ import (
2222
"github.com/stretchr/testify/require"
2323
)
2424

25-
func testContractsSetup(t require.TestingT) (*Contracts, *Addresses) {
26-
contracts, err := LoadContracts()
25+
func testContractsSetup(t require.TestingT) (*Artifacts, *Addresses) {
26+
artifacts, err := LoadArtifacts()
2727
require.NoError(t, err)
2828

2929
addrs := &Addresses{
@@ -33,7 +33,7 @@ func testContractsSetup(t require.TestingT) (*Contracts, *Addresses) {
3333
FeeRecipient: common.Address{0xaa},
3434
}
3535

36-
return contracts, addrs
36+
return artifacts, addrs
3737
}
3838

3939
func MarkdownTracer() vm.EVMLogger {
@@ -45,11 +45,12 @@ type MIPSEVM struct {
4545
evmState *state.StateDB
4646
addrs *Addresses
4747
localOracle PreimageOracle
48+
artifacts *Artifacts
4849
}
4950

50-
func NewMIPSEVM(contracts *Contracts, addrs *Addresses) *MIPSEVM {
51-
env, evmState := NewEVMEnv(contracts, addrs)
52-
return &MIPSEVM{env, evmState, addrs, nil}
51+
func NewMIPSEVM(artifacts *Artifacts, addrs *Addresses) *MIPSEVM {
52+
env, evmState := NewEVMEnv(artifacts, addrs)
53+
return &MIPSEVM{env, evmState, addrs, nil, artifacts}
5354
}
5455

5556
func (m *MIPSEVM) SetTracer(tracer vm.EVMLogger) {
@@ -70,13 +71,13 @@ func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
7071

7172
if stepWitness.HasPreimage() {
7273
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
73-
poInput, err := encodePreimageOracleInput(t, stepWitness, LocalContext{}, m.localOracle)
74+
poInput, err := encodePreimageOracleInput(t, stepWitness, LocalContext{}, m.localOracle, m.artifacts.Oracle)
7475
require.NoError(t, err, "encode preimage oracle input")
7576
_, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.Oracle, poInput, startingGas, common.U2560)
7677
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
7778
}
7879

79-
input := encodeStepInput(t, stepWitness, LocalContext{})
80+
input := encodeStepInput(t, stepWitness, LocalContext{}, m.artifacts.MIPS)
8081
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, common.U2560)
8182
require.NoError(t, err, "evm should not fail")
8283
require.Len(t, ret, 32, "expecting 32-byte state hash")
@@ -95,23 +96,17 @@ func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
9596
return evmPost
9697
}
9798

98-
func encodeStepInput(t *testing.T, wit *StepWitness, localContext LocalContext) []byte {
99-
mipsAbi, err := bindings.MIPSMetaData.GetAbi()
100-
require.NoError(t, err)
101-
102-
input, err := mipsAbi.Pack("step", wit.State, wit.MemProof, localContext)
99+
func encodeStepInput(t *testing.T, wit *StepWitness, localContext LocalContext, mips *foundry.Artifact) []byte {
100+
input, err := mips.ABI.Pack("step", wit.State, wit.MemProof, localContext)
103101
require.NoError(t, err)
104102
return input
105103
}
106104

107-
func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext LocalContext, localOracle PreimageOracle) ([]byte, error) {
105+
func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext LocalContext, localOracle PreimageOracle, oracle *foundry.Artifact) ([]byte, error) {
108106
if wit.PreimageKey == ([32]byte{}) {
109107
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
110108
}
111109

112-
preimageAbi, err := bindings.PreimageOracleMetaData.GetAbi()
113-
require.NoError(t, err, "failed to load pre-image oracle ABI")
114-
115110
switch preimage.KeyType(wit.PreimageKey[0]) {
116111
case preimage.LocalKeyType:
117112
if len(wit.PreimageValue) > 32+8 {
@@ -120,7 +115,7 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca
120115
preimagePart := wit.PreimageValue[8:]
121116
var tmp [32]byte
122117
copy(tmp[:], preimagePart)
123-
input, err := preimageAbi.Pack("loadLocalData",
118+
input, err := oracle.ABI.Pack("loadLocalData",
124119
new(big.Int).SetBytes(wit.PreimageKey[1:]),
125120
localContext,
126121
tmp,
@@ -130,7 +125,7 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca
130125
require.NoError(t, err)
131126
return input, nil
132127
case preimage.Keccak256KeyType:
133-
input, err := preimageAbi.Pack(
128+
input, err := oracle.ABI.Pack(
134129
"loadKeccak256PreimagePart",
135130
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
136131
wit.PreimageValue[8:])
@@ -143,7 +138,7 @@ func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext Loca
143138
preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey())
144139
precompile := common.BytesToAddress(preimage[:20])
145140
callInput := preimage[20:]
146-
input, err := preimageAbi.Pack(
141+
input, err := oracle.ABI.Pack(
147142
"loadPrecompilePreimagePart",
148143
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
149144
precompile,
@@ -290,7 +285,7 @@ func TestEVMFault(t *testing.T) {
290285
State: initialState.EncodeWitness(),
291286
MemProof: insnProof[:],
292287
}
293-
input := encodeStepInput(t, stepWitness, LocalContext{})
288+
input := encodeStepInput(t, stepWitness, LocalContext{}, contracts.MIPS)
294289
startingGas := uint64(30_000_000)
295290

296291
_, _, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, common.U2560)

op-chain-ops/foundry/artifact.go

+64-8
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,87 @@ package foundry
22

33
import (
44
"encoding/json"
5+
"fmt"
6+
"os"
7+
"strings"
58

69
"github.com/ethereum-optimism/optimism/op-chain-ops/solc"
10+
"github.com/ethereum/go-ethereum/accounts/abi"
711
"github.com/ethereum/go-ethereum/common/hexutil"
812
)
913

1014
// Artifact represents a foundry compilation artifact.
11-
// The Abi is specifically left as a json.RawMessage because
12-
// round trip marshaling/unmarshalling of the abi.ABI type
13-
// causes issues.
15+
// JSON marshaling logic is implemented to maintain the ability
16+
// to roundtrip serialize an artifact
1417
type Artifact struct {
15-
Abi json.RawMessage `json:"abi"`
18+
ABI abi.ABI
19+
abi json.RawMessage
20+
StorageLayout solc.StorageLayout
21+
DeployedBytecode DeployedBytecode
22+
Bytecode Bytecode
23+
}
24+
25+
func (a *Artifact) UnmarshalJSON(data []byte) error {
26+
artifact := artifactMarshaling{}
27+
if err := json.Unmarshal(data, &artifact); err != nil {
28+
return err
29+
}
30+
parsed, err := abi.JSON(strings.NewReader(string(artifact.ABI)))
31+
if err != nil {
32+
return err
33+
}
34+
a.ABI = parsed
35+
a.abi = artifact.ABI
36+
a.StorageLayout = artifact.StorageLayout
37+
a.DeployedBytecode = artifact.DeployedBytecode
38+
a.Bytecode = artifact.Bytecode
39+
return nil
40+
}
41+
42+
func (a Artifact) MarshalJSON() ([]byte, error) {
43+
artifact := artifactMarshaling{
44+
ABI: a.abi,
45+
StorageLayout: a.StorageLayout,
46+
DeployedBytecode: a.DeployedBytecode,
47+
Bytecode: a.Bytecode,
48+
}
49+
return json.Marshal(artifact)
50+
}
51+
52+
// artifactMarshaling is a helper struct for marshaling and unmarshaling
53+
// foundry artifacts.
54+
type artifactMarshaling struct {
55+
ABI json.RawMessage `json:"abi"`
1656
StorageLayout solc.StorageLayout `json:"storageLayout"`
1757
DeployedBytecode DeployedBytecode `json:"deployedBytecode"`
1858
Bytecode Bytecode `json:"bytecode"`
1959
}
2060

61+
// DeployedBytecode represents the deployed bytecode section of the solc compiler output.
2162
type DeployedBytecode struct {
2263
SourceMap string `json:"sourceMap"`
2364
Object hexutil.Bytes `json:"object"`
2465
LinkReferences json.RawMessage `json:"linkReferences"`
25-
ImmutableReferences json.RawMessage `json:"immutableReferences"`
66+
ImmutableReferences json.RawMessage `json:"immutableReferences,omitempty"`
2667
}
2768

69+
// DeployedBytecode represents the bytecode section of the solc compiler output.
2870
type Bytecode struct {
29-
SourceMap string `json:"sourceMap"`
30-
Object hexutil.Bytes `json:"object"`
31-
LinkReferences json.RawMessage `json:"linkReferences"`
71+
SourceMap string `json:"sourceMap"`
72+
Object hexutil.Bytes `json:"object"`
73+
LinkReferences json.RawMessage `json:"linkReferences"`
74+
ImmutableReferences json.RawMessage `json:"immutableReferences,omitempty"`
75+
}
76+
77+
// ReadArtifact will read an artifact from disk given a path.
78+
func ReadArtifact(path string) (*Artifact, error) {
79+
file, err := os.ReadFile(path)
80+
if err != nil {
81+
return nil, fmt.Errorf("artifact at %s not found: %w", path, err)
82+
}
83+
artifact := Artifact{}
84+
if err := json.Unmarshal(file, &artifact); err != nil {
85+
return nil, err
86+
}
87+
return &artifact, nil
3288
}

op-chain-ops/foundry/artifact_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package foundry
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// TestArtifactJSON tests roundtrip serialization of a foundry artifact for commonly used fields.
12+
func TestArtifactJSON(t *testing.T) {
13+
artifact, err := ReadArtifact("testdata/OptimismPortal.json")
14+
require.NoError(t, err)
15+
16+
data, err := json.Marshal(artifact)
17+
require.NoError(t, err)
18+
19+
file, err := os.ReadFile("testdata/OptimismPortal.json")
20+
require.NoError(t, err)
21+
22+
got := unmarshalIntoMap(t, data)
23+
expected := unmarshalIntoMap(t, file)
24+
25+
require.JSONEq(t, marshal(t, got["bytecode"]), marshal(t, expected["bytecode"]))
26+
require.JSONEq(t, marshal(t, got["deployedBytecode"]), marshal(t, expected["deployedBytecode"]))
27+
require.JSONEq(t, marshal(t, got["abi"]), marshal(t, expected["abi"]))
28+
require.JSONEq(t, marshal(t, got["storageLayout"]), marshal(t, expected["storageLayout"]))
29+
}
30+
31+
func unmarshalIntoMap(t *testing.T, file []byte) map[string]any {
32+
var result map[string]any
33+
err := json.Unmarshal(file, &result)
34+
require.NoError(t, err)
35+
return result
36+
}
37+
38+
func marshal(t *testing.T, a any) string {
39+
result, err := json.Marshal(a)
40+
require.NoError(t, err)
41+
return string(result)
42+
}

0 commit comments

Comments
 (0)