Commit 926ba712 authored by Joshua Gutow's avatar Joshua Gutow Committed by GitHub

plasma mode: Specify valid commitment type (#10622)

* op-node: Better logging in commitment tracking

* op-node: Properly handle differences in Keccak vs Generic Commitments

* Use daCommitmentType in deploy config

* da-server: Add option to use generic commitments

* CI: Add generic commitment

* op-chain-ops: Set daCommitmentType in testdata

* Fix comm type check

* Fix CI env var

* Set proper default for daCommitmentType

* Use string for daCommitmentType

* Use bytes cast instead of abi.encodePacked
parent 0221590b
...@@ -224,6 +224,12 @@ jobs: ...@@ -224,6 +224,12 @@ jobs:
- run: - run:
name: Copy Plasma allocs to .devnet-plasma name: Copy Plasma allocs to .devnet-plasma
command: cp -r .devnet/ .devnet-plasma/ command: cp -r .devnet/ .devnet-plasma/
- run:
name: Generate Generic Plasma allocs
command: DEVNET_PLASMA="true" GENERIC_PLASMA="true" make devnet-allocs
- run:
name: Copy Plasma allocs to .devnet-plasma
command: cp -r .devnet/ .devnet-plasma-generic/
- run: - run:
name: Generate non-FPAC allocs name: Generate non-FPAC allocs
command: make devnet-allocs command: make devnet-allocs
...@@ -251,6 +257,11 @@ jobs: ...@@ -251,6 +257,11 @@ jobs:
- ".devnet-plasma/allocs-l2.json" - ".devnet-plasma/allocs-l2.json"
- ".devnet-plasma/allocs-l2-delta.json" - ".devnet-plasma/allocs-l2-delta.json"
- ".devnet-plasma/allocs-l2-ecotone.json" - ".devnet-plasma/allocs-l2-ecotone.json"
- ".devnet-plasma-generic/allocs-l1.json"
- ".devnet-plasma-generic/addresses.json"
- ".devnet-plasma-generic/allocs-l2.json"
- ".devnet-plasma-generic/allocs-l2-delta.json"
- ".devnet-plasma-generic/allocs-l2-ecotone.json"
- "packages/contracts-bedrock/deploy-config/devnetL1.json" - "packages/contracts-bedrock/deploy-config/devnetL1.json"
- "packages/contracts-bedrock/deployments/devnetL1" - "packages/contracts-bedrock/deployments/devnetL1"
- notify-failures-on-develop - notify-failures-on-develop
...@@ -1270,6 +1281,16 @@ jobs: ...@@ -1270,6 +1281,16 @@ jobs:
- run: - run:
name: Set DEVNET_PLASMA = true name: Set DEVNET_PLASMA = true
command: echo 'export DEVNET_PLASMA=true' >> $BASH_ENV command: echo 'export DEVNET_PLASMA=true' >> $BASH_ENV
- when:
condition:
equal: ['plasma-generic', <<parameters.fpac>>]
steps:
- run:
name: Set DEVNET_PLASMA = true
command: echo 'export DEVNET_PLASMA=true' >> $BASH_ENV
- run:
name: Set GENERIC_PLASMA = true
command: echo 'export GENERIC_PLASMA=true' >> $BASH_ENV
- check-changed: - check-changed:
patterns: op-(.+),packages,ops-bedrock,bedrock-devnet patterns: op-(.+),packages,ops-bedrock,bedrock-devnet
- run: - run:
...@@ -1878,7 +1899,7 @@ workflows: ...@@ -1878,7 +1899,7 @@ workflows:
- devnet: - devnet:
matrix: matrix:
parameters: parameters:
fpac: ["legacy", "fault-proofs", "plasma"] fpac: ["legacy", "fault-proofs", "plasma", "plasma-generic"]
requires: requires:
- pnpm-monorepo - pnpm-monorepo
- op-batcher-docker-build - op-batcher-docker-build
......
...@@ -30,6 +30,7 @@ log = logging.getLogger() ...@@ -30,6 +30,7 @@ log = logging.getLogger()
DEVNET_NO_BUILD = os.getenv('DEVNET_NO_BUILD') == "true" DEVNET_NO_BUILD = os.getenv('DEVNET_NO_BUILD') == "true"
DEVNET_FPAC = os.getenv('DEVNET_FPAC') == "true" DEVNET_FPAC = os.getenv('DEVNET_FPAC') == "true"
DEVNET_PLASMA = os.getenv('DEVNET_PLASMA') == "true" DEVNET_PLASMA = os.getenv('DEVNET_PLASMA') == "true"
GENERIC_PLASMA = os.getenv('GENERIC_PLASMA') == "true"
class Bunch: class Bunch:
def __init__(self, **kwds): def __init__(self, **kwds):
...@@ -135,6 +136,8 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False): ...@@ -135,6 +136,8 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False):
deploy_config['faultGameWithdrawalDelay'] = 0 deploy_config['faultGameWithdrawalDelay'] = 0
if DEVNET_PLASMA: if DEVNET_PLASMA:
deploy_config['usePlasma'] = True deploy_config['usePlasma'] = True
if GENERIC_PLASMA:
deploy_config['daCommitmentType'] = "GenericCommitment"
write_json(paths.devnet_config_path, deploy_config) write_json(paths.devnet_config_path, deploy_config)
def devnet_l1_allocs(paths): def devnet_l1_allocs(paths):
...@@ -273,11 +276,17 @@ def devnet_deploy(paths): ...@@ -273,11 +276,17 @@ def devnet_deploy(paths):
if DEVNET_PLASMA: if DEVNET_PLASMA:
docker_env['PLASMA_ENABLED'] = 'true' docker_env['PLASMA_ENABLED'] = 'true'
docker_env['PLASMA_DA_SERVICE'] = 'false'
else: else:
docker_env['PLASMA_ENABLED'] = 'false' docker_env['PLASMA_ENABLED'] = 'false'
if GENERIC_PLASMA:
docker_env['PLASMA_GENERIC_DA'] = 'true'
docker_env['PLASMA_DA_SERVICE'] = 'true'
else:
docker_env['PLASMA_GENERIC_DA'] = 'false'
docker_env['PLASMA_DA_SERVICE'] = 'false' docker_env['PLASMA_DA_SERVICE'] = 'false'
# Bring up the rest of the services. # Bring up the rest of the services.
log.info('Bringing up `op-node`, `op-proposer` and `op-batcher`.') log.info('Bringing up `op-node`, `op-proposer` and `op-batcher`.')
run_command(['docker', 'compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher', 'artifact-server'], cwd=paths.ops_bedrock_dir, env=docker_env) run_command(['docker', 'compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher', 'artifact-server'], cwd=paths.ops_bedrock_dir, env=docker_env)
...@@ -287,7 +296,7 @@ def devnet_deploy(paths): ...@@ -287,7 +296,7 @@ def devnet_deploy(paths):
log.info('Bringing up `op-challenger`.') log.info('Bringing up `op-challenger`.')
run_command(['docker', 'compose', 'up', '-d', 'op-challenger'], cwd=paths.ops_bedrock_dir, env=docker_env) run_command(['docker', 'compose', 'up', '-d', 'op-challenger'], cwd=paths.ops_bedrock_dir, env=docker_env)
# Optionally bring up OP Plasma. # Optionally bring up Plasma Mode components.
if DEVNET_PLASMA: if DEVNET_PLASMA:
log.info('Bringing up `da-server`, `sentinel`.') # TODO(10141): We don't have public sentinel images yet log.info('Bringing up `da-server`, `sentinel`.') # TODO(10141): We don't have public sentinel images yet
run_command(['docker', 'compose', 'up', '-d', 'da-server'], cwd=paths.ops_bedrock_dir, env=docker_env) run_command(['docker', 'compose', 'up', '-d', 'da-server'], cwd=paths.ops_bedrock_dir, env=docker_env)
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
plasma "github.com/ethereum-optimism/optimism/op-plasma"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
...@@ -271,6 +272,8 @@ type DeployConfig struct { ...@@ -271,6 +272,8 @@ type DeployConfig struct {
CustomGasTokenAddress common.Address `json:"customGasTokenAddress"` CustomGasTokenAddress common.Address `json:"customGasTokenAddress"`
// UsePlasma is a flag that indicates if the system is using op-plasma // UsePlasma is a flag that indicates if the system is using op-plasma
UsePlasma bool `json:"usePlasma"` UsePlasma bool `json:"usePlasma"`
// DACommitmentType specifies the allowed commitment
DACommitmentType string `json:"daCommitmentType"`
// DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged. // DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged.
DAChallengeWindow uint64 `json:"daChallengeWindow"` DAChallengeWindow uint64 `json:"daChallengeWindow"`
// DAResolveWindow represents the block interval during which a data availability challenge can be resolved. // DAResolveWindow represents the block interval during which a data availability challenge can be resolved.
...@@ -443,6 +446,9 @@ func (d *DeployConfig) Check() error { ...@@ -443,6 +446,9 @@ func (d *DeployConfig) Check() error {
if d.DAResolveWindow == 0 { if d.DAResolveWindow == 0 {
return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using plasma mode", ErrInvalidDeployConfig) return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using plasma mode", ErrInvalidDeployConfig)
} }
if !(d.DACommitmentType == plasma.KeccakCommitmentString || d.DACommitmentType == plasma.GenericCommitmentString) {
return fmt.Errorf("%w: DACommitmentType must be either KeccakCommtiment or GenericCommitment", ErrInvalidDeployConfig)
}
} }
if d.UseCustomGasToken { if d.UseCustomGasToken {
if d.CustomGasTokenAddress == (common.Address{}) { if d.CustomGasTokenAddress == (common.Address{}) {
...@@ -513,9 +519,10 @@ func (d *DeployConfig) CheckAddresses() error { ...@@ -513,9 +519,10 @@ func (d *DeployConfig) CheckAddresses() error {
if d.OptimismPortalProxy == (common.Address{}) { if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
} }
if d.UsePlasma && d.DAChallengeProxy == (common.Address{}) { if d.UsePlasma && d.DACommitmentType == plasma.KeccakCommitmentString && d.DAChallengeProxy == (common.Address{}) {
return fmt.Errorf("%w: DAChallengeContract cannot be address(0) when using plasma mode", ErrInvalidDeployConfig) return fmt.Errorf("%w: DAChallengeContract cannot be address(0) when using plasma mode", ErrInvalidDeployConfig)
} else if d.UsePlasma && d.DACommitmentType == plasma.GenericCommitmentString && d.DAChallengeProxy != (common.Address{}) {
return fmt.Errorf("%w: DAChallengeContract must be address(0) when using generic commitments in plasma mode", ErrInvalidDeployConfig)
} }
return nil return nil
} }
...@@ -612,6 +619,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas ...@@ -612,6 +619,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas
var plasma *rollup.PlasmaConfig var plasma *rollup.PlasmaConfig
if d.UsePlasma { if d.UsePlasma {
plasma = &rollup.PlasmaConfig{ plasma = &rollup.PlasmaConfig{
CommitmentType: d.DACommitmentType,
DAChallengeAddress: d.DAChallengeProxy, DAChallengeAddress: d.DAChallengeProxy,
DAChallengeWindow: d.DAChallengeWindow, DAChallengeWindow: d.DAChallengeWindow,
DAResolveWindow: d.DAResolveWindow, DAResolveWindow: d.DAResolveWindow,
......
...@@ -88,6 +88,7 @@ ...@@ -88,6 +88,7 @@
"useFaultProofs": false, "useFaultProofs": false,
"usePlasma": false, "usePlasma": false,
"daBondSize": 0, "daBondSize": 0,
"daCommitmentType": "KeccakCommtiment",
"daChallengeProxy": "0x0000000000000000000000000000000000000000", "daChallengeProxy": "0x0000000000000000000000000000000000000000",
"daChallengeWindow": 0, "daChallengeWindow": 0,
"daResolveWindow": 0, "daResolveWindow": 0,
......
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
plasma "github.com/ethereum-optimism/optimism/op-plasma"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
...@@ -143,12 +144,13 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * ...@@ -143,12 +144,13 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
l2Genesis.Alloc[addr] = val l2Genesis.Alloc[addr] = val
} }
var plasma *rollup.PlasmaConfig var pcfg *rollup.PlasmaConfig
if deployConf.UsePlasma { if deployConf.UsePlasma {
plasma = &rollup.PlasmaConfig{ pcfg = &rollup.PlasmaConfig{
DAChallengeAddress: l1Deployments.DataAvailabilityChallengeProxy, DAChallengeAddress: l1Deployments.DataAvailabilityChallengeProxy,
DAChallengeWindow: deployConf.DAChallengeWindow, DAChallengeWindow: deployConf.DAChallengeWindow,
DAResolveWindow: deployConf.DAResolveWindow, DAResolveWindow: deployConf.DAResolveWindow,
CommitmentType: plasma.KeccakCommitmentString,
} }
} }
...@@ -180,7 +182,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * ...@@ -180,7 +182,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
EcotoneTime: deployConf.EcotoneTime(uint64(deployConf.L1GenesisBlockTimestamp)), EcotoneTime: deployConf.EcotoneTime(uint64(deployConf.L1GenesisBlockTimestamp)),
FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)), FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)),
InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)), InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)),
PlasmaConfig: plasma, PlasmaConfig: pcfg,
} }
require.NoError(t, rollupCfg.Check()) require.NoError(t, rollupCfg.Check())
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/node/safedb" "github.com/ethereum-optimism/optimism/op-node/node/safedb"
plasma "github.com/ethereum-optimism/optimism/op-plasma"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -1256,6 +1257,7 @@ func TestPlasmaFinalityData(t *testing.T) { ...@@ -1256,6 +1257,7 @@ func TestPlasmaFinalityData(t *testing.T) {
plasmaCfg := &rollup.PlasmaConfig{ plasmaCfg := &rollup.PlasmaConfig{
DAChallengeWindow: 90, DAChallengeWindow: 90,
DAResolveWindow: 90, DAResolveWindow: 90,
CommitmentType: plasma.KeccakCommitmentString,
} }
// shoud return l1 finality if plasma is not enabled // shoud return l1 finality if plasma is not enabled
require.Equal(t, uint64(finalityLookback), calcFinalityLookback(cfg)) require.Equal(t, uint64(finalityLookback), calcFinalityLookback(cfg))
......
...@@ -92,7 +92,7 @@ func (s *PlasmaDataSource) Next(ctx context.Context) (eth.Data, error) { ...@@ -92,7 +92,7 @@ func (s *PlasmaDataSource) Next(ctx context.Context) (eth.Data, error) {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch input data with comm %x from da service: %w", s.comm, err)) return nil, NewTemporaryError(fmt.Errorf("failed to fetch input data with comm %x from da service: %w", s.comm, err))
} }
// inputs are limited to a max size to ensure they can be challenged in the DA contract. // inputs are limited to a max size to ensure they can be challenged in the DA contract.
if len(data) > plasma.MaxInputSize { if s.comm.CommitmentType() == plasma.Keccak256CommitmentType && len(data) > plasma.MaxInputSize {
s.log.Warn("input data exceeds max size", "size", len(data), "max", plasma.MaxInputSize) s.log.Warn("input data exceeds max size", "size", len(data), "max", plasma.MaxInputSize)
s.comm = nil s.comm = nil
return s.Next(ctx) return s.Next(ctx)
......
...@@ -91,6 +91,7 @@ func TestPlasmaDataSource(t *testing.T) { ...@@ -91,6 +91,7 @@ func TestPlasmaDataSource(t *testing.T) {
PlasmaConfig: &rollup.PlasmaConfig{ PlasmaConfig: &rollup.PlasmaConfig{
DAChallengeWindow: pcfg.ChallengeWindow, DAChallengeWindow: pcfg.ChallengeWindow,
DAResolveWindow: pcfg.ResolveWindow, DAResolveWindow: pcfg.ResolveWindow,
CommitmentType: plasma.KeccakCommitmentString,
}, },
} }
// keep track of random input data to validate against // keep track of random input data to validate against
...@@ -333,6 +334,7 @@ func TestPlasmaDataSourceStall(t *testing.T) { ...@@ -333,6 +334,7 @@ func TestPlasmaDataSourceStall(t *testing.T) {
PlasmaConfig: &rollup.PlasmaConfig{ PlasmaConfig: &rollup.PlasmaConfig{
DAChallengeWindow: pcfg.ChallengeWindow, DAChallengeWindow: pcfg.ChallengeWindow,
DAResolveWindow: pcfg.ResolveWindow, DAResolveWindow: pcfg.ResolveWindow,
CommitmentType: plasma.KeccakCommitmentString,
}, },
} }
...@@ -451,6 +453,7 @@ func TestPlasmaDataSourceInvalidData(t *testing.T) { ...@@ -451,6 +453,7 @@ func TestPlasmaDataSourceInvalidData(t *testing.T) {
PlasmaConfig: &rollup.PlasmaConfig{ PlasmaConfig: &rollup.PlasmaConfig{
DAChallengeWindow: pcfg.ChallengeWindow, DAChallengeWindow: pcfg.ChallengeWindow,
DAResolveWindow: pcfg.ResolveWindow, DAResolveWindow: pcfg.ResolveWindow,
CommitmentType: plasma.KeccakCommitmentString,
}, },
} }
......
...@@ -52,6 +52,8 @@ type Genesis struct { ...@@ -52,6 +52,8 @@ type Genesis struct {
type PlasmaConfig struct { type PlasmaConfig struct {
// L1 DataAvailabilityChallenge contract proxy address // L1 DataAvailabilityChallenge contract proxy address
DAChallengeAddress common.Address `json:"da_challenge_contract_address,omitempty"` DAChallengeAddress common.Address `json:"da_challenge_contract_address,omitempty"`
// CommitmentType specifies which commitment type can be used. Defaults to Keccak (type 0) if not present
CommitmentType string `json:"da_commitment_type"`
// DA challenge window value set on the DAC contract. Used in plasma mode // DA challenge window value set on the DAC contract. Used in plasma mode
// to compute when a commitment can no longer be challenged. // to compute when a commitment can no longer be challenged.
DAChallengeWindow uint64 `json:"da_challenge_window"` DAChallengeWindow uint64 `json:"da_challenge_window"`
...@@ -345,6 +347,18 @@ func validatePlasmaConfig(cfg *Config) error { ...@@ -345,6 +347,18 @@ func validatePlasmaConfig(cfg *Config) error {
if cfg.LegacyDAResolveWindow != cfg.PlasmaConfig.DAResolveWindow { if cfg.LegacyDAResolveWindow != cfg.PlasmaConfig.DAResolveWindow {
return fmt.Errorf("LegacyDAResolveWindow (%v) != PlasmaConfig.DAResolveWindow (%v)", cfg.LegacyDAResolveWindow, cfg.PlasmaConfig.DAResolveWindow) return fmt.Errorf("LegacyDAResolveWindow (%v) != PlasmaConfig.DAResolveWindow (%v)", cfg.LegacyDAResolveWindow, cfg.PlasmaConfig.DAResolveWindow)
} }
if cfg.PlasmaConfig.CommitmentType != plasma.KeccakCommitmentString {
return errors.New("Cannot set CommitmentType with the legacy config")
}
} else if cfg.PlasmaConfig != nil {
if !(cfg.PlasmaConfig.CommitmentType == plasma.KeccakCommitmentString || cfg.PlasmaConfig.CommitmentType == plasma.GenericCommitmentString) {
return fmt.Errorf("invalid commitment type: %v", cfg.PlasmaConfig.CommitmentType)
}
if cfg.PlasmaConfig.CommitmentType == plasma.KeccakCommitmentString && cfg.PlasmaConfig.DAChallengeAddress == (common.Address{}) {
return errors.New("Must set da_challenge_contract_address for keccak commitments")
} else if cfg.PlasmaConfig.CommitmentType == plasma.GenericCommitmentString && cfg.PlasmaConfig.DAChallengeAddress != (common.Address{}) {
return errors.New("Must set empty da_challenge_contract_address for generic commitments")
}
} }
return nil return nil
} }
...@@ -485,19 +499,21 @@ func (c *Config) GetOPPlasmaConfig() (plasma.Config, error) { ...@@ -485,19 +499,21 @@ func (c *Config) GetOPPlasmaConfig() (plasma.Config, error) {
if c.PlasmaConfig == nil { if c.PlasmaConfig == nil {
return plasma.Config{}, errors.New("no plasma config") return plasma.Config{}, errors.New("no plasma config")
} }
if c.PlasmaConfig.DAChallengeAddress == (common.Address{}) {
return plasma.Config{}, errors.New("missing DAChallengeAddress")
}
if c.PlasmaConfig.DAChallengeWindow == uint64(0) { if c.PlasmaConfig.DAChallengeWindow == uint64(0) {
return plasma.Config{}, errors.New("missing DAChallengeWindow") return plasma.Config{}, errors.New("missing DAChallengeWindow")
} }
if c.PlasmaConfig.DAResolveWindow == uint64(0) { if c.PlasmaConfig.DAResolveWindow == uint64(0) {
return plasma.Config{}, errors.New("missing DAResolveWindow") return plasma.Config{}, errors.New("missing DAResolveWindow")
} }
t, err := plasma.CommitmentTypeFromString(c.PlasmaConfig.CommitmentType)
if err != nil {
return plasma.Config{}, err
}
return plasma.Config{ return plasma.Config{
DAChallengeContractAddress: c.PlasmaConfig.DAChallengeAddress, DAChallengeContractAddress: c.PlasmaConfig.DAChallengeAddress,
ChallengeWindow: c.PlasmaConfig.DAChallengeWindow, ChallengeWindow: c.PlasmaConfig.DAChallengeWindow,
ResolveWindow: c.PlasmaConfig.DAResolveWindow, ResolveWindow: c.PlasmaConfig.DAResolveWindow,
CommitmentType: t,
}, nil }, nil
} }
...@@ -550,6 +566,9 @@ func (c *Config) Description(l2Chains map[string]string) string { ...@@ -550,6 +566,9 @@ func (c *Config) Description(l2Chains map[string]string) string {
banner += fmt.Sprintf(" - Interop: %s\n", fmtForkTimeOrUnset(c.InteropTime)) banner += fmt.Sprintf(" - Interop: %s\n", fmtForkTimeOrUnset(c.InteropTime))
// Report the protocol version // Report the protocol version
banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport) banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport)
if c.PlasmaConfig != nil {
banner += fmt.Sprintf("Node supports Plasma Mode with CommitmentType %v\n", c.PlasmaConfig.CommitmentType)
}
return banner return banner
} }
...@@ -569,6 +588,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { ...@@ -569,6 +588,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) {
if networkL1 == "" { if networkL1 == "" {
networkL1 = "unknown L1" networkL1 = "unknown L1"
} }
log.Info("Rollup Config", "l2_chain_id", c.L2ChainID, "l2_network", networkL2, "l1_chain_id", c.L1ChainID, log.Info("Rollup Config", "l2_chain_id", c.L2ChainID, "l2_network", networkL2, "l1_chain_id", c.L1ChainID,
"l1_network", networkL1, "l2_start_time", c.Genesis.L2Time, "l2_block_hash", c.Genesis.L2.Hash.String(), "l1_network", networkL1, "l2_start_time", c.Genesis.L2Time, "l2_block_hash", c.Genesis.L2.Hash.String(),
"l2_block_number", c.Genesis.L2.Number, "l1_block_hash", c.Genesis.L1.Hash.String(), "l2_block_number", c.Genesis.L2.Number, "l1_block_hash", c.Genesis.L1.Hash.String(),
...@@ -578,6 +598,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { ...@@ -578,6 +598,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) {
"ecotone_time", fmtForkTimeOrUnset(c.EcotoneTime), "ecotone_time", fmtForkTimeOrUnset(c.EcotoneTime),
"fjord_time", fmtForkTimeOrUnset(c.FjordTime), "fjord_time", fmtForkTimeOrUnset(c.FjordTime),
"interop_time", fmtForkTimeOrUnset(c.InteropTime), "interop_time", fmtForkTimeOrUnset(c.InteropTime),
"plasma_mode", c.PlasmaConfig != nil,
) )
} }
......
...@@ -41,7 +41,7 @@ func StartDAServer(cliCtx *cli.Context) error { ...@@ -41,7 +41,7 @@ func StartDAServer(cliCtx *cli.Context) error {
store = s3 store = s3
} }
server := plasma.NewDAServer(cliCtx.String(ListenAddrFlagName), cliCtx.Int(PortFlagName), store, l) server := plasma.NewDAServer(cliCtx.String(ListenAddrFlagName), cliCtx.Int(PortFlagName), store, l, cfg.UseGenericComm)
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
return fmt.Errorf("failed to start the DA server") return fmt.Errorf("failed to start the DA server")
......
...@@ -18,6 +18,7 @@ const ( ...@@ -18,6 +18,7 @@ const (
S3AccessKeyIDFlagName = "s3.access-key-id" S3AccessKeyIDFlagName = "s3.access-key-id"
S3AccessKeySecretFlagName = "s3.access-key-secret" S3AccessKeySecretFlagName = "s3.access-key-secret"
FileStorePathFlagName = "file.path" FileStorePathFlagName = "file.path"
GenericCommFlagName = "generic-commitment"
) )
const EnvVarPrefix = "OP_PLASMA_DA_SERVER" const EnvVarPrefix = "OP_PLASMA_DA_SERVER"
...@@ -44,6 +45,12 @@ var ( ...@@ -44,6 +45,12 @@ var (
Usage: "path to directory for file storage", Usage: "path to directory for file storage",
EnvVars: prefixEnvVars("FILESTORE_PATH"), EnvVars: prefixEnvVars("FILESTORE_PATH"),
} }
GenericCommFlag = &cli.BoolFlag{
Name: GenericCommFlagName,
Usage: "enable generic commitments for testing. Not for production use.",
EnvVars: prefixEnvVars("GENERIC_COMMITMENT"),
Value: false,
}
S3BucketFlag = &cli.StringFlag{ S3BucketFlag = &cli.StringFlag{
Name: S3BucketFlagName, Name: S3BucketFlagName,
Usage: "bucket name for S3 storage", Usage: "bucket name for S3 storage",
...@@ -80,6 +87,7 @@ var optionalFlags = []cli.Flag{ ...@@ -80,6 +87,7 @@ var optionalFlags = []cli.Flag{
S3EndpointFlag, S3EndpointFlag,
S3AccessKeyIDFlag, S3AccessKeyIDFlag,
S3AccessKeySecretFlag, S3AccessKeySecretFlag,
GenericCommFlag,
} }
func init() { func init() {
...@@ -96,6 +104,7 @@ type CLIConfig struct { ...@@ -96,6 +104,7 @@ type CLIConfig struct {
S3Endpoint string S3Endpoint string
S3AccessKeyID string S3AccessKeyID string
S3AccessKeySecret string S3AccessKeySecret string
UseGenericComm bool
} }
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
...@@ -105,6 +114,7 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig { ...@@ -105,6 +114,7 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig {
S3Endpoint: ctx.String(S3EndpointFlagName), S3Endpoint: ctx.String(S3EndpointFlagName),
S3AccessKeyID: ctx.String(S3AccessKeyIDFlagName), S3AccessKeyID: ctx.String(S3AccessKeyIDFlagName),
S3AccessKeySecret: ctx.String(S3AccessKeySecretFlagName), S3AccessKeySecret: ctx.String(S3AccessKeySecretFlagName),
UseGenericComm: ctx.Bool(GenericCommFlagName),
} }
} }
......
...@@ -3,6 +3,7 @@ package plasma ...@@ -3,6 +3,7 @@ package plasma
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
...@@ -16,12 +17,25 @@ var ErrCommitmentMismatch = errors.New("commitment mismatch") ...@@ -16,12 +17,25 @@ var ErrCommitmentMismatch = errors.New("commitment mismatch")
// CommitmentType is the commitment type prefix. // CommitmentType is the commitment type prefix.
type CommitmentType byte type CommitmentType byte
func CommitmentTypeFromString(s string) (CommitmentType, error) {
switch s {
case KeccakCommitmentString:
return Keccak256CommitmentType, nil
case GenericCommitmentString:
return GenericCommitmentType, nil
default:
return 0, fmt.Errorf("invalid commitment type: %s", s)
}
}
// CommitmentType describes the binary format of the commitment. // CommitmentType describes the binary format of the commitment.
// KeccakCommitmentType is the default commitment type for the centralized DA storage. // KeccakCommitmentStringType is the default commitment type for the centralized DA storage.
// GenericCommitmentType indicates an opaque bytestring that the op-node never opens. // GenericCommitmentType indicates an opaque bytestring that the op-node never opens.
const ( const (
Keccak256CommitmentType CommitmentType = 0 Keccak256CommitmentType CommitmentType = 0
GenericCommitmentType CommitmentType = 1 GenericCommitmentType CommitmentType = 1
KeccakCommitmentString string = "KeccakCommitment"
GenericCommitmentString string = "GenericCommitment"
) )
// CommitmentData is the binary representation of a commitment. // CommitmentData is the binary representation of a commitment.
......
...@@ -130,7 +130,7 @@ func (c *DAClient) setInput(ctx context.Context, img []byte) (CommitmentData, er ...@@ -130,7 +130,7 @@ func (c *DAClient) setInput(ctx context.Context, img []byte) (CommitmentData, er
return nil, err return nil, err
} }
comm, err := DecodeGenericCommitment(b) comm, err := DecodeCommitmentData(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -51,7 +51,7 @@ func TestDAClientPrecomputed(t *testing.T) { ...@@ -51,7 +51,7 @@ func TestDAClientPrecomputed(t *testing.T) {
ctx := context.Background() ctx := context.Background()
server := NewDAServer("127.0.0.1", 0, store, logger) server := NewDAServer("127.0.0.1", 0, store, logger, false)
require.NoError(t, server.Start()) require.NoError(t, server.Start())
...@@ -108,7 +108,7 @@ func TestDAClientService(t *testing.T) { ...@@ -108,7 +108,7 @@ func TestDAClientService(t *testing.T) {
ctx := context.Background() ctx := context.Background()
server := NewDAServer("127.0.0.1", 0, store, logger) server := NewDAServer("127.0.0.1", 0, store, logger, false)
require.NoError(t, server.Start()) require.NoError(t, server.Start())
......
...@@ -51,6 +51,8 @@ type HeadSignalFn func(eth.L1BlockRef) ...@@ -51,6 +51,8 @@ type HeadSignalFn func(eth.L1BlockRef)
type Config struct { type Config struct {
// Required for filtering contract events // Required for filtering contract events
DAChallengeContractAddress common.Address DAChallengeContractAddress common.Address
// Allowed CommitmentType
CommitmentType CommitmentType
// The number of l1 blocks after the input is committed during which one can challenge. // The number of l1 blocks after the input is committed during which one can challenge.
ChallengeWindow uint64 ChallengeWindow uint64
// The number of l1 blocks after a commitment is challenged during which one can resolve. // The number of l1 blocks after a commitment is challenged during which one can resolve.
...@@ -166,6 +168,10 @@ func (d *DA) Reset(ctx context.Context, base eth.L1BlockRef, baseCfg eth.SystemC ...@@ -166,6 +168,10 @@ func (d *DA) Reset(ctx context.Context, base eth.L1BlockRef, baseCfg eth.SystemC
// GetInput returns the input data for the given commitment bytes. blockNumber is required to lookup // GetInput returns the input data for the given commitment bytes. blockNumber is required to lookup
// the challenge status in the DataAvailabilityChallenge L1 contract. // the challenge status in the DataAvailabilityChallenge L1 contract.
func (d *DA) GetInput(ctx context.Context, l1 L1Fetcher, comm CommitmentData, blockId eth.BlockID) (eth.Data, error) { func (d *DA) GetInput(ctx context.Context, l1 L1Fetcher, comm CommitmentData, blockId eth.BlockID) (eth.Data, error) {
// If it's not the right commitment type, report it as an expired commitment in order to skip it
if d.cfg.CommitmentType != comm.CommitmentType() {
return nil, fmt.Errorf("invalid commitment type; expected: %v, got: %v: %w", d.cfg.CommitmentType, comm.CommitmentType(), ErrExpiredChallenge)
}
// If the challenge head is ahead in the case of a pipeline reset or stall, we might have synced a // If the challenge head is ahead in the case of a pipeline reset or stall, we might have synced a
// challenge event for this commitment. Otherwise we mark the commitment as part of the canonical // challenge event for this commitment. Otherwise we mark the commitment as part of the canonical
// chain so potential future challenge events can be selected. // chain so potential future challenge events can be selected.
...@@ -205,6 +211,10 @@ func (d *DA) GetInput(ctx context.Context, l1 L1Fetcher, comm CommitmentData, bl ...@@ -205,6 +211,10 @@ func (d *DA) GetInput(ctx context.Context, l1 L1Fetcher, comm CommitmentData, bl
if !notFound { if !notFound {
return data, nil return data, nil
} }
// Generic Commitments don't resolve from L1 so if we still can't find the data with out of luck
if comm.CommitmentType() == GenericCommitmentType {
return nil, ErrMissingPastWindow
}
// data not found in storage, return from challenge resolved input // data not found in storage, return from challenge resolved input
resolvedInput, err := d.state.GetResolvedInput(comm.Encode()) resolvedInput, err := d.state.GetResolvedInput(comm.Encode())
if err != nil { if err != nil {
...@@ -310,8 +320,10 @@ func (d *DA) LoadChallengeEvents(ctx context.Context, l1 L1Fetcher, block eth.Bl ...@@ -310,8 +320,10 @@ func (d *DA) LoadChallengeEvents(ctx context.Context, l1 L1Fetcher, block eth.Bl
d.log.Error("tx hash mismatch", "block", block.Number, "txIdx", i, "log", log.Index, "txHash", tx.Hash(), "receiptTxHash", log.TxHash) d.log.Error("tx hash mismatch", "block", block.Number, "txIdx", i, "log", log.Index, "txHash", tx.Hash(), "receiptTxHash", log.TxHash)
continue continue
} }
var input []byte
if d.cfg.CommitmentType == Keccak256CommitmentType {
// Decode the input from resolver tx calldata // Decode the input from resolver tx calldata
input, err := DecodeResolvedInput(tx.Data()) input, err = DecodeResolvedInput(tx.Data())
if err != nil { if err != nil {
d.log.Error("failed to decode resolved input", "block", block.Number, "txIdx", i, "err", err) d.log.Error("failed to decode resolved input", "block", block.Number, "txIdx", i, "err", err)
continue continue
...@@ -320,21 +332,27 @@ func (d *DA) LoadChallengeEvents(ctx context.Context, l1 L1Fetcher, block eth.Bl ...@@ -320,21 +332,27 @@ func (d *DA) LoadChallengeEvents(ctx context.Context, l1 L1Fetcher, block eth.Bl
d.log.Error("failed to verify commitment", "block", block.Number, "txIdx", i, "err", err) d.log.Error("failed to verify commitment", "block", block.Number, "txIdx", i, "err", err)
continue continue
} }
d.log.Debug("challenge resolved", "block", block, "txIdx", i) }
d.log.Info("challenge resolved", "block", block, "txIdx", i, "comm", comm.Encode())
d.state.SetResolvedChallenge(comm.Encode(), input, log.BlockNumber) d.state.SetResolvedChallenge(comm.Encode(), input, log.BlockNumber)
case ChallengeActive: case ChallengeActive:
d.log.Info("detected new active challenge", "block", block) d.log.Info("detected new active challenge", "block", block, "comm", comm.Encode())
d.state.SetActiveChallenge(comm.Encode(), log.BlockNumber, d.cfg.ResolveWindow) d.state.SetActiveChallenge(comm.Encode(), log.BlockNumber, d.cfg.ResolveWindow)
default: default:
d.log.Warn("skipping unknown challenge status", "block", block.Number, "tx", i, "log", log.Index, "status", status) d.log.Warn("skipping unknown challenge status", "block", block.Number, "tx", i, "log", log.Index, "status", status, "comm", comm.Encode())
} }
} }
return nil return nil
} }
// fetchChallengeLogs returns logs for challenge events if any for the given block // fetchChallengeLogs returns logs for challenge events if any for the given block
func (d *DA) fetchChallengeLogs(ctx context.Context, l1 L1Fetcher, block eth.BlockID) ([]*types.Log, error) { //cached with deposits events call so not expensive func (d *DA) fetchChallengeLogs(ctx context.Context, l1 L1Fetcher, block eth.BlockID) ([]*types.Log, error) {
var logs []*types.Log var logs []*types.Log
// Don't look at the challenge contract if there is no challenge contract.
if d.cfg.CommitmentType == GenericCommitmentType {
return logs, nil
}
//cached with deposits events call so not expensive
_, receipts, err := l1.FetchReceipts(ctx, block.Hash) _, receipts, err := l1.FetchReceipts(ctx, block.Hash)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -2,9 +2,12 @@ package plasma ...@@ -2,9 +2,12 @@ package plasma
import ( import (
"context" "context"
"crypto/rand"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/big"
"net" "net"
"net/http" "net/http"
"path" "path"
...@@ -13,7 +16,6 @@ import ( ...@@ -13,7 +16,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -31,9 +33,10 @@ type DAServer struct { ...@@ -31,9 +33,10 @@ type DAServer struct {
tls *rpc.ServerTLSConfig tls *rpc.ServerTLSConfig
httpServer *http.Server httpServer *http.Server
listener net.Listener listener net.Listener
useGenericComm bool
} }
func NewDAServer(host string, port int, store KVStore, log log.Logger) *DAServer { func NewDAServer(host string, port int, store KVStore, log log.Logger, useGenericComm bool) *DAServer {
endpoint := net.JoinHostPort(host, strconv.Itoa(port)) endpoint := net.JoinHostPort(host, strconv.Itoa(port))
return &DAServer{ return &DAServer{
log: log, log: log,
...@@ -42,6 +45,7 @@ func NewDAServer(host string, port int, store KVStore, log log.Logger) *DAServer ...@@ -42,6 +45,7 @@ func NewDAServer(host string, port int, store KVStore, log log.Logger) *DAServer
httpServer: &http.Server{ httpServer: &http.Server{
Addr: endpoint, Addr: endpoint,
}, },
useGenericComm: useGenericComm,
} }
} }
...@@ -118,9 +122,8 @@ func (d *DAServer) HandleGet(w http.ResponseWriter, r *http.Request) { ...@@ -118,9 +122,8 @@ func (d *DAServer) HandleGet(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusOK)
} }
func (d *DAServer) HandlePut(w http.ResponseWriter, r *http.Request) { func (d *DAServer) HandlePut(w http.ResponseWriter, r *http.Request) {
d.log.Info("PUT", "url", r.URL) d.log.Info("PUT", "url", r.URL)
...@@ -138,20 +141,34 @@ func (d *DAServer) HandlePut(w http.ResponseWriter, r *http.Request) { ...@@ -138,20 +141,34 @@ func (d *DAServer) HandlePut(w http.ResponseWriter, r *http.Request) {
} }
if r.URL.Path == "/put" || r.URL.Path == "/put/" { // without commitment if r.URL.Path == "/put" || r.URL.Path == "/put/" { // without commitment
var comm []byte
if d.useGenericComm {
n, err := rand.Int(rand.Reader, big.NewInt(99999999999999))
if err != nil {
d.log.Error("Failed to generate commitment", "err", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
comm = append(comm, 0x01)
comm = append(comm, 0xff)
comm = append(comm, n.Bytes()...)
comm := GenericCommitment(crypto.Keccak256Hash(input).Bytes()) } else {
if err = d.store.Put(r.Context(), comm.Encode(), input); err != nil { comm = NewKeccak256Commitment(input).Encode()
}
if err = d.store.Put(r.Context(), comm, input); err != nil {
d.log.Error("Failed to store commitment to the DA server", "err", err, "comm", comm) d.log.Error("Failed to store commitment to the DA server", "err", err, "comm", comm)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
d.log.Info("stored commitment", "key", hex.EncodeToString(comm), "input_len", len(input))
if _, err := w.Write(comm); err != nil { if _, err := w.Write(comm); err != nil {
d.log.Error("Failed to write commitment request body", "err", err, "comm", comm) d.log.Error("Failed to write commitment request body", "err", err, "comm", comm)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
} else { } else {
key := path.Base(r.URL.Path) key := path.Base(r.URL.Path)
comm, err := hexutil.Decode(key) comm, err := hexutil.Decode(key)
...@@ -166,10 +183,8 @@ func (d *DAServer) HandlePut(w http.ResponseWriter, r *http.Request) { ...@@ -166,10 +183,8 @@ func (d *DAServer) HandlePut(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
}
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
}
} }
func (b *DAServer) Endpoint() string { func (b *DAServer) Endpoint() string {
......
...@@ -206,6 +206,7 @@ services: ...@@ -206,6 +206,7 @@ services:
--addr=0.0.0.0 --addr=0.0.0.0
--port=3100 --port=3100
--log.level=debug --log.level=debug
--generic-commitment="${PLASMA_GENERIC_DA}"
ports: ports:
- "3100:3100" - "3100:3100"
volumes: volumes:
......
...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
"respectedGameType": 0, "respectedGameType": 0,
"useFaultProofs": false, "useFaultProofs": false,
"usePlasma": false, "usePlasma": false,
"daCommitmentType": "KeccakCommitment",
"daChallengeWindow": 160, "daChallengeWindow": 160,
"daResolveWindow": 160, "daResolveWindow": 160,
"daBondSize": 1000000, "daBondSize": 1000000,
......
...@@ -288,8 +288,12 @@ contract Deploy is Deployer { ...@@ -288,8 +288,12 @@ contract Deploy is Deployer {
setupSuperchain(); setupSuperchain();
console.log("set up superchain!"); console.log("set up superchain!");
if (cfg.usePlasma()) { if (cfg.usePlasma()) {
bytes32 typeHash = keccak256(bytes(cfg.daCommitmentType()));
bytes32 keccakHash = keccak256(bytes("KeccakCommitment"));
if (typeHash == keccakHash) {
setupOpPlasma(); setupOpPlasma();
} }
}
setupOpChain(); setupOpChain();
console.log("set up op chain!"); console.log("set up op chain!");
} }
......
...@@ -69,6 +69,7 @@ contract DeployConfig is Script { ...@@ -69,6 +69,7 @@ contract DeployConfig is Script {
uint256 public respectedGameType; uint256 public respectedGameType;
bool public useFaultProofs; bool public useFaultProofs;
bool public usePlasma; bool public usePlasma;
string public daCommitmentType;
uint256 public daChallengeWindow; uint256 public daChallengeWindow;
uint256 public daResolveWindow; uint256 public daResolveWindow;
uint256 public daBondSize; uint256 public daBondSize;
...@@ -147,6 +148,7 @@ contract DeployConfig is Script { ...@@ -147,6 +148,7 @@ contract DeployConfig is Script {
preimageOracleChallengePeriod = stdJson.readUint(_json, "$.preimageOracleChallengePeriod"); preimageOracleChallengePeriod = stdJson.readUint(_json, "$.preimageOracleChallengePeriod");
usePlasma = _readOr(_json, "$.usePlasma", false); usePlasma = _readOr(_json, "$.usePlasma", false);
daCommitmentType = _readOr(_json, "$.daCommitmentType", "KeccakCommitment");
daChallengeWindow = _readOr(_json, "$.daChallengeWindow", 1000); daChallengeWindow = _readOr(_json, "$.daChallengeWindow", 1000);
daResolveWindow = _readOr(_json, "$.daResolveWindow", 1000); daResolveWindow = _readOr(_json, "$.daResolveWindow", 1000);
daBondSize = _readOr(_json, "$.daBondSize", 1000000000); daBondSize = _readOr(_json, "$.daBondSize", 1000000000);
...@@ -232,4 +234,16 @@ contract DeployConfig is Script { ...@@ -232,4 +234,16 @@ contract DeployConfig is Script {
function _readOr(string memory json, string memory key, address defaultValue) internal view returns (address) { function _readOr(string memory json, string memory key, address defaultValue) internal view returns (address) {
return vm.keyExists(json, key) ? stdJson.readAddress(json, key) : defaultValue; return vm.keyExists(json, key) ? stdJson.readAddress(json, key) : defaultValue;
} }
function _readOr(
string memory json,
string memory key,
string memory defaultValue
)
internal
view
returns (string memory)
{
return vm.keyExists(json, key) ? stdJson.readString(json, key) : defaultValue;
}
} }
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