Files
smom-dbis-138/scripts/forge/scope.sh
defiQUG 768168de5e chore: .gitignore and README updates
Made-with: Cursor
2026-04-21 22:00:55 -07:00

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 "$@"