Skip to content

Commit 06730b8

Browse files
committed
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.
1 parent 2b18d37 commit 06730b8

5 files changed

Lines changed: 204 additions & 78 deletions

File tree

main.star

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,13 @@ def run(plan, args={}):
9999
wait_for_sync.wait_for_startup(plan, l1_config_env_vars)
100100

101101
deployment_output = contract_deployer.deploy_contracts(
102-
plan,
103-
l1_priv_key,
104-
l1_config_env_vars,
105-
optimism_args,
106-
l1_network,
107-
altda_deploy_config,
102+
plan=plan,
103+
priv_key=l1_priv_key,
104+
l1_config_env_vars=l1_config_env_vars,
105+
optimism_args=optimism_args,
106+
l1_network=l1_network,
107+
altda_args=altda_deploy_config,
108+
registry=registry,
108109
)
109110

110111
jwt_file = plan.upload_files(

src/contracts/contract_deployer.star

Lines changed: 127 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ FACTORY_DEPLOYER_CODE = "0xf8a58085174876e800830186a08080b853604580600e600039806
66

77
FUND_SCRIPT_FILEPATH = "../../static_files/scripts"
88

9-
utils = import_module("../util.star")
9+
_utils = import_module("../util.star")
1010
_filter = import_module("../util/filter.star")
11+
_registry = import_module("/src/package_io/registry.star")
12+
_file = import_module("/src/util/file.star")
1113

1214
ethereum_package_genesis_constants = import_module(
1315
"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):
7274

7375

7476
def deploy_contracts(
75-
plan, priv_key, l1_config_env_vars, optimism_args, l1_network, altda_args
77+
plan, priv_key, l1_config_env_vars, optimism_args, l1_network, altda_args, registry
7678
):
7779
l2_chain_ids_list = [
7880
str(chain.network_params.network_id) for chain in optimism_args.chains
@@ -116,10 +118,11 @@ def deploy_contracts(
116118
name="op-deployer-fund-script",
117119
)
118120

121+
# fund wallets
119122
plan.run_sh(
120123
name="op-deployer-fund",
121124
description="Collect keys, and fund addresses",
122-
image=utils.DEPLOYMENT_UTILS_IMAGE,
125+
image=_utils.DEPLOYMENT_UTILS_IMAGE,
123126
env_vars={
124127
"DEPLOYER_PRIVATE_KEY": priv_key,
125128
"FUND_PRIVATE_KEY": ethereum_package_genesis_constants.PRE_FUNDED_ACCOUNTS[
@@ -142,6 +145,103 @@ def deploy_contracts(
142145
run='bash /fund-script/fund.sh "{0}"'.format(l2_chain_ids),
143146
)
144147

148+
apply_files = {
149+
"/network-data": op_deployer_init.files_artifacts[0],
150+
}
151+
152+
# deploy safes if necessary
153+
safes_params = optimism_args.op_contract_deployer_params.safes
154+
155+
if safes_params.enabled:
156+
roles = {}
157+
for name, spec in safes_params.roles.items():
158+
parts = spec.split(":")
159+
threshold = int(parts[0])
160+
num_owners = int(parts[1])
161+
owners = [
162+
ethereum_package_genesis_constants.PRE_FUNDED_ACCOUNTS[
163+
i
164+
].address
165+
for i in range(num_owners)
166+
]
167+
roles[name] = {
168+
"threshold": threshold,
169+
"owners": owners,
170+
}
171+
172+
safe_utils_image = safes_params.image
173+
pk = priv_key
174+
node_url = l1_config_env_vars["L1_RPC_URL"]
175+
mount_point = "/safes-data"
176+
contracts_path = "{0}/contracts.json".format(mount_point)
177+
safes_path = "{0}/safes.json".format(mount_point)
178+
roles_path = "/roles/roles.json"
179+
180+
# first deploy the contracts if need be
181+
safe = plan.run_sh(
182+
name="op-deployer-deploy-safe-contracts",
183+
description="Deploy safe contracts",
184+
image=safe_utils_image,
185+
store=[
186+
StoreSpec(
187+
src=mount_point,
188+
name="safe-utils-data",
189+
)
190+
],
191+
env_vars={
192+
"PK": pk,
193+
"NODE_URL": node_url,
194+
"OUTPUT": contracts_path,
195+
},
196+
run=" && ".join(
197+
[
198+
"mkdir -p {0}".format(mount_point),
199+
"/deploy_contracts.sh",
200+
]
201+
),
202+
)
203+
204+
# then create the roles spec file
205+
roles_spec = _file.from_string(
206+
plan,
207+
"/roles.json",
208+
json.encode(roles),
209+
"op-deployer-roles-spec",
210+
"Rendered roles spec",
211+
)
212+
213+
# then create the safes
214+
plan.run_sh(
215+
name="op-deployer-deploy-safes",
216+
description="Deploy safes",
217+
image=safe_utils_image,
218+
env_vars={
219+
"PK": pk,
220+
"NODE_URL": node_url,
221+
"CONTRACTS_JSON": contracts_path,
222+
"ROLES_JSON": roles_path,
223+
"OUTPUT": safes_path,
224+
},
225+
files={
226+
mount_point: safe.files_artifacts[0],
227+
"/roles": roles_spec,
228+
},
229+
store=[
230+
StoreSpec(
231+
src=mount_point,
232+
name="safe-utils-data",
233+
)
234+
],
235+
run=" && ".join(
236+
[
237+
"mkdir -p {0}".format(mount_point),
238+
"/provision_wallets.sh",
239+
]
240+
),
241+
)
242+
243+
apply_files[mount_point] = safe.files_artifacts[0]
244+
145245
hardfork_schedule = []
146246
for index, chain in enumerate(optimism_args.chains):
147247
np = chain.network_params
@@ -168,13 +268,9 @@ def deploy_contracts(
168268
"l1ContractsLocator": l1_artifacts_locator,
169269
"l2ContractsLocator": l2_artifacts_locator,
170270
"superchainRoles": {
171-
"superchainGuardian": read_chain_cmd("l1ProxyAdmin", l2_chain_ids_list[0]),
172-
"protocolVersionsOwner": read_chain_cmd(
173-
"l1ProxyAdmin", l2_chain_ids_list[0]
174-
),
175-
"superchainProxyAdminOwner": read_chain_cmd(
176-
"l1ProxyAdmin", l2_chain_ids_list[0]
177-
),
271+
"superchainGuardian": _read_chain_cmd(role="superchainGuardian", fallback="l1ProxyAdmin", chain_id=l2_chain_ids_list[0]),
272+
"protocolVersionsOwner": _read_chain_cmd(role="protocolVersionsOwner", fallback="l1ProxyAdmin", chain_id=l2_chain_ids_list[0]),
273+
"superchainProxyAdminOwner": _read_chain_cmd(role="superchainProxyAdminOwner", fallback="l1ProxyAdmin", chain_id=l2_chain_ids_list[0]),
178274
},
179275
"chains": [],
180276
}
@@ -195,6 +291,7 @@ def deploy_contracts(
195291
overrides = _filter.remove_none(optimism_args.op_contract_deployer_params.overrides)
196292
vm_type = overrides.get("vmType", "CANNON")
197293

294+
198295
for i, chain in enumerate(optimism_args.chains):
199296
chain_id = str(chain.network_params.network_id)
200297
intent_chain = dict(CANNED_VALUES)
@@ -206,21 +303,17 @@ def deploy_contracts(
206303
True if chain.network_params.fund_dev_accounts else False
207304
),
208305
},
209-
"baseFeeVaultRecipient": read_chain_cmd(
210-
"baseFeeVaultRecipient", chain_id
211-
),
212-
"l1FeeVaultRecipient": read_chain_cmd("l1FeeVaultRecipient", chain_id),
213-
"sequencerFeeVaultRecipient": read_chain_cmd(
214-
"sequencerFeeVaultRecipient", chain_id
215-
),
306+
"baseFeeVaultRecipient": _read_chain_cmd(role="baseFeeVaultRecipient", chain_id=chain_id),
307+
"l1FeeVaultRecipient": _read_chain_cmd(role="l1FeeVaultRecipient", chain_id=chain_id),
308+
"sequencerFeeVaultRecipient": _read_chain_cmd(role="sequencerFeeVaultRecipient", chain_id=chain_id),
216309
"roles": {
217-
"batcher": read_chain_cmd("batcher", chain_id),
218-
"challenger": read_chain_cmd("challenger", chain_id),
219-
"l1ProxyAdminOwner": read_chain_cmd("l1ProxyAdmin", chain_id),
220-
"l2ProxyAdminOwner": read_chain_cmd("l2ProxyAdmin", chain_id),
221-
"proposer": read_chain_cmd("proposer", chain_id),
222-
"systemConfigOwner": read_chain_cmd("systemConfigOwner", chain_id),
223-
"unsafeBlockSigner": read_chain_cmd("sequencer", chain_id),
310+
"batcher": _read_chain_cmd(role="batcher", chain_id=chain_id),
311+
"challenger": _read_chain_cmd(role="challenger", chain_id=chain_id),
312+
"l1ProxyAdminOwner": _read_chain_cmd(role="l1ProxyAdminOwner", fallback="l1ProxyAdmin", chain_id=chain_id),
313+
"l2ProxyAdminOwner": _read_chain_cmd(role="l2ProxyAdminOwner", fallback="l2ProxyAdmin", chain_id=chain_id),
314+
"proposer": _read_chain_cmd(role="proposer", chain_id=chain_id),
315+
"systemConfigOwner": _read_chain_cmd(role="systemConfigOwner", chain_id=chain_id),
316+
"unsafeBlockSigner": _read_chain_cmd(role="unsafeBlockSigner", fallback="sequencer", chain_id=chain_id),
224317
},
225318
"dangerousAdditionalDisputeGames": [
226319
{
@@ -252,22 +345,21 @@ def deploy_contracts(
252345
intent["chains"].append(intent_chain)
253346

254347
intent_json = json.encode(intent)
255-
intent_json_artifact = utils.write_to_file(plan, intent_json, "/tmp", "intent.json")
348+
intent_json_artifact = _utils.write_to_file(plan, intent_json, "/tmp", "intent.json")
349+
350+
apply_files["/tmp"] = intent_json_artifact
256351

257352
op_deployer_configure = plan.run_sh(
258353
name="op-deployer-configure",
259354
description="Configure L2 contract deployments",
260-
image=utils.DEPLOYMENT_UTILS_IMAGE,
355+
image=_utils.DEPLOYMENT_UTILS_IMAGE,
261356
store=[
262357
StoreSpec(
263358
src="/network-data",
264359
name="op-deployer-configs",
265360
)
266361
],
267-
files={
268-
"/network-data": op_deployer_init.files_artifacts[0],
269-
"/tmp": intent_json_artifact,
270-
},
362+
files=apply_files,
271363
run=" && ".join(
272364
[
273365
# zhwrd: this mess is temporary until we implement json reading for op-deployer intent file
@@ -327,7 +419,7 @@ def deploy_contracts(
327419
plan.run_sh(
328420
name="op-deployer-generate-chainspec",
329421
description="Generate chainspec",
330-
image=utils.DEPLOYMENT_UTILS_IMAGE,
422+
image=_utils.DEPLOYMENT_UTILS_IMAGE,
331423
env_vars={"CHAIN_ID": str(chain.network_params.network_id)},
332424
store=[
333425
StoreSpec(
@@ -349,5 +441,7 @@ def chain_key(index, key):
349441
return "chains.[{0}].{1}".format(index, key)
350442

351443

352-
def read_chain_cmd(filename, l2_chain_id):
353-
return "`jq -r .address /network-data/{0}-{1}.json`".format(filename, l2_chain_id)
444+
def _read_chain_cmd(role, chain_id, fallback=None):
445+
# if the role is in the safes.json file, use it
446+
# otherwise, read the fallback from the network-data file
447+
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)

src/contracts/input_parser.star

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ _net = import_module("/src/util/net.star")
33
_id = import_module("/src/util/id.star")
44
_registry = import_module("/src/package_io/registry.star")
55

6+
_DEFAULT_SAFES_ARGS = {
7+
"enabled": False,
8+
"image": None,
9+
"roles": {},
10+
}
11+
12+
613
_DEFAULT_ARGS = {
714
"image": None,
815
"l1_artifacts_locator": None,
916
"l2_artifacts_locator": None,
1017
"overrides": {},
11-
"multisig": {},
18+
"safes": _DEFAULT_SAFES_ARGS,
1219
}
1320

1421

1522
def parse(args, registry):
1623
# Any extra attributes will cause an error
17-
extra_keys = _filter.assert_keys(
24+
_filter.assert_keys(
1825
args or {},
1926
_DEFAULT_ARGS.keys(),
2027
"Invalid attributes in op-deployer configuration: {}",
@@ -34,16 +41,55 @@ def parse(args, registry):
3441
_registry.OP_CONTRACTS
3542
)
3643

37-
_validate_string_map("overrides", op_deployer_params["overrides"])
38-
_validate_string_map("multisig", op_deployer_params["multisig"])
44+
_validate_overrides(op_deployer_params["overrides"])
45+
46+
op_deployer_params["safes"] = _parse_safes(op_deployer_params["safes"], registry)
3947

4048
return struct(**op_deployer_params)
4149

4250

43-
def _validate_string_map(name, string_map):
44-
if type(string_map) != "dict":
45-
fail("{} must be a dict, got {}".format(name, type(string_map)))
51+
def _validate_overrides(overrides):
52+
if type(overrides) != "dict":
53+
fail("overrides must be a dict, got {}".format(type(overrides)))
4654

47-
for key, value in string_map.items():
55+
for key, value in overrides.items():
4856
if type(value) != "string":
49-
fail("{} must be a dict of strings, got {}".format(name, type(value)))
57+
fail("overrides must be a dict of strings, got {}".format(type(overrides)))
58+
59+
60+
def _parse_safes(safes, registry):
61+
safes = _validate_safes(safes)
62+
63+
if safes["enabled"]:
64+
safes["image"] = safes["image"] or registry.get(_registry.SAFE_UTILS)
65+
66+
return struct(**safes)
67+
68+
69+
def _validate_safes(safes):
70+
_filter.assert_keys(
71+
safes or {},
72+
_DEFAULT_SAFES_ARGS.keys(),
73+
"Invalid attributes in safes configuration: {}",
74+
)
75+
76+
safes = _DEFAULT_SAFES_ARGS | _filter.remove_none(safes or {})
77+
78+
roles = safes["roles"]
79+
if type(roles) != "dict":
80+
fail("safes.roles must be a dict, got {}".format(type(roles)))
81+
82+
for name, spec in roles.items():
83+
if type(spec) != "string":
84+
fail("safes.roles must be a dict of strings, got {}".format(type(roles)))
85+
86+
parts = spec.split(":")
87+
if len(parts) != 2:
88+
fail("Invalid safes.roles spec for {}: {}".format(name, spec))
89+
90+
threshold = int(parts[0])
91+
num_owners = int(parts[1])
92+
if num_owners < threshold:
93+
fail("safes.roles must have at least as many owners as the threshold for {}".format(name))
94+
95+
return safes

src/package_io/sanity_check.star

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,6 @@ ALTDA_DEPLOY_CONFIG_PARAMS = [
7272
"da_resolver_refund_percentage",
7373
]
7474

75-
OP_CONTRACT_DEPLOYER_PARAMS = [
76-
"image",
77-
"l1_artifacts_locator",
78-
"l2_artifacts_locator",
79-
"overrides",
80-
]
81-
82-
OP_CONTRACT_DEPLOYER_OVERRIDES = [
83-
"faultGameAbsolutePrestate",
84-
"vmType",
85-
]
86-
8775
EXTERNAL_L1_NETWORK_PARAMS = [
8876
"network_id",
8977
"rpc_kind",
@@ -178,22 +166,7 @@ def sanity_check(plan, optimism_config):
178166
if type(chains) != "dict":
179167
fail("Invalid input_args type, expected dict, got {}".format(type(chains)))
180168

181-
# If everything passes, print a message
182-
183-
if "op_contract_deployer_params" in optimism_config:
184-
validate_params(
185-
plan,
186-
optimism_config,
187-
"op_contract_deployer_params",
188-
OP_CONTRACT_DEPLOYER_PARAMS,
189-
)
190-
validate_params(
191-
plan,
192-
optimism_config["op_contract_deployer_params"],
193-
"overrides",
194-
OP_CONTRACT_DEPLOYER_OVERRIDES,
195-
)
196-
169+
# If everything passes, print a message
197170
plan.print("Sanity check for OP package passed")
198171

199172

0 commit comments

Comments
 (0)