-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update main.star #2019
base: main
Are you sure you want to change the base?
Update main.star #2019
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,119 +1,140 @@ | ||||||||||||||||||
import shlex | ||||||||||||||||||
|
||||||||||||||||||
SOURCE_DIR_PATH = "/app/contracts" | ||||||||||||||||||
IMAGE_FOUNDRY = "ghcr.io/foundry-rs/foundry:latest" | ||||||||||||||||||
ENTRYPOINT = ["/bin/sh"] | ||||||||||||||||||
DEPENDENCY_DIR_PATH = "/app/dependency" | ||||||||||||||||||
OUTPUT_FILE = "output.json" | ||||||||||||||||||
CONTRACTS_DIR = "contracts" | ||||||||||||||||||
DEPENDENCY_DIR = "dependency" | ||||||||||||||||||
TRANSACTION_FILE_CMD = "grep 'Transactions saved to' output.json | awk -F': ' '{print $2}'" | ||||||||||||||||||
testBaseFee = 123 | ||||||||||||||||||
|
||||||||||||||||||
def run(plan, deployment = {}): | ||||||||||||||||||
def run(plan, deployment={}): | ||||||||||||||||||
deploy_contracts(plan, deployment) | ||||||||||||||||||
|
||||||||||||||||||
# Define the function to run the Forge script for deployment | ||||||||||||||||||
def deploy_contracts(plan, deployment): | ||||||||||||||||||
repository = deployment["repository"] | ||||||||||||||||||
contracts_path = deployment["contracts_path"] | ||||||||||||||||||
script_path = deployment["script_path"] | ||||||||||||||||||
contract_name = deployment["contract_name"] | ||||||||||||||||||
rpc_url = deployment["rpc_url"] | ||||||||||||||||||
wallet = deployment["wallet"] | ||||||||||||||||||
dependency = deployment["dependency"] | ||||||||||||||||||
dependency_type = dependency["type"] | ||||||||||||||||||
|
||||||||||||||||||
# TODO: Support other wallet options such as mnemonics, keystore, hardware wallets. | ||||||||||||||||||
if wallet["type"] == "private_key": | ||||||||||||||||||
wallet_command = "--private-key {}".format(wallet["value"]) | ||||||||||||||||||
# Function to run commands and check for errors | ||||||||||||||||||
def run_command(plan, service_name, command, error_message): | ||||||||||||||||||
result = plan.exec( | ||||||||||||||||||
service_name=service_name, | ||||||||||||||||||
recipe=ExecRecipe( | ||||||||||||||||||
command=["/bin/sh", "-c", command], | ||||||||||||||||||
), | ||||||||||||||||||
) | ||||||||||||||||||
if result["code"] != 0: | ||||||||||||||||||
fail(error_message) | ||||||||||||||||||
return result | ||||||||||||||||||
|
||||||||||||||||||
# Get the wallet command | ||||||||||||||||||
def get_wallet_command(wallet): | ||||||||||||||||||
wallet_type = wallet["type"] | ||||||||||||||||||
if wallet_type == "private_key": | ||||||||||||||||||
return "--private-key {}".format(shlex.quote(wallet["value"])) | ||||||||||||||||||
else: | ||||||||||||||||||
Comment on lines
+31
to
33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for missing The code assumes that Add a check to ensure def get_wallet_command(wallet):
wallet_type = wallet["type"]
if wallet_type == "private_key":
+ if "value" not in wallet:
+ fail("Wallet value not specified for private_key type")
return "--private-key {}".format(shlex.quote(wallet["value"]))
else:
fail("Wallet type {} not supported.".format(wallet_type)) Committable suggestion
Suggested change
|
||||||||||||||||||
fail("Wallet type {} not supported.".format(wallet["type"])) | ||||||||||||||||||
fail("Wallet type {} not supported.".format(wallet_type)) | ||||||||||||||||||
|
||||||||||||||||||
folder = plan.upload_files(src = repository, name = "contracts") | ||||||||||||||||||
# Main function for deploying contracts | ||||||||||||||||||
def deploy_contracts(plan, deployment): | ||||||||||||||||||
# Fetching data from deployment with checks | ||||||||||||||||||
repository = deployment.get("repository") | ||||||||||||||||||
if not repository: | ||||||||||||||||||
fail("Repository not specified in deployment") | ||||||||||||||||||
contracts_path = deployment.get("contracts_path", "") | ||||||||||||||||||
script_path = deployment.get("script_path", "") | ||||||||||||||||||
contract_name = deployment.get("contract_name", "") | ||||||||||||||||||
rpc_url = shlex.quote(deployment.get("rpc_url", "")) | ||||||||||||||||||
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check for missing The Add checks to ensure contract_name = deployment.get("contract_name", "")
+if not contract_name:
+ fail("Contract name not specified in deployment")
rpc_url = shlex.quote(deployment.get("rpc_url", ""))
+if not deployment.get("rpc_url"):
+ fail("RPC URL not specified in deployment") Committable suggestion
Suggested change
|
||||||||||||||||||
wallet = deployment.get("wallet") | ||||||||||||||||||
if not wallet: | ||||||||||||||||||
fail("Wallet not specified") | ||||||||||||||||||
dependency = deployment.get("dependency", {}) | ||||||||||||||||||
dependency_type = dependency.get("type", "") | ||||||||||||||||||
|
||||||||||||||||||
wallet_command = get_wallet_command(wallet) | ||||||||||||||||||
|
||||||||||||||||||
folder = plan.upload_files(src=repository, name=CONTRACTS_DIR) | ||||||||||||||||||
|
||||||||||||||||||
dependency_artifact_name = "" | ||||||||||||||||||
if dependency_type == "local" or dependency_type == "git": | ||||||||||||||||||
dependency_path = dependency["path"] | ||||||||||||||||||
|
||||||||||||||||||
plan.upload_files(src = "dependency", name = "dependency") | ||||||||||||||||||
dependency_artifact_name = "dependency" | ||||||||||||||||||
dependency_path = dependency.get("path", "") | ||||||||||||||||||
plan.upload_files(src=DEPENDENCY_DIR, name=DEPENDENCY_DIR) | ||||||||||||||||||
Comment on lines
+58
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check for missing The Add a check to ensure dependency_path = dependency.get("path", "")
+if not dependency_path:
+ fail("Dependency path not specified for dependency type '{}'".format(dependency_type)) Committable suggestion
Suggested change
|
||||||||||||||||||
dependency_artifact_name = DEPENDENCY_DIR | ||||||||||||||||||
|
||||||||||||||||||
foundry_service = plan.add_service( | ||||||||||||||||||
name = "foundry", | ||||||||||||||||||
config = get_service_config(dependency_artifact_name), | ||||||||||||||||||
name="foundry", | ||||||||||||||||||
config=get_service_config(dependency_artifact_name), | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
if contracts_path: | ||||||||||||||||||
contract_path = "{}/{}".format(SOURCE_DIR_PATH, contracts_path) | ||||||||||||||||||
else: | ||||||||||||||||||
contract_path = SOURCE_DIR_PATH | ||||||||||||||||||
contract_path = "{}/{}".format(SOURCE_DIR_PATH, contracts_path) if contracts_path else SOURCE_DIR_PATH | ||||||||||||||||||
|
||||||||||||||||||
if dependency_type == "local": | ||||||||||||||||||
# Run shell script | ||||||||||||||||||
plan.exec( | ||||||||||||||||||
service_name = foundry_service.name, | ||||||||||||||||||
recipe = ExecRecipe( | ||||||||||||||||||
command = ["/bin/sh", "-c", "sh {}/{}".format(DEPENDENCY_DIR_PATH, dependency_path)], | ||||||||||||||||||
), | ||||||||||||||||||
) | ||||||||||||||||||
run_command(plan, foundry_service.name, "sh {}/{}".format(DEPENDENCY_DIR_PATH, dependency_path), "Local dependency execution failed") | ||||||||||||||||||
elif dependency_type == "git": | ||||||||||||||||||
plan.exec( | ||||||||||||||||||
service_name = foundry_service.name, | ||||||||||||||||||
recipe = ExecRecipe( | ||||||||||||||||||
command = ["/bin/sh", "-c", "cd {} && sh {}".format(contract_path, dependency_path)], | ||||||||||||||||||
), | ||||||||||||||||||
) | ||||||||||||||||||
run_command(plan, foundry_service.name, "cd {} && sh {}".format(contract_path, dependency_path), "Git dependency execution failed") | ||||||||||||||||||
Comment on lines
+70
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Secure The Apply if dependency_type == "local":
- run_command(plan, foundry_service.name, "sh {}/{}".format(DEPENDENCY_DIR_PATH, dependency_path), "Local dependency execution failed")
+ run_command(plan, foundry_service.name, "sh {}/{}".format(DEPENDENCY_DIR_PATH, shlex.quote(dependency_path)), "Local dependency execution failed")
elif dependency_type == "git":
- run_command(plan, foundry_service.name, "cd {} && sh {}".format(contract_path, dependency_path), "Git dependency execution failed")
+ run_command(plan, foundry_service.name, "cd {} && sh {}".format(contract_path, shlex.quote(dependency_path)), "Git dependency execution failed")
|
||||||||||||||||||
|
||||||||||||||||||
if script_path: | ||||||||||||||||||
result = plan.exec( | ||||||||||||||||||
service_name = foundry_service.name, | ||||||||||||||||||
recipe = ExecRecipe( | ||||||||||||||||||
command = ["/bin/sh", "-c", "cd {} && forge build".format(contract_path)], | ||||||||||||||||||
), | ||||||||||||||||||
) | ||||||||||||||||||
plan.verify(result["code"], "==", 0) | ||||||||||||||||||
# Logging the build and deployment process | ||||||||||||||||||
plan.print("Running deployment script: {}".format(script_path)) | ||||||||||||||||||
run_command(plan, foundry_service.name, "cd {} && forge build".format(contract_path), "Forge build failed") | ||||||||||||||||||
Comment on lines
+67
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanitize The Apply contract_path = "{}/{}".format(SOURCE_DIR_PATH, contracts_path) if contracts_path else SOURCE_DIR_PATH
+contract_path_quoted = shlex.quote(contract_path)
- run_command(plan, foundry_service.name, "cd {} && forge build".format(contract_path), "Forge build failed")
+ run_command(plan, foundry_service.name, "cd {} && forge build".format(contract_path_quoted), "Forge build failed")
...
- "cd {} && {}".format(contract_path, TRANSACTION_FILE_CMD)
+ "cd {} && {}".format(contract_path_quoted, TRANSACTION_FILE_CMD)
...
- "cat {}/{}".format(contract_path, OUTPUT_FILE),
+ "cat {}/{}".format(contract_path_quoted, OUTPUT_FILE), Also applies to: 83-89, 96-96, 105-105
Comment on lines
+75
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanitize The Apply plan.print("Running deployment script: {}".format(script_path))
+script_path_quoted = shlex.quote(script_path)
+contract_name_quoted = shlex.quote(contract_name)
- run_command(plan, foundry_service.name, "cd {} && forge build".format(contract_path), "Forge build failed")
+ run_command(plan, foundry_service.name, "cd {} && forge build".format(contract_path_quoted), "Forge build failed")
...
script_output = exec_on_service(
plan,
foundry_service.name,
- "cd {} && forge script {}:{} --broadcast --rpc-url {} {} --json --skip test > {}".format(
- contract_path,
- script_path,
- contract_name,
+ "cd {} && forge script {}:{} --broadcast --rpc-url {} {} --json --skip test > {}".format(
+ contract_path_quoted,
+ script_path_quoted,
+ contract_name_quoted,
rpc_url,
wallet_command,
OUTPUT_FILE
),
) Also applies to: 83-89 |
||||||||||||||||||
|
||||||||||||||||||
# Executing forge script | ||||||||||||||||||
script_output = exec_on_service( | ||||||||||||||||||
plan, | ||||||||||||||||||
foundry_service.name, | ||||||||||||||||||
"cd {} && forge script {}:{} --broadcast --rpc-url {} {} --json --skip test > output.json ".format( | ||||||||||||||||||
"cd {} && forge script {}:{} --broadcast --rpc-url {} {} --json --skip test > {}".format( | ||||||||||||||||||
contract_path, | ||||||||||||||||||
script_path, | ||||||||||||||||||
contract_name, | ||||||||||||||||||
rpc_url, | ||||||||||||||||||
wallet_command, | ||||||||||||||||||
OUTPUT_FILE | ||||||||||||||||||
Comment on lines
+83
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve readability by refactoring command construction The command string used in the Refactor the command construction as follows: script_command = "cd {} && forge script {}:{} --broadcast --rpc-url {} {} --json --skip test > {}".format(
contract_path_quoted,
script_path_quoted,
contract_name_quoted,
rpc_url,
wallet_command,
OUTPUT_FILE
)
# Then use the command in exec_on_service or run_command
run_command(
plan,
foundry_service.name,
script_command,
"Forge script execution failed",
) |
||||||||||||||||||
), | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
exec_on_service( | ||||||||||||||||||
plan, | ||||||||||||||||||
foundry_service.name, | ||||||||||||||||||
"cat {}/output.json ".format(contract_path), | ||||||||||||||||||
"cat {}/{}".format(contract_path, OUTPUT_FILE), | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
if script_path: | ||||||||||||||||||
# Get the forge script output in a output.json file and grep from it | ||||||||||||||||||
transaction_file = "grep 'Transactions saved to' output.json | awk -F': ' '{print $2}'" | ||||||||||||||||||
plan.print("transaction_file", transaction_file) | ||||||||||||||||||
|
||||||||||||||||||
transaction_file_details = exec_on_service(plan, foundry_service.name, "cd {} && {}".format(contract_path, transaction_file)) | ||||||||||||||||||
|
||||||||||||||||||
# Fetch and check the transaction file | ||||||||||||||||||
plan.print("Retrieving transaction file...") | ||||||||||||||||||
transaction_file_details = exec_on_service( | ||||||||||||||||||
plan, | ||||||||||||||||||
foundry_service.name, | ||||||||||||||||||
"cd {} && {}".format(contract_path, TRANSACTION_FILE_CMD) | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
if not transaction_file_details["output"]: | ||||||||||||||||||
fail("Transaction file not found.") | ||||||||||||||||||
exec_output = exec_on_service(plan, foundry_service.name, "chmod -R 777 /app/contracts && cat {}".format(transaction_file_details["output"])) | ||||||||||||||||||
|
||||||||||||||||||
exec_output = exec_on_service( | ||||||||||||||||||
plan, | ||||||||||||||||||
foundry_service.name, | ||||||||||||||||||
"chmod -R 777 /app/contracts && cat {}".format(transaction_file_details["output"]) | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanitize The Apply exec_output = exec_on_service(
plan,
foundry_service.name,
- "chmod -R 777 /app/contracts && cat {}".format(transaction_file_details["output"])
+ "chmod -R 777 /app/contracts && cat {}".format(shlex.quote(transaction_file_details["output"]))
)
|
||||||||||||||||||
) | ||||||||||||||||||
Comment on lines
+79
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Unify command execution and error handling Multiple commands are executed using Refactor the code to use -# Executing forge script
-script_output = exec_on_service(
- plan,
- foundry_service.name,
- "cd {} && forge script ...".format(...),
-)
+run_command(
+ plan,
+ foundry_service.name,
+ "cd {} && forge script ...".format(...),
+ "Forge script execution failed",
+)
...
-transaction_file_details = exec_on_service(
- plan,
- foundry_service.name,
- "cd {} && {}".format(contract_path_quoted, TRANSACTION_FILE_CMD)
-)
+transaction_file_details = run_command(
+ plan,
+ foundry_service.name,
+ "cd {} && {}".format(contract_path_quoted, TRANSACTION_FILE_CMD),
+ "Transaction file retrieval failed",
+)
...
-exec_output = exec_on_service(
- plan,
- foundry_service.name,
- "chmod -R 777 /app/contracts && cat {}".format(shlex.quote(transaction_file_details["output"]))
-)
+exec_output = run_command(
+ plan,
+ foundry_service.name,
+ "chmod -R 777 /app/contracts && cat {}".format(shlex.quote(transaction_file_details["output"])),
+ "Failed to read transaction file",
+)
|
||||||||||||||||||
plan.verify(exec_output["code"], "==", 0) | ||||||||||||||||||
|
||||||||||||||||||
# Function to execute commands inside the service | ||||||||||||||||||
def exec_on_service(plan, service_name, command): | ||||||||||||||||||
return plan.exec( | ||||||||||||||||||
service_name = service_name, | ||||||||||||||||||
recipe = ExecRecipe( | ||||||||||||||||||
command = ["/bin/sh", "-c", command], | ||||||||||||||||||
service_name=service_name, | ||||||||||||||||||
recipe=ExecRecipe( | ||||||||||||||||||
command=["/bin/sh", "-c", command], | ||||||||||||||||||
), | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
def get_service_config(dependency_artifact_name = None): | ||||||||||||||||||
# Service configuration | ||||||||||||||||||
def get_service_config(dependency_artifact_name=None): | ||||||||||||||||||
files = { | ||||||||||||||||||
SOURCE_DIR_PATH: "contracts", | ||||||||||||||||||
SOURCE_DIR_PATH: CONTRACTS_DIR, | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if dependency_artifact_name: | ||||||||||||||||||
files[DEPENDENCY_DIR_PATH] = dependency_artifact_name | ||||||||||||||||||
|
||||||||||||||||||
return ServiceConfig( | ||||||||||||||||||
image = IMAGE_FOUNDRY, | ||||||||||||||||||
entrypoint = ENTRYPOINT, | ||||||||||||||||||
files = files, | ||||||||||||||||||
image=IMAGE_FOUNDRY, | ||||||||||||||||||
entrypoint=ENTRYPOINT, | ||||||||||||||||||
files=files, | ||||||||||||||||||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid mutable default arguments
In the
run
function, using a mutable default argument like{}
fordeployment
can lead to unexpected behaviors because the default mutable object is shared across all calls to the function.Apply this diff to fix the issue:
Committable suggestion