Skip to content
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

Add shell script for prod deployments + modify Makefile accordingly #972

Open
wants to merge 9 commits into
base: foundry
Choose a base branch
from
41 changes: 39 additions & 2 deletions packages/foundry/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ deploy:
forge script $(DEPLOY_SCRIPT) --rpc-url localhost --broadcast --legacy --ffi; \
fi \
else \
forge script $(DEPLOY_SCRIPT) --rpc-url $(RPC_URL) --broadcast --legacy --ffi; \
if [ -n "$(ETH_KEYSTORE_ACCOUNT)" ]; then \
echo "Using keystore: $(ETH_KEYSTORE_ACCOUNT)"; \
forge script $(DEPLOY_SCRIPT) --rpc-url $$RPC_URL --broadcast --legacy --ffi --account ~/.foundry/keystores/"$(ETH_KEYSTORE_ACCOUNT)"; \
else \
KEYSTORE=$$(bash scripts/select_keystore.sh "$$RPC_URL"); \
if [ -n "$$KEYSTORE" ]; then \
forge script $(DEPLOY_SCRIPT) --rpc-url $$RPC_URL --broadcast --legacy --ffi --account ~/.foundry/keystores/"$$KEYSTORE"; \
else \
echo "Keystore selection failed. Aborting deployment."; \
exit 1; \
fi; \
fi; \
fi

# Deploy and generate ABIs
Expand All @@ -53,7 +64,6 @@ account:
# Generate a new account
account-generate:
@cast wallet import $(ACCOUNT_NAME) --private-key $$(cast wallet new | grep 'Private key:' | awk '{print $$3}')
@echo "Please update .env file with ETH_KEYSTORE_ACCOUNT=$(ACCOUNT_NAME)"

# Import an existing account
account-import:
Expand All @@ -63,6 +73,29 @@ account-import:
compile:
forge compile

# Deploy and verify
deploy-verify:
@if [ ! -f "$(DEPLOY_SCRIPT)" ]; then \
echo "Error: Deploy script '$(DEPLOY_SCRIPT)' not found"; \
exit 1; \
fi
@if [ "$(RPC_URL)" = "localhost" ]; then \
if [ "$(ETH_KEYSTORE_ACCOUNT)" = "scaffold-eth-default" ]; then \
forge script $(DEPLOY_SCRIPT) --rpc-url localhost --password localhost --broadcast --legacy --ffi --verify; \
else \
forge script $(DEPLOY_SCRIPT) --rpc-url localhost --broadcast --legacy --ffi --verify; \
fi \
else \
KEYSTORE=$$(bash scripts/select_keystore.sh); \
if [ -n "$$KEYSTORE" ]; then \
forge script $(DEPLOY_SCRIPT) --rpc-url $(RPC_URL) --broadcast --legacy --ffi --verify --account ~/.foundry/keystores/"$$KEYSTORE"; \
else \
echo "Keystore selection failed. Aborting deployment."; \
exit 1; \
fi; \
fi
node scripts-js/generateTsAbis.js

# Flatten contracts
flatten:
forge flatten
Expand All @@ -75,6 +108,10 @@ format:
lint:
forge fmt --check && prettier --check ./script/**/*.js

# Run tests
test:
forge test

# Verify contracts
verify:
forge script script/VerifyAll.s.sol --ffi --rpc-url $(RPC_URL)
81 changes: 49 additions & 32 deletions packages/foundry/scripts-js/parseArgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ config();
const args = process.argv.slice(2);
let fileName = "Deploy.s.sol";
let network = "localhost";
let specifiedKeystore = null;

// Show help message if --help is provided
if (args.includes("--help") || args.includes("-h")) {
Expand All @@ -20,10 +21,11 @@ Usage: yarn deploy [options]
Options:
--file <filename> Specify the deployment script file (default: Deploy.s.sol)
--network <network> Specify the network (default: localhost)
--keystore <name> Specify the keystore to use (skips interactive selection)
--help, -h Show this help message
Examples:
yarn deploy --file DeployYourContract.s.sol --network sepolia
yarn deploy --network sepolia
yarn deploy --network sepolia --keystore scaffold-eth-custom
yarn deploy --file DeployYourContract.s.sol
yarn deploy
`);
Expand All @@ -38,10 +40,14 @@ for (let i = 0; i < args.length; i++) {
} else if (args[i] === "--file" && args[i + 1]) {
fileName = args[i + 1];
i++; // Skip next arg since we used it
} else if (args[i] === "--keystore" && args[i + 1]) {
specifiedKeystore = args[i + 1];
i++; // Skip next arg since we used it
}
}

// Check if the network exists in rpc_endpoints
let rpcUrl;
try {
const foundryTomlPath = join(__dirname, "..", "foundry.toml");
const tomlString = readFileSync(foundryTomlPath, "utf-8");
Expand All @@ -54,45 +60,55 @@ try {
);
process.exit(1);
}

rpcUrl = parsedToml.rpc_endpoints[network].replace(/\${(\w+)}/g, (_, key) => {
const value = process.env[key];
if (!value) {
console.log(`\n❌ Error: Environment variable ${key} is not set!`);
process.exit(1);
}
return value;
});
} catch (error) {
console.error("\n❌ Error reading or parsing foundry.toml:", error);
process.exit(1);
}

// Check for default account on live network
if (
process.env.ETH_KEYSTORE_ACCOUNT === "scaffold-eth-default" &&
network !== "localhost"
) {
console.log(`
❌ Error: Cannot deploy to live network using default keystore account!

To deploy to ${network}, please follow these steps:

1. If you haven't generated a keystore account yet:
$ yarn generate

2. Update your .env file:
ETH_KEYSTORE_ACCOUNT='scaffold-eth-custom'
let selectedKeystore = process.env.ETH_KEYSTORE_ACCOUNT;
if (network !== "localhost") {
if (specifiedKeystore) {
// If keystore is specified, verify it exists
const keystoreResult = spawnSync(
"test",
[
"-f",
join(process.env.HOME, ".foundry", "keystores", specifiedKeystore),
],
{ stdio: "pipe" }
);

The default account (scaffold-eth-default) can only be used for localhost deployments.
`);
process.exit(0);
}
if (keystoreResult.status !== 0) {
console.error(`\n❌ Error: Keystore '${specifiedKeystore}' not found!`);
process.exit(1);
}

selectedKeystore = specifiedKeystore;
} else {
// Interactive keystore selection if not specified
const keystoreResult = spawnSync(
"bash",
[join(__dirname, "..", "scripts", "select_keystore.sh"), rpcUrl],
{ stdio: ["inherit", "pipe", "inherit"], encoding: "utf-8" }
);

if (
process.env.ETH_KEYSTORE_ACCOUNT !== "scaffold-eth-default" &&
network === "localhost"
) {
console.log(`
⚠️ Warning: Using ${process.env.ETH_KEYSTORE_ACCOUNT} keystore account on localhost.
if (keystoreResult.status !== 0) {
console.error("\n❌ Error selecting keystore");
process.exit(1);
}

You can either:
1. Enter the password for ${process.env.ETH_KEYSTORE_ACCOUNT} account
OR
2. Set the default keystore account in your .env and re-run the command to skip password prompt:
ETH_KEYSTORE_ACCOUNT='scaffold-eth-default'
`);
selectedKeystore = keystoreResult.stdout.trim();
}
process.env.ETH_KEYSTORE_ACCOUNT = selectedKeystore;
}

// Set environment variables for the make command
Expand All @@ -105,6 +121,7 @@ const result = spawnSync(
"deploy-and-generate-abis",
`DEPLOY_SCRIPT=${process.env.DEPLOY_SCRIPT}`,
`RPC_URL=${process.env.RPC_URL}`,
`ETH_KEYSTORE_ACCOUNT=${process.env.ETH_KEYSTORE_ACCOUNT}`,
],
{
stdio: "inherit",
Expand Down
131 changes: 131 additions & 0 deletions packages/foundry/scripts/select_keystore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/bin/bash

set -e

KEYSTORE_DIR="$HOME/.foundry/keystores"
RPC_URL=$1

# Function to check balance
check_balance() {
local address=$1
local rpc_url=$2
local result=$(curl -s -X POST -H "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"$address\", \"latest\"],\"id\":1}" $rpc_url)
local balance=$(echo $result | sed 's/.*"result":"\(0x[a-fA-F0-9]*\)".*/\1/')
echo $balance
}

hex_to_decimal() {
printf '%d\n' "$1"
}

# Function to convert wei to ether (simplified, shows only up to 6 decimal places)
wei_to_ether() {
awk -v wei="$1" 'BEGIN {printf "%.6f\n", wei / 1000000000000000000}'
}

# Function to display balance
display_balance() {
local address=$1
local rpc_url=$2
local balance_hex=$(check_balance "$address" "$rpc_url")
local balance_wei=$(hex_to_decimal "$balance_hex")
local balance_eth=$(wei_to_ether "$balance_wei")
echo "Current balance: $balance_eth ETH" >&2
}

# Check if the keystore directory exists
if [ ! -d "$KEYSTORE_DIR" ]; then
echo "No keystores found in $KEYSTORE_DIR, generating a new account..." >&2
read -p "Enter a name for your new keystore: " keystore_name
yarn account:generate --name "$keystore_name"
if [ $? -ne 0 ]; then
echo "Failed to generate a new account" >&2
exit 1
fi
fi

# List all keystores
keystores=($(ls "$KEYSTORE_DIR" | grep -v "scaffold-eth-default"))

if [ ${#keystores[@]} -eq 0 ]; then
echo "No keystores found in $KEYSTORE_DIR, generating a new account..." >&2
read -p "Enter a name for your new keystore: " keystore_name
yarn account:generate --name "$keystore_name"
if [ $? -ne 0 ]; then
echo "Failed to generate a new account" >&2
exit 1
fi
keystores=($(ls "$KEYSTORE_DIR"))
fi

echo "You can also create a new keystore with a custom name by running 'yarn account:generate --name <name>'" >&2
echo "Available options/keystores:" >&2
echo "0. Create new keystore" >&2
for i in "${!keystores[@]}"; do
echo "$((i+1)). ${keystores[$i]}" >&2
done

# Prompt user to select a keystore
read -p "Select a keystore by entering the number (0-${#keystores[@]}): " selection

# Validate selection
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 0 ] || [ "$selection" -gt "${#keystores[@]}" ]; then
echo "Invalid selection" >&2
exit 1
fi

if [ "$selection" -eq 0 ]; then
read -p "Enter a name for your new keystore: " keystore_name
echo "Generating a new keystore with the name: $keystore_name" >&2
output=$(yarn account:generate --name "$keystore_name" 2>&1)
if [ $? -ne 0 ]; then
echo "Failed to generate a new account" >&2
echo "$output" >&2
exit 1
fi
# Extract the address from the output
address=$(echo "$output" | grep -o '0x[0-9a-fA-F]\{40\}')
if [ -z "$address" ]; then
echo "Failed to extract address from output" >&2
echo "$output" >&2
exit 1
fi
selected_keystore="$keystore_name"
echo "New keystore '$keystore_name' created and selected." >&2
else
# Get the selected keystore
selected_keystore="${keystores[$((selection-1))]}"
fi

# After selecting or creating the keystore
if [ -n "$selected_keystore" ]; then
# Get the address from the keystore if not already set
if [ -z "$address" ]; then
address=$(cast wallet address --account $KEYSTORE_DIR/"$selected_keystore")
fi
echo "Selected address: $address" >&2

while true; do
display_balance $address $RPC_URL

read -p "Do you want to proceed with deployment? (y/n/c for check balance again): " confirm
case $confirm in
[Yy]* )
break
;;
[Nn]* )
echo "Deployment cancelled." >&2
exit 1
;;
[Cc]* )
continue
;;
* )
echo "Please answer y (yes), n (no), or c (check balance again)." >&2
;;
esac
done
fi

# Output only the selected keystore filename
echo "$selected_keystore"