Commit 49a4e9fa authored by protolambda's avatar protolambda Committed by GitHub

op-chain-ops: prep / utils for interop genesis work (#11790)

parent afcc51a4
...@@ -55,18 +55,26 @@ type SuperchainOperatorRole uint64 ...@@ -55,18 +55,26 @@ type SuperchainOperatorRole uint64
const ( const (
// SuperchainDeployerKey is the deployer of the superchain contracts. // SuperchainDeployerKey is the deployer of the superchain contracts.
SuperchainDeployerKey SuperchainOperatorRole = 0 SuperchainDeployerKey SuperchainOperatorRole = 0
// SuperchainProxyAdminOwner is the key that owns the superchain ProxyAdmin
SuperchainProxyAdminOwner SuperchainOperatorRole = 1
// SuperchainConfigGuardianKey is the Guardian of the SuperchainConfig. // SuperchainConfigGuardianKey is the Guardian of the SuperchainConfig.
SuperchainConfigGuardianKey SuperchainOperatorRole = 1 SuperchainConfigGuardianKey SuperchainOperatorRole = 2
// SuperchainProtocolVersionsOwner is the key that can make ProtocolVersions changes.
SuperchainProtocolVersionsOwner SuperchainOperatorRole = 3
// DependencySetManagerKey is the key used to manage the dependency set of a superchain. // DependencySetManagerKey is the key used to manage the dependency set of a superchain.
DependencySetManagerKey SuperchainOperatorRole = 2 DependencySetManagerKey SuperchainOperatorRole = 4
) )
func (role SuperchainOperatorRole) String() string { func (role SuperchainOperatorRole) String() string {
switch role { switch role {
case SuperchainDeployerKey: case SuperchainDeployerKey:
return "superchain-deployer" return "superchain-deployer"
case SuperchainProxyAdminOwner:
return "superchain-proxy-admin-owner"
case SuperchainConfigGuardianKey: case SuperchainConfigGuardianKey:
return "superchain-config-guardian" return "superchain-config-guardian"
case SuperchainProtocolVersionsOwner:
return "superchain-protocol-versions-owner"
case DependencySetManagerKey: case DependencySetManagerKey:
return "dependency-set-manager" return "dependency-set-manager"
default: default:
...@@ -122,6 +130,8 @@ const ( ...@@ -122,6 +130,8 @@ const (
L1FeeVaultRecipientRole ChainOperatorRole = 8 L1FeeVaultRecipientRole ChainOperatorRole = 8
// SequencerFeeVaultRecipientRole is the key that receives form the SequencerFeeVault predeploy // SequencerFeeVaultRecipientRole is the key that receives form the SequencerFeeVault predeploy
SequencerFeeVaultRecipientRole ChainOperatorRole = 9 SequencerFeeVaultRecipientRole ChainOperatorRole = 9
// SystemConfigOwner is the key that can make SystemConfig changes.
SystemConfigOwner ChainOperatorRole = 10
) )
func (role ChainOperatorRole) String() string { func (role ChainOperatorRole) String() string {
...@@ -146,6 +156,8 @@ func (role ChainOperatorRole) String() string { ...@@ -146,6 +156,8 @@ func (role ChainOperatorRole) String() string {
return "l1-fee-vault-recipient" return "l1-fee-vault-recipient"
case SequencerFeeVaultRecipientRole: case SequencerFeeVaultRecipientRole:
return "sequencer-fee-vault-recipient" return "sequencer-fee-vault-recipient"
case SystemConfigOwner:
return "system-config-owner"
default: default:
return fmt.Sprintf("unknown-operator-%d", uint64(role)) return fmt.Sprintf("unknown-operator-%d", uint64(role))
} }
......
...@@ -10,11 +10,15 @@ import ( ...@@ -10,11 +10,15 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
var setterFnSig = "set(bytes4,address)"
var setterFnBytes4 = bytes4(setterFnSig)
// precompileFunc is a prepared function to perform a method call / field read with ABI decoding/encoding. // precompileFunc is a prepared function to perform a method call / field read with ABI decoding/encoding.
type precompileFunc struct { type precompileFunc struct {
goName string goName string
...@@ -52,12 +56,20 @@ func rightPad32(data []byte) []byte { ...@@ -52,12 +56,20 @@ func rightPad32(data []byte) []byte {
return append(out, make([]byte, 32-(len(out)%32))...) return append(out, make([]byte, 32-(len(out)%32))...)
} }
type settableField struct {
name string
value *reflect.Value
}
// Precompile is a wrapper around a Go object, making it a precompile. // Precompile is a wrapper around a Go object, making it a precompile.
type Precompile[E any] struct { type Precompile[E any] struct {
Precompile E Precompile E
fieldsOnly bool fieldsOnly bool
fieldSetter bool
settable map[[4]byte]*settableField
// abiMethods is effectively the jump-table for 4-byte ABI calls to the precompile. // abiMethods is effectively the jump-table for 4-byte ABI calls to the precompile.
abiMethods map[[4]byte]*precompileFunc abiMethods map[[4]byte]*precompileFunc
} }
...@@ -70,6 +82,10 @@ func WithFieldsOnly[E any](p *Precompile[E]) { ...@@ -70,6 +82,10 @@ func WithFieldsOnly[E any](p *Precompile[E]) {
p.fieldsOnly = true p.fieldsOnly = true
} }
func WithFieldSetter[E any](p *Precompile[E]) {
p.fieldSetter = true
}
// NewPrecompile wraps a Go object into a Precompile. // NewPrecompile wraps a Go object into a Precompile.
// All exported fields and methods will have a corresponding ABI interface. // All exported fields and methods will have a corresponding ABI interface.
// Fields with a tag `evm:"-"` will be ignored, or can override their ABI name to x with this tag: `evm:"x"`. // Fields with a tag `evm:"-"` will be ignored, or can override their ABI name to x with this tag: `evm:"x"`.
...@@ -80,9 +96,11 @@ func WithFieldsOnly[E any](p *Precompile[E]) { ...@@ -80,9 +96,11 @@ func WithFieldsOnly[E any](p *Precompile[E]) {
// All precompile methods have 0 gas cost. // All precompile methods have 0 gas cost.
func NewPrecompile[E any](e E, opts ...PrecompileOption[E]) (*Precompile[E], error) { func NewPrecompile[E any](e E, opts ...PrecompileOption[E]) (*Precompile[E], error) {
out := &Precompile[E]{ out := &Precompile[E]{
Precompile: e, Precompile: e,
abiMethods: make(map[[4]byte]*precompileFunc), abiMethods: make(map[[4]byte]*precompileFunc),
fieldsOnly: false, fieldsOnly: false,
fieldSetter: false,
settable: make(map[[4]byte]*settableField),
} }
for _, opt := range opts { for _, opt := range opts {
opt(out) opt(out)
...@@ -96,6 +114,8 @@ func NewPrecompile[E any](e E, opts ...PrecompileOption[E]) (*Precompile[E], err ...@@ -96,6 +114,8 @@ func NewPrecompile[E any](e E, opts ...PrecompileOption[E]) (*Precompile[E], err
if err := out.setupFields(&elemVal); err != nil { if err := out.setupFields(&elemVal); err != nil {
return nil, fmt.Errorf("failed to setup fields of precompile: %w", err) return nil, fmt.Errorf("failed to setup fields of precompile: %w", err)
} }
// create setter that can handle of the fields
out.setupFieldSetter()
return out, nil return out, nil
} }
...@@ -496,9 +516,42 @@ func (p *Precompile[E]) setupStructField(fieldDef *reflect.StructField, fieldVal ...@@ -496,9 +516,42 @@ func (p *Precompile[E]) setupStructField(fieldDef *reflect.StructField, fieldVal
abiSignature: methodSig, abiSignature: methodSig,
fn: fn, fn: fn,
} }
// register field as settable
if p.fieldSetter && fieldDef.Type.AssignableTo(typeFor[common.Address]()) {
p.settable[byte4Sig] = &settableField{
name: fieldDef.Name,
value: fieldVal,
}
}
return nil return nil
} }
func (p *Precompile[E]) setupFieldSetter() {
if !p.fieldSetter {
return
}
p.abiMethods[setterFnBytes4] = &precompileFunc{
goName: "__fieldSetter___",
abiSignature: setterFnSig,
fn: func(input []byte) ([]byte, error) {
if len(input) != 32*2 {
return nil, fmt.Errorf("cannot set address field to %d bytes", len(input))
}
if [32 - 4]byte(input[4:32]) != ([32 - 4]byte{}) {
return nil, fmt.Errorf("unexpected selector content, input: %x", input[:])
}
selector := [4]byte(input[:4])
f, ok := p.settable[selector]
if !ok {
return nil, fmt.Errorf("unknown address field selector 0x%x", selector)
}
addr := common.Address(input[32*2-20 : 32*2])
f.value.Set(reflect.ValueOf(addr))
return nil, nil
},
}
}
// RequiredGas is part of the vm.PrecompiledContract interface, and all system precompiles use 0 gas. // RequiredGas is part of the vm.PrecompiledContract interface, and all system precompiles use 0 gas.
func (p *Precompile[E]) RequiredGas(input []byte) uint64 { func (p *Precompile[E]) RequiredGas(input []byte) uint64 {
return 0 return 0
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
...@@ -119,3 +120,24 @@ func TestPrecompile(t *testing.T) { ...@@ -119,3 +120,24 @@ func TestPrecompile(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, b32((42+100+7)*3), out) require.Equal(t, b32((42+100+7)*3), out)
} }
type DeploymentExample struct {
FooBar common.Address
}
func TestDeploymentOutputPrecompile(t *testing.T) {
e := &DeploymentExample{}
p, err := NewPrecompile[*DeploymentExample](e, WithFieldSetter[*DeploymentExample])
require.NoError(t, err)
addr := common.Address{0: 0x42, 19: 0xaa}
fooBarSelector := bytes4("fooBar()")
var input []byte
input = append(input, setterFnBytes4[:]...)
input = append(input, rightPad32(fooBarSelector[:])...)
input = append(input, leftPad32(addr[:])...)
out, err := p.Run(input)
require.NoError(t, err)
require.Empty(t, out)
require.Equal(t, addr, e.FooBar)
}
...@@ -29,6 +29,9 @@ import ( ...@@ -29,6 +29,9 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap"
) )
// jumpHistory is the amount of successful jumps to track for debugging.
const jumpHistory = 5
// CallFrame encodes the scope context of the current call // CallFrame encodes the scope context of the current call
type CallFrame struct { type CallFrame struct {
Depth int Depth int
...@@ -39,7 +42,9 @@ type CallFrame struct { ...@@ -39,7 +42,9 @@ type CallFrame struct {
// Reverts often happen in generated code. // Reverts often happen in generated code.
// We want to fallback to logging the source-map position of // We want to fallback to logging the source-map position of
// the non-generated code, i.e. the origin of the last successful jump. // the non-generated code, i.e. the origin of the last successful jump.
LastJumpPC uint64 // And beyond that, a short history of the latest jumps is useful for debugging.
// This is a list of program-counters at the time of the jump (i.e. before raching JUMPDEST).
LastJumps []uint64
Ctx *vm.ScopeContext Ctx *vm.ScopeContext
...@@ -79,6 +84,8 @@ type Host struct { ...@@ -79,6 +84,8 @@ type Host struct {
// src-maps are disabled if this is nil. // src-maps are disabled if this is nil.
srcFS *foundry.SourceMapFS srcFS *foundry.SourceMapFS
srcMaps map[common.Address]*srcmap.SourceMap srcMaps map[common.Address]*srcmap.SourceMap
onLabel []func(name string, addr common.Address)
} }
// NewHost creates a Host that can load contracts from the given Artifacts FS, // NewHost creates a Host that can load contracts from the given Artifacts FS,
...@@ -378,11 +385,10 @@ func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpCo ...@@ -378,11 +385,10 @@ func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpCo
// We do this here, instead of onEnter, to capture an initialized scope. // We do this here, instead of onEnter, to capture an initialized scope.
if len(h.callStack) == 0 || h.callStack[len(h.callStack)-1].Depth < depth { if len(h.callStack) == 0 || h.callStack[len(h.callStack)-1].Depth < depth {
h.callStack = append(h.callStack, CallFrame{ h.callStack = append(h.callStack, CallFrame{
Depth: depth, Depth: depth,
LastOp: vm.OpCode(op), LastOp: vm.OpCode(op),
LastPC: pc, LastPC: pc,
LastJumpPC: pc, Ctx: scopeCtx,
Ctx: scopeCtx,
}) })
} }
// Sanity check that top of the call-stack matches the scope context now // Sanity check that top of the call-stack matches the scope context now
...@@ -391,7 +397,11 @@ func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpCo ...@@ -391,7 +397,11 @@ func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpCo
} }
cf := &h.callStack[len(h.callStack)-1] cf := &h.callStack[len(h.callStack)-1]
if vm.OpCode(op) == vm.JUMPDEST { // remember the last PC before successful jump if vm.OpCode(op) == vm.JUMPDEST { // remember the last PC before successful jump
cf.LastJumpPC = cf.LastPC cf.LastJumps = append(cf.LastJumps, cf.LastPC)
if len(cf.LastJumps) > jumpHistory {
copy(cf.LastJumps[:], cf.LastJumps[len(cf.LastJumps)-jumpHistory:])
cf.LastJumps = cf.LastJumps[:jumpHistory]
}
} }
cf.LastOp = vm.OpCode(op) cf.LastOp = vm.OpCode(op)
cf.LastPC = pc cf.LastPC = pc
...@@ -527,10 +537,14 @@ func (h *Host) EnforceMaxCodeSize(v bool) { ...@@ -527,10 +537,14 @@ func (h *Host) EnforceMaxCodeSize(v bool) {
func (h *Host) LogCallStack() { func (h *Host) LogCallStack() {
for _, cf := range h.callStack { for _, cf := range h.callStack {
callsite := "" callsite := ""
if srcMap, ok := h.srcMaps[cf.Ctx.Address()]; ok { srcMap, ok := h.srcMaps[cf.Ctx.Address()]
if !ok && cf.Ctx.Contract.CodeAddr != nil { // if delegate-call, we might know the implementation code.
srcMap, ok = h.srcMaps[*cf.Ctx.Contract.CodeAddr]
}
if ok {
callsite = srcMap.FormattedInfo(cf.LastPC) callsite = srcMap.FormattedInfo(cf.LastPC)
if callsite == "unknown:0:0" { if callsite == "unknown:0:0" && len(cf.LastJumps) > 0 {
callsite = srcMap.FormattedInfo(cf.LastJumpPC) callsite = srcMap.FormattedInfo(cf.LastJumps[len(cf.LastJumps)-1])
} }
} }
input := cf.Ctx.CallInput() input := cf.Ctx.CallInput()
...@@ -538,9 +552,14 @@ func (h *Host) LogCallStack() { ...@@ -538,9 +552,14 @@ func (h *Host) LogCallStack() {
if len(input) >= 4 { if len(input) >= 4 {
byte4 = fmt.Sprintf("0x%x", input[:4]) byte4 = fmt.Sprintf("0x%x", input[:4])
} }
h.log.Debug("callframe", "depth", cf.Depth, "input", hexutil.Bytes(input), "pc", cf.LastPC, "op", cf.LastOp) h.log.Debug("callframe input", "depth", cf.Depth, "input", hexutil.Bytes(input), "pc", cf.LastPC, "op", cf.LastOp)
h.log.Warn("callframe", "depth", cf.Depth, "byte4", byte4, h.log.Warn("callframe", "depth", cf.Depth, "byte4", byte4,
"addr", cf.Ctx.Address(), "callsite", callsite, "label", h.labels[cf.Ctx.Address()]) "addr", cf.Ctx.Address(), "callsite", callsite, "label", h.labels[cf.Ctx.Address()])
if srcMap != nil {
for _, jmpPC := range cf.LastJumps {
h.log.Debug("recent jump", "depth", cf.Depth, "callsite", srcMap.FormattedInfo(jmpPC), "pc", jmpPC)
}
}
} }
} }
...@@ -548,4 +567,40 @@ func (h *Host) LogCallStack() { ...@@ -548,4 +567,40 @@ func (h *Host) LogCallStack() {
func (h *Host) Label(addr common.Address, label string) { func (h *Host) Label(addr common.Address, label string) {
h.log.Debug("labeling", "addr", addr, "label", label) h.log.Debug("labeling", "addr", addr, "label", label)
h.labels[addr] = label h.labels[addr] = label
for _, fn := range h.onLabel {
fn(label, addr)
}
}
// NewScriptAddress creates a new address for the ScriptDeployer account, and bumps the nonce.
func (h *Host) NewScriptAddress() common.Address {
deployer := ScriptDeployer
deployNonce := h.state.GetNonce(deployer)
// compute address of script contract to be deployed
addr := crypto.CreateAddress(deployer, deployNonce)
h.state.SetNonce(deployer, deployNonce+1)
return addr
}
func (h *Host) ChainID() *big.Int {
return new(big.Int).Set(h.chainCfg.ChainID)
}
func (h *Host) Artifacts() *foundry.ArtifactsFS {
return h.af
}
// RememberOnLabel links the contract source-code of srcFile upon a given label
func (h *Host) RememberOnLabel(label, srcFile, contract string) error {
artifact, err := h.af.ReadArtifact(srcFile, contract)
if err != nil {
return fmt.Errorf("failed to read artifact %s (contract %s) for label %q", srcFile, contract, label)
}
h.onLabel = append(h.onLabel, func(v string, addr common.Address) {
if label == v {
h.RememberArtifact(addr, artifact, contract)
}
})
return nil
} }
...@@ -120,6 +120,10 @@ type SourceMap struct { ...@@ -120,6 +120,10 @@ type SourceMap struct {
// This location is the source file-path, the line number, and column number. // This location is the source file-path, the line number, and column number.
// This may return an error, as the source-file is lazy-loaded to calculate the position data. // This may return an error, as the source-file is lazy-loaded to calculate the position data.
func (s *SourceMap) Info(pc uint64) (source string, line uint32, col uint32, err error) { func (s *SourceMap) Info(pc uint64) (source string, line uint32, col uint32, err error) {
if pc >= uint64(len(s.Instr)) {
source = "invalid"
return
}
instr := s.Instr[pc] instr := s.Instr[pc]
if instr.F < 0 || instr == (InstrMapping{}) { if instr.F < 0 || instr == (InstrMapping{}) {
return "generated", 0, 0, nil return "generated", 0, 0, nil
......
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