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

state-surgery: add immutables package (#3211)

* state-surgery: add immutables package

Add a package for dealing with immutables so that
the deployed bytecode can be set directly in state.
Each contract from the `op-bindings` package is imported
and deployed to a simulated backend. The `op-bindings`
package is enforced to be up to date by CI. Each of the
contracts was double checked that the arguments passed are
not immutables, the existing immutable values are hardcoded
into the contract itself. To handle the case where immutables
are dynamic, we will need to read in config and pass that
through. The methodology on how to do that is documented
in a TODO message.

The build step runs in CI meaning that changes to the
contract constructor interfaces will result in this
package failing to build.

This code could be slightly modularized to be used in
`op-e2e` for creating the initial L2 state. Will leave
that for an additional follow up PR.

* state-surgery: additional sanity check
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 4dda5247
package immutables
import (
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/l2geth/common/hexutil"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
)
// testKey is the same test key that geth uses
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
// chainID is the chain id used for simulated backends
var chainID = big.NewInt(1337)
// TODO(tynes): we are planning on making some constructor arguments
// into immutables before the final deployment of the system. This
// means that this struct will need to be updated with an additional
// parameter: Args []interface{}{} and each step will need to typecast
// each argument before doing the simulated deployment
type Deployment struct {
Name string
}
// DeploymentResults represents the output of deploying each of the
// contracts so that the immutables can be set properly in the bytecode.
type DeploymentResults map[string]hexutil.Bytes
// TODO(tynes): once there are deploy time config params,
// pass in a config struct to this function that comes from
// a JSON file/cli flags and then populate the Deployment
// Args.
func OptimismBuild() (DeploymentResults, error) {
deployments := []Deployment{
{
Name: "GasPriceOracle",
},
{
Name: "L1Block",
},
{
Name: "L2CrossDomainMessenger",
},
{
Name: "L2StandardBridge",
},
{
Name: "L2ToL1MessagePasser",
},
{
Name: "SequencerFeeVault",
},
}
return Build(deployments)
}
// Build will deploy contracts to a simulated backend so that their immutables
// can be properly set. The bytecode returned in the results is suitable to be
// inserted into the state via state surgery.
func Build(deployments []Deployment) (DeploymentResults, error) {
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
15000000,
)
results := make(DeploymentResults)
opts, err := bind.NewKeyedTransactorWithChainID(testKey, chainID)
if err != nil {
return nil, err
}
for _, deployment := range deployments {
var addr common.Address
switch deployment.Name {
case "GasPriceOracle":
// The owner of the gas price oracle is not immutable, not required
// to be set here. It cannot be `address(0)`
owner := common.Address{1}
addr, _, _, err = bindings.DeployGasPriceOracle(opts, backend, owner)
if err != nil {
return nil, err
}
case "L1Block":
// No arguments required for the L1Block contract
addr, _, _, err = bindings.DeployL1Block(opts, backend)
if err != nil {
return nil, err
}
case "L2CrossDomainMessenger":
// The L1CrossDomainMessenger value is not immutable, no need to set
// it here correctly
l1CrossDomainMessenger := common.Address{}
addr, _, _, err = bindings.DeployL2CrossDomainMessenger(opts, backend, l1CrossDomainMessenger)
if err != nil {
return nil, err
}
case "L2StandardBridge":
// The OtherBridge value is not immutable, no need to set
otherBridge := common.Address{}
addr, _, _, err = bindings.DeployL2StandardBridge(opts, backend, otherBridge)
case "L2ToL1MessagePasser":
// No arguments required for L2ToL1MessagePasser
addr, _, _, err = bindings.DeployL2ToL1MessagePasser(opts, backend)
case "SequencerFeeVault":
// No arguments to SequencerFeeVault
addr, _, _, err = bindings.DeploySequencerFeeVault(opts, backend)
default:
return nil, fmt.Errorf("unknown contract: %s", deployment.Name)
}
backend.Commit()
if addr == (common.Address{}) {
return nil, fmt.Errorf("no address for %s", deployment.Name)
}
code, err := backend.CodeAt(context.Background(), addr, nil)
if len(code) == 0 {
return nil, fmt.Errorf("no code found for %s", deployment.Name)
}
if err != nil {
return nil, fmt.Errorf("cannot fetch code for %s", deployment.Name)
}
results[deployment.Name] = code
}
return results, nil
}
package immutables_test
import (
"testing"
"github.com/ethereum-optimism/optimism/state-surgery/immutables"
"github.com/stretchr/testify/require"
)
func TestBuildOptimism(t *testing.T) {
results, err := immutables.OptimismBuild()
require.Nil(t, err)
require.NotNil(t, results)
// Only the exact contracts that we care about are being
// modified
require.Equal(t, len(results), 6)
contracts := map[string]bool{
"GasPriceOracle": true,
"L1Block": true,
"L2CrossDomainMessenger": true,
"L2StandardBridge": true,
"L2ToL1MessagePasser": true,
"SequencerFeeVault": true,
}
for name, bytecode := range results {
// There is bytecode there
require.Greater(t, len(bytecode), 0)
// It is in the set of contracts that we care about
require.True(t, contracts[name])
}
}
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