335 lines
9.0 KiB
Bash
Executable File
335 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
REPO_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
|
|
|
|
declare -A ROOT_SCRIPT_SCOPE_ALIASES=(
|
|
["DeployAddressMapper.s.sol"]="utils"
|
|
["DeployAddressMapperOtherChain.s.sol"]="utils"
|
|
["DeployCCIPRouter.s.sol"]="ccip"
|
|
["DeployCCIPSender.s.sol"]="ccip"
|
|
["DeployCCIPSenderMainnet.s.sol"]="ccip"
|
|
["DeployCCIPReceiver.s.sol"]="ccip"
|
|
["DeployCCIPReceiverMainnet.s.sol"]="ccip"
|
|
["DeployCCIPRelay.s.sol"]="relay"
|
|
["DeployCCIPWETH9Bridge.s.sol"]="ccip"
|
|
["DeployCCIPWETH10Bridge.s.sol"]="ccip"
|
|
["DeployComplianceRegistry.s.sol"]="compliance"
|
|
["DeployOMNLStack.s.sol"]="hybx-omnl"
|
|
["DeployMirrorCoordinator.s.sol"]="hybx-omnl"
|
|
["DeployCompliantUSDC.s.sol"]="tokens"
|
|
["DeployCompliantUSDT.s.sol"]="tokens"
|
|
["DeployCWAssetReserveVerifier.s.sol"]="bridge/integration"
|
|
["DeployCWReserveVerifier.s.sol"]="bridge/integration"
|
|
["DeployFeeCollector.s.sol"]="utils"
|
|
["DeployGenericStateChannelManager.s.sol"]="channels"
|
|
["DeployMainnetTether.s.sol"]="tether"
|
|
["DeployMirrorManager.s.sol"]="mirror"
|
|
["DeployMulticall.s.sol"]="utils"
|
|
["DeployMultiSig.s.sol"]="governance"
|
|
["DeployOfficialUSDC138.s.sol"]="tokens"
|
|
["DeployOfficialUSDT138.s.sol"]="tokens"
|
|
["DeployOracle.s.sol"]="oracle"
|
|
["DeployPaymentChannelManager.s.sol"]="channels"
|
|
["DeployTokenRegistry.s.sol"]="utils"
|
|
["DeployTransactionMirror.s.sol"]="mirror"
|
|
["DeployWETH.s.sol"]="tokens"
|
|
["DeployWETH10.s.sol"]="tokens"
|
|
["DeployWETHWithCCIP.s.sol"]="full"
|
|
)
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
bash scripts/forge/scope.sh list
|
|
bash scripts/forge/scope.sh build [scope] [forge build args...]
|
|
bash scripts/forge/scope.sh test [scope] [forge test args...]
|
|
bash scripts/forge/scope.sh script [scope] <script-target> [forge script args...]
|
|
bash scripts/forge/scope.sh create [scope] <Contract.sol:Name> [forge create args...]
|
|
bash scripts/forge/scope.sh orphans [--json]
|
|
|
|
Examples:
|
|
bash scripts/forge/scope.sh build treasury
|
|
bash scripts/forge/scope.sh test flash --match-path 'test/flash/*.t.sol'
|
|
bash scripts/forge/scope.sh script bridge/trustless script/bridge/trustless/DeployTrustlessBridge.s.sol:DeployTrustlessBridge --rpc-url "$RPC_URL_138"
|
|
bash scripts/forge/scope.sh create tokens contracts/tokens/CompliantFiatToken.sol:CompliantFiatToken --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --legacy
|
|
FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-path 'test/vault/*.t.sol'
|
|
|
|
Notes:
|
|
- Root `forge test` / `forge build` (no scope) honor `foundry.toml` `[profile.default] skip` for
|
|
legacy Uniswap V2 vendor trees (old solc); scoped builds unchanged.
|
|
- Omit [scope] to use FORGE_SCOPE, otherwise default to 'full'.
|
|
- 'full' preserves the historical repo-wide Forge behavior.
|
|
- Any other scope is resolved relative to contracts/, for example:
|
|
treasury -> contracts/treasury
|
|
bridge/trustless -> contracts/bridge/trustless
|
|
- If no explicit scope is given, the runner tries to infer one from
|
|
script/test/build paths and common root-level deployment script names.
|
|
EOF
|
|
}
|
|
|
|
die() {
|
|
echo "error: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
info() {
|
|
echo "$*" >&2
|
|
}
|
|
|
|
resolve_scope() {
|
|
local raw="${1:-${FORGE_SCOPE:-full}}"
|
|
raw="${raw#contracts/}"
|
|
raw="${raw#/}"
|
|
if [[ -z "$raw" ]]; then
|
|
raw="full"
|
|
fi
|
|
printf '%s\n' "$raw"
|
|
}
|
|
|
|
scope_label() {
|
|
printf '%s\n' "${1//\//-}"
|
|
}
|
|
|
|
scope_exists() {
|
|
local scope="$1"
|
|
[[ "$scope" == "full" || -d "$REPO_ROOT/contracts/$scope" ]]
|
|
}
|
|
|
|
infer_scope_from_script_alias() {
|
|
local raw="${1%%:*}"
|
|
local base
|
|
base=$(basename "$raw")
|
|
if [[ -n "${ROOT_SCRIPT_SCOPE_ALIASES[$base]:-}" ]]; then
|
|
printf '%s\n' "${ROOT_SCRIPT_SCOPE_ALIASES[$base]}"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
infer_scope_from_script_imports() {
|
|
local raw="${1%%:*}"
|
|
local script_path="$REPO_ROOT/${raw#./}"
|
|
[[ -f "$script_path" ]] || return 1
|
|
|
|
python3 - "$REPO_ROOT" "$script_path" <<'PY'
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
|
|
repo_root = Path(sys.argv[1]).resolve()
|
|
script_path = Path(sys.argv[2]).resolve()
|
|
text = script_path.read_text(errors="ignore")
|
|
imports = re.findall(r'^\s*import\s+(?:\{[^}]+\}\s+from\s+)?["\']([^"\']+)["\'];', text, re.M)
|
|
|
|
for imp in imports:
|
|
if not imp.startswith(("./", "../")):
|
|
continue
|
|
candidate = (script_path.parent / imp).resolve()
|
|
try:
|
|
rel = candidate.relative_to(repo_root).as_posix()
|
|
except ValueError:
|
|
continue
|
|
if not rel.startswith("contracts/"):
|
|
continue
|
|
scope = rel[len("contracts/"):]
|
|
if candidate.is_file():
|
|
scope = str(Path(scope).parent).replace("\\", "/")
|
|
scope = scope.strip(".")
|
|
while scope:
|
|
if (repo_root / "contracts" / scope).is_dir():
|
|
print(scope)
|
|
raise SystemExit(0)
|
|
scope = scope.rsplit("/", 1)[0] if "/" in scope else ""
|
|
|
|
raise SystemExit(1)
|
|
PY
|
|
}
|
|
|
|
extract_scope_from_path() {
|
|
local raw="${1%%:*}"
|
|
raw="${raw#./}"
|
|
|
|
case "$raw" in
|
|
contracts/*)
|
|
raw="${raw#contracts/}"
|
|
;;
|
|
test/*)
|
|
raw="${raw#test/}"
|
|
;;
|
|
script/*)
|
|
raw="${raw#script/}"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
local candidate
|
|
for candidate in "$raw" "${raw#deploy/}"; do
|
|
[[ -n "$candidate" ]] || continue
|
|
|
|
if [[ -d "$REPO_ROOT/contracts/$candidate" ]]; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
local probe="${candidate%/*}"
|
|
if [[ "$probe" != "$candidate" ]]; then
|
|
while [[ -n "$probe" ]]; do
|
|
if [[ -d "$REPO_ROOT/contracts/$probe" ]]; then
|
|
printf '%s\n' "$probe"
|
|
return 0
|
|
fi
|
|
if [[ "$probe" != *"/"* ]]; then
|
|
break
|
|
fi
|
|
probe="${probe%/*}"
|
|
done
|
|
fi
|
|
done
|
|
|
|
if [[ "$1" == script/* ]]; then
|
|
infer_scope_from_script_alias "$1" && return 0
|
|
infer_scope_from_script_imports "$1" && return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
infer_scope_from_args() {
|
|
local arg inferred
|
|
for arg in "$@"; do
|
|
inferred=$(extract_scope_from_path "$arg" || true)
|
|
if [[ -n "$inferred" ]]; then
|
|
printf '%s\n' "$inferred"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
list_scopes() {
|
|
(
|
|
cd "$REPO_ROOT"
|
|
echo "full"
|
|
find contracts -mindepth 1 -maxdepth 2 -type d | sed 's#^contracts/##' | sort
|
|
)
|
|
}
|
|
|
|
prepare_scope_env() {
|
|
local scope="$1"
|
|
local command="$2"
|
|
|
|
if [[ "$scope" == "full" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local src_dir="contracts/$scope"
|
|
[[ -d "$REPO_ROOT/$src_dir" ]] || die "unknown scope '$scope' (expected directory '$src_dir')"
|
|
|
|
local label
|
|
label=$(scope_label "$scope")
|
|
|
|
export FOUNDRY_SRC="$src_dir"
|
|
export FOUNDRY_OUT="out/scopes/$label"
|
|
export FOUNDRY_CACHE_PATH="cache/scopes/$label"
|
|
export FOUNDRY_SPARSE_MODE="${FOUNDRY_SPARSE_MODE:-true}"
|
|
|
|
if [[ "$command" == "test" ]]; then
|
|
local test_dir="test/$scope"
|
|
if [[ -d "$REPO_ROOT/$test_dir" ]]; then
|
|
export FOUNDRY_TEST="$test_dir"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$command" == "script" ]]; then
|
|
local script_dir="script/$scope"
|
|
if [[ -d "$REPO_ROOT/$script_dir" ]]; then
|
|
export FOUNDRY_SCRIPT="$script_dir"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
cd "$REPO_ROOT"
|
|
|
|
local command="${1:-}"
|
|
[[ -n "$command" ]] || {
|
|
usage
|
|
exit 1
|
|
}
|
|
shift || true
|
|
|
|
case "$command" in
|
|
help|-h|--help)
|
|
usage
|
|
;;
|
|
list)
|
|
list_scopes
|
|
;;
|
|
orphans)
|
|
exec python3 scripts/forge/report-contract-reachability.py "$@"
|
|
;;
|
|
build|test|script|create)
|
|
local scope=""
|
|
if [[ $# -gt 0 && "$1" != --* ]]; then
|
|
local maybe_scope
|
|
maybe_scope=$(resolve_scope "$1")
|
|
if scope_exists "$maybe_scope"; then
|
|
scope="$maybe_scope"
|
|
shift
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$scope" ]]; then
|
|
if [[ -n "${FORGE_SCOPE:-}" ]]; then
|
|
scope=$(resolve_scope)
|
|
else
|
|
scope=$(infer_scope_from_args "$@" || printf 'full\n')
|
|
fi
|
|
fi
|
|
|
|
prepare_scope_env "$scope" "$command"
|
|
|
|
if [[ "$scope" == "full" ]]; then
|
|
info "Forge scope: full repo"
|
|
else
|
|
info "Forge scope: $scope"
|
|
info " src=$FOUNDRY_SRC out=$FOUNDRY_OUT cache=$FOUNDRY_CACHE_PATH"
|
|
[[ -n "${FOUNDRY_TEST:-}" ]] && info " test=$FOUNDRY_TEST"
|
|
[[ -n "${FOUNDRY_SCRIPT:-}" ]] && info " script=$FOUNDRY_SCRIPT"
|
|
fi
|
|
|
|
case "$command" in
|
|
build)
|
|
if [[ "$scope" == "full" ]]; then
|
|
exec forge build "$@"
|
|
fi
|
|
exec forge build --skip test --skip script "$@"
|
|
;;
|
|
test)
|
|
if [[ "$scope" != "full" && -z "${FOUNDRY_TEST:-}" ]]; then
|
|
die "scope '$scope' has no matching test/<scope> directory"
|
|
fi
|
|
exec forge test "$@"
|
|
;;
|
|
script)
|
|
[[ $# -gt 0 ]] || die "script command requires a script target, e.g. script/treasury/DeployTreasuryExecutor138.s.sol:DeployTreasuryExecutor138"
|
|
exec forge script "$@"
|
|
;;
|
|
create)
|
|
[[ $# -gt 0 ]] || die "create command requires a contract target, e.g. contracts/tokens/CompliantFiatToken.sol:CompliantFiatToken"
|
|
exec forge create "$@"
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
die "unknown command '$command'"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|