From ed5b8bb2d599878182013ea8afe63998f8a78350 Mon Sep 17 00:00:00 2001 From: Yann Hodique Date: Fri, 20 Jun 2025 15:42:23 +0200 Subject: [PATCH 1/3] feat(op-deployer): migrate to per-module input parser --- src/contracts/input_parser.star | 37 +++++++++++++++++++++++++ src/package_io/input_parser.star | 28 +++---------------- src/package_io/registry.star | 4 +++ test/contracts/input_parser_test.star | 39 +++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 src/contracts/input_parser.star create mode 100644 test/contracts/input_parser_test.star diff --git a/src/contracts/input_parser.star b/src/contracts/input_parser.star new file mode 100644 index 00000000..5ffaca86 --- /dev/null +++ b/src/contracts/input_parser.star @@ -0,0 +1,37 @@ +_filter = import_module("/src/util/filter.star") +_net = import_module("/src/util/net.star") +_id = import_module("/src/util/id.star") +_registry = import_module("/src/package_io/registry.star") + +_DEFAULT_ARGS = { + "image": None, + "l1_artifacts_locator": None, + "l2_artifacts_locator": None, + "overrides": {}, +} + + +def parse(args, registry): + # Any extra attributes will cause an error + extra_keys = _filter.assert_keys( + args or {}, + _DEFAULT_ARGS.keys(), + "Invalid attributes in op-deployer configuration: {}", + ) + + op_deployer_params = _DEFAULT_ARGS | _filter.remove_none(args or {}) + + op_deployer_params["image"] = op_deployer_params["image"] or registry.get( + _registry.OP_DEPLOYER + ) + + op_deployer_params["l1_artifacts_locator"] = op_deployer_params["l1_artifacts_locator"] or registry.get( + _registry.OP_CONTRACTS + ) + + op_deployer_params["l2_artifacts_locator"] = op_deployer_params["l2_artifacts_locator"] or registry.get( + _registry.OP_CONTRACTS + ) + + return struct(**op_deployer_params) + diff --git a/src/package_io/input_parser.star b/src/package_io/input_parser.star index c6158b8d..1d44c57e 100644 --- a/src/package_io/input_parser.star +++ b/src/package_io/input_parser.star @@ -18,6 +18,7 @@ _proxyd_input_parser = import_module("/src/proxyd/input_parser.star") _supervisor_input_parser = import_module("/src/supervisor/input_parser.star") _tx_fuzzer_parser = import_module("/src/tx-fuzzer/input_parser.star") _interop_mon_input_parser = import_module("/src/interop-mon/input_parser.star") +_contracts_input_parser = import_module("/src/contracts/input_parser.star") constants = import_module("../package_io/constants.star") sanity_check = import_module("./sanity_check.star") @@ -124,16 +125,7 @@ def input_parser( challengers=results["challengers"], superchains=results["superchains"], supervisors=results["supervisors"], - op_contract_deployer_params=struct( - image=results["op_contract_deployer_params"]["image"], - l1_artifacts_locator=results["op_contract_deployer_params"][ - "l1_artifacts_locator" - ], - l2_artifacts_locator=results["op_contract_deployer_params"][ - "l2_artifacts_locator" - ], - overrides=results["op_contract_deployer_params"]["overrides"], - ), + op_contract_deployer_params=results["op_contract_deployer_params"], global_log_level=results["global_log_level"], global_node_selectors=results["global_node_selectors"], global_tolerations=results["global_tolerations"], @@ -212,11 +204,8 @@ def parse_network_params(plan, registry, input_args): # configure op-deployer - results["op_contract_deployer_params"] = default_op_contract_deployer_params( - registry - ) - results["op_contract_deployer_params"].update( - input_args.get("op_contract_deployer_params", {}) + results["op_contract_deployer_params"] = _contracts_input_parser.parse( + args=input_args.get("op_contract_deployer_params"), registry=registry ) # configure global args @@ -353,15 +342,6 @@ def _default_proposer_params(registry): } -def default_op_contract_deployer_params(registry): - return { - "image": registry.get(_registry.OP_DEPLOYER), - "l1_artifacts_locator": "https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", - "l2_artifacts_locator": "https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", - "overrides": {}, - } - - def default_ethereum_package_network_params(): return { "participants": [ diff --git a/src/package_io/registry.star b/src/package_io/registry.star index 624c783f..63e2d6ea 100644 --- a/src/package_io/registry.star +++ b/src/package_io/registry.star @@ -40,6 +40,8 @@ PROMTAIL = "promtail" OP_BLOCKSCOUT = "op-blockscout" OP_BLOCKSCOUT_VERIFIER = "op-blockscout-verifier" +OP_CONTRACTS = "op-contracts" + _DEFAULT_IMAGES = { # EL images @@ -91,6 +93,8 @@ _DEFAULT_IMAGES = { # Explorers OP_BLOCKSCOUT: "blockscout/blockscout-optimism:6.8.0", OP_BLOCKSCOUT_VERIFIER: "ghcr.io/blockscout/smart-contract-verifier:v1.9.0", + # Contracts artifact + OP_CONTRACTS: "https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", } diff --git a/test/contracts/input_parser_test.star b/test/contracts/input_parser_test.star new file mode 100644 index 00000000..43b29865 --- /dev/null +++ b/test/contracts/input_parser_test.star @@ -0,0 +1,39 @@ +_input_parser = import_module("/src/contracts/input_parser.star") + +_registry = import_module("/src/package_io/registry.star") + +_default_registry = _registry.Registry() + + +def test_contracts_input_parser_default_args(plan): + expect.eq( + _input_parser.parse( + args=None, + registry=_default_registry, + ), + struct( + image="us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.4.0-rc.2", + l1_artifacts_locator="https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", + l2_artifacts_locator="https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", + overrides={}, + ), + ) + + +def test_contracts_input_parser_custom_args(plan): + expect.eq( + _input_parser.parse( + args={ + "image": "op-deployer:latest", + "l1_artifacts_locator": "artifact://l1-artifacts", + "l2_artifacts_locator": "artifact://l2-artifacts", + }, + registry=_default_registry, + ), + struct( + image="op-deployer:latest", + l1_artifacts_locator="artifact://l1-artifacts", + l2_artifacts_locator="artifact://l2-artifacts", + overrides={}, + ), + ) \ No newline at end of file From bba9e57cc618d236874c9ce3454066eac6445dcd Mon Sep 17 00:00:00 2001 From: Yann Hodique Date: Fri, 20 Jun 2025 15:57:54 +0200 Subject: [PATCH 2/3] feat(op-deployer): make room for multisig specs in deployer args --- src/contracts/input_parser.star | 12 ++++++++++++ src/package_io/registry.star | 3 +++ test/contracts/input_parser_test.star | 4 +++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/contracts/input_parser.star b/src/contracts/input_parser.star index 5ffaca86..0a2e8d79 100644 --- a/src/contracts/input_parser.star +++ b/src/contracts/input_parser.star @@ -8,6 +8,7 @@ _DEFAULT_ARGS = { "l1_artifacts_locator": None, "l2_artifacts_locator": None, "overrides": {}, + "multisig": {}, } @@ -33,5 +34,16 @@ def parse(args, registry): _registry.OP_CONTRACTS ) + _validate_string_map("overrides", op_deployer_params["overrides"]) + _validate_string_map("multisig", op_deployer_params["multisig"]) + return struct(**op_deployer_params) + +def _validate_string_map(name, string_map): + if type(string_map) != "dict": + fail("{} must be a dict, got {}".format(name, type(string_map))) + + for key, value in string_map.items(): + if type(value) != "string": + fail("{} must be a dict of strings, got {}".format(name, type(value))) diff --git a/src/package_io/registry.star b/src/package_io/registry.star index 63e2d6ea..ba14e66a 100644 --- a/src/package_io/registry.star +++ b/src/package_io/registry.star @@ -41,6 +41,7 @@ OP_BLOCKSCOUT = "op-blockscout" OP_BLOCKSCOUT_VERIFIER = "op-blockscout-verifier" OP_CONTRACTS = "op-contracts" +SAFE_UTILS = "safe-utils" _DEFAULT_IMAGES = { @@ -95,6 +96,8 @@ _DEFAULT_IMAGES = { OP_BLOCKSCOUT_VERIFIER: "ghcr.io/blockscout/smart-contract-verifier:v1.9.0", # Contracts artifact OP_CONTRACTS: "https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", + # Safe-utils + SAFE_UTILS: "us-docker.pkg.dev/oplabs-tools-artifacts/images/safe-utils:develop", } diff --git a/test/contracts/input_parser_test.star b/test/contracts/input_parser_test.star index 43b29865..e2162418 100644 --- a/test/contracts/input_parser_test.star +++ b/test/contracts/input_parser_test.star @@ -16,6 +16,7 @@ def test_contracts_input_parser_default_args(plan): l1_artifacts_locator="https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", l2_artifacts_locator="https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", overrides={}, + multisig={}, ), ) @@ -35,5 +36,6 @@ def test_contracts_input_parser_custom_args(plan): l1_artifacts_locator="artifact://l1-artifacts", l2_artifacts_locator="artifact://l2-artifacts", overrides={}, + multisig={}, ), - ) \ No newline at end of file + ) From e669f1efe7dd87791704b50925773e1213311e09 Mon Sep 17 00:00:00 2001 From: Yann Hodique Date: Fri, 20 Jun 2025 16:14:51 +0200 Subject: [PATCH 3/3] feat(op-deployer): integrate safe deployment/provisioning This adds safe contracts once L1 is ready, then provisions the safe wallets based on provided specification, and finally integrate these wallets into the op-deployer workflow. --- main.star | 13 +- src/contracts/contract_deployer.star | 190 ++++++++++++++++++++++---- src/contracts/input_parser.star | 80 +++++++++-- src/package_io/sanity_check.star | 29 +--- test/contracts/input_parser_test.star | 16 ++- 5 files changed, 247 insertions(+), 81 deletions(-) diff --git a/main.star b/main.star index 597dbcd5..f4db0641 100644 --- a/main.star +++ b/main.star @@ -99,12 +99,13 @@ def run(plan, args={}): wait_for_sync.wait_for_startup(plan, l1_config_env_vars) deployment_output = contract_deployer.deploy_contracts( - plan, - l1_priv_key, - l1_config_env_vars, - optimism_args, - l1_network, - altda_deploy_config, + plan=plan, + priv_key=l1_priv_key, + l1_config_env_vars=l1_config_env_vars, + optimism_args=optimism_args, + l1_network=l1_network, + altda_args=altda_deploy_config, + registry=registry, ) jwt_file = plan.upload_files( diff --git a/src/contracts/contract_deployer.star b/src/contracts/contract_deployer.star index aa5f3b29..0dad361b 100644 --- a/src/contracts/contract_deployer.star +++ b/src/contracts/contract_deployer.star @@ -6,8 +6,10 @@ FACTORY_DEPLOYER_CODE = "0xf8a58085174876e800830186a08080b853604580600e600039806 FUND_SCRIPT_FILEPATH = "../../static_files/scripts" -utils = import_module("../util.star") +_utils = import_module("../util.star") _filter = import_module("../util/filter.star") +_registry = import_module("/src/package_io/registry.star") +_file = import_module("/src/util/file.star") ethereum_package_genesis_constants = import_module( "github.com/ethpandaops/ethereum-package/src/prelaunch_data_generator/genesis_constants/genesis_constants.star" @@ -72,7 +74,7 @@ def _normalize_artifacts_locators(plan, l1_locator, l2_locator): def deploy_contracts( - plan, priv_key, l1_config_env_vars, optimism_args, l1_network, altda_args + plan, priv_key, l1_config_env_vars, optimism_args, l1_network, altda_args, registry ): l2_chain_ids_list = [ str(chain.network_params.network_id) for chain in optimism_args.chains @@ -116,10 +118,11 @@ def deploy_contracts( name="op-deployer-fund-script", ) + # fund wallets plan.run_sh( name="op-deployer-fund", description="Collect keys, and fund addresses", - image=utils.DEPLOYMENT_UTILS_IMAGE, + image=_utils.DEPLOYMENT_UTILS_IMAGE, env_vars={ "DEPLOYER_PRIVATE_KEY": priv_key, "FUND_PRIVATE_KEY": ethereum_package_genesis_constants.PRE_FUNDED_ACCOUNTS[ @@ -142,6 +145,101 @@ def deploy_contracts( run='bash /fund-script/fund.sh "{0}"'.format(l2_chain_ids), ) + apply_files = { + "/network-data": op_deployer_init.files_artifacts[0], + } + + # deploy safes if necessary + safes_params = optimism_args.op_contract_deployer_params.safes + + if safes_params.enabled: + roles = {} + for name, spec in safes_params.roles.items(): + parts = spec.split(":") + threshold = int(parts[0]) + num_owners = int(parts[1]) + owners = [ + ethereum_package_genesis_constants.PRE_FUNDED_ACCOUNTS[i].address + for i in range(num_owners) + ] + roles[name] = { + "threshold": threshold, + "owners": owners, + } + + safe_utils_image = safes_params.image + pk = priv_key + node_url = l1_config_env_vars["L1_RPC_URL"] + mount_point = "/safes-data" + contracts_path = "{0}/contracts.json".format(mount_point) + safes_path = "{0}/safes.json".format(mount_point) + roles_path = "/roles/roles.json" + + # first deploy the contracts if need be + safe = plan.run_sh( + name="op-deployer-deploy-safe-contracts", + description="Deploy safe contracts", + image=safe_utils_image, + store=[ + StoreSpec( + src=mount_point, + name="safe-utils-data", + ) + ], + env_vars={ + "PK": pk, + "NODE_URL": node_url, + "OUTPUT": contracts_path, + }, + run=" && ".join( + [ + "mkdir -p {0}".format(mount_point), + "/deploy_contracts.sh", + ] + ), + ) + + # then create the roles spec file + roles_spec = _file.from_string( + plan, + "/roles.json", + json.encode(roles), + "op-deployer-roles-spec", + "Rendered roles spec", + ) + + # then create the safes + plan.run_sh( + name="op-deployer-deploy-safes", + description="Deploy safes", + image=safe_utils_image, + env_vars={ + "PK": pk, + "NODE_URL": node_url, + "CONTRACTS_JSON": contracts_path, + "ROLES_JSON": roles_path, + "OUTPUT": safes_path, + }, + files={ + mount_point: safe.files_artifacts[0], + "/roles": roles_spec, + }, + store=[ + StoreSpec( + src=mount_point, + name="safe-utils-data", + ) + ], + run=" && ".join( + [ + "mkdir -p {0}".format(mount_point), + "/provision_wallets.sh", + ] + ), + ) + + apply_files[mount_point] = safe.files_artifacts[0] + hardfork_schedule = [] for index, chain in enumerate(optimism_args.chains): np = chain.network_params @@ -168,14 +266,25 @@ def deploy_contracts( "l1ContractsLocator": l1_artifacts_locator, "l2ContractsLocator": l2_artifacts_locator, "superchainRoles": { - "superchainGuardian": read_chain_cmd("l1ProxyAdmin", l2_chain_ids_list[0]), - "protocolVersionsOwner": read_chain_cmd( - "l1ProxyAdmin", l2_chain_ids_list[0] + "superchainGuardian": _read_chain_cmd( + role="superchainGuardian", + fallback="l1ProxyAdmin", + chain_id=l2_chain_ids_list[0], + ), + "protocolVersionsOwner": _read_chain_cmd( + role="protocolVersionsOwner", + fallback="l1ProxyAdmin", + chain_id=l2_chain_ids_list[0], ), - "superchainProxyAdminOwner": read_chain_cmd( - "l1ProxyAdmin", l2_chain_ids_list[0] + "superchainProxyAdminOwner": _read_chain_cmd( + role="superchainProxyAdminOwner", + fallback="l1ProxyAdmin", + chain_id=l2_chain_ids_list[0], + ), + "challenger": _read_chain_cmd( + role="challenger", + chain_id=l2_chain_ids_list[0], ), - "challenger": read_chain_cmd("challenger", l2_chain_ids_list[0]), }, "chains": [], } @@ -207,21 +316,37 @@ def deploy_contracts( True if chain.network_params.fund_dev_accounts else False ), }, - "baseFeeVaultRecipient": read_chain_cmd( - "baseFeeVaultRecipient", chain_id + "baseFeeVaultRecipient": _read_chain_cmd( + role="baseFeeVaultRecipient", chain_id=chain_id + ), + "l1FeeVaultRecipient": _read_chain_cmd( + role="l1FeeVaultRecipient", chain_id=chain_id ), - "l1FeeVaultRecipient": read_chain_cmd("l1FeeVaultRecipient", chain_id), - "sequencerFeeVaultRecipient": read_chain_cmd( - "sequencerFeeVaultRecipient", chain_id + "sequencerFeeVaultRecipient": _read_chain_cmd( + role="sequencerFeeVaultRecipient", chain_id=chain_id ), "roles": { - "batcher": read_chain_cmd("batcher", chain_id), - "challenger": read_chain_cmd("challenger", chain_id), - "l1ProxyAdminOwner": read_chain_cmd("l1ProxyAdmin", chain_id), - "l2ProxyAdminOwner": read_chain_cmd("l2ProxyAdmin", chain_id), - "proposer": read_chain_cmd("proposer", chain_id), - "systemConfigOwner": read_chain_cmd("systemConfigOwner", chain_id), - "unsafeBlockSigner": read_chain_cmd("sequencer", chain_id), + "batcher": _read_chain_cmd(role="batcher", chain_id=chain_id), + "challenger": _read_chain_cmd(role="challenger", chain_id=chain_id), + "l1ProxyAdminOwner": _read_chain_cmd( + role="l1ProxyAdminOwner", + fallback="l1ProxyAdmin", + chain_id=chain_id, + ), + "l2ProxyAdminOwner": _read_chain_cmd( + role="l2ProxyAdminOwner", + fallback="l2ProxyAdmin", + chain_id=chain_id, + ), + "proposer": _read_chain_cmd(role="proposer", chain_id=chain_id), + "systemConfigOwner": _read_chain_cmd( + role="systemConfigOwner", chain_id=chain_id + ), + "unsafeBlockSigner": _read_chain_cmd( + role="unsafeBlockSigner", + fallback="sequencer", + chain_id=chain_id, + ), }, "dangerousAdditionalDisputeGames": [ { @@ -253,22 +378,23 @@ def deploy_contracts( intent["chains"].append(intent_chain) intent_json = json.encode(intent) - intent_json_artifact = utils.write_to_file(plan, intent_json, "/tmp", "intent.json") + intent_json_artifact = _utils.write_to_file( + plan, intent_json, "/tmp", "intent.json" + ) + + apply_files["/tmp"] = intent_json_artifact op_deployer_configure = plan.run_sh( name="op-deployer-configure", description="Configure L2 contract deployments", - image=utils.DEPLOYMENT_UTILS_IMAGE, + image=_utils.DEPLOYMENT_UTILS_IMAGE, store=[ StoreSpec( src="/network-data", name="op-deployer-configs", ) ], - files={ - "/network-data": op_deployer_init.files_artifacts[0], - "/tmp": intent_json_artifact, - }, + files=apply_files, run=" && ".join( [ # zhwrd: this mess is temporary until we implement json reading for op-deployer intent file @@ -328,7 +454,7 @@ def deploy_contracts( plan.run_sh( name="op-deployer-generate-chainspec", description="Generate chainspec", - image=utils.DEPLOYMENT_UTILS_IMAGE, + image=_utils.DEPLOYMENT_UTILS_IMAGE, env_vars={"CHAIN_ID": str(chain.network_params.network_id)}, store=[ StoreSpec( @@ -350,5 +476,9 @@ def chain_key(index, key): return "chains.[{0}].{1}".format(index, key) -def read_chain_cmd(filename, l2_chain_id): - return "`jq -r .address /network-data/{0}-{1}.json`".format(filename, l2_chain_id) +def _read_chain_cmd(role, chain_id, fallback=None): + # if the role is in the safes.json file, use it + # otherwise, read the fallback from the network-data file + return "`jq -e -r .{0} /safes-data/safes.json 2>/dev/null | grep -v null || jq -e -r .address /network-data/{1}-{2}.json`".format( + role, fallback or role, chain_id + ) diff --git a/src/contracts/input_parser.star b/src/contracts/input_parser.star index 0a2e8d79..cbc09618 100644 --- a/src/contracts/input_parser.star +++ b/src/contracts/input_parser.star @@ -3,18 +3,25 @@ _net = import_module("/src/util/net.star") _id = import_module("/src/util/id.star") _registry = import_module("/src/package_io/registry.star") +_DEFAULT_SAFES_ARGS = { + "enabled": False, + "image": None, + "roles": {}, +} + + _DEFAULT_ARGS = { "image": None, "l1_artifacts_locator": None, "l2_artifacts_locator": None, "overrides": {}, - "multisig": {}, + "safes": _DEFAULT_SAFES_ARGS, } def parse(args, registry): # Any extra attributes will cause an error - extra_keys = _filter.assert_keys( + _filter.assert_keys( args or {}, _DEFAULT_ARGS.keys(), "Invalid attributes in op-deployer configuration: {}", @@ -26,24 +33,67 @@ def parse(args, registry): _registry.OP_DEPLOYER ) - op_deployer_params["l1_artifacts_locator"] = op_deployer_params["l1_artifacts_locator"] or registry.get( - _registry.OP_CONTRACTS - ) + op_deployer_params["l1_artifacts_locator"] = op_deployer_params[ + "l1_artifacts_locator" + ] or registry.get(_registry.OP_CONTRACTS) - op_deployer_params["l2_artifacts_locator"] = op_deployer_params["l2_artifacts_locator"] or registry.get( - _registry.OP_CONTRACTS - ) + op_deployer_params["l2_artifacts_locator"] = op_deployer_params[ + "l2_artifacts_locator" + ] or registry.get(_registry.OP_CONTRACTS) + + _validate_overrides(op_deployer_params["overrides"]) - _validate_string_map("overrides", op_deployer_params["overrides"]) - _validate_string_map("multisig", op_deployer_params["multisig"]) + op_deployer_params["safes"] = _parse_safes(op_deployer_params["safes"], registry) return struct(**op_deployer_params) -def _validate_string_map(name, string_map): - if type(string_map) != "dict": - fail("{} must be a dict, got {}".format(name, type(string_map))) +def _validate_overrides(overrides): + if type(overrides) != "dict": + fail("overrides must be a dict, got {}".format(type(overrides))) - for key, value in string_map.items(): + for key, value in overrides.items(): if type(value) != "string": - fail("{} must be a dict of strings, got {}".format(name, type(value))) + fail("overrides must be a dict of strings, got {}".format(type(overrides))) + + +def _parse_safes(safes, registry): + safes = _validate_safes(safes) + + if safes["enabled"]: + safes["image"] = safes["image"] or registry.get(_registry.SAFE_UTILS) + + return struct(**safes) + + +def _validate_safes(safes): + _filter.assert_keys( + safes or {}, + _DEFAULT_SAFES_ARGS.keys(), + "Invalid attributes in safes configuration: {}", + ) + + safes = _DEFAULT_SAFES_ARGS | _filter.remove_none(safes or {}) + + roles = safes["roles"] + if type(roles) != "dict": + fail("safes.roles must be a dict, got {}".format(type(roles))) + + for name, spec in roles.items(): + if type(spec) != "string": + fail("safes.roles must be a dict of strings, got {}".format(type(roles))) + + parts = spec.split(":") + if len(parts) != 2: + fail("Invalid safes.roles spec for {}: {}".format(name, spec)) + + threshold = int(parts[0]) + num_owners = int(parts[1]) + if num_owners < threshold: + fail( + "safes.roles must have at least as many owners as the threshold for {}".format( + name + ) + ) + + return safes diff --git a/src/package_io/sanity_check.star b/src/package_io/sanity_check.star index 888b249d..32c83d3f 100644 --- a/src/package_io/sanity_check.star +++ b/src/package_io/sanity_check.star @@ -72,18 +72,6 @@ ALTDA_DEPLOY_CONFIG_PARAMS = [ "da_resolver_refund_percentage", ] -OP_CONTRACT_DEPLOYER_PARAMS = [ - "image", - "l1_artifacts_locator", - "l2_artifacts_locator", - "overrides", -] - -OP_CONTRACT_DEPLOYER_OVERRIDES = [ - "faultGameAbsolutePrestate", - "vmType", -] - EXTERNAL_L1_NETWORK_PARAMS = [ "network_id", "rpc_kind", @@ -178,22 +166,7 @@ def sanity_check(plan, optimism_config): if type(chains) != "dict": fail("Invalid input_args type, expected dict, got {}".format(type(chains))) - # If everything passes, print a message - - if "op_contract_deployer_params" in optimism_config: - validate_params( - plan, - optimism_config, - "op_contract_deployer_params", - OP_CONTRACT_DEPLOYER_PARAMS, - ) - validate_params( - plan, - optimism_config["op_contract_deployer_params"], - "overrides", - OP_CONTRACT_DEPLOYER_OVERRIDES, - ) - + # If everything passes, print a message plan.print("Sanity check for OP package passed") diff --git a/test/contracts/input_parser_test.star b/test/contracts/input_parser_test.star index e2162418..4df354ff 100644 --- a/test/contracts/input_parser_test.star +++ b/test/contracts/input_parser_test.star @@ -16,7 +16,11 @@ def test_contracts_input_parser_default_args(plan): l1_artifacts_locator="https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", l2_artifacts_locator="https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-02024c5a26c16fc1a5c716fff1c46b5bf7f23890d431bb554ddbad60971211d4.tar.gz", overrides={}, - multisig={}, + safes=struct( + enabled=False, + image=None, + roles={}, + ), ), ) @@ -28,6 +32,10 @@ def test_contracts_input_parser_custom_args(plan): "image": "op-deployer:latest", "l1_artifacts_locator": "artifact://l1-artifacts", "l2_artifacts_locator": "artifact://l2-artifacts", + "safes": { + "enabled": True, + "image": "safe-utils:latest", + }, }, registry=_default_registry, ), @@ -36,6 +44,10 @@ def test_contracts_input_parser_custom_args(plan): l1_artifacts_locator="artifact://l1-artifacts", l2_artifacts_locator="artifact://l2-artifacts", overrides={}, - multisig={}, + safes=struct( + enabled=True, + image="safe-utils:latest", + roles={}, + ), ), )