Skip to content

Commit 2656b7a

Browse files
committed
Add command to build a contract deployment SafeTx
When provided with the ABI and bytecode of a contract, the new command `build deploy` will build a Safe transaction that can deploy the contract with the given constructor ARGUMENTs.
1 parent 4ba3682 commit 2656b7a

File tree

4 files changed

+327
-14
lines changed

4 files changed

+327
-14
lines changed

src/simple_safe/console.py

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .abi import Function
1313
from .chaindata import ChainData
1414
from .constants import (
15+
DEFAULT_CREATECALL_ADDRESS,
1516
DEFAULT_FALLBACK_ADDRESS,
1617
DEFAULT_PROXYFACTORY_ADDRESS,
1718
DEFAULT_SAFE_SINGLETON_ADDRESS,
@@ -71,6 +72,22 @@ def activate_logging():
7172
)
7273

7374

75+
def parse_argdata(
76+
args: tuple[Any, ...], argtypes: list[str], argnames: list[str]
77+
) -> dict[str, "RenderableType"]:
78+
argdata: list[tuple[str, str, "RenderableType"]] = []
79+
for i, val in enumerate(args):
80+
if isinstance(val, HexBytes):
81+
val_str = format_hexbytes(val)
82+
else:
83+
val_str = str(val)
84+
argdata.append((argtypes[i], argnames[i], val_str))
85+
return {
86+
r"[secondary]" + f"{argtype}[/secondary] {argname}": val
87+
for (argtype, argname, val) in argdata
88+
}
89+
90+
7491
def get_json_data_renderable(
7592
data: dict[str, Any], pretty: bool = False
7693
) -> "RenderableType":
@@ -216,6 +233,50 @@ def print_line_if_tty(console: "Console", output: Optional[typing.TextIO]):
216233
console.line()
217234

218235

236+
def print_createcall_info(
237+
*,
238+
address: "ChecksumAddress",
239+
method: str,
240+
operation: int,
241+
init_code: HexBytes,
242+
value: int,
243+
deployer_address: "ChecksumAddress",
244+
computed_address: "ChecksumAddress",
245+
deployer_nonce: Optional[int] = None,
246+
salt: Optional[HexBytes] = None,
247+
chaindata: Optional[ChainData] = None,
248+
):
249+
from web3.types import Wei
250+
251+
data: dict[str, "RenderableType"] = {
252+
"Deployer": deployer_address,
253+
}
254+
if method == "CREATE":
255+
data["Deployer Nonce"] = str(deployer_nonce)
256+
else:
257+
assert salt is not None
258+
data["Salt"] = HexBytes(salt).to_0x_hex()
259+
data["Init Code"] = format_hexbytes(init_code)
260+
261+
print_kvtable(
262+
"CreateCall Deployment Parameters",
263+
"",
264+
{
265+
"CreateCall": address
266+
+ (
267+
f" [ok]{SYMBOL_CHECK} CANONICAL[/ok]"
268+
if address == DEFAULT_CREATECALL_ADDRESS
269+
else ""
270+
),
271+
"Deploy Method": method,
272+
"Value": format_native_value(Wei(value), chaindata),
273+
"Operation": f"{operation} ({SafeOperation(operation).name})",
274+
},
275+
data,
276+
{"Computed Address": computed_address},
277+
)
278+
279+
219280
def print_safe_deploy_info(data: DeployParams, safe_address: "ChecksumAddress"):
220281
variant = {
221282
SafeVariant.SAFE: "Safe.sol (without events)",
@@ -385,7 +446,9 @@ def print_version(ctx: Context, param: Parameter, value: Optional[bool]) -> None
385446
ctx.exit()
386447

387448

388-
def print_web3_call_data(function: ContractCall, calldata: HexBytes) -> None:
449+
def print_web3_call_data(
450+
function: ContractCall, calldata: HexBytes, title: str = "Call Data Encoder"
451+
) -> None:
389452
argdata: list[tuple[str, str, "RenderableType"]] = []
390453
for i, argval in enumerate(function.args):
391454
if isinstance(argval, HexBytes):
@@ -400,17 +463,21 @@ def print_web3_call_data(function: ContractCall, calldata: HexBytes) -> None:
400463
and function.abi["stateMutability"] == "payable"
401464
):
402465
function_signature += " [green]💲Payable[/green]"
466+
467+
metadata: dict[str, "RenderableType"] = {}
468+
metadata["Function"] = function_signature
469+
if function.selector:
470+
metadata["Selector"] = function.selector
471+
403472
print_kvtable(
404-
"Call Data Encoder",
473+
title,
405474
"",
406-
{
407-
"Function": function_signature,
408-
"Selector": function.selector,
409-
},
410-
{
411-
r"[secondary]" + f"{argtype}[/secondary] {argname}": val
412-
for (argtype, argname, val) in argdata
413-
},
475+
metadata,
476+
parse_argdata(
477+
function.args,
478+
list(function.argtypes),
479+
list(function.argnames),
480+
),
414481
{
415482
"ABI Encoding": format_hexbytes(calldata),
416483
},

src/simple_safe/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
TRUNCATE_DATA_BYTES = 1000
1212

1313
# Safe v1.4.1 canonical addresses
14+
DEFAULT_CREATECALL_ADDRESS = "0x9b35Af71d77eaf8d7e40252370304687390A1A52"
1415
DEFAULT_FALLBACK_ADDRESS = "0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99"
1516
DEFAULT_PROXYFACTORY_ADDRESS = "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67"
1617
DEFAULT_SAFEL2_SINGLETON_ADDRESS = "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762"

0 commit comments

Comments
 (0)