/
config.go
461 lines (422 loc) · 18.1 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
package genesis
import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
)
var ErrInvalidDeployConfig = errors.New("invalid deploy config")
// DeployConfig represents the deployment configuration for Optimism
type DeployConfig struct {
L1StartingBlockTag *MarshalableRPCBlockNumberOrHash `json:"l1StartingBlockTag"`
L1ChainID uint64 `json:"l1ChainID"`
L2ChainID uint64 `json:"l2ChainID"`
L2BlockTime uint64 `json:"l2BlockTime"`
FinalizationPeriodSeconds uint64 `json:"finalizationPeriodSeconds"`
MaxSequencerDrift uint64 `json:"maxSequencerDrift"`
SequencerWindowSize uint64 `json:"sequencerWindowSize"`
ChannelTimeout uint64 `json:"channelTimeout"`
P2PSequencerAddress common.Address `json:"p2pSequencerAddress"`
BatchInboxAddress common.Address `json:"batchInboxAddress"`
BatchSenderAddress common.Address `json:"batchSenderAddress"`
L2OutputOracleSubmissionInterval uint64 `json:"l2OutputOracleSubmissionInterval"`
L2OutputOracleStartingTimestamp int `json:"l2OutputOracleStartingTimestamp"`
L2OutputOracleProposer common.Address `json:"l2OutputOracleProposer"`
L2OutputOracleChallenger common.Address `json:"l2OutputOracleChallenger"`
L1BlockTime uint64 `json:"l1BlockTime"`
L1GenesisBlockTimestamp hexutil.Uint64 `json:"l1GenesisBlockTimestamp"`
L1GenesisBlockNonce hexutil.Uint64 `json:"l1GenesisBlockNonce"`
CliqueSignerAddress common.Address `json:"cliqueSignerAddress"` // proof of stake genesis if left zeroed.
L1GenesisBlockGasLimit hexutil.Uint64 `json:"l1GenesisBlockGasLimit"`
L1GenesisBlockDifficulty *hexutil.Big `json:"l1GenesisBlockDifficulty"`
L1GenesisBlockMixHash common.Hash `json:"l1GenesisBlockMixHash"`
L1GenesisBlockCoinbase common.Address `json:"l1GenesisBlockCoinbase"`
L1GenesisBlockNumber hexutil.Uint64 `json:"l1GenesisBlockNumber"`
L1GenesisBlockGasUsed hexutil.Uint64 `json:"l1GenesisBlockGasUsed"`
L1GenesisBlockParentHash common.Hash `json:"l1GenesisBlockParentHash"`
L1GenesisBlockBaseFeePerGas *hexutil.Big `json:"l1GenesisBlockBaseFeePerGas"`
L2GenesisBlockNonce hexutil.Uint64 `json:"l2GenesisBlockNonce"`
L2GenesisBlockGasLimit hexutil.Uint64 `json:"l2GenesisBlockGasLimit"`
L2GenesisBlockDifficulty *hexutil.Big `json:"l2GenesisBlockDifficulty"`
L2GenesisBlockMixHash common.Hash `json:"l2GenesisBlockMixHash"`
L2GenesisBlockNumber hexutil.Uint64 `json:"l2GenesisBlockNumber"`
L2GenesisBlockGasUsed hexutil.Uint64 `json:"l2GenesisBlockGasUsed"`
L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"`
L2GenesisBlockBaseFeePerGas *hexutil.Big `json:"l2GenesisBlockBaseFeePerGas"`
// Owner of the ProxyAdmin predeploy
ProxyAdminOwner common.Address `json:"proxyAdminOwner"`
// Owner of the system on L1
FinalSystemOwner common.Address `json:"finalSystemOwner"`
// L1 recipient of fees accumulated in the BaseFeeVault
BaseFeeVaultRecipient common.Address `json:"baseFeeVaultRecipient"`
// L1 recipient of fees accumulated in the L1FeeVault
L1FeeVaultRecipient common.Address `json:"l1FeeVaultRecipient"`
// L1 recipient of fees accumulated in the SequencerFeeVault
SequencerFeeVaultRecipient common.Address `json:"sequencerFeeVaultRecipient"`
// L1StandardBridge proxy address on L1
L1StandardBridgeProxy common.Address `json:"l1StandardBridgeProxy"`
// L1CrossDomainMessenger proxy address on L1
L1CrossDomainMessengerProxy common.Address `json:"l1CrossDomainMessengerProxy"`
// L1ERC721Bridge proxy address on L1
L1ERC721BridgeProxy common.Address `json:"l1ERC721BridgeProxy"`
// SystemConfig proxy address on L1
SystemConfigProxy common.Address `json:"systemConfigProxy"`
// OptimismPortal proxy address on L1
OptimismPortalProxy common.Address `json:"optimismPortalProxy"`
// The initial value of the gas overhead
GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"`
// The initial value of the gas scalar
GasPriceOracleScalar uint64 `json:"gasPriceOracleScalar"`
// The ERC20 symbol of the GovernanceToken
GovernanceTokenSymbol string `json:"governanceTokenSymbol"`
// The ERC20 name of the GovernanceToken
GovernanceTokenName string `json:"governanceTokenName"`
// The owner of the GovernanceToken
GovernanceTokenOwner common.Address `json:"governanceTokenOwner"`
DeploymentWaitConfirmations int `json:"deploymentWaitConfirmations"`
EIP1559Elasticity uint64 `json:"eip1559Elasticity"`
EIP1559Denominator uint64 `json:"eip1559Denominator"`
FundDevAccounts bool `json:"fundDevAccounts"`
}
// Check will ensure that the config is sane and return an error when it is not
func (d *DeployConfig) Check() error {
if d.L1StartingBlockTag == nil {
return fmt.Errorf("%w: L2StartingBlockTag cannot be nil", ErrInvalidDeployConfig)
}
if d.L1ChainID == 0 {
return fmt.Errorf("%w: L1ChainID cannot be 0", ErrInvalidDeployConfig)
}
if d.L2ChainID == 0 {
return fmt.Errorf("%w: L2ChainID cannot be 0", ErrInvalidDeployConfig)
}
if d.L2BlockTime == 0 {
return fmt.Errorf("%w: L2BlockTime cannot be 0", ErrInvalidDeployConfig)
}
if d.FinalizationPeriodSeconds == 0 {
return fmt.Errorf("%w: FinalizationPeriodSeconds cannot be 0", ErrInvalidDeployConfig)
}
if d.MaxSequencerDrift == 0 {
return fmt.Errorf("%w: MaxSequencerDrift cannot be 0", ErrInvalidDeployConfig)
}
if d.SequencerWindowSize == 0 {
return fmt.Errorf("%w: SequencerWindowSize cannot be 0", ErrInvalidDeployConfig)
}
if d.ChannelTimeout == 0 {
return fmt.Errorf("%w: ChannelTimeout cannot be 0", ErrInvalidDeployConfig)
}
if d.P2PSequencerAddress == (common.Address{}) {
return fmt.Errorf("%w: P2PSequencerAddress cannot be address(0)", ErrInvalidDeployConfig)
}
if d.BatchInboxAddress == (common.Address{}) {
return fmt.Errorf("%w: BatchInboxAddress cannot be address(0)", ErrInvalidDeployConfig)
}
if d.BatchSenderAddress == (common.Address{}) {
return fmt.Errorf("%w: BatchSenderAddress cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L2OutputOracleSubmissionInterval == 0 {
return fmt.Errorf("%w: L2OutputOracleSubmissionInterval cannot be 0", ErrInvalidDeployConfig)
}
if d.L2OutputOracleStartingTimestamp == 0 {
log.Warn("L2OutputOracleStartingTimestamp is 0")
}
if d.L2OutputOracleProposer == (common.Address{}) {
return fmt.Errorf("%w: L2OutputOracleProposer cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L2OutputOracleChallenger == (common.Address{}) {
return fmt.Errorf("%w: L2OutputOracleChallenger cannot be address(0)", ErrInvalidDeployConfig)
}
if d.FinalSystemOwner == (common.Address{}) {
return fmt.Errorf("%w: FinalSystemOwner cannot be address(0)", ErrInvalidDeployConfig)
}
if d.ProxyAdminOwner == (common.Address{}) {
return fmt.Errorf("%w: ProxyAdminOwner cannot be address(0)", ErrInvalidDeployConfig)
}
if d.BaseFeeVaultRecipient == (common.Address{}) {
return fmt.Errorf("%w: BaseFeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1FeeVaultRecipient == (common.Address{}) {
return fmt.Errorf("%w: L1FeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SequencerFeeVaultRecipient == (common.Address{}) {
return fmt.Errorf("%w: SequencerFeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
}
if d.GasPriceOracleOverhead == 0 {
log.Warn("GasPriceOracleOverhead is 0")
}
if d.GasPriceOracleScalar == 0 {
return fmt.Errorf("%w: GasPriceOracleScalar cannot be 0", ErrInvalidDeployConfig)
}
if d.L1StandardBridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
return fmt.Errorf("%w: L1CrossDomainMessengerProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1ERC721BridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SystemConfigProxy == (common.Address{}) {
return fmt.Errorf("%w: SystemConfigProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
}
if d.EIP1559Elasticity == 0 {
return fmt.Errorf("%w: EIP1559Elasticity cannot be 0", ErrInvalidDeployConfig)
}
if d.L2GenesisBlockGasLimit == 0 {
return fmt.Errorf("%w: L2 genesis block gas limit cannot be 0", ErrInvalidDeployConfig)
}
if d.L2GenesisBlockBaseFeePerGas == nil {
return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig)
}
if d.GovernanceTokenName == "" {
return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig)
}
if d.GovernanceTokenSymbol == "" {
return fmt.Errorf("%w: GovernanceToken.symbol cannot be empty", ErrInvalidDeployConfig)
}
if d.GovernanceTokenOwner == (common.Address{}) {
return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig)
}
return nil
}
// GetDeployedAddresses will get the deployed addresses of deployed L1 contracts
// required for the L2 genesis creation. Legacy systems use the `Proxy__` prefix
// while modern systems use the `Proxy` suffix. First check for the legacy
// deployments so that this works with upgrading a system.
func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
var err error
if d.L1StandardBridgeProxy == (common.Address{}) {
var l1StandardBridgeProxyDeployment *hardhat.Deployment
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1StandardBridge")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("L1StandardBridgeProxy")
if err != nil {
return err
}
}
d.L1StandardBridgeProxy = l1StandardBridgeProxyDeployment.Address
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
var l1CrossDomainMessengerProxyDeployment *hardhat.Deployment
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1CrossDomainMessenger")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("L1CrossDomainMessengerProxy")
if err != nil {
return err
}
}
d.L1CrossDomainMessengerProxy = l1CrossDomainMessengerProxyDeployment.Address
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
// There is no legacy deployment of this contract
l1ERC721BridgeProxyDeployment, err := hh.GetDeployment("L1ERC721BridgeProxy")
if err != nil {
return err
}
d.L1ERC721BridgeProxy = l1ERC721BridgeProxyDeployment.Address
}
if d.SystemConfigProxy == (common.Address{}) {
systemConfigProxyDeployment, err := hh.GetDeployment("SystemConfigProxy")
if err != nil {
return err
}
d.SystemConfigProxy = systemConfigProxyDeployment.Address
}
if d.OptimismPortalProxy == (common.Address{}) {
optimismPortalProxyDeployment, err := hh.GetDeployment("OptimismPortalProxy")
if err != nil {
return err
}
d.OptimismPortalProxy = optimismPortalProxyDeployment.Address
}
return nil
}
// InitDeveloperDeployedAddresses will set the dev addresses on the DeployConfig
func (d *DeployConfig) InitDeveloperDeployedAddresses() error {
d.L1StandardBridgeProxy = predeploys.DevL1StandardBridgeAddr
d.L1CrossDomainMessengerProxy = predeploys.DevL1CrossDomainMessengerAddr
d.L1ERC721BridgeProxy = predeploys.DevL1ERC721BridgeAddr
d.OptimismPortalProxy = predeploys.DevOptimismPortalAddr
d.SystemConfigProxy = predeploys.DevSystemConfigAddr
return nil
}
// RollupConfig converts a DeployConfig to a rollup.Config
func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHash common.Hash, l2GenesisBlockNumber uint64) (*rollup.Config, error) {
if d.OptimismPortalProxy == (common.Address{}) {
return nil, errors.New("OptimismPortalProxy cannot be address(0)")
}
if d.SystemConfigProxy == (common.Address{}) {
return nil, errors.New("SystemConfigProxy cannot be address(0)")
}
return &rollup.Config{
Genesis: rollup.Genesis{
L1: eth.BlockID{
Hash: l1StartBlock.Hash(),
Number: l1StartBlock.NumberU64(),
},
L2: eth.BlockID{
Hash: l2GenesisBlockHash,
Number: l2GenesisBlockNumber,
},
L2Time: l1StartBlock.Time(),
SystemConfig: eth.SystemConfig{
BatcherAddr: d.BatchSenderAddress,
Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))),
Scalar: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleScalar))),
GasLimit: uint64(d.L2GenesisBlockGasLimit),
},
},
BlockTime: d.L2BlockTime,
MaxSequencerDrift: d.MaxSequencerDrift,
SeqWindowSize: d.SequencerWindowSize,
ChannelTimeout: d.ChannelTimeout,
L1ChainID: new(big.Int).SetUint64(d.L1ChainID),
L2ChainID: new(big.Int).SetUint64(d.L2ChainID),
BatchInboxAddress: d.BatchInboxAddress,
DepositContractAddress: d.OptimismPortalProxy,
L1SystemConfigAddress: d.SystemConfigProxy,
}, nil
}
// NewDeployConfig reads a config file given a path on the filesystem.
func NewDeployConfig(path string) (*DeployConfig, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("deploy config at %s not found: %w", path, err)
}
var config DeployConfig
if err := json.Unmarshal(file, &config); err != nil {
return nil, fmt.Errorf("cannot unmarshal deploy config: %w", err)
}
return &config, nil
}
// NewDeployConfigWithNetwork takes a path to a deploy config directory
// and the network name. The config file in the deploy config directory
// must match the network name and be a JSON file.
func NewDeployConfigWithNetwork(network, path string) (*DeployConfig, error) {
deployConfig := filepath.Join(path, network+".json")
return NewDeployConfig(deployConfig)
}
// NewL2ImmutableConfig will create an ImmutableConfig given an instance of a
// Hardhat and a DeployConfig.
func NewL2ImmutableConfig(config *DeployConfig, block *types.Block) (immutables.ImmutableConfig, error) {
immutable := make(immutables.ImmutableConfig)
if config.L1ERC721BridgeProxy == (common.Address{}) {
return immutable, errors.New("L1ERC721BridgeProxy cannot be address(0)")
}
immutable["L2StandardBridge"] = immutables.ImmutableValues{
"otherBridge": config.L1StandardBridgeProxy,
}
immutable["L2CrossDomainMessenger"] = immutables.ImmutableValues{
"otherMessenger": config.L1CrossDomainMessengerProxy,
}
immutable["L2ERC721Bridge"] = immutables.ImmutableValues{
"messenger": predeploys.L2CrossDomainMessengerAddr,
"otherBridge": config.L1ERC721BridgeProxy,
}
immutable["OptimismMintableERC721Factory"] = immutables.ImmutableValues{
"bridge": predeploys.L2ERC721BridgeAddr,
"remoteChainId": new(big.Int).SetUint64(config.L1ChainID),
}
immutable["SequencerFeeVault"] = immutables.ImmutableValues{
"recipient": config.SequencerFeeVaultRecipient,
}
immutable["L1FeeVault"] = immutables.ImmutableValues{
"recipient": config.L1FeeVaultRecipient,
}
immutable["BaseFeeVault"] = immutables.ImmutableValues{
"recipient": config.BaseFeeVaultRecipient,
}
return immutable, nil
}
// NewL2StorageConfig will create a StorageConfig given an instance of a
// Hardhat and a DeployConfig.
func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.StorageConfig, error) {
storage := make(state.StorageConfig)
if block.Number() == nil {
return storage, errors.New("block number not set")
}
if block.BaseFee() == nil {
return storage, errors.New("block base fee not set")
}
storage["L2ToL1MessagePasser"] = state.StorageValues{
"msgNonce": 0,
}
storage["L2CrossDomainMessenger"] = state.StorageValues{
"_initialized": 1,
"_owner": config.ProxyAdminOwner,
// re-entrency lock
"_status": 1,
"_initializing": false,
"_paused": false,
"xDomainMsgSender": "0x000000000000000000000000000000000000dEaD",
"msgNonce": 0,
}
storage["L1Block"] = state.StorageValues{
"number": block.Number(),
"timestamp": block.Time(),
"basefee": block.BaseFee(),
"hash": block.Hash(),
"sequenceNumber": 0,
"batcherHash": config.BatchSenderAddress.Hash(),
"l1FeeOverhead": config.GasPriceOracleOverhead,
"l1FeeScalar": config.GasPriceOracleScalar,
}
storage["LegacyERC20ETH"] = state.StorageValues{
"_name": "Ether",
"_symbol": "ETH",
}
storage["WETH9"] = state.StorageValues{
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18,
}
storage["GovernanceToken"] = state.StorageValues{
"_name": config.GovernanceTokenName,
"_symbol": config.GovernanceTokenSymbol,
"_owner": config.GovernanceTokenOwner,
}
storage["ProxyAdmin"] = state.StorageValues{
"_owner": config.ProxyAdminOwner,
}
return storage, nil
}
type MarshalableRPCBlockNumberOrHash rpc.BlockNumberOrHash
func (m *MarshalableRPCBlockNumberOrHash) MarshalJSON() ([]byte, error) {
r := rpc.BlockNumberOrHash(*m)
if hash, ok := r.Hash(); ok {
return json.Marshal(hash)
}
if num, ok := r.Number(); ok {
// never errors
text, _ := num.MarshalText()
return json.Marshal(string(text))
}
return json.Marshal(nil)
}
func (m *MarshalableRPCBlockNumberOrHash) UnmarshalJSON(b []byte) error {
var r rpc.BlockNumberOrHash
if err := json.Unmarshal(b, &r); err != nil {
return err
}
asMarshalable := MarshalableRPCBlockNumberOrHash(r)
*m = asMarshalable
return nil
}