Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions kurtosis-devnet/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ op-wheel-image TAG='op-wheel:devnet': (_docker_build_stack TAG "op-wheel-target"
op-faucet-image TAG='op-faucet:devnet': (_docker_build_stack TAG "op-faucet-target")
op-interop-mon-image TAG='op-interop-mon:devnet': (_docker_build_stack TAG "op-interop-mon-target")

safe-utils-image TAG='safe-utils:devnet': (_docker_build TAG "" "./safe-utils" "Dockerfile")

op-program-builder-image TAG='op-program-builder:devnet': _prerequisites
just op-program-svc/op-program-svc {{TAG}}

Expand Down
41 changes: 41 additions & 0 deletions kurtosis-devnet/safe-utils/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
FROM node:22

RUN apt-get update && apt-get install -y python3 python3-venv python3-dev curl jq
RUN python3 -m venv /venv

ARG FOUNDRY_VERSION=1.1.0
RUN curl -L https://foundry.paradigm.xyz | bash
RUN /root/.foundry/bin/foundryup -i $FOUNDRY_VERSION

ARG SAFE_SINGLETON_VERSION=v1.0.43

#ARG SAFE_CONTRACT_VERSION=v1.4.1-3
ARG SAFE_CONTRACT_VERSION=main

ARG SAFE_CLI_VERSION=1.5.0

ARG SOLIDITY_VERSION=0.8.16 # needs to be aligned between contracts and singleton

ENV SOLIDITY_VERSION=$SOLIDITY_VERSION

WORKDIR /
RUN git clone --branch $SAFE_SINGLETON_VERSION https://github.com/safe-global/safe-singleton-factory
WORKDIR /safe-singleton-factory
RUN npm i

WORKDIR /
RUN git clone --branch $SAFE_CONTRACT_VERSION https://github.com/safe-global/safe-smart-account
# force use of the pinned singleton factory
RUN jq '.devDependencies["@safe-global/safe-singleton-factory"] = "file:///safe-singleton-factory"' safe-smart-account/package.json > package.json && mv package.json safe-smart-account/
WORKDIR /safe-smart-account
RUN npm i

WORKDIR /
RUN /venv/bin/pip install -U safe-cli==$SAFE_CLI_VERSION

ADD deploy_contracts.sh .
RUN chmod +x deploy_contracts.sh
ADD provision_wallets.sh .
RUN chmod +x provision_wallets.sh

ENTRYPOINT ["/bin/bash"]
91 changes: 91 additions & 0 deletions kurtosis-devnet/safe-utils/deploy_contracts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash

# output will be a dictionary of the form
# { "contract1": "addr1", "contract2": "addr2", ... }

# Parse command line arguments
while [ $# -gt 0 ]; do
case "$1" in
--private-key)
export PK="$2"
shift 2
;;
--rpc-url)
export NODE_URL="$2"
shift 2
;;
--contracts-output)
export OUTPUT="$2"
shift 2
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 --private-key <key> --rpc-url <url> --contracts-output <file>"
exit 1
;;
esac
done

# Validate required arguments
if [ -z "$PK" ] || [ -z "$NODE_URL" ] || [ -z "$OUTPUT" ]; then
echo "Missing required arguments"
echo "Usage: $0 --private-key <key> --rpc-url <url> --contracts-output <file>"
exit 1
fi

export PATH=/root/.foundry/bin:$PATH
export RPC=$NODE_URL

# silence stupid telemetry interactive question.
export CI=1

# create a hash of the versions involved
SINGLETON_SHA1=$(git --git-dir /safe-singleton-factory/.git rev-parse HEAD)
CONTRACT_SHA1=$(git --git-dir /safe-smart-account/.git rev-parse HEAD)

# a seed for new-mnemonic entropy. It doesn't have to be super robust, just to
# differentiate between successive runs if we operate under different
# conditions.
SEED=$(jq -n \
--arg funded_wallet "$PK" \
--arg singleton_sha1 "$SINGLETON_SHA1" \
--arg contract_sha1 "$CONTRACT_SHA1" \
'{
"singleton_sha1": $singleton_sha1,
"contract_sha1": $contract_sha1,
"funded_wallet": $funded_wallet
}' | md5sum | cut -d' ' -f1)

# we need a fresh mnemonic to own the singleton factory, because the installer requires its NONCE to be 0 to succeed.
TMP_MNEMONIC=$(cast wallet new-mnemonic -e "0x$SEED" --json | jq -r '.mnemonic')

# owner of the singleton factory
OWNER_ADDR=$(cast wallet address --mnemonic "$TMP_MNEMONIC")

NONCE=$(cast nonce --rpc-url "$NODE_URL" "$OWNER_ADDR")
if [ "$NONCE" -eq 0 ]; then # otherwise, we need to assume the singleton factory is already deployed
# fund the owner of the singleton factory
cast send --rpc-url "$NODE_URL" --private-key "$PK" --value 1ether "$OWNER_ADDR"

# deploy the singleton factory
pushd /safe-singleton-factory || exit 1
MNEMONIC="$TMP_MNEMONIC" npm run estimate-compile
MNEMONIC="$TMP_MNEMONIC" npm run submit
popd || exit
fi

# This part is idempotent, so for clarify let's leave it alone. Worst case it's
# slightly wasteful when re-running.
pushd /safe-smart-account || exit 1
npx --yes hardhat --network custom deploy \
| grep 0x \
| jq -Rn '[inputs |
if contains("reusing") then
capture("reusing \"(?<key>.*)\" at (?<value>.*)")
else
capture("deploying \"(?<key>.*)\" \\(tx: [^)]+\\)\\.\\.\\.: deployed at (?<value>[0-9a-fA-Fx]+)")
end
] | from_entries' \
| tee "$OUTPUT"
npx hardhat --network custom local-verify
popd || exit
100 changes: 100 additions & 0 deletions kurtosis-devnet/safe-utils/provision_wallets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/bin/bash

# --contracts-input should point to the output of deploy_contracts.sh

# --roles-spec should be a json file structured as
# request_example.json. It associates each role to a set of owners, and
# a threshold of signatures to collect.

# The output will be of the form:
# { "role": "safe_address", ... }

# Parse command line arguments
while [ $# -gt 0 ]; do
case "$1" in
--private-key)
export PK="$2"
shift 2
;;
--rpc-url)
export NODE_URL="$2"
shift 2
;;
--contracts-input)
export CONTRACTS_JSON="$2"
shift 2
;;
--roles-spec)
export ROLES_JSON="$2"
shift 2
;;
--safes-output)
export OUTPUT="$2"
shift 2
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 --private-key <key> --rpc-url <url> --contracts-input <file> --roles-spec <file> --safes-output <file>"
exit 1
;;
esac
done

# Validate required arguments
if [ -z "$PK" ] || [ -z "$NODE_URL" ] || [ -z "$CONTRACTS_JSON" ] || [ -z "$ROLES_JSON" ] || [ -z "$OUTPUT" ]; then
echo "Missing required arguments"
echo "Usage: $0 --private-key <key> --rpc-url <url> --contracts-input <file> --roles-spec <file> --safes-output <file>"
exit 1
fi

export PATH=/venv/bin:$PATH

if [ ! -f "$CONTRACTS_JSON" ]; then
echo "Error: $CONTRACTS_JSON not found. Did you run deploy_contracts.sh?"
exit 1
fi

if [ ! -f "$ROLES_JSON" ]; then
echo "Error: $ROLES_JSON not found. Please provide a valid roles specification file."
exit 1
fi

EXT_FALLBACK_HANDLER=$(cat "$CONTRACTS_JSON" | jq -r .ExtensibleFallbackHandler)
SAFE=$(cat "$CONTRACTS_JSON" | jq -r .Safe)
SAFE_PROXY_FACTORY=$(cat "$CONTRACTS_JSON" | jq -r .SafeProxyFactory)

# just something to make the calls repeatable
SALT_NONCE=1234567890

# Start with an empty JSON document
echo "{}" > "$OUTPUT"

# Iterate through each role in the JSON file
for role in $(cat "$ROLES_JSON" | jq -r 'keys[]'); do
echo "Creating Safe wallet for role: $role"

# Extract owners and threshold for this role
owners=$(cat "$ROLES_JSON" | jq -r ".$role.owners | join(\" \")")
threshold=$(cat "$ROLES_JSON" | jq -r ".$role.threshold")

# Run safe-creator and capture the output
safe_output=$(safe-creator \
--callback-handler "$EXT_FALLBACK_HANDLER" \
--safe-contract "$SAFE" \
--proxy-factory "$SAFE_PROXY_FACTORY" \
--salt-nonce "$SALT_NONCE" \
--owners "$owners" \
--threshold "$threshold" \
--no-confirm \
"$NODE_URL" "$PK" 2>&1)

# Extract the safe address from the output
safe_address=$(echo "$safe_output" | grep -E "(contract_address=|Safe on)" | head -1 | sed -E "s/.*contract_address='([^']*)'.*/\1/" | sed -E 's/.*Safe on (0x[0-9a-fA-F]*).*/\1/')

# Add the role and safe address to the output JSON
jq --arg role "$role" --arg address "$safe_address" '. + {($role): $address}' "$OUTPUT" > "$OUTPUT.tmp" && mv "$OUTPUT.tmp" "$OUTPUT"
done

# Display the result
echo "Deployed Safes:"
cat "$OUTPUT"
16 changes: 16 additions & 0 deletions kurtosis-devnet/safe-utils/request_example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"role1": {
"owners": [
"0x1234567890123456789012345678901234567890"
],
"threshold": 1
},
"role2": {
"owners": [
"0x1234567890123456789012345678901234567890",
"0x1234567890123456789012345678901234567891",
"0x1234567890123456789012345678901234567892"
],
"threshold": 2
}
}