Created
March 26, 2025 20:25
-
-
Save d10r/af24be51bd650687950ba08c5755c2ea to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
set -eu | |
set +x | |
# Usage: ./deploy_with_proxy.sh <contract_name> <constructor_types> <arg1> <arg2> ... | |
# Example: ./deploy_with_proxy.sh MyContract "uint256,address" 42 0x1234567890123456789012345678901234567890 | |
# Requires: RPC and PRIVKEY env vars set, build artifacts in directory out/ | |
# Uses the deterministic deployment proxy contract at 0x4e59b44847b379578588920cA78FbF26c0B4956C | |
# see https://github.com/Arachnid/deterministic-deployment-proxy for more details | |
# | |
# Note that the proxy contract will be the msg.sender from the to-be-deployed contract's perspective. | |
# The contract address will change if constructor args change. | |
# Check for required tools | |
command -v cast >/dev/null 2>&1 || { echo "Error: 'cast' is not installed. Install Foundry first."; exit 1; } | |
command -v jq >/dev/null 2>&1 || { echo "Error: 'jq' is not installed. Run 'sudo apt install jq' or equivalent."; exit 1; } | |
# Check for required env vars | |
if [ -z "$RPC" ] || [ -z "$PRIVKEY" ]; then | |
echo "Error: RPC and PRIVKEY environment variables must be set." | |
echo "Example: export RPC=http://localhost:8545 PRIVKEY=0x..." | |
exit 1 | |
fi | |
# Check for Etherscan API key | |
if [ -z "${ETHERSCAN_API_KEY:-}" ]; then | |
echo "Warning: ETHERSCAN_API_KEY is not set. Contract verification will not be possible." | |
echo "To enable verification: export ETHERSCAN_API_KEY=your_api_key" | |
fi | |
# Get deployer address and balance | |
DEPLOYER_ADDRESS=$(cast wallet address "$PRIVKEY") | |
DEPLOYER_BALANCE=$(cast balance "$DEPLOYER_ADDRESS" --rpc-url "$RPC") | |
DEPLOYER_BALANCE_ETH=$(cast --from-wei "$DEPLOYER_BALANCE") | |
echo "Deployer address: $DEPLOYER_ADDRESS" | |
echo "Deployer balance: $DEPLOYER_BALANCE_ETH ETH" | |
# Input validation | |
if [ $# -lt 2 ]; then | |
echo "Usage: $0 <contract_name> <constructor_types> [args...]" | |
echo "Example: $0 MyContract \"uint256,address\" 42 0x1234567890123456789012345678901234567890" | |
exit 1 | |
fi | |
CONTRACT_NAME="$1" | |
CONSTRUCTOR_TYPES="$2" | |
shift 2 # Remove contract_name and constructor_types from args, leaving only values | |
# Proxy address (deterministic-deployment-proxy) | |
PROXY_ADDRESS="0x4e59b44847b379578588920cA78FbF26c0B4956C" | |
# Check if proxy is deployed | |
PROXY_CODE=$(cast code "$PROXY_ADDRESS" --rpc-url "$RPC") | |
if [ "$PROXY_CODE" = "0x" ] || [ -z "$PROXY_CODE" ]; then | |
echo "Error: Proxy contract not deployed at $PROXY_ADDRESS on this chain." | |
echo "This script requires the deterministic deployment proxy to be deployed." | |
exit 1 | |
fi | |
# Fixed salt (32 bytes of zeros) | |
SALT="0x0000000000000000000000000000000000000000000000000000000000000000" | |
# Compile the contract | |
echo "Compiling contract: $CONTRACT_NAME..." | |
forge build || { echo "Error: Compilation failed."; exit 1; } | |
# Extract base bytecode | |
BYTECODE_FILE="out/${CONTRACT_NAME}.sol/${CONTRACT_NAME}.json" | |
if [ ! -f "$BYTECODE_FILE" ]; then | |
echo "Error: Bytecode file $BYTECODE_FILE not found. Check contract name and compilation." | |
exit 1 | |
fi | |
BASE_BYTECODE=$(jq -r '.bytecode.object' "$BYTECODE_FILE") | |
# Encode constructor arguments if provided | |
if [ $# -gt 0 ]; then | |
echo "Encoding constructor arguments: $CONSTRUCTOR_TYPES with values $@..." | |
ARGS=$(cast abi-encode "constructor($CONSTRUCTOR_TYPES)" "$@") || { | |
echo "Error: Failed to encode constructor arguments. Check types and values." | |
exit 1 | |
} | |
FULL_BYTECODE="${BASE_BYTECODE}${ARGS:2}" | |
else | |
echo "No constructor arguments provided." | |
FULL_BYTECODE="$BASE_BYTECODE" | |
fi | |
# Prepare proxy call data | |
DATA="${SALT}${FULL_BYTECODE:2}" | |
# Calculate deterministic address | |
BYTECODE_HASH=$(cast keccak "$FULL_BYTECODE") | |
CREATE2_INPUT="0xff${PROXY_ADDRESS:2}${SALT:2}${BYTECODE_HASH:2}" | |
DEPLOYED_ADDRESS="0x$(cast keccak "$CREATE2_INPUT" | cut -c 27-66)" | |
echo "Predicted deployment address: $DEPLOYED_ADDRESS" | |
# Check if contract is already deployed | |
EXISTING_CODE=$(cast code "$DEPLOYED_ADDRESS" --rpc-url "$RPC") | |
if [ "$EXISTING_CODE" != "0x" ] && [ -n "$EXISTING_CODE" ]; then | |
echo "Contract already deployed at $DEPLOYED_ADDRESS. Skipping deployment." | |
exit 0 | |
fi | |
# Deploy the contract | |
echo "Deploying contract via proxy at $PROXY_ADDRESS..." | |
TX_HASH=$(cast send --rpc-url "$RPC" \ | |
--private-key "$PRIVKEY" \ | |
"$PROXY_ADDRESS" \ | |
"$DATA" \ | |
--json | jq -r '.transactionHash') | |
if [ $? -ne 0 ] || [ -z "$TX_HASH" ]; then | |
echo "Error: Deployment failed." | |
exit 1 | |
fi | |
echo "Transaction sent: $TX_HASH" | |
echo "Waiting for confirmation..." | |
# Wait for transaction to be mined | |
cast receipt --rpc-url "$RPC" "$TX_HASH" > /dev/null || { | |
echo "Error: Transaction failed or reverted." | |
exit 1 | |
} | |
# Verify deployment | |
DEPLOYED_CODE=$(cast code "$DEPLOYED_ADDRESS" --rpc-url "$RPC") | |
if [ "$DEPLOYED_CODE" != "0x" ] && [ -n "$DEPLOYED_CODE" ]; then | |
echo "Contract successfully deployed at $DEPLOYED_ADDRESS!" | |
# Verify on Etherscan if API key is set | |
if [ -n "${ETHERSCAN_API_KEY:-}" ]; then | |
echo "Verifying contract on Etherscan..." | |
CHAIN_ID=$(cast chain-id --rpc-url "$RPC") | |
# Prepare constructor args if needed | |
CONSTRUCTOR_ARGS="" | |
if [ $# -gt 0 ]; then | |
CONSTRUCTOR_ARGS="--constructor-args $(cast abi-encode "constructor($CONSTRUCTOR_TYPES)" "$@")" | |
fi | |
# Run verification | |
forge verify-contract \ | |
--chain-id "$CHAIN_ID" \ | |
$CONSTRUCTOR_ARGS \ | |
--etherscan-api-key "$ETHERSCAN_API_KEY" \ | |
"$DEPLOYED_ADDRESS" \ | |
"$CONTRACT_NAME" | |
fi | |
else | |
echo "Error: Contract not found at $DEPLOYED_ADDRESS after deployment." | |
exit 1 | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment