Skip to content

Instantly share code, notes, and snippets.

@d10r
Created March 26, 2025 20:25
Show Gist options
  • Save d10r/af24be51bd650687950ba08c5755c2ea to your computer and use it in GitHub Desktop.
Save d10r/af24be51bd650687950ba08c5755c2ea to your computer and use it in GitHub Desktop.
#!/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