├── .gitignore ├── README.md ├── bin └── activate ├── forge-ctf └── src │ ├── CTFDeployer.sol │ └── CTFSolver.sol ├── kctf-challenge ├── Dockerfile ├── kctf_persist_env ├── kctf_restore_env └── nsjail.cfg ├── kubernetes └── ctf-server.yaml ├── paradigmctf.py ├── .dockerignore ├── .gitignore ├── Dockerfile ├── ctf_launchers │ ├── __init__.py │ ├── daemon.py │ ├── koth_launcher.py │ ├── launcher.py │ ├── pwn_launcher.py │ ├── score_submitter.py │ ├── team_provider.py │ └── utils.py ├── ctf_server │ ├── __init__.py │ ├── anvil_proxy.py │ ├── backends │ │ ├── __init__.py │ │ ├── backend.py │ │ ├── docker_backend.py │ │ └── kubernetes_backend.py │ ├── databases │ │ ├── __init__.py │ │ ├── database.py │ │ ├── redisdb.py │ │ └── sqlitedb.py │ ├── orchestrator.py │ ├── types │ │ └── __init__.py │ └── utils.py ├── ctf_solvers │ ├── __init__.py │ ├── koth_solver.py │ ├── pwn_solver.py │ ├── solver.py │ └── utils.py ├── docker-compose.yml ├── foundry │ ├── __init__.py │ └── anvil.py ├── requirements.txt └── setup.py └── templates ├── eth-koth ├── .challengeignore ├── challenge.yaml └── challenge │ ├── Dockerfile │ ├── challenge.py │ ├── docker-compose.yml │ ├── project │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── lib │ │ ├── forge-ctf │ │ │ └── src │ │ │ │ ├── CTFDeployer.sol │ │ │ │ └── CTFSolver.sol │ │ └── forge-std │ │ │ ├── .github │ │ │ └── workflows │ │ │ │ ├── ci.yml │ │ │ │ └── sync.yml │ │ │ ├── .gitignore │ │ │ ├── .gitmodules │ │ │ ├── LICENSE-APACHE │ │ │ ├── LICENSE-MIT │ │ │ ├── README.md │ │ │ ├── foundry.toml │ │ │ ├── lib │ │ │ └── ds-test │ │ │ │ ├── .github │ │ │ │ └── workflows │ │ │ │ │ └── build.yml │ │ │ │ ├── .gitignore │ │ │ │ ├── LICENSE │ │ │ │ ├── Makefile │ │ │ │ ├── default.nix │ │ │ │ ├── demo │ │ │ │ └── demo.sol │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ ├── test.sol │ │ │ │ └── test.t.sol │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── Base.sol │ │ │ ├── Script.sol │ │ │ ├── StdAssertions.sol │ │ │ ├── StdChains.sol │ │ │ ├── StdCheats.sol │ │ │ ├── StdError.sol │ │ │ ├── StdInvariant.sol │ │ │ ├── StdJson.sol │ │ │ ├── StdMath.sol │ │ │ ├── StdStorage.sol │ │ │ ├── StdStyle.sol │ │ │ ├── StdUtils.sol │ │ │ ├── Test.sol │ │ │ ├── Vm.sol │ │ │ ├── console.sol │ │ │ ├── console2.sol │ │ │ ├── interfaces │ │ │ │ ├── IERC1155.sol │ │ │ │ ├── IERC165.sol │ │ │ │ ├── IERC20.sol │ │ │ │ ├── IERC4626.sol │ │ │ │ ├── IERC721.sol │ │ │ │ └── IMulticall3.sol │ │ │ └── safeconsole.sol │ │ │ └── test │ │ │ ├── StdAssertions.t.sol │ │ │ ├── StdChains.t.sol │ │ │ ├── StdCheats.t.sol │ │ │ ├── StdError.t.sol │ │ │ ├── StdMath.t.sol │ │ │ ├── StdStorage.t.sol │ │ │ ├── StdStyle.t.sol │ │ │ ├── StdUtils.t.sol │ │ │ ├── Vm.t.sol │ │ │ ├── compilation │ │ │ ├── CompilationScript.sol │ │ │ ├── CompilationScriptBase.sol │ │ │ ├── CompilationTest.sol │ │ │ └── CompilationTestBase.sol │ │ │ └── fixtures │ │ │ └── broadcast.log.json │ ├── remappings.txt │ ├── script │ │ ├── Deploy.s.sol │ │ ├── Solve.s.sol │ │ └── exploit │ │ │ └── Exploit.sol │ └── src │ │ └── Challenge.sol │ └── solve.py └── eth-pwn ├── .challengeignore ├── README.md ├── challenge.yaml └── challenge ├── Dockerfile ├── challenge.py ├── docker-compose.yml ├── project ├── .gitignore ├── README.md ├── foundry.toml ├── lib │ ├── forge-ctf │ │ └── src │ │ │ ├── CTFDeployer.sol │ │ │ └── CTFSolver.sol │ └── forge-std │ │ ├── .github │ │ └── workflows │ │ │ ├── ci.yml │ │ │ └── sync.yml │ │ ├── .gitignore │ │ ├── .gitmodules │ │ ├── LICENSE-APACHE │ │ ├── LICENSE-MIT │ │ ├── README.md │ │ ├── foundry.toml │ │ ├── lib │ │ └── ds-test │ │ │ ├── .github │ │ │ └── workflows │ │ │ │ └── build.yml │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── default.nix │ │ │ ├── demo │ │ │ └── demo.sol │ │ │ ├── package.json │ │ │ └── src │ │ │ ├── test.sol │ │ │ └── test.t.sol │ │ ├── package.json │ │ ├── src │ │ ├── Base.sol │ │ ├── Script.sol │ │ ├── StdAssertions.sol │ │ ├── StdChains.sol │ │ ├── StdCheats.sol │ │ ├── StdError.sol │ │ ├── StdInvariant.sol │ │ ├── StdJson.sol │ │ ├── StdMath.sol │ │ ├── StdStorage.sol │ │ ├── StdStyle.sol │ │ ├── StdUtils.sol │ │ ├── Test.sol │ │ ├── Vm.sol │ │ ├── console.sol │ │ ├── console2.sol │ │ ├── interfaces │ │ │ ├── IERC1155.sol │ │ │ ├── IERC165.sol │ │ │ ├── IERC20.sol │ │ │ ├── IERC4626.sol │ │ │ ├── IERC721.sol │ │ │ └── IMulticall3.sol │ │ └── safeconsole.sol │ │ └── test │ │ ├── StdAssertions.t.sol │ │ ├── StdChains.t.sol │ │ ├── StdCheats.t.sol │ │ ├── StdError.t.sol │ │ ├── StdMath.t.sol │ │ ├── StdStorage.t.sol │ │ ├── StdStyle.t.sol │ │ ├── StdUtils.t.sol │ │ ├── Vm.t.sol │ │ ├── compilation │ │ ├── CompilationScript.sol │ │ ├── CompilationScriptBase.sol │ │ ├── CompilationTest.sol │ │ └── CompilationTestBase.sol │ │ └── fixtures │ │ └── broadcast.log.json ├── remappings.txt ├── script │ ├── Deploy.s.sol │ ├── Solve.s.sol │ └── exploit │ │ └── Exploit.sol └── src │ └── Challenge.sol └── solve.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paradigm CTF Infra 2 | 3 | This repository contains all the files used to host [Paradigm CTF](https://ctf.paradigm.xyz). For help and support, join the [Discord](https://discord.gg/J2PwXQG7qR). 4 | 5 | ## Getting Started 6 | 7 | To run the CTF infrastructure locally, simply run the following commands: 8 | 9 | ```bash 10 | cd paradigmctf.py 11 | docker compose up 12 | ``` 13 | 14 | To run the CTF infrastructure in kCTF, you'll need to do the following: 15 | 16 | ```bash 17 | # create the cluster if it doesn't exist 18 | kctf cluster create --type kind local-cluster 19 | 20 | # build the image 21 | (cd paradigmctf.py; docker build gcr.io/paradigmxyz/infra/paradigmctf.py:latest) 22 | 23 | # push the image to kind 24 | kind load docker-image --name "${CLUSTER_NAME}" "gcr.io/paradigmxyz/infra/paradigmctf.py:latest" 25 | 26 | # create all the resources 27 | kubectl apply -f kubernetes/ctf-server.yaml 28 | 29 | # port forward the anvil proxy for local access 30 | kubectl port-forward service/anvil-proxy 8545:8545 & 31 | ``` 32 | 33 | Now you'll be able to build and test challenges in kCTF: 34 | ```bash 35 | # start the challenge 36 | kctf chal start 37 | 38 | # port forward the challenge 39 | kctf chal debug port-forward --port 1337 --local-port 1337 & 40 | 41 | # connect to the challenge 42 | nc 127.0.0.1 1337 43 | ``` 44 | 45 | ## Images 46 | 47 | Paradigm CTF is hosted using [kCTF](https://google.github.io/kctf/), a Kubernetes-based CTF platform. Follow the kCTF setup instructions to get a local cluster running on your computer. 48 | 49 | ### kctf-challenge 50 | The [kctf-challenge](/kctf-challenge/) image acts as a standard image on top of the kCTF base image. It's optional, not required, but provides the following features: 51 | - Adds the `/bin/kctf_persist_env` and `/bin/kctf_restore_env` scripts for use with `kctf_drop_privs`, which resets all environment variables (this might be removed if a better way of passing configuration variables is identified) 52 | - Adds a common `nsjail.cfg` for use with Anvil. The usefulness of running the Anvil server inside nsjail is debatable, as a lot of security features need to be disabled (timeouts, resource limits, etc). The file is also poorly-named, and may be changed in the future 53 | 54 | ### paradigmctf.py 55 | The [paradigmctf.py](/paradigmctf.py/) image acts as the base image for all challenges. It provides the following features: 56 | - Installs the `ctf_launchers`, `ctf_solvers`, and `ctf_server` libraries. These can be used to orchestrate CTF challenge instances. 57 | 58 | ## Libraries 59 | 60 | ### forge-ctf 61 | The [forge-ctf](/forge-ctf/) library provides two Forge scripts which can be used to deploy and solve challenges. They are intended to be used with the `eth_launchers` package. 62 | 63 | The `CTFDeployment` script can be overridden to implement the `deploy(address system, address player) internal returns (address challenge)` function. It defaults to using the `test [...] test junk` mnemonic, but will read from the `MNEMONIC` environment variable. It writes the address that the challenge was deployed at to `/tmp/deploy.txt`, or the value of `OUTPUT_FILE`. 64 | 65 | The `CTFSolver` script can be overriden to implement the `solve(address challenge, address player)` function. The challenge address must be specified as the `CHALLENGE` environment variable. The player private key defaults to the first key generated from the `test [...] junk` mnemonic, but can be overridden with `PLAYER`. 66 | 67 | ## Templates 68 | 69 | Templates are provided for you to quickly get started with creating challenges of your own. To use them, copy the [templates](/templates/) into `kctf/challenge-templates`. Then, you will be able to use `kctf chal create --template eth-pwn`. 70 | 71 | ## TODO 72 | Huff support is pretty bad, needs the following changes upstream: 73 | - https://github.com/huff-language/foundry-huff/issues/47 74 | - Needs to support broadcasting from specific address 75 | - Needs to stop using hexdump to generate some random bytes 76 | 77 | Kubernetes support is not complete yet 78 | -------------------------------------------------------------------------------- /bin/activate: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | deactivate 2>/dev/null || true 4 | source kctf/activate 5 | 6 | function bundle-challenge { 7 | _kctf_set_active_challenge 8 | 9 | if [[ -z "$CHALLENGE_NAME" ]]; then 10 | echo "bundle-challenge: no active challenge, cd to a challenge dir first" >&2 11 | return 1 12 | fi 13 | 14 | CHAL_SPEC="$CHALLENGE_DIR/challenge.yaml" 15 | 16 | mkdir "$KCTF_CTF_DIR/artifacts" 2>/dev/null || true 17 | 18 | OUTPUT_FILE=$(realpath "$KCTF_CTF_DIR/artifacts/${CHALLENGE_NAME}.zip") 19 | 20 | echo "[+] bundling $CHALLENGE_NAME into $OUTPUT_FILE" >&2 21 | 22 | rm "$OUTPUT_FILE" 2>/dev/null || true 23 | 24 | (cd "$CHALLENGE_DIR"; zip -qr "$OUTPUT_FILE" "challenge" -x"@.challengeignore") 25 | 26 | echo "[+] done!" >&2 27 | } 28 | 29 | function start-challenge { 30 | DEV_HOST="challenges-dev.paradigm.xyz" 31 | PROD_HOST="challenges.paradigm.xyz" 32 | SECRET="secret" 33 | 34 | ENV="${ENV:-local}" 35 | 36 | _kctf_set_active_challenge 37 | 38 | if [[ -z "$CHALLENGE_NAME" ]]; then 39 | echo "bundle-challenge: no active challenge, cd to a challenge dir first" >&2 40 | return 1 41 | fi 42 | 43 | FLAG="$($KCTF_BIN/yq eval '.metadata.annotations.flag' ${CHALLENGE_DIR}/challenge.yaml)" 44 | 45 | function append_container_env() { 46 | SPEC="$1" 47 | KEY="$2" 48 | VAL="$3" 49 | 50 | echo "$SPEC" | jq --arg key "$KEY" --arg val "$VAL" '.env += [{"name": $key, "value": $val}]' 51 | } 52 | 53 | function append_network_port() { 54 | SPEC="$1" 55 | PROTOCOL="$2" 56 | TARGET_PORT="$3" 57 | DOMAIN="$4" 58 | 59 | echo "$SPEC" | \ 60 | jq --arg protocol "$PROTOCOL" --arg targetPort "$TARGET_PORT" --arg domain "$DOMAIN" \ 61 | '.ports += [{"protocol": $protocol, "targetPort": $targetPort, "domains": [$domain]}]' 62 | } 63 | 64 | CONTAINER_SPEC=$(echo '{}' | jq '.name = "challenge" | .env = []') 65 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_ENV" "$ENV") 66 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_CHALLENGE_ID" "$CHALLENGE_NAME") 67 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_FLAG" "$FLAG") 68 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_ETH_RPC_URL" "https://mainnet.infura.io/v3/a5d448ae5f454f1488d667b98b9963ec") 69 | 70 | if [ "$ENV" = "local" ]; then 71 | NETWORK_SPEC=$(echo '{}' | jq '.public = false') 72 | elif [ "$ENV" = "dev" ]; then 73 | NETWORK_SPEC=$(echo '{}' | jq '.public = true') 74 | NETWORK_SPEC=$(append_network_port "$NETWORK_SPEC" "TCP" "1337" "$DEV_HOST") 75 | NETWORK_SPEC=$(append_network_port "$NETWORK_SPEC" "TCP" "8545" "$DEV_HOST") 76 | 77 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_PUBLIC_HOST" "http://${CHALLENGE_NAME}.${DEV_HOST}:8545") 78 | elif [ "$ENV" = "prod" ]; then 79 | NETWORK_SPEC=$(echo '{}' | jq '.public = true') 80 | NETWORK_SPEC=$(append_network_port "$NETWORK_SPEC" "TCP" "1337" "$PROD_HOST") 81 | NETWORK_SPEC=$(append_network_port "$NETWORK_SPEC" "TCP" "8545" "$PROD_HOST") 82 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_PUBLIC_HOST" "http://${CHALLENGE_NAME}.${PROD_HOST}:8545") 83 | CONTAINER_SPEC=$(append_container_env "$CONTAINER_SPEC" "PERSIST_SECRET" "$SECRET") 84 | CONTAINER_SPEC="$(echo "$CONTAINER_SPEC" | jq -c ".resources.limits.memory = \"8G\"")" 85 | CONTAINER_SPEC="$(echo "$CONTAINER_SPEC" | jq -c ".resources.limits.cpu = \"2\"")" 86 | fi 87 | 88 | ${KCTF_BIN}/yq eval ".spec.podTemplate.template.spec.serviceAccountName=\"default\"" --inplace "${CHALLENGE_DIR}/challenge.yaml" 89 | ${KCTF_BIN}/yq eval ".spec.podTemplate.template.spec.automountServiceAccountToken=true" --inplace "${CHALLENGE_DIR}/challenge.yaml" 90 | ${KCTF_BIN}/yq eval ".spec.podTemplate.template.spec.containers[0]=$CONTAINER_SPEC" --inplace "${CHALLENGE_DIR}/challenge.yaml" 91 | ${KCTF_BIN}/yq eval ".spec.network=$NETWORK_SPEC" --inplace "${CHALLENGE_DIR}/challenge.yaml" 92 | 93 | kctf chal start 94 | } 95 | 96 | function port-forward-challenge { 97 | _kctf_set_active_challenge 98 | 99 | if [[ -z "$CHALLENGE_NAME" ]]; then 100 | echo "port-forward-challenge: no active challenge, cd to a challenge dir first" >&2 101 | return 1 102 | fi 103 | 104 | if [[ $# -eq 0 ]]; then 105 | echo "port-forward-challenge: no ports specified" >&2 106 | return 1 107 | fi 108 | 109 | LATEST_REVISION=$(kubectl get deployment \ 110 | "$CHALLENGE_NAME" --output jsonpath='{.metadata.annotations.deployment\.kubernetes\.io/revision}' \ 111 | ) 112 | 113 | AVAILABLE_REPLICAS=$(kubectl get replicaset \ 114 | --selector app="$CHALLENGE_NAME" \ 115 | --no-headers \ 116 | --output 'custom-columns=hash:metadata.labels.pod-template-hash,version:metadata.annotations.deployment\.kubernetes\.io/revision' \ 117 | ) 118 | 119 | DESIRE_POD_REPLICA_HASH=$(echo "$AVAILABLE_REPLICAS" | grep -E "\s+$LATEST_REVISION$" | cut -d' ' -f1) 120 | 121 | kubectl wait \ 122 | --for=condition=ready pod \ 123 | --selector app=$CHALLENGE_NAME --selector pod-template-hash=$DESIRE_POD_REPLICA_HASH >/dev/null 124 | 125 | pkill kubectl 126 | while [[ $# -gt 0 ]]; do 127 | echo "[+] forwarding port $1" >&2 128 | kctf chal debug port-forward --port "$1" --local-port "$1" >/dev/null 2>&1 129 | shift 130 | done 131 | } -------------------------------------------------------------------------------- /forge-ctf/src/CTFDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | abstract contract CTFDeployer is Script { 7 | function run() external { 8 | address player = getAddress(0); 9 | address system = getAddress(1); 10 | 11 | address challenge = deploy(system, player); 12 | 13 | vm.writeFile(vm.envOr("OUTPUT_FILE", string("/tmp/deploy.txt")), vm.toString(challenge)); 14 | } 15 | 16 | function deploy(address system, address player) virtual internal returns (address); 17 | 18 | function getAdditionalAddress(uint32 index) internal returns (address) { 19 | return getAddress(index + 2); 20 | } 21 | 22 | function getPrivateKey(uint32 index) private returns (uint) { 23 | string memory mnemonic = vm.envOr("MNEMONIC", string("test test test test test test test test test test test junk")); 24 | return vm.deriveKey(mnemonic, index); 25 | } 26 | 27 | function getAddress(uint32 index) private returns (address) { 28 | return vm.addr(getPrivateKey(index)); 29 | } 30 | } -------------------------------------------------------------------------------- /forge-ctf/src/CTFSolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | abstract contract CTFSolver is Script { 7 | function run() external { 8 | uint256 playerPrivateKey = vm.envOr("PLAYER", uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80)); 9 | address challenge = vm.envAddress("CHALLENGE"); 10 | 11 | vm.startBroadcast(playerPrivateKey); 12 | 13 | solve(challenge, vm.addr(playerPrivateKey)); 14 | 15 | vm.stopBroadcast(); 16 | } 17 | 18 | function solve(address challenge, address player) virtual internal; 19 | } -------------------------------------------------------------------------------- /kctf-challenge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/kctf-docker/challenge@sha256:0f7d757bcda470c3bbc063606335b915e03795d72ba1d8fdb6f0f9ff3757364f 2 | 3 | VOLUME [ "/paradigm" ] 4 | 5 | COPY kctf_persist_env kctf_restore_env /usr/bin/ 6 | 7 | COPY nsjail.cfg / 8 | -------------------------------------------------------------------------------- /kctf-challenge/kctf_persist_env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat /dev/null > /paradigm/environ 4 | 5 | env | while IFS= read -r line; do 6 | value=${line#*=} 7 | name=${line%%=*} 8 | 9 | if [[ "$name" == PERSIST_* ]]; then 10 | printf 'export %s=%q\n' "$(echo "$name" | sed 's/^PERSIST_//g')" "$value" >> /paradigm/environ 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /kctf-challenge/kctf_restore_env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f /environ ]; then 4 | source /environ 5 | fi 6 | 7 | exec $@ 8 | -------------------------------------------------------------------------------- /kctf-challenge/nsjail.cfg: -------------------------------------------------------------------------------- 1 | name: "anvil-nsjail" 2 | description: "nsjail configuration for anvil & uvicorn server." 3 | 4 | mode: ONCE 5 | uidmap {inside_id: "1000"} 6 | gidmap {inside_id: "1000"} 7 | disable_rl: true 8 | time_limit: 2147483647 9 | 10 | clone_newnet: false # share net namespace 11 | 12 | envar: "PYTHONPATH=/opt/python/lib" 13 | envar: "INSTANCE_DIR=/tmp/instances" 14 | envar: "UNIX_SOCKET_LOCATION=/home/user/anvil_server.sock" 15 | 16 | cwd: "/home/user" 17 | 18 | mount: [ 19 | { 20 | src: "/chroot" 21 | dst: "/" 22 | is_bind: true 23 | rw: true # foundry insists on spewing cache files everywhere 24 | }, 25 | { 26 | src: "/usr/bin/kctf_restore_env" 27 | dst: "/bin/kctf_restore_env" 28 | is_bind: true 29 | is_dir: false 30 | }, 31 | { 32 | src: "/paradigm/environ" 33 | dst: "/environ" 34 | is_bind: true 35 | is_dir: false 36 | mandatory: false 37 | }, 38 | { 39 | src: "/tmp" 40 | dst: "/tmp" 41 | rw: true 42 | is_bind: true 43 | }, 44 | { 45 | dst: "/proc" 46 | fstype: "proc" 47 | rw: true 48 | }, 49 | { 50 | src: "/etc/resolv.conf" 51 | dst: "/etc/resolv.conf" 52 | is_bind: true 53 | }, 54 | { 55 | src: "/dev" 56 | dst: "/dev" 57 | is_bind: true 58 | }, 59 | { 60 | src: "/dev/null", 61 | dst: "/dev/null", 62 | is_bind: true, 63 | } 64 | ] 65 | -------------------------------------------------------------------------------- /kubernetes/ctf-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: ctf-server 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | namespace: default 10 | name: ctf-server 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["pods"] 14 | verbs: ["get", "watch", "list", "create", "delete", "deletecollection", "patch", "update"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: RoleBinding 18 | metadata: 19 | name: ctf-server 20 | namespace: default 21 | subjects: 22 | - kind: ServiceAccount 23 | name: ctf-server 24 | namespace: default 25 | roleRef: 26 | kind: Role 27 | name: ctf-server 28 | apiGroup: rbac.authorization.k8s.io 29 | --- 30 | apiVersion: apps/v1 31 | kind: Deployment 32 | metadata: 33 | name: redis 34 | spec: 35 | selector: 36 | matchLabels: 37 | app: redis 38 | replicas: 1 39 | template: 40 | metadata: 41 | labels: 42 | app: redis 43 | spec: 44 | containers: 45 | - name: redis 46 | image: redis/redis-stack:latest 47 | env: 48 | - name: REDIS_ARGS 49 | value: --save 60 1 50 | volumeMounts: 51 | - name: database 52 | mountPath: /data 53 | ports: 54 | - containerPort: 6379 55 | resources: 56 | limits: 57 | cpu: 1.0 58 | memory: 1G 59 | volumes: 60 | - name: database 61 | emptyDir: {} 62 | --- 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: redis 67 | spec: 68 | selector: 69 | app: redis 70 | ports: 71 | - protocol: TCP 72 | port: 6379 73 | targetPort: 6379 74 | --- 75 | apiVersion: apps/v1 76 | kind: Deployment 77 | metadata: 78 | name: ctf-server 79 | spec: 80 | selector: 81 | matchLabels: 82 | app: ctf-server 83 | replicas: 1 84 | template: 85 | metadata: 86 | labels: 87 | app: ctf-server 88 | spec: 89 | serviceAccountName: ctf-server 90 | securityContext: 91 | runAsNonRoot: true 92 | containers: 93 | - name: orchestrator 94 | image: gcr.io/paradigmxyz/infra/paradigmctf.py:latest 95 | command: ["uvicorn", "--host", "0.0.0.0", "--port", "7283", "ctf_server:orchestrator"] 96 | env: 97 | - name: BACKEND 98 | value: kubernetes 99 | - name: DATABASE 100 | value: redis 101 | - name: REDIS_URL 102 | value: redis://redis:6379/0 103 | ports: 104 | - containerPort: 7283 105 | imagePullPolicy: IfNotPresent 106 | securityContext: 107 | allowPrivilegeEscalation: false 108 | resources: 109 | limits: 110 | cpu: 1.0 111 | memory: 1G 112 | --- 113 | apiVersion: v1 114 | kind: Service 115 | metadata: 116 | name: orchestrator 117 | spec: 118 | selector: 119 | app: ctf-server 120 | ports: 121 | - protocol: TCP 122 | port: 7283 123 | targetPort: 7283 124 | 125 | --- 126 | apiVersion: apps/v1 127 | kind: Deployment 128 | metadata: 129 | name: anvil-proxy 130 | spec: 131 | selector: 132 | matchLabels: 133 | app: anvil-proxy 134 | replicas: 1 135 | template: 136 | metadata: 137 | labels: 138 | app: anvil-proxy 139 | spec: 140 | serviceAccountName: ctf-server 141 | securityContext: 142 | runAsNonRoot: true 143 | containers: 144 | - name: anvil-proxy 145 | image: gcr.io/paradigmxyz/infra/paradigmctf.py:latest 146 | command: ["uvicorn", "--host", "0.0.0.0", "--port", "8545", "--workers", "16", "ctf_server:anvil_proxy"] 147 | env: 148 | - name: DATABASE 149 | value: redis 150 | - name: REDIS_URL 151 | value: redis://redis:6379/0 152 | ports: 153 | - containerPort: 8545 154 | imagePullPolicy: IfNotPresent 155 | securityContext: 156 | allowPrivilegeEscalation: false 157 | resources: 158 | limits: 159 | cpu: 2.0 160 | memory: 4G 161 | --- 162 | apiVersion: v1 163 | kind: Service 164 | metadata: 165 | name: anvil-proxy 166 | spec: 167 | selector: 168 | app: anvil-proxy 169 | ports: 170 | - protocol: TCP 171 | port: 8545 172 | targetPort: 8545 173 | -------------------------------------------------------------------------------- /paradigmctf.py/.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /paradigmctf.py/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.egg-info 3 | **/__pycache__/ -------------------------------------------------------------------------------- /paradigmctf.py/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.6-slim 2 | 3 | # Set up unprivileged user and install dependencies 4 | # TODO: we need bsdmainutils so we have hexdump so foundry-huff can... 5 | # generate some random bytes... :/ 6 | RUN true && \ 7 | useradd -u 1000 -m user && \ 8 | apt-get update && \ 9 | apt-get install -y curl git socat bsdmainutils && \ 10 | rm -rf /var/cache/apt/lists /var/lib/apt/lists/* && \ 11 | true 12 | 13 | # Install Foundry 14 | ENV FOUNDRY_DIR=/opt/foundry 15 | 16 | ENV PATH=${FOUNDRY_DIR}/bin/:${PATH} 17 | 18 | RUN true && \ 19 | curl -L https://foundry.paradigm.xyz | bash && \ 20 | foundryup && \ 21 | true 22 | 23 | # Install Huff 24 | ENV HUFF_DIR=/opt/huff 25 | 26 | ENV PATH=${HUFF_DIR}/bin/:${PATH} 27 | 28 | RUN true && \ 29 | curl -L http://get.huff.sh | bash && \ 30 | huffup && \ 31 | true 32 | 33 | # (Optimization) Install requirements 34 | COPY requirements.txt /tmp/requirements.txt 35 | 36 | RUN pip install -r /tmp/requirements.txt 37 | 38 | # Install the library 39 | COPY . /tmp/paradigmctf.py 40 | 41 | RUN true && \ 42 | pip install /tmp/paradigmctf.py uvicorn && \ 43 | rm -rf /tmp/requirements.txt /tmp/paradigmctf.py && \ 44 | true 45 | 46 | USER 1000 47 | 48 | WORKDIR /home/user -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paradigmxyz/paradigm-ctf-infrastructure/6c39333a674f18458bc27256091ab0306b9d432e/paradigmctf.py/ctf_launchers/__init__.py -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/daemon.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import os 3 | import time 4 | from typing import Dict, List 5 | 6 | import requests 7 | from ctf_server.types import UserData 8 | 9 | ORCHESTRATOR = os.getenv("ORCHESTRATOR_HOST", "http://orchestrator:7283") 10 | INSTANCE_ID = os.getenv("INSTANCE_ID") 11 | 12 | 13 | class Daemon(abc.ABC): 14 | def __init__(self, required_properties: List[str] = []): 15 | self.__required_properties = required_properties 16 | 17 | def start(self): 18 | while True: 19 | instance_body = requests.get( 20 | f"{ORCHESTRATOR}/instances/{INSTANCE_ID}" 21 | ).json() 22 | if instance_body["ok"] == False: 23 | raise Exception("oops") 24 | 25 | user_data = instance_body["data"] 26 | if any( 27 | [v not in user_data["metadata"] for v in self.__required_properties] 28 | ): 29 | time.sleep(1) 30 | continue 31 | 32 | break 33 | 34 | self._run(user_data) 35 | 36 | def update_metadata(self, new_metadata: Dict[str, str]): 37 | resp = requests.post( 38 | f"{ORCHESTRATOR}/instances/{INSTANCE_ID}/metadata", 39 | json=new_metadata, 40 | ) 41 | body = resp.json() 42 | if not body["ok"]: 43 | raise Exception("failed to update metadata", body["message"]) 44 | 45 | @abc.abstractmethod 46 | def _run(self, user_data: UserData): 47 | pass 48 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/koth_launcher.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import requests 4 | from ctf_launchers.launcher import CHALLENGE, ORCHESTRATOR_HOST, Action, Launcher 5 | from ctf_launchers.score_submitter import ScoreSubmitter, get_score_submitter 6 | from ctf_launchers.team_provider import TeamProvider, get_team_provider 7 | from ctf_server.types import UserData, get_privileged_web3 8 | from eth_abi import abi 9 | 10 | 11 | class KothChallengeLauncher(Launcher): 12 | def __init__( 13 | self, 14 | project_location: str = "challenge/project", 15 | provider: TeamProvider = get_team_provider(), 16 | submitter: ScoreSubmitter = get_score_submitter(), 17 | want_metadata: List[str] = [], 18 | ): 19 | super().__init__( 20 | project_location, 21 | provider, 22 | actions=[Action(name="submit score", handler=self.submit_score)], 23 | ) 24 | 25 | self.__score_submitter = submitter 26 | self.__want_metadata = want_metadata 27 | 28 | def submit_score(self) -> int: 29 | instance_body = requests.get( 30 | f"{ORCHESTRATOR_HOST}/instances/{self.get_instance_id()}" 31 | ).json() 32 | if not instance_body["ok"]: 33 | print(instance_body["message"]) 34 | return 1 35 | 36 | user_data = instance_body['data'] 37 | 38 | score = self.get_score( 39 | user_data, user_data['metadata']["challenge_address"] 40 | ) 41 | 42 | print("submitting score", score) 43 | data = {} 44 | for metadata in self.__want_metadata: 45 | data[metadata] = user_data['metadata'][metadata] 46 | 47 | self.__score_submitter.submit_score(self.team, data, score) 48 | 49 | return 0 50 | 51 | def get_score(self, user_data: UserData, addr: str) -> bool: 52 | web3 = get_privileged_web3(user_data, "main") 53 | 54 | (result,) = abi.decode( 55 | ["uint256"], 56 | web3.eth.call( 57 | { 58 | "to": addr, 59 | "data": web3.keccak(text="getScore()")[:4], 60 | } 61 | ), 62 | ) 63 | 64 | return result 65 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/launcher.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import os 3 | import traceback 4 | from dataclasses import dataclass 5 | from typing import Callable, Dict, List 6 | 7 | import requests 8 | from ctf_launchers.team_provider import TeamProvider 9 | from ctf_launchers.utils import deploy, http_url_to_ws 10 | from ctf_server.types import ( 11 | CreateInstanceRequest, 12 | DaemonInstanceArgs, 13 | LaunchAnvilInstanceArgs, 14 | UserData, 15 | get_player_account, 16 | get_privileged_web3, 17 | ) 18 | from eth_account.hdaccount import generate_mnemonic 19 | 20 | CHALLENGE = os.getenv("CHALLENGE", "challenge") 21 | ORCHESTRATOR_HOST = os.getenv("ORCHESTRATOR_HOST", "http://orchestrator:7283") 22 | PUBLIC_HOST = os.getenv("PUBLIC_HOST", "http://127.0.0.1:8545") 23 | 24 | ETH_RPC_URL = os.getenv("ETH_RPC_URL") 25 | TIMEOUT = int(os.getenv("TIMEOUT", "1440")) 26 | 27 | 28 | @dataclass 29 | class Action: 30 | name: str 31 | handler: Callable[[], int] 32 | 33 | 34 | class Launcher(abc.ABC): 35 | def __init__( 36 | self, project_location: str, provider: TeamProvider, actions: List[Action] = [] 37 | ): 38 | self.project_location = project_location 39 | self.__team_provider = provider 40 | 41 | self._actions = [ 42 | Action(name="launch new instance", handler=self.launch_instance), 43 | Action(name="kill instance", handler=self.kill_instance), 44 | ] + actions 45 | 46 | def run(self): 47 | self.team = self.__team_provider.get_team() 48 | if not self.team: 49 | exit(1) 50 | 51 | self.mnemonic = generate_mnemonic(12, lang="english") 52 | 53 | for i, action in enumerate(self._actions): 54 | print(f"{i+1} - {action.name}") 55 | 56 | try: 57 | handler = self._actions[int(input("action? ")) - 1] 58 | except: 59 | print("can you not") 60 | exit(1) 61 | 62 | try: 63 | exit(handler.handler()) 64 | except Exception as e: 65 | traceback.print_exc() 66 | print("an error occurred", e) 67 | exit(1) 68 | 69 | def get_anvil_instances(self) -> Dict[str, LaunchAnvilInstanceArgs]: 70 | return { 71 | "main": self.get_anvil_instance(), 72 | } 73 | 74 | def get_daemon_instances(self) -> Dict[str, DaemonInstanceArgs]: 75 | return {} 76 | 77 | def get_anvil_instance(self, **kwargs) -> LaunchAnvilInstanceArgs: 78 | if not "balance" in kwargs: 79 | kwargs["balance"] = 1000 80 | if not "accounts" in kwargs: 81 | kwargs["accounts"] = 2 82 | if not "fork_url" in kwargs: 83 | kwargs["fork_url"] = ETH_RPC_URL 84 | if not "mnemonic" in kwargs: 85 | kwargs["mnemonic"] = self.mnemonic 86 | return LaunchAnvilInstanceArgs( 87 | **kwargs, 88 | ) 89 | 90 | def get_instance_id(self) -> str: 91 | return f"chal-{CHALLENGE}-{self.team}".lower() 92 | 93 | def update_metadata(self, new_metadata: Dict[str, str]): 94 | resp = requests.post( 95 | f"{ORCHESTRATOR_HOST}/instances/{self.get_instance_id()}/metadata", 96 | json=new_metadata, 97 | ) 98 | body = resp.json() 99 | if not body["ok"]: 100 | print(body["message"]) 101 | return 1 102 | 103 | def launch_instance(self) -> int: 104 | print("creating private blockchain...") 105 | body = requests.post( 106 | f"{ORCHESTRATOR_HOST}/instances", 107 | json=CreateInstanceRequest( 108 | instance_id=self.get_instance_id(), 109 | timeout=TIMEOUT, 110 | anvil_instances=self.get_anvil_instances(), 111 | daemon_instances=self.get_daemon_instances(), 112 | ), 113 | ).json() 114 | if body["ok"] == False: 115 | raise Exception(body["message"]) 116 | 117 | user_data = body["data"] 118 | 119 | print("deploying challenge...") 120 | challenge_addr = self.deploy(user_data, self.mnemonic) 121 | 122 | self.update_metadata( 123 | {"mnemonic": self.mnemonic, "challenge_address": challenge_addr} 124 | ) 125 | 126 | PUBLIC_WEBSOCKET_HOST = http_url_to_ws(PUBLIC_HOST) 127 | 128 | print() 129 | print(f"your private blockchain has been set up") 130 | print(f"it will automatically terminate in {TIMEOUT} minutes") 131 | print(f"---") 132 | print(f"rpc endpoints:") 133 | for id in user_data["anvil_instances"]: 134 | print(f" - {PUBLIC_HOST}/{user_data['external_id']}/{id}") 135 | print(f" - {PUBLIC_WEBSOCKET_HOST}/{user_data['external_id']}/{id}/ws") 136 | 137 | print(f"private key: {get_player_account(self.mnemonic).key.hex()}") 138 | print(f"challenge contract: {challenge_addr}") 139 | return 0 140 | 141 | def kill_instance(self) -> int: 142 | resp = requests.delete(f"{ORCHESTRATOR_HOST}/instances/${self.get_instance_id()}") 143 | body = resp.json() 144 | 145 | print(body["message"]) 146 | return 1 147 | 148 | def deploy(self, user_data: UserData, mnemonic: str) -> str: 149 | web3 = get_privileged_web3(user_data, "main") 150 | 151 | return deploy( 152 | web3, self.project_location, mnemonic, env=self.get_deployment_args(user_data) 153 | ) 154 | 155 | def get_deployment_args(self, user_data: UserData) -> Dict[str, str]: 156 | return {} 157 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/pwn_launcher.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from eth_abi import abi 4 | import requests 5 | from ctf_launchers.launcher import Action, Launcher, ORCHESTRATOR_HOST, CHALLENGE 6 | from ctf_launchers.team_provider import TeamProvider, get_team_provider 7 | from ctf_server.types import UserData, get_privileged_web3 8 | from web3 import Web3 9 | 10 | FLAG = os.getenv("FLAG", "PCTF{flag}") 11 | 12 | 13 | class PwnChallengeLauncher(Launcher): 14 | def __init__( 15 | self, 16 | project_location: str = "challenge/project", 17 | provider: TeamProvider = get_team_provider(), 18 | ): 19 | super().__init__( 20 | project_location, 21 | provider, 22 | [ 23 | Action(name="get flag", handler=self.get_flag), 24 | ], 25 | ) 26 | 27 | def get_flag(self) -> int: 28 | instance_body = requests.get(f"{ORCHESTRATOR_HOST}/instances/{self.get_instance_id()}").json() 29 | if not instance_body['ok']: 30 | print(instance_body['message']) 31 | return 1 32 | 33 | user_data = instance_body['data'] 34 | 35 | if not self.is_solved( 36 | user_data, user_data['metadata']["challenge_address"] 37 | ): 38 | print("are you sure you solved it?") 39 | return 1 40 | 41 | print(FLAG) 42 | return 0 43 | 44 | def is_solved(self, user_data: UserData, addr: str) -> bool: 45 | web3 = get_privileged_web3(user_data, "main") 46 | 47 | (result,) = abi.decode( 48 | ["bool"], 49 | web3.eth.call( 50 | { 51 | "to": addr, 52 | "data": web3.keccak(text="isSolved()")[:4], 53 | } 54 | ), 55 | ) 56 | return result 57 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/score_submitter.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import os 3 | from typing import Any 4 | 5 | import requests 6 | 7 | 8 | class ScoreSubmitter(abc.ABC): 9 | @abc.abstractmethod 10 | def submit_score(self, team_id: str, data: Any, score: int): 11 | pass 12 | 13 | 14 | class RemoteScoreSubmitter(ScoreSubmitter): 15 | def __init__(self, host): 16 | self.__host = host 17 | 18 | def submit_score(self, team_id: str, data: Any, score: int): 19 | secret = os.getenv("SECRET") 20 | challenge_id = os.getenv("CHALLENGE_ID") 21 | 22 | resp = requests.post( 23 | f"{self.__host}/api/internal/submit", 24 | headers={ 25 | "Authorization": f"Bearer {secret}", 26 | "Content-Type": "application/json", 27 | }, 28 | json={ 29 | "teamId": team_id, 30 | "challengeId": challenge_id, 31 | "data": data, 32 | "score": score, 33 | }, 34 | ).json() 35 | 36 | if not resp["ok"]: 37 | raise Exception("failed to submit score", resp["message"]) 38 | 39 | print(f"score successfully submitted (id={resp['id']})") 40 | 41 | 42 | class LocalScoreSubmitter(ScoreSubmitter): 43 | def submit_score(self, team_id: str, data: Any, score: int): 44 | print(f"submitted score for team {team_id}: {score} {data}") 45 | 46 | 47 | def get_score_submitter() -> ScoreSubmitter: 48 | env = os.getenv("ENV", "local") 49 | 50 | if env == "local": 51 | return LocalScoreSubmitter() 52 | elif env == "dev": 53 | return RemoteScoreSubmitter(host="https://dev.ctf.paradigm.xyz") 54 | elif env == "prod": 55 | return RemoteScoreSubmitter(host="https://ctf.paradigm.xyz") 56 | else: 57 | raise Exception("unsupported env") 58 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/team_provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import os 3 | from dataclasses import dataclass 4 | from typing import Optional 5 | 6 | import requests 7 | 8 | 9 | class TeamProvider(abc.ABC): 10 | @abc.abstractmethod 11 | def get_team(self) -> Optional[str]: 12 | pass 13 | 14 | 15 | class TicketTeamProvider(TeamProvider): 16 | @dataclass 17 | class Ticket: 18 | challenge_id: str 19 | team_id: str 20 | 21 | def __init__(self, challenge_id): 22 | self.__challenge_id = challenge_id 23 | 24 | def get_team(self): 25 | ticket = self.__check_ticket(input("ticket? ")) 26 | if not ticket: 27 | print("invalid ticket!") 28 | return None 29 | 30 | if ticket.challenge_id != self.__challenge_id: 31 | print("invalid ticket!") 32 | return None 33 | 34 | return ticket.team_id 35 | 36 | def __check_ticket(self, ticket: str) -> Ticket: 37 | ticket_info = requests.post( 38 | "https://ctf.paradigm.xyz/api/internal/check-ticket", 39 | json={ 40 | "ticket": ticket, 41 | }, 42 | ).json() 43 | if not ticket_info["ok"]: 44 | return None 45 | 46 | return TicketTeamProvider.Ticket( 47 | challenge_id=ticket_info["ticket"]["challengeId"], 48 | team_id=ticket_info["ticket"]["teamId"], 49 | ) 50 | 51 | 52 | class StaticTeamProvider(TeamProvider): 53 | def __init__(self, team_id, ticket): 54 | self.__team_id = team_id 55 | self.__ticket = ticket 56 | 57 | def get_team(self) -> str | None: 58 | ticket = input("ticket? ") 59 | 60 | if ticket != self.__ticket: 61 | print("invalid ticket!") 62 | return None 63 | 64 | return self.__team_id 65 | 66 | 67 | class LocalTeamProvider(TeamProvider): 68 | def __init__(self, team_id): 69 | self.__team_id = team_id 70 | 71 | def get_team(self): 72 | return self.__team_id 73 | 74 | 75 | def get_team_provider() -> TeamProvider: 76 | env = os.getenv("ENV", "local") 77 | if env == "local": 78 | return LocalTeamProvider(team_id="local") 79 | elif env == "dev": 80 | return StaticTeamProvider(team_id="dev", ticket="dev2023") 81 | else: 82 | return TicketTeamProvider(challenge_id=os.getenv("CHALLENGE_ID")) 83 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_launchers/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import shutil 4 | import subprocess 5 | from typing import Dict 6 | 7 | from eth_account.account import LocalAccount 8 | from web3 import Web3 9 | 10 | from foundry.anvil import anvil_autoImpersonateAccount, anvil_setCode 11 | 12 | 13 | 14 | def deploy( 15 | web3: Web3, 16 | project_location: str, 17 | mnemonic: str, 18 | deploy_script: str = "script/Deploy.s.sol:Deploy", 19 | env: Dict = {}, 20 | ) -> str: 21 | anvil_autoImpersonateAccount(web3, True) 22 | 23 | rfd, wfd = os.pipe2(os.O_NONBLOCK) 24 | 25 | proc = subprocess.Popen( 26 | args=[ 27 | "/opt/foundry/bin/forge", 28 | "script", 29 | "--rpc-url", 30 | web3.provider.endpoint_uri, 31 | "--out", 32 | "/artifacts/out", 33 | "--cache-path", 34 | "/artifacts/cache", 35 | "--broadcast", 36 | "--unlocked", 37 | "--sender", 38 | "0x0000000000000000000000000000000000000000", 39 | deploy_script, 40 | ], 41 | env={ 42 | "PATH": "/opt/huff/bin:/opt/foundry/bin:/usr/bin:" + os.getenv("PATH", "/fake"), 43 | "MNEMONIC": mnemonic, 44 | "OUTPUT_FILE": f"/proc/self/fd/{wfd}", 45 | } 46 | | env, 47 | pass_fds=[wfd], 48 | cwd=project_location, 49 | text=True, 50 | encoding="utf8", 51 | stdin=subprocess.DEVNULL, 52 | stdout=subprocess.PIPE, 53 | stderr=subprocess.PIPE, 54 | ) 55 | stdout, stderr = proc.communicate() 56 | 57 | anvil_autoImpersonateAccount(web3, False) 58 | 59 | if proc.returncode != 0: 60 | print(stdout) 61 | print(stderr) 62 | raise Exception("forge failed to run") 63 | 64 | result = os.read(rfd, 256).decode("utf8") 65 | 66 | os.close(rfd) 67 | os.close(wfd) 68 | 69 | return result 70 | 71 | 72 | def anvil_setCodeFromFile( 73 | web3: Web3, 74 | addr: str, 75 | target: str, # "ContractFile.sol:ContractName", 76 | ): 77 | file, contract = target.split(":") 78 | 79 | with open(f"/artifacts/out/{file}/{contract}.json", "r") as f: 80 | cache = json.load(f) 81 | 82 | bytecode = cache["deployedBytecode"]["object"] 83 | 84 | anvil_setCode(web3, addr, bytecode) 85 | 86 | def http_url_to_ws(url: str) -> str: 87 | if url.startswith("http://"): 88 | return "ws://" + url[len("http://") :] 89 | elif url.startswith("https://"): 90 | return "wss://" + url[len("https://") :] 91 | 92 | return url 93 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/__init__.py: -------------------------------------------------------------------------------- 1 | from ctf_server.anvil_proxy import app as anvil_proxy 2 | from ctf_server.orchestrator import app as orchestrator -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/anvil_proxy.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from ast import Dict, List 4 | from contextlib import asynccontextmanager 5 | from typing import Any, Optional 6 | 7 | import aiohttp 8 | import asyncio 9 | from fastapi import FastAPI, Request, WebSocket 10 | import websockets 11 | 12 | from .utils import load_database 13 | 14 | 15 | ALLOWED_NAMESPACES = ["web3", "eth", "net"] 16 | DISALLOWED_METHODS = [ 17 | "eth_sign", 18 | "eth_signTransaction", 19 | "eth_signTypedData", 20 | "eth_signTypedData_v3", 21 | "eth_signTypedData_v4", 22 | "eth_sendTransaction", 23 | "eth_sendUnsignedTransaction", 24 | ] 25 | 26 | 27 | @asynccontextmanager 28 | async def lifespan(app: FastAPI): 29 | global session, database 30 | session = aiohttp.ClientSession() 31 | database = load_database() 32 | 33 | yield 34 | 35 | await session.close() 36 | 37 | 38 | app = FastAPI(lifespan=lifespan) 39 | 40 | 41 | @app.get("/") 42 | async def root(): 43 | return "rpc proxy running" 44 | 45 | 46 | def jsonrpc_fail(id: Any, code: int, message: str) -> Dict: 47 | return { 48 | "jsonrpc": "2.0", 49 | "id": id, 50 | "error": { 51 | "code": code, 52 | "message": message, 53 | }, 54 | } 55 | 56 | 57 | def validate_request(request: Any) -> Optional[Dict]: 58 | if not isinstance(request, dict): 59 | return jsonrpc_fail(None, -32600, "expected json object") 60 | 61 | request_id = request.get("id") 62 | request_method = request.get("method") 63 | 64 | if request_id is None: 65 | return jsonrpc_fail(None, -32600, "invalid jsonrpc id") 66 | 67 | if not isinstance(request_method, str): 68 | return jsonrpc_fail(request["id"], -32600, "invalid jsonrpc method") 69 | 70 | if ( 71 | request_method.split("_")[0] not in ALLOWED_NAMESPACES 72 | or request_method in DISALLOWED_METHODS 73 | ): 74 | return jsonrpc_fail(request["id"], -32600, "forbidden jsonrpc method") 75 | 76 | return None 77 | 78 | 79 | async def proxy_request( 80 | external_id: str, anvil_id: str, request_id: Optional[str], body: Any 81 | ) -> Optional[Any]: 82 | user_data = database.get_instance_by_external_id(external_id) 83 | if user_data is None: 84 | return jsonrpc_fail(request_id, -32602, "invalid rpc url, instance not found") 85 | 86 | anvil_instance = user_data.get("anvil_instances", {}).get(anvil_id, None) 87 | if anvil_instance is None: 88 | return jsonrpc_fail(request_id, -32602, "invalid rpc url, chain not found") 89 | 90 | instance_host = f"http://{anvil_instance['ip']}:{anvil_instance['port']}" 91 | 92 | try: 93 | async with session.post(instance_host, json=body) as resp: 94 | return await resp.json() 95 | except Exception as e: 96 | logging.error( 97 | "failed to proxy anvil request to %s/%s", external_id, anvil_id, exc_info=e 98 | ) 99 | return jsonrpc_fail(request_id, -32602, str(e)) 100 | 101 | 102 | @app.post("/{external_id}/{anvil_id}") 103 | async def rpc(external_id: str, anvil_id: str, request: Request): 104 | try: 105 | body = await request.json() 106 | except json.JSONDecodeError: 107 | return jsonrpc_fail(None, -32600, "expected json body") 108 | 109 | # special handling for batch requests 110 | if isinstance(body, list): 111 | responses = [] 112 | for idx, req in enumerate(body): 113 | validation_error = validate_request(req) 114 | responses.append(validation_error) 115 | 116 | if validation_error is not None: 117 | # neuter the request 118 | body[idx] = { 119 | "jsonrpc": "2.0", 120 | "id": idx, 121 | "method": "web3_clientVersion", 122 | } 123 | 124 | upstream_responses = await proxy_request(external_id, anvil_id, None, body) 125 | 126 | for idx in range(len(responses)): 127 | if responses[idx] is None: 128 | if isinstance(upstream_responses, List): 129 | responses[idx] = upstream_responses[idx] 130 | else: 131 | responses[idx] = upstream_responses 132 | 133 | return responses 134 | 135 | validation_resp = validate_request(body) 136 | if validation_resp is not None: 137 | return validation_resp 138 | 139 | return await proxy_request(external_id, anvil_id, body["id"], body) 140 | 141 | async def forward_message(client_to_remote: bool, client_ws: WebSocket, remote_ws: websockets): 142 | if client_to_remote: 143 | async for message in client_ws.iter_text(): 144 | try: 145 | json_msg = json.loads(message) 146 | except json.JSONDecodeError: 147 | await client_ws.send_json(jsonrpc_fail(None, -32600, "expected json body")) 148 | 149 | validation = validate_request(json_msg) 150 | if validation is not None: 151 | await client_ws.send_json(validation) 152 | else: 153 | await remote_ws.send(message) 154 | else: 155 | async for message in remote_ws: 156 | await client_ws.send_text(message) 157 | 158 | @app.websocket("/{external_id}/{anvil_id}/ws") 159 | async def ws_rpc(external_id: str, anvil_id: str, client_ws: WebSocket): 160 | user_data = database.get_instance_by_external_id(external_id) 161 | if user_data is None: 162 | client_ws.send_json(jsonrpc_fail(None, -32602, "invalid rpc url, instance not found")) 163 | return 164 | 165 | anvil_instance = user_data.get("anvil_instances", {}).get(anvil_id, None) 166 | if anvil_instance is None: 167 | client_ws.send_json(jsonrpc_fail(None, -32602, "invalid rpc url, chain not found")) 168 | return 169 | 170 | instance_host = f"ws://{anvil_instance['ip']}:{anvil_instance['port']}" 171 | 172 | async with websockets.connect(instance_host) as remote_ws: 173 | await client_ws.accept() 174 | task_a = asyncio.create_task(forward_message(True, client_ws, remote_ws)) 175 | task_b = asyncio.create_task(forward_message(False, client_ws, remote_ws)) 176 | 177 | try: 178 | await asyncio.wait([task_a, task_b], return_when=asyncio.FIRST_COMPLETED) 179 | task_a.cancel() 180 | task_b.cancel() 181 | except: 182 | pass -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/backends/__init__.py: -------------------------------------------------------------------------------- 1 | from .backend import Backend 2 | from .kubernetes_backend import KubernetesBackend 3 | from .docker_backend import DockerBackend 4 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/backends/backend.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import logging 3 | import random 4 | import string 5 | import time 6 | from threading import Thread 7 | 8 | from ctf_server.databases.database import Database 9 | from ctf_server.types import ( 10 | DEFAULT_ACCOUNTS, 11 | DEFAULT_BALANCE, 12 | DEFAULT_DERIVATION_PATH, 13 | DEFAULT_MNEMONIC, 14 | CreateInstanceRequest, 15 | LaunchAnvilInstanceArgs, 16 | UserData, 17 | ) 18 | from eth_account import Account 19 | from eth_account.hdaccount import key_from_seed, seed_from_mnemonic 20 | from foundry.anvil import anvil_setBalance 21 | from web3 import Web3 22 | 23 | 24 | class InstanceExists(Exception): 25 | pass 26 | 27 | 28 | class Backend(abc.ABC): 29 | def __init__(self, database: Database): 30 | self._database = database 31 | 32 | Thread( 33 | target=self.__instance_pruner_thread, 34 | name=f"{self.__class__.__name__} Anvil Pruner", 35 | daemon=True, 36 | ).start() 37 | 38 | def __instance_pruner_thread(self): 39 | while True: 40 | try: 41 | for instance in self._database.get_expired_instances(): 42 | logging.info( 43 | "pruning expired instance: %s", instance["instance_id"] 44 | ) 45 | 46 | self.kill_instance(instance["instance_id"]) 47 | except Exception as e: 48 | logging.error("failed to prune instances", exc_info=e) 49 | time.sleep(1) 50 | 51 | def launch_instance(self, args: CreateInstanceRequest) -> UserData: 52 | if self._database.get_instance(args["instance_id"]) is not None: 53 | raise InstanceExists() 54 | 55 | try: 56 | user_data = self._launch_instance_impl(args) 57 | self._database.register_instance(args["instance_id"], user_data) 58 | return user_data 59 | 60 | except: 61 | self._cleanup_instance(args) 62 | raise 63 | 64 | def _launch_instance_impl(self, args: CreateInstanceRequest) -> UserData: 65 | pass 66 | 67 | def _cleanup_instance(self, args: CreateInstanceRequest): 68 | pass 69 | 70 | @abc.abstractmethod 71 | def kill_instance(self, id: str) -> UserData: 72 | pass 73 | 74 | def _generate_rpc_id(self, N: int = 24) -> str: 75 | return "".join( 76 | random.SystemRandom().choice(string.ascii_letters) for _ in range(N) 77 | ) 78 | 79 | def __derive_account(self, derivation_path: str, mnemonic: str, index: int) -> str: 80 | seed = seed_from_mnemonic(mnemonic, "") 81 | private_key = key_from_seed(seed, f"{derivation_path}{index}") 82 | 83 | return Account.from_key(private_key) 84 | 85 | def _prepare_node(self, args: LaunchAnvilInstanceArgs, web3: Web3): 86 | while not web3.is_connected(): 87 | time.sleep(0.1) 88 | continue 89 | 90 | for i in range(args.get("accounts", DEFAULT_ACCOUNTS)): 91 | anvil_setBalance( 92 | web3, 93 | self.__derive_account( 94 | args.get("derivation_path", DEFAULT_DERIVATION_PATH), 95 | args.get("mnemonic", DEFAULT_MNEMONIC), 96 | i, 97 | ).address, 98 | hex(int(args.get("balance", DEFAULT_BALANCE) * 10**18)), 99 | ) 100 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/backends/docker_backend.py: -------------------------------------------------------------------------------- 1 | import http.client 2 | import logging 3 | import shlex 4 | import time 5 | from typing import Dict, List 6 | 7 | import docker 8 | from ctf_server.databases.database import Database 9 | from ctf_server.types import ( 10 | DEFAULT_IMAGE, 11 | CreateInstanceRequest, 12 | InstanceInfo, 13 | UserData, 14 | format_anvil_args, 15 | ) 16 | from docker.errors import APIError, NotFound 17 | from docker.models.containers import Container 18 | from docker.models.volumes import Volume 19 | from docker.types import Mount, RestartPolicy 20 | from docker.types.services import RestartConditionTypesEnum 21 | from web3 import Web3 22 | 23 | from .backend import Backend 24 | 25 | 26 | class DockerBackend(Backend): 27 | def __init__(self, database: Database): 28 | super().__init__(database) 29 | 30 | self.__client = docker.from_env() 31 | 32 | def _launch_instance_impl(self, request: CreateInstanceRequest) -> UserData: 33 | instance_id = request["instance_id"] 34 | 35 | volume: Volume = self.__client.volumes.create(name=instance_id) 36 | 37 | anvil_containers: Dict[str, Container] = {} 38 | for anvil_id, anvil_args in request["anvil_instances"].items(): 39 | anvil_containers[anvil_id] = self.__client.containers.run( 40 | name=f"{instance_id}-{anvil_id}", 41 | image=anvil_args.get("image", DEFAULT_IMAGE), 42 | network="paradigmctf", 43 | entrypoint=["sh", "-c"], 44 | command=[ 45 | "while true; do anvil " 46 | + " ".join( 47 | [ 48 | shlex.quote(str(v)) 49 | for v in format_anvil_args(anvil_args, anvil_id) 50 | ] 51 | ) 52 | + "; sleep 1; done;" 53 | ], 54 | restart_policy={"Name": "always"}, 55 | detach=True, 56 | mounts=[ 57 | Mount(target="/data", source=volume.id), 58 | ], 59 | ) 60 | 61 | daemon_containers: Dict[str, Container] = {} 62 | for daemon_id, daemon_args in request.get("daemon_instances", {}).items(): 63 | daemon_containers[daemon_id] = self.__client.containers.run( 64 | name=f"{instance_id}-{daemon_id}", 65 | image=daemon_args["image"], 66 | network="paradigmctf", 67 | restart_policy={"Name": "always"}, 68 | detach=True, 69 | environment={ 70 | "INSTANCE_ID": instance_id, 71 | }, 72 | ) 73 | 74 | anvil_instances: Dict[str, InstanceInfo] = {} 75 | for anvil_id, anvil_container in anvil_containers.items(): 76 | container: Container = self.__client.containers.get(anvil_container.id) 77 | 78 | anvil_instances[anvil_id] = { 79 | "id": anvil_id, 80 | "ip": container.attrs["NetworkSettings"]["Networks"]["paradigmctf"][ 81 | "IPAddress" 82 | ], 83 | "port": 8545, 84 | } 85 | 86 | self._prepare_node( 87 | request["anvil_instances"][anvil_id], 88 | Web3( 89 | Web3.HTTPProvider( 90 | f"http://{anvil_instances[anvil_id]['ip']}:{anvil_instances[anvil_id]['port']}" 91 | ) 92 | ), 93 | ) 94 | 95 | daemon_instances = {} 96 | for daemon_id, daemon_container in daemon_containers.items(): 97 | daemon_instances[daemon_id] = { 98 | "id": daemon_id, 99 | } 100 | 101 | now = time.time() 102 | return UserData( 103 | instance_id=instance_id, 104 | external_id=self._generate_rpc_id(), 105 | created_at=now, 106 | expires_at=now + request["timeout"], 107 | anvil_instances=anvil_instances, 108 | daemon_instances=daemon_instances, 109 | metadata={}, 110 | ) 111 | 112 | def _cleanup_instance(self, args: CreateInstanceRequest): 113 | instance_id = args["instance_id"] 114 | 115 | self.__try_delete( 116 | instance_id, 117 | args.get("anvil_instances", {}).keys(), 118 | args.get("daemon_instances", {}).keys(), 119 | ) 120 | 121 | def kill_instance(self, instance_id: str) -> UserData: 122 | instance = self._database.unregister_instance(instance_id) 123 | if instance is None: 124 | return None 125 | 126 | self.__try_delete( 127 | instance_id, 128 | instance.get("anvil_instances", {}).keys(), 129 | instance.get("daemon_instances", {}).keys(), 130 | ) 131 | 132 | return instance 133 | 134 | def __try_delete( 135 | self, instance_id: str, anvil_ids: List[str], daemon_ids: List[str] 136 | ): 137 | for anvil_id in anvil_ids: 138 | self.__try_delete_container(f"{instance_id}-{anvil_id}") 139 | 140 | for daemon_id in daemon_ids: 141 | self.__try_delete_container(f"{instance_id}-{daemon_id}") 142 | 143 | self.__try_delete_volume(instance_id) 144 | 145 | def __try_delete_container(self, container_name: str): 146 | try: 147 | try: 148 | container: Container = self.__client.containers.get(container_name) 149 | except NotFound: 150 | return 151 | 152 | logging.info("deleting container %s (%s)", container.id, container.name) 153 | 154 | try: 155 | container.kill() 156 | except APIError as api_error: 157 | # http conflict = container not running, which is fine 158 | if api_error.status_code != http.client.CONFLICT: 159 | raise 160 | 161 | container.remove() 162 | except Exception as e: 163 | logging.error( 164 | "failed to delete container %s (%s)", 165 | container.id, 166 | container.name, 167 | exc_info=e, 168 | ) 169 | 170 | def __try_delete_volume(self, volume_name: str): 171 | try: 172 | try: 173 | volume: Volume = self.__client.volumes.get(volume_name) 174 | except NotFound: 175 | return 176 | 177 | logging.info("deleting volume %s (%s)", volume.id, volume.name) 178 | 179 | volume.remove() 180 | except Exception as e: 181 | logging.error( 182 | "failed to delete volume %s (%s)", volume.id, volume.name, exc_info=e 183 | ) 184 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/backends/kubernetes_backend.py: -------------------------------------------------------------------------------- 1 | import http.client 2 | import shlex 3 | import time 4 | from typing import Any, List 5 | 6 | from web3 import Web3 7 | 8 | from ctf_server.databases.database import Database 9 | from ctf_server.types import ( 10 | DEFAULT_IMAGE, 11 | CreateInstanceRequest, 12 | UserData, 13 | format_anvil_args, 14 | ) 15 | from kubernetes.client.api import core_v1_api 16 | from kubernetes.client.exceptions import ApiException 17 | from kubernetes.client.models import V1Pod 18 | 19 | from kubernetes import config 20 | 21 | from .backend import Backend 22 | 23 | 24 | class KubernetesBackend(Backend): 25 | def __init__(self, database: Database, kubeconfig: str) -> None: 26 | super().__init__(database) 27 | 28 | if kubeconfig == "incluster": 29 | config.load_incluster_config() 30 | else: 31 | config.load_kube_config(kubeconfig) 32 | 33 | self.__core_v1 = core_v1_api.CoreV1Api() 34 | 35 | def _launch_instance_impl(self, request: CreateInstanceRequest) -> UserData: 36 | instance_id = request["instance_id"] 37 | 38 | pod_manifest = { 39 | "apiVersion": "v1", 40 | "kind": "Pod", 41 | "metadata": {"name": instance_id}, 42 | "spec": { 43 | "volumes": [{"name": "workdir", "emptyDir": {}}], 44 | "containers": self.__get_anvil_containers(request) 45 | + self.__get_daemon_containers(request), 46 | }, 47 | } 48 | 49 | api_response: V1Pod = self.__core_v1.create_namespaced_pod( 50 | namespace="default", body=pod_manifest 51 | ) 52 | 53 | while True: 54 | api_response = self.__core_v1.read_namespaced_pod( 55 | name=pod_manifest["metadata"]["name"], namespace="default" 56 | ) 57 | if api_response.status.phase != "Pending": 58 | break 59 | time.sleep(1) 60 | 61 | anvil_instances = {} 62 | for offset, anvil_id in enumerate(request.get("anvil_instances", []).keys()): 63 | anvil_instances[anvil_id] = { 64 | "id": anvil_id, 65 | "ip": api_response.status.pod_ip, 66 | "port": 8545 + offset, 67 | } 68 | 69 | self._prepare_node( 70 | request["anvil_instances"][anvil_id], 71 | Web3( 72 | Web3.HTTPProvider( 73 | f"http://{anvil_instances[anvil_id]['ip']}:{anvil_instances[anvil_id]['port']}" 74 | ) 75 | ), 76 | ) 77 | 78 | daemon_instances = {} 79 | for daemon_id in request.get("daemon_instances", []).keys(): 80 | daemon_instances[daemon_id] = {"id": daemon_id} 81 | 82 | now = time.time() 83 | return UserData( 84 | instance_id=instance_id, 85 | external_id=self._generate_rpc_id(), 86 | created_at=now, 87 | expires_at=now + request["timeout"], 88 | anvil_instances=anvil_instances, 89 | daemon_instances=daemon_instances, 90 | metadata={}, 91 | ) 92 | 93 | def __get_anvil_containers(self, args: CreateInstanceRequest) -> List[Any]: 94 | return [ 95 | { 96 | "name": anvil_id, 97 | "image": anvil_args.get("image", DEFAULT_IMAGE), 98 | "command": ["sh", "-c"], 99 | "args": [ 100 | "while true; do anvil " 101 | + " ".join( 102 | [ 103 | shlex.quote(str(v)) 104 | for v in format_anvil_args( 105 | anvil_args, anvil_id, 8545 + offset 106 | ) 107 | ] 108 | ) 109 | + "; sleep 1; done;" 110 | ], 111 | "volumeMounts": [ 112 | { 113 | "mountPath": "/data", 114 | "name": "workdir", 115 | } 116 | ], 117 | } 118 | for offset, (anvil_id, anvil_args) in enumerate( 119 | args.get("anvil_instances", []).items() 120 | ) 121 | ] 122 | 123 | def __get_daemon_containers(self, args: CreateInstanceRequest) -> List[Any]: 124 | return [ 125 | { 126 | "name": daemon_id, 127 | "image": daemon_args["image"], 128 | "env": [ 129 | { 130 | "name": "INSTANCE_ID", 131 | "value": args["instance_id"], 132 | } 133 | ], 134 | } 135 | for (daemon_id, daemon_args) in args.get("daemon_instances", []).items() 136 | ] 137 | 138 | def kill_instance(self, instance_id: str) -> UserData: 139 | instance = self._database.unregister_instance(instance_id) 140 | if instance is None: 141 | return None 142 | 143 | self.__core_v1.delete_namespaced_pod(namespace="default", name=instance_id, grace_period_seconds=0) 144 | 145 | while True: 146 | try: 147 | self.__core_v1.read_namespaced_pod( 148 | namespace="default", 149 | name=instance_id, 150 | ) 151 | except ApiException as e: 152 | if e.status == http.client.NOT_FOUND: 153 | break 154 | 155 | time.sleep(0.5) 156 | 157 | return instance 158 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/databases/__init__.py: -------------------------------------------------------------------------------- 1 | from .database import Database 2 | from .sqlitedb import SQLiteDatabase 3 | from .redisdb import RedisDatabase 4 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/databases/database.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Dict, List, Optional 3 | from ctf_server.types import UserData 4 | 5 | class Database(abc.ABC): 6 | def __init__(self) -> None: 7 | super().__init__() 8 | 9 | @abc.abstractmethod 10 | def register_instance(self, instance_id: str, instance: UserData): 11 | pass 12 | 13 | @abc.abstractmethod 14 | def unregister_instance(self, instance_id: str) -> UserData: 15 | pass 16 | 17 | @abc.abstractmethod 18 | def get_instance(self, instance_id: str) -> Optional[UserData]: 19 | pass 20 | 21 | @abc.abstractmethod 22 | def get_instance_by_external_id(self, external_id: str) -> Optional[UserData]: 23 | pass 24 | 25 | def get_expired_instances(self) -> List[UserData]: 26 | pass 27 | 28 | def get_metadata(self, instance_id: str) -> Optional[Dict[str, str]]: 29 | pass 30 | 31 | def update_metadata(self, instance_id: str, metadata: Dict[str, str]): 32 | pass 33 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/databases/redisdb.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from typing import Any, Dict, List, Optional 4 | 5 | import redis 6 | from ctf_server.types import UserData 7 | 8 | from .database import Database 9 | 10 | 11 | class RedisDatabase(Database): 12 | def __init__(self, url: str, redis_kwargs: Dict[str, Any] = {}) -> None: 13 | super().__init__() 14 | 15 | self.__client: redis.Redis = redis.Redis.from_url( 16 | url, 17 | decode_responses=True, 18 | **redis_kwargs, 19 | ) 20 | 21 | def register_instance(self, instance_id: str, instance: UserData): 22 | pipeline = self.__client.pipeline() 23 | 24 | try: 25 | pipeline.json().set(f"instance/{instance['instance_id']}", "$", instance) 26 | pipeline.hset( 27 | "external_ids", instance["external_id"], instance["instance_id"] 28 | ) 29 | pipeline.zadd( 30 | "expiries", 31 | { 32 | instance["instance_id"]: int(instance["expires_at"]), 33 | }, 34 | ) 35 | finally: 36 | pipeline.execute() 37 | 38 | def update_instance(self, instance_id: str, instance: UserData): 39 | raise Exception("not supported") 40 | 41 | def unregister_instance(self, instance_id: str) -> UserData: 42 | instance = self.__client.json().get(f"instance/{instance_id}") 43 | if instance is None: 44 | return None 45 | 46 | pipeline = self.__client.pipeline() 47 | try: 48 | pipeline.json().delete(f"instance/{instance_id}") 49 | pipeline.hdel("external_ids", instance["external_id"]) 50 | pipeline.zrem("expiries", instance_id) 51 | pipeline.delete(f"metadata/{instance_id}") 52 | return instance 53 | finally: 54 | pipeline.execute() 55 | 56 | def get_instance(self, instance_id: str) -> Optional[UserData]: 57 | instance: Optional[UserData] = self.__client.json().get(f"instance/{instance_id}") 58 | if instance is None: 59 | return None 60 | 61 | instance['metadata'] = {} 62 | metadata = self.__client.hgetall(f"metadata/{instance_id}") 63 | if metadata is not None: 64 | instance['metadata'] = metadata 65 | 66 | return instance 67 | 68 | 69 | def get_instance_by_external_id(self, rpc_id: str) -> Optional[UserData]: 70 | instance_id = self.__client.hget("external_ids", rpc_id) 71 | if instance_id is None: 72 | return None 73 | 74 | return self.get_instance(instance_id) 75 | 76 | def get_all_instances(self) -> List[UserData]: 77 | keys = self.__client.keys("instance/*") 78 | 79 | result = [] 80 | for key in keys: 81 | result.append(self.get_instance(key.split("/")[1])) 82 | 83 | return result 84 | 85 | def get_expired_instances(self) -> List[UserData]: 86 | instance_ids = self.__client.zrange( 87 | "expiries", 0, int(time.time()), byscore=True 88 | ) 89 | 90 | instances = [] 91 | for instance_id in instance_ids: 92 | instances.append(self.get_instance(instance_id)) 93 | 94 | return instances 95 | 96 | def update_metadata(self, instance_id: str, metadata: Dict[str, str]): 97 | pipeline = self.__client.pipeline() 98 | try: 99 | for k, v in metadata.items(): 100 | pipeline.hset(f"metadata/{instance_id}", k, v) 101 | finally: 102 | pipeline.execute() 103 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/databases/sqlitedb.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sqlite3 3 | from typing import List, Optional 4 | from ctf_server.databases import Database 5 | from ctf_server.types import InstanceInfo 6 | from threading import Lock 7 | 8 | 9 | class SQLiteDatabase(Database): 10 | def __init__(self, db_path: str): 11 | super().__init__() 12 | 13 | self.__conn_lock = Lock() 14 | self.__conn = sqlite3.connect(database=db_path, check_same_thread=False) 15 | self.__conn.execute( 16 | """ 17 | CREATE TABLE IF NOT EXISTS anvil_instances 18 | ( 19 | instance_id VARCHAR PRIMARY KEY, 20 | rpc_id VARCHAR, 21 | instance_data JSON 22 | );""" 23 | ) 24 | 25 | def register_instance(self, instance_id: str, instance: InstanceInfo): 26 | self.__conn_lock.acquire() 27 | try: 28 | cursor = self.__conn.execute( 29 | """INSERT INTO anvil_instances(instance_id, instance_data) VALUES (?, ?)""", 30 | (instance_id, json.dumps(instance)), 31 | ) 32 | finally: 33 | cursor.close() 34 | self.__conn_lock.release() 35 | 36 | def update_instance(self, instance_id: str, instance: InstanceInfo): 37 | self.__conn_lock.acquire() 38 | try: 39 | cursor = self.__conn.execute( 40 | """UPDATE anvil_instances SET instance_data = ? WHERE instance_id = ?""", 41 | (json.dumps(instance), instance_id), 42 | ) 43 | finally: 44 | cursor.close() 45 | self.__conn_lock.release() 46 | 47 | def unregister_instance(self, instance_id: str) -> InstanceInfo: 48 | self.__conn_lock.acquire() 49 | try: 50 | cursor = self.__conn.execute( 51 | """DELETE FROM anvil_instances WHERE instance_id = ? RETURNING instance_data""", (instance_id,) 52 | ) 53 | row = cursor.fetchone() 54 | if row is None: 55 | return None 56 | 57 | return json.loads(row[0]) 58 | finally: 59 | cursor.close() 60 | self.__conn_lock.release() 61 | 62 | def get_all_instances(self) -> List[InstanceInfo]: 63 | self.__conn_lock.acquire() 64 | try: 65 | cursor = self.__conn.execute( 66 | """SELECT instance_data FROM anvil_instances""" 67 | ) 68 | result = [] 69 | while True: 70 | row = cursor.fetchone() 71 | if row is None: 72 | break 73 | 74 | result.append(json.loads(row[0])) 75 | return result 76 | finally: 77 | cursor.close() 78 | self.__conn_lock.release() 79 | 80 | def get_instance_by_external_id(self, rpc_id: str) -> InstanceInfo | None: 81 | self.__conn_lock.acquire() 82 | try: 83 | cursor = self.__conn.execute( 84 | """SELECT instance_data FROM anvil_instances WHERE rpc_id = ?""", (rpc_id,) 85 | ) 86 | row = cursor.fetchone() 87 | if row is None: 88 | return None 89 | 90 | return json.loads(row[0]) 91 | finally: 92 | cursor.close() 93 | self.__conn_lock.release() 94 | 95 | def get_instance(self, instance_id: str) -> InstanceInfo | None: 96 | self.__conn_lock.acquire() 97 | try: 98 | cursor = self.__conn.execute( 99 | """SELECT instance_data FROM anvil_instances WHERE instance_id = ?""", (instance_id,) 100 | ) 101 | row = cursor.fetchone() 102 | if row is None: 103 | return None 104 | 105 | return json.loads(row[0]) 106 | finally: 107 | cursor.close() 108 | self.__conn_lock.release() 109 | 110 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/orchestrator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import traceback 4 | from contextlib import asynccontextmanager 5 | from typing import Dict 6 | 7 | from fastapi import FastAPI 8 | 9 | from .backends.backend import InstanceExists 10 | from .types import CreateInstanceRequest 11 | from .utils import load_backend, load_database 12 | 13 | 14 | @asynccontextmanager 15 | async def lifespan(app: FastAPI): 16 | global database, backend 17 | database = load_database() 18 | backend = load_backend(database) 19 | 20 | logging.root.setLevel(logging.INFO) 21 | 22 | yield 23 | 24 | 25 | app = FastAPI(lifespan=lifespan) 26 | 27 | 28 | @app.post("/instances") 29 | def create_instance(args: CreateInstanceRequest): 30 | logging.info("launching new instance: %s", args["instance_id"]) 31 | 32 | try: 33 | user_data = backend.launch_instance(args) 34 | except InstanceExists: 35 | logging.warning("instance already exists: %s", args["instance_id"]) 36 | 37 | return { 38 | "ok": False, 39 | "message": "instance already exists", 40 | } 41 | except Exception as e: 42 | logging.error( 43 | "failed to launch instance: %s", args["instance_id"], exc_info=e 44 | ) 45 | return { 46 | 'ok': False, 47 | 'message': 'an internal error occurred', 48 | } 49 | 50 | logging.info("launched new instance: %s", args["instance_id"]) 51 | return { 52 | "ok": True, 53 | "message": "instance launched", 54 | "data": user_data, 55 | } 56 | 57 | @app.get("/instances/{instance_id}") 58 | def get_instance(instance_id: str): 59 | user_data = database.get_instance(instance_id) 60 | if user_data is None: 61 | return { 62 | 'ok': False, 63 | 'message': 'instance does not exist', 64 | } 65 | 66 | return { 67 | 'ok': True, 68 | 'message': 'fetched metadata', 69 | 'data': user_data 70 | } 71 | 72 | @app.post("/instances/{instance_id}/metadata") 73 | def update_metadata(instance_id: str, metadata: Dict[str, str]): 74 | try: 75 | database.update_metadata(instance_id, metadata) 76 | except: 77 | return { 78 | 'ok': False, 79 | 'message': 'instance does not exist' 80 | } 81 | 82 | return { 83 | 'ok': True, 84 | 'message': 'metadata updated', 85 | } 86 | 87 | 88 | @app.delete("/instances/{instance_id}") 89 | def delete_instance(instance_id: str): 90 | logging.info("killing instance: %s", instance_id) 91 | 92 | instance = backend.kill_instance(instance_id) 93 | if instance is None: 94 | return { 95 | "ok": False, 96 | "message": "no instance found", 97 | } 98 | 99 | return { 100 | "ok": True, 101 | "message": "instance deleted", 102 | } 103 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/types/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | from dataclasses import dataclass 4 | from typing import Dict, List, NotRequired, Optional 5 | 6 | from eth_account import Account 7 | from eth_account.account import LocalAccount 8 | from eth_account.hdaccount import key_from_seed, seed_from_mnemonic 9 | from typing_extensions import TypedDict 10 | from web3 import Web3 11 | 12 | DEFAULT_IMAGE = "ghcr.io/foundry-rs/foundry:latest" 13 | DEFAULT_DERIVATION_PATH = "m/44'/60'/0'/0/" 14 | DEFAULT_ACCOUNTS = 10 15 | DEFAULT_BALANCE = 1000 16 | DEFAULT_MNEMONIC = "test test test test test test test test test test test junk" 17 | 18 | PUBLIC_HOST = os.getenv("PUBLIC_HOST", "http://127.0.0.1:8545") 19 | 20 | 21 | class LaunchAnvilInstanceArgs(TypedDict): 22 | image: NotRequired[Optional[str]] 23 | accounts: NotRequired[Optional[int]] 24 | balance: NotRequired[Optional[float]] 25 | derivation_path: NotRequired[Optional[str]] 26 | mnemonic: NotRequired[Optional[str]] 27 | fork_url: NotRequired[Optional[str]] 28 | fork_block_num: NotRequired[Optional[int]] 29 | fork_chain_id: NotRequired[Optional[int]] 30 | no_rate_limit: NotRequired[Optional[bool]] 31 | chain_id: NotRequired[Optional[int]] 32 | code_size_limit: NotRequired[Optional[int]] 33 | block_time: NotRequired[Optional[int]] 34 | 35 | 36 | def format_anvil_args(args: LaunchAnvilInstanceArgs, anvil_id: str, port: int = 8545) -> List[str]: 37 | cmd_args = [] 38 | cmd_args += ["--host", "0.0.0.0"] 39 | cmd_args += ["--port", str(port)] 40 | cmd_args += ["--accounts", "0"] 41 | cmd_args += ["--state", f"/data/{anvil_id}-state.json"] 42 | cmd_args += ["--state-interval", "5"] 43 | 44 | if args.get("fork_url") is not None: 45 | cmd_args += ["--fork-url", args["fork_url"]] 46 | 47 | if args.get("fork_chain_id") is not None: 48 | cmd_args += ["--fork-chain-id", str(args["fork_chain_id"])] 49 | 50 | if args.get("fork_block_num") is not None: 51 | cmd_args += ["--fork-block-number", str(args["fork_block_num"])] 52 | 53 | if args.get("no_rate_limit") == True: 54 | cmd_args += ["--no-rate-limit"] 55 | 56 | if args.get("chain_id") is not None: 57 | cmd_args += ["--chain-id", str(args["chain_id"])] 58 | 59 | if args.get("code_size_limit") is not None: 60 | cmd_args += ["--code-size-limit", str(args["code_size_limit"])] 61 | 62 | if args.get("block_time") is not None: 63 | cmd_args += ["--block-time", str(args["block_time"])] 64 | 65 | return cmd_args 66 | 67 | 68 | class DaemonInstanceArgs(TypedDict): 69 | image: str 70 | 71 | 72 | class CreateInstanceRequest(TypedDict): 73 | instance_id: str 74 | timeout: int 75 | anvil_instances: NotRequired[Dict[str, LaunchAnvilInstanceArgs]] 76 | daemon_instances: NotRequired[Dict[str, DaemonInstanceArgs]] 77 | 78 | 79 | class InstanceInfo(TypedDict): 80 | id: str 81 | ip: str 82 | port: int 83 | 84 | 85 | @dataclass 86 | class AnvilInstance: 87 | proc: subprocess.Popen 88 | id: str 89 | 90 | ip: str 91 | port: int 92 | 93 | 94 | class UserData(TypedDict): 95 | instance_id: str 96 | external_id: str 97 | created_at: float 98 | expires_at: float 99 | # launch_args: Dict[str, LaunchAnvilInstanceArgs] 100 | anvil_instances: Dict[str, InstanceInfo] 101 | daemon_instances: Dict[str, InstanceInfo] 102 | metadata: Dict 103 | 104 | # def get_privileged_account(self, offset: int) -> LocalAccount: 105 | # seed = seed_from_mnemonic(self.mnemonic, "") 106 | # private_key = key_from_seed(seed, f"m/44'/60'/0'/0/{offset}") 107 | 108 | # return Account.from_key(private_key) 109 | 110 | # def get_player_account(self) -> LocalAccount: 111 | # return self.get_privileged_account(0) 112 | 113 | # def get_system_account(self) -> LocalAccount: 114 | # return self.get_privileged_account(1) 115 | 116 | # def get_additional_account(self, offset: int) -> LocalAccount: 117 | # return self.get_privileged_account(offset + 2) 118 | 119 | # def get_privileged_web3(self, id: str) -> Web3: 120 | # return Web3(Web3.HTTPProvider(f"http://127.0.0.1:{self.instances[id].port}")) 121 | 122 | # def get_unprivileged_web3(self, id: str) -> Web3: 123 | # return Web3( 124 | # Web3.HTTPProvider( 125 | # f"http://127.0.0.1:8545/{self.internal_id}/{id}", 126 | # request_kwargs={"timeout": 60}, 127 | # ) 128 | # ) 129 | 130 | 131 | def get_account(mnemonic: str, offset: int) -> LocalAccount: 132 | seed = seed_from_mnemonic(mnemonic, "") 133 | private_key = key_from_seed(seed, f"{DEFAULT_DERIVATION_PATH}{offset}") 134 | 135 | return Account.from_key(private_key) 136 | 137 | 138 | def get_player_account(mnemonic: str) -> LocalAccount: 139 | return get_account(mnemonic, 0) 140 | 141 | 142 | def get_system_account(mnemonic: str) -> LocalAccount: 143 | return get_account(mnemonic, 1) 144 | 145 | 146 | def get_additional_account(mnemonic: str, offset: int) -> LocalAccount: 147 | return get_account(mnemonic, offset + 2) 148 | 149 | 150 | def get_privileged_web3(user_data: UserData, anvil_id: str) -> Web3: 151 | anvil_instance = user_data["anvil_instances"][anvil_id] 152 | return Web3( 153 | Web3.HTTPProvider(f"http://{anvil_instance['ip']}:{anvil_instance['port']}") 154 | ) 155 | 156 | 157 | def get_unprivileged_web3(user_data: UserData, anvil_id: str) -> Web3: 158 | return Web3( 159 | Web3.HTTPProvider( 160 | f"http://anvil-proxy:8545/{user_data['external_id']}/{anvil_id}" 161 | ) 162 | ) 163 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_server/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .backends import Backend, KubernetesBackend, DockerBackend 4 | from .databases import Database, RedisDatabase, SQLiteDatabase 5 | 6 | 7 | def load_database() -> Database: 8 | dbtype = os.getenv("DATABASE", "sqlite") 9 | if dbtype == "sqlite": 10 | dbpath = os.getenv("SQLITE_PATH", ":memory:") 11 | return SQLiteDatabase(dbpath) 12 | elif dbtype == "redis": 13 | url = os.getenv("REDIS_URL", "redis://127.0.0.1:6379/0") 14 | return RedisDatabase(url) 15 | 16 | raise Exception("invalid database type", dbtype) 17 | 18 | 19 | def load_backend(database: Database) -> Backend: 20 | backend_type = os.getenv("BACKEND", "docker") 21 | if backend_type == "docker": 22 | return DockerBackend(database=database) 23 | elif backend_type == "kubernetes": 24 | config_file = os.getenv("KUBECONFIG", "incluster") 25 | return KubernetesBackend(database, config_file) 26 | 27 | raise Exception("invalid backend type", backend_type) 28 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_solvers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paradigmxyz/paradigm-ctf-infrastructure/6c39333a674f18458bc27256091ab0306b9d432e/paradigmctf.py/ctf_solvers/__init__.py -------------------------------------------------------------------------------- /paradigmctf.py/ctf_solvers/koth_solver.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from ctf_solvers.solver import TicketedRemote, kill_instance, launch_instance 4 | from web3 import Web3 5 | 6 | 7 | class KothChallengeSolver(abc.ABC): 8 | def start(self): 9 | kill_instance() 10 | 11 | data = self.launch_instance() 12 | private_key = "0x" + hex(data["private key"])[2:].ljust(64, "0") 13 | challenge = Web3.to_checksum_address( 14 | "0x" + hex(data["challenge contract"])[2:].ljust(40, "0") 15 | ) 16 | 17 | print("[+] submitting solution") 18 | print(f'[+] rpc endpoints: {data["rpc endpoints"]}') 19 | print(f"[+] private key: {private_key}") 20 | print(f"[+] challenge: {challenge}") 21 | 22 | self._submit(data["rpc endpoints"], private_key, challenge) 23 | 24 | with TicketedRemote() as r: 25 | r.recvuntil(b"?") 26 | r.send(b"3\n") 27 | data = r.recvall().decode("utf8").strip() 28 | 29 | print(f"[+] response: {data}") 30 | 31 | def launch_instance(self): 32 | return launch_instance() 33 | 34 | @abc.abstractmethod 35 | def _submit(self, rpc, player, challenge): 36 | pass 37 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_solvers/pwn_solver.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from ctf_solvers.solver import TicketedRemote, kill_instance, launch_instance 4 | from ctf_solvers.utils import solve 5 | from web3 import Web3 6 | 7 | 8 | class PwnChallengeSolver(abc.ABC): 9 | def start(self): 10 | kill_instance() 11 | 12 | data = launch_instance() 13 | private_key = "0x" + hex(data["private key"])[2:].ljust(64, "0") 14 | challenge = Web3.to_checksum_address( 15 | "0x" + hex(data["challenge contract"])[2:].ljust(40, "0") 16 | ) 17 | 18 | print("[+] solving challenge") 19 | print(f'[+] rpc endpoints: {data["rpc endpoints"]}') 20 | print(f"[+] private key: {private_key}") 21 | print(f"[+] challenge: {challenge}") 22 | 23 | self._solve(data["rpc endpoints"], private_key, challenge) 24 | 25 | with TicketedRemote() as r: 26 | r.recvuntil(b"?") 27 | r.send(b"3\n") 28 | data = r.recvall().decode("utf8").strip() 29 | 30 | print(f"[+] response: {data}") 31 | 32 | def _solve(self, rpcs, player, challenge): 33 | web3 = Web3(Web3.HTTPProvider(rpcs[0])) 34 | solve(web3, "project", player, challenge, "script/Solve.s.sol:Solve") 35 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_solvers/solver.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pwn import * 4 | import yaml 5 | 6 | 7 | class TicketedRemote: 8 | def __enter__(self) -> remote: 9 | challenge_id = sys.path[0].split(os.path.sep)[-2] 10 | 11 | env = os.getenv("ENV", "local") 12 | if env == "local": 13 | host = "127.0.0.1" 14 | elif env == "dev": 15 | host = f"{challenge_id}.dev.ctf.paradigm.xyz" 16 | elif env == "prod": 17 | host = f"{challenge_id}.challenges.paradigm.xyz" 18 | else: 19 | raise Exception("unsupported env") 20 | 21 | self.__r = remote(host, "1337") 22 | 23 | data = self.__r.recvuntil(b"?") 24 | if "ticket" not in data.decode(): 25 | self.__r.unrecv(data) 26 | return self.__r 27 | 28 | if env == "dev": 29 | ticket = "dev2023" 30 | else: 31 | ticket = os.getenv("SECRET") + ":healthcheck-team:" + challenge_id 32 | 33 | self.__r.send(ticket.encode("utf8")) 34 | self.__r.send(b"\n") 35 | 36 | return self.__r 37 | 38 | def __exit__(self, exc_type, exc_val, exc_tb): 39 | self.__r.close() 40 | 41 | 42 | def kill_instance(): 43 | with TicketedRemote() as r: 44 | r.recvuntil(b"?") 45 | r.send(b"2\n") 46 | r.recvall() 47 | 48 | 49 | def launch_instance(): 50 | with TicketedRemote() as r: 51 | r.recvuntil(b"?") 52 | r.send(b"1\n") 53 | try: 54 | r.recvuntil(b"---\n") 55 | except: 56 | log = r.recvall().decode("utf8") 57 | print(log) 58 | raise Exception("failed to create instance") 59 | 60 | data_raw = r.recvall().decode("utf8") 61 | 62 | # todo: this fails when the private key has a leading zero 63 | return yaml.safe_load(data_raw) 64 | -------------------------------------------------------------------------------- /paradigmctf.py/ctf_solvers/utils.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import subprocess 3 | 4 | from web3 import Web3 5 | 6 | 7 | def solve( 8 | web3: Web3, 9 | project_location: str, 10 | player_key: str, 11 | challenge_addr: str, 12 | solve_script: str = "script/Solve.s.sol:Solve", 13 | ) -> str: 14 | forge_location = shutil.which("forge") 15 | if forge_location is None: 16 | forge_location = "/opt/foundry/bin/forge" 17 | 18 | proc = subprocess.Popen( 19 | args=[ 20 | forge_location, 21 | "script", 22 | "--rpc-url", 23 | web3.provider.endpoint_uri, 24 | "--slow", 25 | "-vvvvv", 26 | "--broadcast", 27 | solve_script, 28 | ], 29 | env={ 30 | "PLAYER": player_key, 31 | "CHALLENGE": challenge_addr, 32 | }, 33 | cwd=project_location, 34 | text=True, 35 | encoding="utf8", 36 | stdin=subprocess.DEVNULL, 37 | stdout=subprocess.PIPE, 38 | stderr=subprocess.PIPE, 39 | ) 40 | stdout, stderr = proc.communicate() 41 | print(stdout) 42 | print(stderr) 43 | 44 | if proc.returncode != 0: 45 | raise Exception("forge failed to run") 46 | -------------------------------------------------------------------------------- /paradigmctf.py/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: paradigm-ctf 2 | services: 3 | database: 4 | container_name: database 5 | image: redis/redis-stack:latest 6 | ports: 7 | - '6379:6379' 8 | - '8001:8001' 9 | environment: 10 | - REDIS_ARGS=--save 60 1 11 | networks: 12 | - ctf_network 13 | volumes: 14 | - database:/data 15 | ctf-server-orchestrator: 16 | container_name: orchestrator 17 | image: gcr.io/paradigmxyz/infra/paradigmctf.py:latest 18 | build: . 19 | user: root 20 | command: uvicorn ctf_server:orchestrator --host 0.0.0.0 --port 7283 21 | volumes: 22 | - "/var/run/docker.sock:/var/run/docker.sock" 23 | ports: 24 | - "7283:7283" 25 | environment: 26 | - BACKEND=docker 27 | - DATABASE=redis 28 | - REDIS_URL=redis://database:6379/0 29 | networks: 30 | - ctf_network 31 | depends_on: 32 | - database 33 | ctf-server-anvil-proxy: 34 | container_name: anvil-proxy 35 | image: gcr.io/paradigmxyz/infra/paradigmctf.py:latest 36 | build: . 37 | command: uvicorn ctf_server:anvil_proxy --host 0.0.0.0 --port 8545 38 | ports: 39 | - "8545:8545" 40 | environment: 41 | - DATABASE=redis 42 | - REDIS_URL=redis://database:6379/0 43 | networks: 44 | - ctf_network 45 | depends_on: 46 | - database 47 | volumes: 48 | database: 49 | driver: local 50 | 51 | networks: 52 | ctf_network: 53 | name: paradigmctf -------------------------------------------------------------------------------- /paradigmctf.py/foundry/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paradigmxyz/paradigm-ctf-infrastructure/6c39333a674f18458bc27256091ab0306b9d432e/paradigmctf.py/foundry/__init__.py -------------------------------------------------------------------------------- /paradigmctf.py/foundry/anvil.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | from web3.types import RPCResponse 3 | 4 | 5 | def check_error(resp: RPCResponse): 6 | if "error" in resp: 7 | raise Exception("rpc exception", resp["error"]) 8 | 9 | 10 | def anvil_autoImpersonateAccount(web3: Web3, enabled: bool): 11 | check_error(web3.provider.make_request("anvil_autoImpersonateAccount", [enabled])) 12 | 13 | 14 | def anvil_setCode(web3: Web3, addr: str, bytecode: str): 15 | check_error(web3.provider.make_request("anvil_setCode", [addr, bytecode])) 16 | 17 | 18 | def anvil_setStorageAt( 19 | web3: Web3, 20 | addr: str, 21 | slot: str, 22 | value: str, 23 | ): 24 | check_error(web3.provider.make_request("anvil_setStorageAt", [addr, slot, value])) 25 | 26 | 27 | def anvil_setBalance( 28 | web3: Web3, 29 | addr: str, 30 | balance: str, 31 | ): 32 | check_error(web3.provider.make_request("anvil_setBalance", [addr, balance])) 33 | -------------------------------------------------------------------------------- /paradigmctf.py/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.6 2 | aiosignal==1.3.1 3 | annotated-types==0.6.0 4 | anyio==3.7.1 5 | async-timeout==4.0.3 6 | asyncio==3.4.3 7 | attrs==23.1.0 8 | bcrypt==4.0.1 9 | bitarray==2.8.2 10 | cachetools==5.3.2 11 | capstone==5.0.1 12 | certifi==2023.7.22 13 | cffi==1.16.0 14 | charset-normalizer==3.3.2 15 | click==8.1.7 16 | colored-traceback==0.3.0 17 | cryptography==41.0.5 18 | cytoolz==0.12.2 19 | docker==6.1.3 20 | eth-abi==4.2.1 21 | eth-account==0.10.0 22 | eth-hash==0.5.2 23 | eth-keyfile==0.6.1 24 | eth-keys==0.4.0 25 | eth-rlp==0.3.0 26 | eth-typing==3.5.2 27 | eth-utils==2.3.1 28 | fastapi==0.104.1 29 | frozenlist==1.4.0 30 | google-auth==2.23.4 31 | h11==0.14.0 32 | hexbytes==0.3.1 33 | idna==3.4 34 | intervaltree==3.1.0 35 | jsonschema==4.19.2 36 | jsonschema-specifications==2023.7.1 37 | kubernetes==28.1.0 38 | lru-dict==1.2.0 39 | Mako==1.3.0 40 | MarkupSafe==2.1.3 41 | multidict==6.0.4 42 | oauthlib==3.2.2 43 | packaging==23.2 44 | paramiko==3.3.1 45 | parsimonious==0.9.0 46 | plumbum==1.8.2 47 | protobuf==4.25.0 48 | psutil==5.9.6 49 | pwntools==4.11.0 50 | pyasn1==0.5.0 51 | pyasn1-modules==0.3.0 52 | pycparser==2.21 53 | pycryptodome==3.19.0 54 | pydantic==2.4.2 55 | pydantic_core==2.10.1 56 | pyelftools==0.30 57 | Pygments==2.16.1 58 | PyNaCl==1.5.0 59 | pyserial==3.5 60 | PySocks==1.7.1 61 | python-dateutil==2.8.2 62 | pyunormalize==15.0.0 63 | PyYAML==6.0.1 64 | redis==5.0.1 65 | referencing==0.30.2 66 | regex==2023.10.3 67 | requests==2.31.0 68 | requests-oauthlib==1.3.1 69 | rlp==3.0.0 70 | ROPGadget==7.4 71 | rpds-py==0.12.0 72 | rpyc==5.3.1 73 | rsa==4.9 74 | six==1.16.0 75 | sniffio==1.3.0 76 | sortedcontainers==2.4.0 77 | starlette==0.27.0 78 | toolz==0.12.0 79 | typing_extensions==4.8.0 80 | unicorn==2.0.1.post1 81 | urllib3==1.26.18 82 | uvicorn==0.24.0.post1 83 | web3==6.11.3 84 | websocket-client==1.6.4 85 | websockets==12.0 86 | yarl==1.9.2 87 | -------------------------------------------------------------------------------- /paradigmctf.py/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name="paradigmctf.py", 5 | version="1.0.0", 6 | description="Packages used for Paradigm CTF", 7 | packages=find_packages(), 8 | python_requires=">=3.7, <4", 9 | install_requires=[ 10 | "web3==6.11.3", 11 | "kubernetes==28.1.0", 12 | "redis==5.0.1", 13 | "fastapi==0.104.1", 14 | "docker==6.1.3", 15 | "pwntools==4.11.0", 16 | ], 17 | py_modules=["foundry", "ctf_server", "ctf_launchers", "ctf_solvers"], 18 | ) 19 | -------------------------------------------------------------------------------- /templates/eth-koth/.challengeignore: -------------------------------------------------------------------------------- 1 | challenge/project/cache/* 2 | challenge/project/out/* 3 | challenge/project/broadcast/* 4 | 5 | challenge/solve.py 6 | challenge/project/script/Solve.s.sol 7 | challenge/project/script/exploit/* -------------------------------------------------------------------------------- /templates/eth-koth/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kctf.dev/v1 2 | kind: Challenge 3 | metadata: 4 | name: eth-koth 5 | annotations: 6 | type: KOTH 7 | koth_score_order: DESC 8 | name: ETH KOTH 9 | description: "In this KOTH challenge, the highest score wins. That's why the sort order is DESC" 10 | author: "Paradigm" 11 | tags: "koth" 12 | spec: 13 | deployed: true 14 | powDifficultySeconds: 0 15 | network: 16 | public: true 17 | healthcheck: 18 | # TIP: disable the healthcheck during development 19 | enabled: false 20 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/foundry-rs/foundry:latest AS foundry 2 | 3 | COPY project /project 4 | 5 | # artifacts must be the same path 6 | RUN true && \ 7 | cd /project && \ 8 | forge build --out /artifacts/out --cache-path /artifacts/cache && \ 9 | true 10 | 11 | FROM gcr.io/paradigmxyz/infra/paradigmctf.py:latest as chroot 12 | 13 | # ideally in the future, we can skip the chowns, but for now Forge wants to write the cache and broadcast artifacts 14 | 15 | COPY --chown=user:user . /home/user/challenge/ 16 | 17 | COPY --from=foundry --chown=user:user /artifacts /artifacts 18 | 19 | FROM gcr.io/paradigmxyz/ctf/kctf-challenge:latest 20 | 21 | VOLUME [ "/chroot", "/tmp" ] 22 | 23 | COPY --from=chroot / /chroot 24 | 25 | # nsjail help 26 | RUN touch /chroot/bin/kctf_restore_env && touch /chroot/environ 27 | 28 | CMD kctf_setup && \ 29 | kctf_persist_env && \ 30 | kctf_drop_privs socat TCP-LISTEN:1337,reuseaddr,fork EXEC:"nsjail --config /nsjail.cfg -- /bin/kctf_restore_env /usr/local/bin/python3 -u challenge/challenge.py" 31 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/challenge.py: -------------------------------------------------------------------------------- 1 | from ctf_launchers.koth_launcher import KothChallengeLauncher 2 | 3 | KothChallengeLauncher().run() 4 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: paradigm-ctf-challenge 2 | services: 3 | launcher: 4 | container_name: challenge 5 | image: challenge 6 | build: 7 | context: . 8 | target: chroot 9 | command: socat TCP-LISTEN:1337,reuseaddr,fork exec:"python3 -u challenge/challenge.py" 10 | expose: 11 | - 1337 12 | ports: 13 | - "1337:1337" 14 | networks: 15 | - ctf_network 16 | networks: 17 | ctf_network: 18 | name: paradigmctf 19 | external: true -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/.gitignore: -------------------------------------------------------------------------------- 1 | broadcast/ 2 | cache/ 3 | out/ -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | fs_permissions = [{ access = 'read-write', path = '/'}] 7 | 8 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 9 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-ctf/src/CTFDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | abstract contract CTFDeployer is Script { 7 | function run() external { 8 | address player = getAddress(0); 9 | address system = getAddress(1); 10 | 11 | address challenge = deploy(system, player); 12 | 13 | vm.writeFile(vm.envOr("OUTPUT_FILE", string("/tmp/deploy.txt")), vm.toString(challenge)); 14 | } 15 | 16 | function deploy(address system, address player) virtual internal returns (address); 17 | 18 | function getAdditionalAddress(uint32 index) internal returns (address) { 19 | return getAddress(index + 2); 20 | } 21 | 22 | function getPrivateKey(uint32 index) private returns (uint) { 23 | string memory mnemonic = vm.envOr("MNEMONIC", string("test test test test test test test test test test test junk")); 24 | return vm.deriveKey(mnemonic, index); 25 | } 26 | 27 | function getAddress(uint32 index) private returns (address) { 28 | return vm.addr(getPrivateKey(index)); 29 | } 30 | } -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-ctf/src/CTFSolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | abstract contract CTFSolver is Script { 7 | function run() external { 8 | uint256 playerPrivateKey = vm.envOr("PLAYER", uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80)); 9 | address challenge = vm.envAddress("CHALLENGE"); 10 | 11 | vm.startBroadcast(playerPrivateKey); 12 | 13 | solve(challenge, vm.addr(playerPrivateKey)); 14 | 15 | vm.stopBroadcast(); 16 | } 17 | 18 | function solve(address challenge, address player) virtual internal; 19 | } -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Install Foundry 17 | uses: onbjerg/foundry-toolchain@v1 18 | with: 19 | version: nightly 20 | 21 | - name: Print forge version 22 | run: forge --version 23 | 24 | # Backwards compatibility checks: 25 | # - the oldest and newest version of each supported minor version 26 | # - versions with specific issues 27 | - name: Check compatibility with latest 28 | if: always() 29 | run: | 30 | output=$(forge build --skip test) 31 | 32 | if echo "$output" | grep -q "Warning"; then 33 | echo "$output" 34 | exit 1 35 | fi 36 | 37 | - name: Check compatibility with 0.8.0 38 | if: always() 39 | run: | 40 | output=$(forge build --skip test --use solc:0.8.0) 41 | 42 | if echo "$output" | grep -q "Warning"; then 43 | echo "$output" 44 | exit 1 45 | fi 46 | 47 | - name: Check compatibility with 0.7.6 48 | if: always() 49 | run: | 50 | output=$(forge build --skip test --use solc:0.7.6) 51 | 52 | if echo "$output" | grep -q "Warning"; then 53 | echo "$output" 54 | exit 1 55 | fi 56 | 57 | - name: Check compatibility with 0.7.0 58 | if: always() 59 | run: | 60 | output=$(forge build --skip test --use solc:0.7.0) 61 | 62 | if echo "$output" | grep -q "Warning"; then 63 | echo "$output" 64 | exit 1 65 | fi 66 | 67 | - name: Check compatibility with 0.6.12 68 | if: always() 69 | run: | 70 | output=$(forge build --skip test --use solc:0.6.12) 71 | 72 | if echo "$output" | grep -q "Warning"; then 73 | echo "$output" 74 | exit 1 75 | fi 76 | 77 | - name: Check compatibility with 0.6.2 78 | if: always() 79 | run: | 80 | output=$(forge build --skip test --use solc:0.6.2) 81 | 82 | if echo "$output" | grep -q "Warning"; then 83 | echo "$output" 84 | exit 1 85 | fi 86 | 87 | # via-ir compilation time checks. 88 | - name: Measure compilation time of Test with 0.8.17 --via-ir 89 | if: always() 90 | run: forge build --skip test --contracts test/compilation/CompilationTest.sol --use solc:0.8.17 --via-ir 91 | 92 | - name: Measure compilation time of TestBase with 0.8.17 --via-ir 93 | if: always() 94 | run: forge build --skip test --contracts test/compilation/CompilationTestBase.sol --use solc:0.8.17 --via-ir 95 | 96 | - name: Measure compilation time of Script with 0.8.17 --via-ir 97 | if: always() 98 | run: forge build --skip test --contracts test/compilation/CompilationScript.sol --use solc:0.8.17 --via-ir 99 | 100 | - name: Measure compilation time of ScriptBase with 0.8.17 --via-ir 101 | if: always() 102 | run: forge build --skip test --contracts test/compilation/CompilationScriptBase.sol --use solc:0.8.17 --via-ir 103 | 104 | test: 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: actions/checkout@v3 108 | 109 | - name: Install Foundry 110 | uses: onbjerg/foundry-toolchain@v1 111 | with: 112 | version: nightly 113 | 114 | - name: Print forge version 115 | run: forge --version 116 | 117 | - name: Run tests 118 | run: forge test -vvv 119 | 120 | fmt: 121 | runs-on: ubuntu-latest 122 | steps: 123 | - uses: actions/checkout@v3 124 | 125 | - name: Install Foundry 126 | uses: onbjerg/foundry-toolchain@v1 127 | with: 128 | version: nightly 129 | 130 | - name: Print forge version 131 | run: forge --version 132 | 133 | - name: Check formatting 134 | run: forge fmt --check 135 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Sync Release Branch 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | sync-release-branch: 10 | runs-on: ubuntu-latest 11 | if: startsWith(github.event.release.tag_name, 'v1') 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | ref: v1 18 | 19 | - name: Configure Git 20 | run: | 21 | git config user.name github-actions[bot] 22 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 23 | 24 | - name: Sync Release Branch 25 | run: | 26 | git fetch --tags 27 | git checkout v1 28 | git reset --hard ${GITHUB_REF} 29 | git push --force 30 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | .vscode 4 | .idea 5 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright Contributors to Forge Standard Library 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE O THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE.R 26 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | fs_permissions = [{ access = "read-write", path = "./"}] 3 | 4 | [rpc_endpoints] 5 | # The RPC URLs are modified versions of the default for testing initialization. 6 | mainnet = "https://mainnet.infura.io/v3/b1d3925804e74152b316ca7da97060d3" # Different API key. 7 | optimism_goerli = "https://goerli.optimism.io/" # Adds a trailing slash. 8 | arbitrum_one_goerli = "https://goerli-rollup.arbitrum.io/rpc/" # Adds a trailing slash. 9 | needs_undefined_env_var = "${UNDEFINED_RPC_URL_PLACEHOLDER}" 10 | 11 | [fmt] 12 | # These are all the `forge fmt` defaults. 13 | line_length = 120 14 | tab_width = 4 15 | bracket_spacing = false 16 | int_types = 'long' 17 | multiline_func_header = 'attributes_first' 18 | quote_style = 'double' 19 | number_underscore = 'preserve' 20 | single_line_statement_blocks = 'preserve' 21 | ignore = ["src/console.sol", "src/console2.sol"] -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/lib/ds-test/.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build" 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: cachix/install-nix-action@v20 11 | with: 12 | nix_path: nixpkgs=channel:nixos-unstable 13 | extra_nix_config: | 14 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 15 | 16 | - name: setup dapp binary cache 17 | uses: cachix/cachix-action@v12 18 | with: 19 | name: dapp 20 | 21 | - name: install dapptools 22 | run: nix profile install github:dapphub/dapptools#dapp --accept-flake-config 23 | 24 | - name: install foundry 25 | uses: foundry-rs/foundry-toolchain@v1 26 | 27 | - name: test with solc-0.5.17 28 | run: dapp --use solc-0.5.17 test -v 29 | 30 | - name: test with solc-0.6.11 31 | run: dapp --use solc-0.6.11 test -v 32 | 33 | - name: test with solc-0.7.6 34 | run: dapp --use solc-0.7.6 test -v 35 | 36 | - name: test with solc-0.8.18 37 | run: dapp --use solc-0.8.18 test -v 38 | 39 | - name: Run tests with foundry 40 | run: forge test -vvv 41 | 42 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/lib/ds-test/.gitignore: -------------------------------------------------------------------------------- 1 | /.dapple 2 | /build 3 | /out 4 | /cache/ 5 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/lib/ds-test/Makefile: -------------------------------------------------------------------------------- 1 | all:; dapp build 2 | 3 | test: 4 | -dapp --use solc:0.4.23 build 5 | -dapp --use solc:0.4.26 build 6 | -dapp --use solc:0.5.17 build 7 | -dapp --use solc:0.6.12 build 8 | -dapp --use solc:0.7.5 build 9 | 10 | demo: 11 | DAPP_SRC=demo dapp --use solc:0.7.5 build 12 | -hevm dapp-test --verbose 3 13 | 14 | .PHONY: test demo 15 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/lib/ds-test/default.nix: -------------------------------------------------------------------------------- 1 | { solidityPackage, dappsys }: solidityPackage { 2 | name = "ds-test"; 3 | src = ./src; 4 | } 5 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/lib/ds-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ds-test", 3 | "version": "1.0.0", 4 | "description": "Assertions, equality checks and other test helpers ", 5 | "bugs": "https://github.com/dapphub/ds-test/issues", 6 | "license": "GPL-3.0", 7 | "author": "Contributors to ds-test", 8 | "files": [ 9 | "src/*" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/dapphub/ds-test.git" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forge-std", 3 | "version": "1.7.1", 4 | "description": "Forge Standard Library is a collection of helpful contracts and libraries for use with Forge and Foundry.", 5 | "homepage": "https://book.getfoundry.sh/forge/forge-std", 6 | "bugs": "https://github.com/foundry-rs/forge-std/issues", 7 | "license": "(Apache-2.0 OR MIT)", 8 | "author": "Contributors to Forge Standard Library", 9 | "files": [ 10 | "src/**/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/foundry-rs/forge-std.git" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import {StdStorage} from "./StdStorage.sol"; 5 | import {Vm, VmSafe} from "./Vm.sol"; 6 | 7 | abstract contract CommonBase { 8 | // Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. 9 | address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); 10 | // console.sol and console2.sol work by executing a staticcall to this address. 11 | address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67; 12 | // Used when deploying with create2, https://github.com/Arachnid/deterministic-deployment-proxy. 13 | address internal constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; 14 | // Default address for tx.origin and msg.sender, 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38. 15 | address internal constant DEFAULT_SENDER = address(uint160(uint256(keccak256("foundry default caller")))); 16 | // Address of the test contract, deployed by the DEFAULT_SENDER. 17 | address internal constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; 18 | // Deterministic deployment address of the Multicall3 contract. 19 | address internal constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11; 20 | // The order of the secp256k1 curve. 21 | uint256 internal constant SECP256K1_ORDER = 22 | 115792089237316195423570985008687907852837564279074904382605163141518161494337; 23 | 24 | uint256 internal constant UINT256_MAX = 25 | 115792089237316195423570985008687907853269984665640564039457584007913129639935; 26 | 27 | Vm internal constant vm = Vm(VM_ADDRESS); 28 | StdStorage internal stdstore; 29 | } 30 | 31 | abstract contract TestBase is CommonBase {} 32 | 33 | abstract contract ScriptBase is CommonBase { 34 | VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); 35 | } 36 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/Script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | // 💬 ABOUT 5 | // Forge Std's default Script. 6 | 7 | // 🧩 MODULES 8 | import {console} from "./console.sol"; 9 | import {console2} from "./console2.sol"; 10 | import {safeconsole} from "./safeconsole.sol"; 11 | import {StdChains} from "./StdChains.sol"; 12 | import {StdCheatsSafe} from "./StdCheats.sol"; 13 | import {stdJson} from "./StdJson.sol"; 14 | import {stdMath} from "./StdMath.sol"; 15 | import {StdStorage, stdStorageSafe} from "./StdStorage.sol"; 16 | import {StdStyle} from "./StdStyle.sol"; 17 | import {StdUtils} from "./StdUtils.sol"; 18 | import {VmSafe} from "./Vm.sol"; 19 | 20 | // 📦 BOILERPLATE 21 | import {ScriptBase} from "./Base.sol"; 22 | 23 | // ⭐️ SCRIPT 24 | abstract contract Script is ScriptBase, StdChains, StdCheatsSafe, StdUtils { 25 | // Note: IS_SCRIPT() must return true. 26 | bool public IS_SCRIPT = true; 27 | } 28 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/StdError.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Panics work for versions >=0.8.0, but we lowered the pragma to make this compatible with Test 3 | pragma solidity >=0.6.2 <0.9.0; 4 | 5 | library stdError { 6 | bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); 7 | bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); 8 | bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); 9 | bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); 10 | bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); 11 | bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); 12 | bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); 13 | bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); 14 | bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); 15 | } 16 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/StdInvariant.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | abstract contract StdInvariant { 7 | struct FuzzSelector { 8 | address addr; 9 | bytes4[] selectors; 10 | } 11 | 12 | struct FuzzInterface { 13 | address addr; 14 | string[] artifacts; 15 | } 16 | 17 | address[] private _excludedContracts; 18 | address[] private _excludedSenders; 19 | address[] private _targetedContracts; 20 | address[] private _targetedSenders; 21 | 22 | string[] private _excludedArtifacts; 23 | string[] private _targetedArtifacts; 24 | 25 | FuzzSelector[] private _targetedArtifactSelectors; 26 | FuzzSelector[] private _targetedSelectors; 27 | 28 | FuzzInterface[] private _targetedInterfaces; 29 | 30 | // Functions for users: 31 | // These are intended to be called in tests. 32 | 33 | function excludeContract(address newExcludedContract_) internal { 34 | _excludedContracts.push(newExcludedContract_); 35 | } 36 | 37 | function excludeSender(address newExcludedSender_) internal { 38 | _excludedSenders.push(newExcludedSender_); 39 | } 40 | 41 | function excludeArtifact(string memory newExcludedArtifact_) internal { 42 | _excludedArtifacts.push(newExcludedArtifact_); 43 | } 44 | 45 | function targetArtifact(string memory newTargetedArtifact_) internal { 46 | _targetedArtifacts.push(newTargetedArtifact_); 47 | } 48 | 49 | function targetArtifactSelector(FuzzSelector memory newTargetedArtifactSelector_) internal { 50 | _targetedArtifactSelectors.push(newTargetedArtifactSelector_); 51 | } 52 | 53 | function targetContract(address newTargetedContract_) internal { 54 | _targetedContracts.push(newTargetedContract_); 55 | } 56 | 57 | function targetSelector(FuzzSelector memory newTargetedSelector_) internal { 58 | _targetedSelectors.push(newTargetedSelector_); 59 | } 60 | 61 | function targetSender(address newTargetedSender_) internal { 62 | _targetedSenders.push(newTargetedSender_); 63 | } 64 | 65 | function targetInterface(FuzzInterface memory newTargetedInterface_) internal { 66 | _targetedInterfaces.push(newTargetedInterface_); 67 | } 68 | 69 | // Functions for forge: 70 | // These are called by forge to run invariant tests and don't need to be called in tests. 71 | 72 | function excludeArtifacts() public view returns (string[] memory excludedArtifacts_) { 73 | excludedArtifacts_ = _excludedArtifacts; 74 | } 75 | 76 | function excludeContracts() public view returns (address[] memory excludedContracts_) { 77 | excludedContracts_ = _excludedContracts; 78 | } 79 | 80 | function excludeSenders() public view returns (address[] memory excludedSenders_) { 81 | excludedSenders_ = _excludedSenders; 82 | } 83 | 84 | function targetArtifacts() public view returns (string[] memory targetedArtifacts_) { 85 | targetedArtifacts_ = _targetedArtifacts; 86 | } 87 | 88 | function targetArtifactSelectors() public view returns (FuzzSelector[] memory targetedArtifactSelectors_) { 89 | targetedArtifactSelectors_ = _targetedArtifactSelectors; 90 | } 91 | 92 | function targetContracts() public view returns (address[] memory targetedContracts_) { 93 | targetedContracts_ = _targetedContracts; 94 | } 95 | 96 | function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) { 97 | targetedSelectors_ = _targetedSelectors; 98 | } 99 | 100 | function targetSenders() public view returns (address[] memory targetedSenders_) { 101 | targetedSenders_ = _targetedSenders; 102 | } 103 | 104 | function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces_) { 105 | targetedInterfaces_ = _targetedInterfaces; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/StdJson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import {VmSafe} from "./Vm.sol"; 7 | 8 | // Helpers for parsing and writing JSON files 9 | // To parse: 10 | // ``` 11 | // using stdJson for string; 12 | // string memory json = vm.readFile("some_peth"); 13 | // json.parseUint(""); 14 | // ``` 15 | // To write: 16 | // ``` 17 | // using stdJson for string; 18 | // string memory json = "deploymentArtifact"; 19 | // Contract contract = new Contract(); 20 | // json.serialize("contractAddress", address(contract)); 21 | // json = json.serialize("deploymentTimes", uint(1)); 22 | // // store the stringified JSON to the 'json' variable we have been using as a key 23 | // // as we won't need it any longer 24 | // string memory json2 = "finalArtifact"; 25 | // string memory final = json2.serialize("depArtifact", json); 26 | // final.write(""); 27 | // ``` 28 | 29 | library stdJson { 30 | VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); 31 | 32 | function parseRaw(string memory json, string memory key) internal pure returns (bytes memory) { 33 | return vm.parseJson(json, key); 34 | } 35 | 36 | function readUint(string memory json, string memory key) internal pure returns (uint256) { 37 | return vm.parseJsonUint(json, key); 38 | } 39 | 40 | function readUintArray(string memory json, string memory key) internal pure returns (uint256[] memory) { 41 | return vm.parseJsonUintArray(json, key); 42 | } 43 | 44 | function readInt(string memory json, string memory key) internal pure returns (int256) { 45 | return vm.parseJsonInt(json, key); 46 | } 47 | 48 | function readIntArray(string memory json, string memory key) internal pure returns (int256[] memory) { 49 | return vm.parseJsonIntArray(json, key); 50 | } 51 | 52 | function readBytes32(string memory json, string memory key) internal pure returns (bytes32) { 53 | return vm.parseJsonBytes32(json, key); 54 | } 55 | 56 | function readBytes32Array(string memory json, string memory key) internal pure returns (bytes32[] memory) { 57 | return vm.parseJsonBytes32Array(json, key); 58 | } 59 | 60 | function readString(string memory json, string memory key) internal pure returns (string memory) { 61 | return vm.parseJsonString(json, key); 62 | } 63 | 64 | function readStringArray(string memory json, string memory key) internal pure returns (string[] memory) { 65 | return vm.parseJsonStringArray(json, key); 66 | } 67 | 68 | function readAddress(string memory json, string memory key) internal pure returns (address) { 69 | return vm.parseJsonAddress(json, key); 70 | } 71 | 72 | function readAddressArray(string memory json, string memory key) internal pure returns (address[] memory) { 73 | return vm.parseJsonAddressArray(json, key); 74 | } 75 | 76 | function readBool(string memory json, string memory key) internal pure returns (bool) { 77 | return vm.parseJsonBool(json, key); 78 | } 79 | 80 | function readBoolArray(string memory json, string memory key) internal pure returns (bool[] memory) { 81 | return vm.parseJsonBoolArray(json, key); 82 | } 83 | 84 | function readBytes(string memory json, string memory key) internal pure returns (bytes memory) { 85 | return vm.parseJsonBytes(json, key); 86 | } 87 | 88 | function readBytesArray(string memory json, string memory key) internal pure returns (bytes[] memory) { 89 | return vm.parseJsonBytesArray(json, key); 90 | } 91 | 92 | function serialize(string memory jsonKey, string memory rootObject) internal returns (string memory) { 93 | return vm.serializeJson(jsonKey, rootObject); 94 | } 95 | 96 | function serialize(string memory jsonKey, string memory key, bool value) internal returns (string memory) { 97 | return vm.serializeBool(jsonKey, key, value); 98 | } 99 | 100 | function serialize(string memory jsonKey, string memory key, bool[] memory value) 101 | internal 102 | returns (string memory) 103 | { 104 | return vm.serializeBool(jsonKey, key, value); 105 | } 106 | 107 | function serialize(string memory jsonKey, string memory key, uint256 value) internal returns (string memory) { 108 | return vm.serializeUint(jsonKey, key, value); 109 | } 110 | 111 | function serialize(string memory jsonKey, string memory key, uint256[] memory value) 112 | internal 113 | returns (string memory) 114 | { 115 | return vm.serializeUint(jsonKey, key, value); 116 | } 117 | 118 | function serialize(string memory jsonKey, string memory key, int256 value) internal returns (string memory) { 119 | return vm.serializeInt(jsonKey, key, value); 120 | } 121 | 122 | function serialize(string memory jsonKey, string memory key, int256[] memory value) 123 | internal 124 | returns (string memory) 125 | { 126 | return vm.serializeInt(jsonKey, key, value); 127 | } 128 | 129 | function serialize(string memory jsonKey, string memory key, address value) internal returns (string memory) { 130 | return vm.serializeAddress(jsonKey, key, value); 131 | } 132 | 133 | function serialize(string memory jsonKey, string memory key, address[] memory value) 134 | internal 135 | returns (string memory) 136 | { 137 | return vm.serializeAddress(jsonKey, key, value); 138 | } 139 | 140 | function serialize(string memory jsonKey, string memory key, bytes32 value) internal returns (string memory) { 141 | return vm.serializeBytes32(jsonKey, key, value); 142 | } 143 | 144 | function serialize(string memory jsonKey, string memory key, bytes32[] memory value) 145 | internal 146 | returns (string memory) 147 | { 148 | return vm.serializeBytes32(jsonKey, key, value); 149 | } 150 | 151 | function serialize(string memory jsonKey, string memory key, bytes memory value) internal returns (string memory) { 152 | return vm.serializeBytes(jsonKey, key, value); 153 | } 154 | 155 | function serialize(string memory jsonKey, string memory key, bytes[] memory value) 156 | internal 157 | returns (string memory) 158 | { 159 | return vm.serializeBytes(jsonKey, key, value); 160 | } 161 | 162 | function serialize(string memory jsonKey, string memory key, string memory value) 163 | internal 164 | returns (string memory) 165 | { 166 | return vm.serializeString(jsonKey, key, value); 167 | } 168 | 169 | function serialize(string memory jsonKey, string memory key, string[] memory value) 170 | internal 171 | returns (string memory) 172 | { 173 | return vm.serializeString(jsonKey, key, value); 174 | } 175 | 176 | function write(string memory jsonKey, string memory path) internal { 177 | vm.writeJson(jsonKey, path); 178 | } 179 | 180 | function write(string memory jsonKey, string memory path, string memory valueKey) internal { 181 | vm.writeJson(jsonKey, path, valueKey); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/StdMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | library stdMath { 5 | int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968; 6 | 7 | function abs(int256 a) internal pure returns (uint256) { 8 | // Required or it will fail when `a = type(int256).min` 9 | if (a == INT256_MIN) { 10 | return 57896044618658097711785492504343953926634992332820282019728792003956564819968; 11 | } 12 | 13 | return uint256(a > 0 ? a : -a); 14 | } 15 | 16 | function delta(uint256 a, uint256 b) internal pure returns (uint256) { 17 | return a > b ? a - b : b - a; 18 | } 19 | 20 | function delta(int256 a, int256 b) internal pure returns (uint256) { 21 | // a and b are of the same sign 22 | // this works thanks to two's complement, the left-most bit is the sign bit 23 | if ((a ^ b) > -1) { 24 | return delta(abs(a), abs(b)); 25 | } 26 | 27 | // a and b are of opposite signs 28 | return abs(a) + abs(b); 29 | } 30 | 31 | function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) { 32 | uint256 absDelta = delta(a, b); 33 | 34 | return absDelta * 1e18 / b; 35 | } 36 | 37 | function percentDelta(int256 a, int256 b) internal pure returns (uint256) { 38 | uint256 absDelta = delta(a, b); 39 | uint256 absB = abs(b); 40 | 41 | return absDelta * 1e18 / absB; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | // 💬 ABOUT 7 | // Forge Std's default Test. 8 | 9 | // 🧩 MODULES 10 | import {console} from "./console.sol"; 11 | import {console2} from "./console2.sol"; 12 | import {safeconsole} from "./safeconsole.sol"; 13 | import {StdAssertions} from "./StdAssertions.sol"; 14 | import {StdChains} from "./StdChains.sol"; 15 | import {StdCheats} from "./StdCheats.sol"; 16 | import {stdError} from "./StdError.sol"; 17 | import {StdInvariant} from "./StdInvariant.sol"; 18 | import {stdJson} from "./StdJson.sol"; 19 | import {stdMath} from "./StdMath.sol"; 20 | import {StdStorage, stdStorage} from "./StdStorage.sol"; 21 | import {StdStyle} from "./StdStyle.sol"; 22 | import {StdUtils} from "./StdUtils.sol"; 23 | import {Vm} from "./Vm.sol"; 24 | 25 | // 📦 BOILERPLATE 26 | import {TestBase} from "./Base.sol"; 27 | import {DSTest} from "ds-test/test.sol"; 28 | 29 | // ⭐️ TEST 30 | abstract contract Test is TestBase, DSTest, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { 31 | // Note: IS_TEST() must return true. 32 | // Note: Must have failure system, https://github.com/dapphub/ds-test/blob/cd98eff28324bfac652e63a239a60632a761790b/src/test.sol#L39-L76. 33 | } 34 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceID The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | /// @dev Interface of the ERC20 standard as defined in the EIP. 5 | /// @dev This includes the optional name, symbol, and decimals metadata. 6 | interface IERC20 { 7 | /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`). 8 | event Transfer(address indexed from, address indexed to, uint256 value); 9 | 10 | /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value` 11 | /// is the new allowance. 12 | event Approval(address indexed owner, address indexed spender, uint256 value); 13 | 14 | /// @notice Returns the amount of tokens in existence. 15 | function totalSupply() external view returns (uint256); 16 | 17 | /// @notice Returns the amount of tokens owned by `account`. 18 | function balanceOf(address account) external view returns (uint256); 19 | 20 | /// @notice Moves `amount` tokens from the caller's account to `to`. 21 | function transfer(address to, uint256 amount) external returns (bool); 22 | 23 | /// @notice Returns the remaining number of tokens that `spender` is allowed 24 | /// to spend on behalf of `owner` 25 | function allowance(address owner, address spender) external view returns (uint256); 26 | 27 | /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens. 28 | /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 29 | function approve(address spender, uint256 amount) external returns (bool); 30 | 31 | /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. 32 | /// `amount` is then deducted from the caller's allowance. 33 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 34 | 35 | /// @notice Returns the name of the token. 36 | function name() external view returns (string memory); 37 | 38 | /// @notice Returns the symbol of the token. 39 | function symbol() external view returns (string memory); 40 | 41 | /// @notice Returns the decimals places of the token. 42 | function decimals() external view returns (uint8); 43 | } 44 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/src/interfaces/IMulticall3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | interface IMulticall3 { 7 | struct Call { 8 | address target; 9 | bytes callData; 10 | } 11 | 12 | struct Call3 { 13 | address target; 14 | bool allowFailure; 15 | bytes callData; 16 | } 17 | 18 | struct Call3Value { 19 | address target; 20 | bool allowFailure; 21 | uint256 value; 22 | bytes callData; 23 | } 24 | 25 | struct Result { 26 | bool success; 27 | bytes returnData; 28 | } 29 | 30 | function aggregate(Call[] calldata calls) 31 | external 32 | payable 33 | returns (uint256 blockNumber, bytes[] memory returnData); 34 | 35 | function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData); 36 | 37 | function aggregate3Value(Call3Value[] calldata calls) external payable returns (Result[] memory returnData); 38 | 39 | function blockAndAggregate(Call[] calldata calls) 40 | external 41 | payable 42 | returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); 43 | 44 | function getBasefee() external view returns (uint256 basefee); 45 | 46 | function getBlockHash(uint256 blockNumber) external view returns (bytes32 blockHash); 47 | 48 | function getBlockNumber() external view returns (uint256 blockNumber); 49 | 50 | function getChainId() external view returns (uint256 chainid); 51 | 52 | function getCurrentBlockCoinbase() external view returns (address coinbase); 53 | 54 | function getCurrentBlockDifficulty() external view returns (uint256 difficulty); 55 | 56 | function getCurrentBlockGasLimit() external view returns (uint256 gaslimit); 57 | 58 | function getCurrentBlockTimestamp() external view returns (uint256 timestamp); 59 | 60 | function getEthBalance(address addr) external view returns (uint256 balance); 61 | 62 | function getLastBlockHash() external view returns (bytes32 blockHash); 63 | 64 | function tryAggregate(bool requireSuccess, Call[] calldata calls) 65 | external 66 | payable 67 | returns (Result[] memory returnData); 68 | 69 | function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) 70 | external 71 | payable 72 | returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); 73 | } 74 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/StdError.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/StdError.sol"; 5 | import "../src/Test.sol"; 6 | 7 | contract StdErrorsTest is Test { 8 | ErrorsTest test; 9 | 10 | function setUp() public { 11 | test = new ErrorsTest(); 12 | } 13 | 14 | function test_ExpectAssertion() public { 15 | vm.expectRevert(stdError.assertionError); 16 | test.assertionError(); 17 | } 18 | 19 | function test_ExpectArithmetic() public { 20 | vm.expectRevert(stdError.arithmeticError); 21 | test.arithmeticError(10); 22 | } 23 | 24 | function test_ExpectDiv() public { 25 | vm.expectRevert(stdError.divisionError); 26 | test.divError(0); 27 | } 28 | 29 | function test_ExpectMod() public { 30 | vm.expectRevert(stdError.divisionError); 31 | test.modError(0); 32 | } 33 | 34 | function test_ExpectEnum() public { 35 | vm.expectRevert(stdError.enumConversionError); 36 | test.enumConversion(1); 37 | } 38 | 39 | function test_ExpectEncodeStg() public { 40 | vm.expectRevert(stdError.encodeStorageError); 41 | test.encodeStgError(); 42 | } 43 | 44 | function test_ExpectPop() public { 45 | vm.expectRevert(stdError.popError); 46 | test.pop(); 47 | } 48 | 49 | function test_ExpectOOB() public { 50 | vm.expectRevert(stdError.indexOOBError); 51 | test.indexOOBError(1); 52 | } 53 | 54 | function test_ExpectMem() public { 55 | vm.expectRevert(stdError.memOverflowError); 56 | test.mem(); 57 | } 58 | 59 | function test_ExpectIntern() public { 60 | vm.expectRevert(stdError.zeroVarError); 61 | test.intern(); 62 | } 63 | } 64 | 65 | contract ErrorsTest { 66 | enum T {T1} 67 | 68 | uint256[] public someArr; 69 | bytes someBytes; 70 | 71 | function assertionError() public pure { 72 | assert(false); 73 | } 74 | 75 | function arithmeticError(uint256 a) public pure { 76 | a -= 100; 77 | } 78 | 79 | function divError(uint256 a) public pure { 80 | 100 / a; 81 | } 82 | 83 | function modError(uint256 a) public pure { 84 | 100 % a; 85 | } 86 | 87 | function enumConversion(uint256 a) public pure { 88 | T(a); 89 | } 90 | 91 | function encodeStgError() public { 92 | /// @solidity memory-safe-assembly 93 | assembly { 94 | sstore(someBytes.slot, 1) 95 | } 96 | keccak256(someBytes); 97 | } 98 | 99 | function pop() public { 100 | someArr.pop(); 101 | } 102 | 103 | function indexOOBError(uint256 a) public pure { 104 | uint256[] memory t = new uint256[](0); 105 | t[a]; 106 | } 107 | 108 | function mem() public pure { 109 | uint256 l = 2 ** 256 / 32; 110 | new uint256[](l); 111 | } 112 | 113 | function intern() public returns (uint256) { 114 | function(uint256) internal returns (uint256) x; 115 | x(2); 116 | return 7; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/StdStyle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.7.0 <0.9.0; 3 | 4 | import "../src/Test.sol"; 5 | 6 | contract StdStyleTest is Test { 7 | function test_StyleColor() public pure { 8 | console2.log(StdStyle.red("StdStyle.red String Test")); 9 | console2.log(StdStyle.red(uint256(10e18))); 10 | console2.log(StdStyle.red(int256(-10e18))); 11 | console2.log(StdStyle.red(true)); 12 | console2.log(StdStyle.red(address(0))); 13 | console2.log(StdStyle.redBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 14 | console2.log(StdStyle.redBytes32("StdStyle.redBytes32")); 15 | console2.log(StdStyle.green("StdStyle.green String Test")); 16 | console2.log(StdStyle.green(uint256(10e18))); 17 | console2.log(StdStyle.green(int256(-10e18))); 18 | console2.log(StdStyle.green(true)); 19 | console2.log(StdStyle.green(address(0))); 20 | console2.log(StdStyle.greenBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 21 | console2.log(StdStyle.greenBytes32("StdStyle.greenBytes32")); 22 | console2.log(StdStyle.yellow("StdStyle.yellow String Test")); 23 | console2.log(StdStyle.yellow(uint256(10e18))); 24 | console2.log(StdStyle.yellow(int256(-10e18))); 25 | console2.log(StdStyle.yellow(true)); 26 | console2.log(StdStyle.yellow(address(0))); 27 | console2.log(StdStyle.yellowBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 28 | console2.log(StdStyle.yellowBytes32("StdStyle.yellowBytes32")); 29 | console2.log(StdStyle.blue("StdStyle.blue String Test")); 30 | console2.log(StdStyle.blue(uint256(10e18))); 31 | console2.log(StdStyle.blue(int256(-10e18))); 32 | console2.log(StdStyle.blue(true)); 33 | console2.log(StdStyle.blue(address(0))); 34 | console2.log(StdStyle.blueBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 35 | console2.log(StdStyle.blueBytes32("StdStyle.blueBytes32")); 36 | console2.log(StdStyle.magenta("StdStyle.magenta String Test")); 37 | console2.log(StdStyle.magenta(uint256(10e18))); 38 | console2.log(StdStyle.magenta(int256(-10e18))); 39 | console2.log(StdStyle.magenta(true)); 40 | console2.log(StdStyle.magenta(address(0))); 41 | console2.log(StdStyle.magentaBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 42 | console2.log(StdStyle.magentaBytes32("StdStyle.magentaBytes32")); 43 | console2.log(StdStyle.cyan("StdStyle.cyan String Test")); 44 | console2.log(StdStyle.cyan(uint256(10e18))); 45 | console2.log(StdStyle.cyan(int256(-10e18))); 46 | console2.log(StdStyle.cyan(true)); 47 | console2.log(StdStyle.cyan(address(0))); 48 | console2.log(StdStyle.cyanBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 49 | console2.log(StdStyle.cyanBytes32("StdStyle.cyanBytes32")); 50 | } 51 | 52 | function test_StyleFontWeight() public pure { 53 | console2.log(StdStyle.bold("StdStyle.bold String Test")); 54 | console2.log(StdStyle.bold(uint256(10e18))); 55 | console2.log(StdStyle.bold(int256(-10e18))); 56 | console2.log(StdStyle.bold(address(0))); 57 | console2.log(StdStyle.bold(true)); 58 | console2.log(StdStyle.boldBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 59 | console2.log(StdStyle.boldBytes32("StdStyle.boldBytes32")); 60 | console2.log(StdStyle.dim("StdStyle.dim String Test")); 61 | console2.log(StdStyle.dim(uint256(10e18))); 62 | console2.log(StdStyle.dim(int256(-10e18))); 63 | console2.log(StdStyle.dim(address(0))); 64 | console2.log(StdStyle.dim(true)); 65 | console2.log(StdStyle.dimBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 66 | console2.log(StdStyle.dimBytes32("StdStyle.dimBytes32")); 67 | console2.log(StdStyle.italic("StdStyle.italic String Test")); 68 | console2.log(StdStyle.italic(uint256(10e18))); 69 | console2.log(StdStyle.italic(int256(-10e18))); 70 | console2.log(StdStyle.italic(address(0))); 71 | console2.log(StdStyle.italic(true)); 72 | console2.log(StdStyle.italicBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 73 | console2.log(StdStyle.italicBytes32("StdStyle.italicBytes32")); 74 | console2.log(StdStyle.underline("StdStyle.underline String Test")); 75 | console2.log(StdStyle.underline(uint256(10e18))); 76 | console2.log(StdStyle.underline(int256(-10e18))); 77 | console2.log(StdStyle.underline(address(0))); 78 | console2.log(StdStyle.underline(true)); 79 | console2.log(StdStyle.underlineBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 80 | console2.log(StdStyle.underlineBytes32("StdStyle.underlineBytes32")); 81 | console2.log(StdStyle.inverse("StdStyle.inverse String Test")); 82 | console2.log(StdStyle.inverse(uint256(10e18))); 83 | console2.log(StdStyle.inverse(int256(-10e18))); 84 | console2.log(StdStyle.inverse(address(0))); 85 | console2.log(StdStyle.inverse(true)); 86 | console2.log(StdStyle.inverseBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 87 | console2.log(StdStyle.inverseBytes32("StdStyle.inverseBytes32")); 88 | } 89 | 90 | function test_StyleCombined() public pure { 91 | console2.log(StdStyle.red(StdStyle.bold("Red Bold String Test"))); 92 | console2.log(StdStyle.green(StdStyle.dim(uint256(10e18)))); 93 | console2.log(StdStyle.yellow(StdStyle.italic(int256(-10e18)))); 94 | console2.log(StdStyle.blue(StdStyle.underline(address(0)))); 95 | console2.log(StdStyle.magenta(StdStyle.inverse(true))); 96 | } 97 | 98 | function test_StyleCustom() public pure { 99 | console2.log(h1("Custom Style 1")); 100 | console2.log(h2("Custom Style 2")); 101 | } 102 | 103 | function h1(string memory a) private pure returns (string memory) { 104 | return StdStyle.cyan(StdStyle.inverse(StdStyle.bold(a))); 105 | } 106 | 107 | function h2(string memory a) private pure returns (string memory) { 108 | return StdStyle.magenta(StdStyle.bold(StdStyle.underline(a))); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/Vm.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {Test} from "../src/Test.sol"; 5 | import {Vm, VmSafe} from "../src/Vm.sol"; 6 | 7 | contract VmTest is Test { 8 | // This test ensures that functions are never accidentally removed from a Vm interface, or 9 | // inadvertently moved between Vm and VmSafe. This test must be updated each time a function is 10 | // added to or removed from Vm or VmSafe. 11 | function test_interfaceId() public { 12 | assertEq(type(VmSafe).interfaceId, bytes4(0x329f5e71), "VmSafe"); 13 | assertEq(type(Vm).interfaceId, bytes4(0x82ccbb14), "Vm"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/compilation/CompilationScript.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Script.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationScript is Script {} 11 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/compilation/CompilationScriptBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Script.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationScriptBase is ScriptBase {} 11 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/compilation/CompilationTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Test.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationTest is Test {} 11 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/lib/forge-std/test/compilation/CompilationTestBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Test.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationTestBase is TestBase {} 11 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | forge-ctf/=lib/forge-ctf/src/ 4 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-ctf/CTFDeployer.sol"; 5 | 6 | import "src/Challenge.sol"; 7 | 8 | contract Deploy is CTFDeployer { 9 | function deploy(address system, address player) internal override returns (address challenge) { 10 | vm.startBroadcast(system); 11 | 12 | challenge = address(new Challenge(player)); 13 | 14 | vm.stopBroadcast(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/script/Solve.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-ctf/CTFSolver.sol"; 5 | 6 | import "script/exploit/Exploit.sol"; 7 | 8 | contract Solve is CTFSolver { 9 | function solve(address challengeAddress, address) internal override { 10 | Challenge challenge = Challenge(challengeAddress); 11 | Exploit exploit = new Exploit(challenge); 12 | exploit.exploit(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/script/exploit/Exploit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "src/Challenge.sol"; 5 | 6 | contract Exploit { 7 | Challenge private immutable CHALLENGE; 8 | 9 | constructor(Challenge challenge) { 10 | CHALLENGE = challenge; 11 | } 12 | 13 | function exploit() external { 14 | for (uint i = 0; i < 1337; i++) { 15 | CHALLENGE.click(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/project/src/Challenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | contract Challenge { 5 | address public immutable PLAYER; 6 | 7 | uint private clicks; 8 | 9 | constructor(address player) { 10 | PLAYER = player; 11 | } 12 | 13 | function click() external { 14 | clicks++; 15 | } 16 | 17 | function getScore() external view returns (uint256) { 18 | return clicks; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/eth-koth/challenge/solve.py: -------------------------------------------------------------------------------- 1 | from ctf_solvers.koth_solver import KothChallengeSolver 2 | 3 | KothChallengeSolver().start() 4 | -------------------------------------------------------------------------------- /templates/eth-pwn/.challengeignore: -------------------------------------------------------------------------------- 1 | challenge/project/cache/* 2 | challenge/project/out/* 3 | challenge/project/broadcast/* 4 | 5 | challenge/solve.py 6 | challenge/project/script/Solve.s.sol 7 | challenge/project/script/exploit/* -------------------------------------------------------------------------------- /templates/eth-pwn/README.md: -------------------------------------------------------------------------------- 1 | # Quickstart guide to writing a challenge 2 | 3 | The basic steps when preparing a challenge are: 4 | 5 | * A Docker image is built from the `challenge` directory. 6 | * Edit the Foundry project at `challenge/project`. Note the deploy script at `script/Deploy.s.sol` and the solve script at `Solve.s.sol` 7 | * To try the challenge locally, you will need to 8 | * create a a local cluster with `kctf cluster create --type kind --start $configname` 9 | * deploy the challenge with `kctf chal start` 10 | * To access the challenge, create a port forward with `kctf chal debug port-forward` and connect to it via `nc localhost PORT` using the printed port. 11 | * Check out `kctf chal ` for more commands. 12 | 13 | ## Directory layout 14 | 15 | The following files/directories are available: 16 | 17 | ### /challenge.yaml 18 | 19 | `challenge.yaml` is the main configuration file. You can use it to change 20 | settings like the name and namespace of the challenge, the exposed ports, the 21 | proof-of-work difficulty etc. 22 | For documentation on the available fields, you can run `kubectl explain challenge` and 23 | `kubectl explain challenge.spec`. 24 | 25 | ### /challenge 26 | 27 | The `challenge` directory contains a Dockerfile that describes the challenge and 28 | any challenge files. 29 | 30 | ### /healthcheck 31 | 32 | The `healthcheck` directory is optional. If you don't want to write a healthcheck, feel free to delete it. However, we strongly recommend that you implement a healthcheck :). 33 | 34 | We provide a basic healthcheck skeleton that uses pwntools to implement the 35 | healthcheck code. The only requirement is that the healthcheck replies to GET 36 | requests to http://$host:45281/healthz with either a success or an error status 37 | code. 38 | 39 | In most cases, you will only have to modify `healthcheck/healthcheck.py`. 40 | 41 | ## API contract 42 | 43 | Ensure your setup fulfills the following requirements to ensure it works with kCTF: 44 | 45 | * Verify `kctf_setup` is used as the first command in the CMD instruction of your `challenge/Dockerfile`. 46 | * You can do pretty much whatever you want in the `challenge` directory but: 47 | * We strongly recommend using nsjail in all challenges. While nsjail is already installed, you need to configure it in `challenge/nsjail.cfg`. For more information on nsjail, see the [official website](https://nsjail.dev/). 48 | * Your challenge receives connections on port 1337. The port can be changed in `challenge.yaml`. 49 | * The healthcheck directory is optional. 50 | * If it exists, the image should run a webserver on port 45281 and respond to `/healthz` requests. -------------------------------------------------------------------------------- /templates/eth-pwn/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kctf.dev/v1 2 | kind: Challenge 3 | metadata: 4 | name: eth-pwn 5 | annotations: 6 | type: PWN 7 | name: ETH Pwn 8 | description: "This is an example description" 9 | author: "Paradigm" 10 | tags: "pwn" 11 | flag: "PCTF{flagflagflag}" 12 | spec: 13 | deployed: true 14 | powDifficultySeconds: 0 15 | network: 16 | public: true 17 | healthcheck: 18 | # TIP: disable the healthcheck during development 19 | enabled: false 20 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/foundry-rs/foundry:latest AS foundry 2 | 3 | COPY project /project 4 | 5 | # artifacts must be the same path 6 | RUN true && \ 7 | cd /project && \ 8 | forge build --out /artifacts/out --cache-path /artifacts/cache && \ 9 | true 10 | 11 | FROM gcr.io/paradigmxyz/infra/paradigmctf.py:latest as chroot 12 | 13 | # ideally in the future, we can skip the chowns, but for now Forge wants to write the cache and broadcast artifacts 14 | 15 | COPY --chown=user:user . /home/user/challenge/ 16 | 17 | COPY --from=foundry --chown=user:user /artifacts /artifacts 18 | 19 | FROM gcr.io/paradigmxyz/ctf/kctf-challenge:latest 20 | 21 | VOLUME [ "/chroot", "/tmp" ] 22 | 23 | COPY --from=chroot / /chroot 24 | 25 | # nsjail help 26 | RUN touch /chroot/bin/kctf_restore_env && touch /chroot/environ 27 | 28 | CMD kctf_setup && \ 29 | kctf_persist_env && \ 30 | kctf_drop_privs socat TCP-LISTEN:1337,reuseaddr,fork EXEC:"nsjail --config /nsjail.cfg -- /bin/kctf_restore_env /usr/local/bin/python3 -u challenge/challenge.py" 31 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/challenge.py: -------------------------------------------------------------------------------- 1 | from ctf_launchers.pwn_launcher import PwnChallengeLauncher 2 | 3 | PwnChallengeLauncher().run() 4 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: paradigm-ctf-challenge 2 | services: 3 | launcher: 4 | container_name: challenge 5 | image: challenge 6 | build: 7 | context: . 8 | target: chroot 9 | command: socat TCP-LISTEN:1337,reuseaddr,fork exec:"python3 -u challenge/challenge.py" 10 | expose: 11 | - 1337 12 | ports: 13 | - "1337:1337" 14 | networks: 15 | - ctf_network 16 | networks: 17 | ctf_network: 18 | name: paradigmctf 19 | external: true -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/.gitignore: -------------------------------------------------------------------------------- 1 | broadcast/ 2 | cache/ 3 | out/ -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | fs_permissions = [{ access = 'read-write', path = '/'}] 7 | 8 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 9 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-ctf/src/CTFDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | abstract contract CTFDeployer is Script { 7 | function run() external { 8 | address player = getAddress(0); 9 | address system = getAddress(1); 10 | 11 | address challenge = deploy(system, player); 12 | 13 | vm.writeFile(vm.envOr("OUTPUT_FILE", string("/tmp/deploy.txt")), vm.toString(challenge)); 14 | } 15 | 16 | function deploy(address system, address player) virtual internal returns (address); 17 | 18 | function getAdditionalAddress(uint32 index) internal returns (address) { 19 | return getAddress(index + 2); 20 | } 21 | 22 | function getPrivateKey(uint32 index) private returns (uint) { 23 | string memory mnemonic = vm.envOr("MNEMONIC", string("test test test test test test test test test test test junk")); 24 | return vm.deriveKey(mnemonic, index); 25 | } 26 | 27 | function getAddress(uint32 index) private returns (address) { 28 | return vm.addr(getPrivateKey(index)); 29 | } 30 | } -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-ctf/src/CTFSolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | abstract contract CTFSolver is Script { 7 | function run() external { 8 | uint256 playerPrivateKey = vm.envOr("PLAYER", uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80)); 9 | address challenge = vm.envAddress("CHALLENGE"); 10 | 11 | vm.startBroadcast(playerPrivateKey); 12 | 13 | solve(challenge, vm.addr(playerPrivateKey)); 14 | 15 | vm.stopBroadcast(); 16 | } 17 | 18 | function solve(address challenge, address player) virtual internal; 19 | } -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Install Foundry 17 | uses: onbjerg/foundry-toolchain@v1 18 | with: 19 | version: nightly 20 | 21 | - name: Print forge version 22 | run: forge --version 23 | 24 | # Backwards compatibility checks: 25 | # - the oldest and newest version of each supported minor version 26 | # - versions with specific issues 27 | - name: Check compatibility with latest 28 | if: always() 29 | run: | 30 | output=$(forge build --skip test) 31 | 32 | if echo "$output" | grep -q "Warning"; then 33 | echo "$output" 34 | exit 1 35 | fi 36 | 37 | - name: Check compatibility with 0.8.0 38 | if: always() 39 | run: | 40 | output=$(forge build --skip test --use solc:0.8.0) 41 | 42 | if echo "$output" | grep -q "Warning"; then 43 | echo "$output" 44 | exit 1 45 | fi 46 | 47 | - name: Check compatibility with 0.7.6 48 | if: always() 49 | run: | 50 | output=$(forge build --skip test --use solc:0.7.6) 51 | 52 | if echo "$output" | grep -q "Warning"; then 53 | echo "$output" 54 | exit 1 55 | fi 56 | 57 | - name: Check compatibility with 0.7.0 58 | if: always() 59 | run: | 60 | output=$(forge build --skip test --use solc:0.7.0) 61 | 62 | if echo "$output" | grep -q "Warning"; then 63 | echo "$output" 64 | exit 1 65 | fi 66 | 67 | - name: Check compatibility with 0.6.12 68 | if: always() 69 | run: | 70 | output=$(forge build --skip test --use solc:0.6.12) 71 | 72 | if echo "$output" | grep -q "Warning"; then 73 | echo "$output" 74 | exit 1 75 | fi 76 | 77 | - name: Check compatibility with 0.6.2 78 | if: always() 79 | run: | 80 | output=$(forge build --skip test --use solc:0.6.2) 81 | 82 | if echo "$output" | grep -q "Warning"; then 83 | echo "$output" 84 | exit 1 85 | fi 86 | 87 | # via-ir compilation time checks. 88 | - name: Measure compilation time of Test with 0.8.17 --via-ir 89 | if: always() 90 | run: forge build --skip test --contracts test/compilation/CompilationTest.sol --use solc:0.8.17 --via-ir 91 | 92 | - name: Measure compilation time of TestBase with 0.8.17 --via-ir 93 | if: always() 94 | run: forge build --skip test --contracts test/compilation/CompilationTestBase.sol --use solc:0.8.17 --via-ir 95 | 96 | - name: Measure compilation time of Script with 0.8.17 --via-ir 97 | if: always() 98 | run: forge build --skip test --contracts test/compilation/CompilationScript.sol --use solc:0.8.17 --via-ir 99 | 100 | - name: Measure compilation time of ScriptBase with 0.8.17 --via-ir 101 | if: always() 102 | run: forge build --skip test --contracts test/compilation/CompilationScriptBase.sol --use solc:0.8.17 --via-ir 103 | 104 | test: 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: actions/checkout@v3 108 | 109 | - name: Install Foundry 110 | uses: onbjerg/foundry-toolchain@v1 111 | with: 112 | version: nightly 113 | 114 | - name: Print forge version 115 | run: forge --version 116 | 117 | - name: Run tests 118 | run: forge test -vvv 119 | 120 | fmt: 121 | runs-on: ubuntu-latest 122 | steps: 123 | - uses: actions/checkout@v3 124 | 125 | - name: Install Foundry 126 | uses: onbjerg/foundry-toolchain@v1 127 | with: 128 | version: nightly 129 | 130 | - name: Print forge version 131 | run: forge --version 132 | 133 | - name: Check formatting 134 | run: forge fmt --check 135 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Sync Release Branch 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | sync-release-branch: 10 | runs-on: ubuntu-latest 11 | if: startsWith(github.event.release.tag_name, 'v1') 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | ref: v1 18 | 19 | - name: Configure Git 20 | run: | 21 | git config user.name github-actions[bot] 22 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 23 | 24 | - name: Sync Release Branch 25 | run: | 26 | git fetch --tags 27 | git checkout v1 28 | git reset --hard ${GITHUB_REF} 29 | git push --force 30 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | .vscode 4 | .idea 5 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright Contributors to Forge Standard Library 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE O THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE.R 26 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | fs_permissions = [{ access = "read-write", path = "./"}] 3 | 4 | [rpc_endpoints] 5 | # The RPC URLs are modified versions of the default for testing initialization. 6 | mainnet = "https://mainnet.infura.io/v3/b1d3925804e74152b316ca7da97060d3" # Different API key. 7 | optimism_goerli = "https://goerli.optimism.io/" # Adds a trailing slash. 8 | arbitrum_one_goerli = "https://goerli-rollup.arbitrum.io/rpc/" # Adds a trailing slash. 9 | needs_undefined_env_var = "${UNDEFINED_RPC_URL_PLACEHOLDER}" 10 | 11 | [fmt] 12 | # These are all the `forge fmt` defaults. 13 | line_length = 120 14 | tab_width = 4 15 | bracket_spacing = false 16 | int_types = 'long' 17 | multiline_func_header = 'attributes_first' 18 | quote_style = 'double' 19 | number_underscore = 'preserve' 20 | single_line_statement_blocks = 'preserve' 21 | ignore = ["src/console.sol", "src/console2.sol"] -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/lib/ds-test/.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build" 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: cachix/install-nix-action@v20 11 | with: 12 | nix_path: nixpkgs=channel:nixos-unstable 13 | extra_nix_config: | 14 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 15 | 16 | - name: setup dapp binary cache 17 | uses: cachix/cachix-action@v12 18 | with: 19 | name: dapp 20 | 21 | - name: install dapptools 22 | run: nix profile install github:dapphub/dapptools#dapp --accept-flake-config 23 | 24 | - name: install foundry 25 | uses: foundry-rs/foundry-toolchain@v1 26 | 27 | - name: test with solc-0.5.17 28 | run: dapp --use solc-0.5.17 test -v 29 | 30 | - name: test with solc-0.6.11 31 | run: dapp --use solc-0.6.11 test -v 32 | 33 | - name: test with solc-0.7.6 34 | run: dapp --use solc-0.7.6 test -v 35 | 36 | - name: test with solc-0.8.18 37 | run: dapp --use solc-0.8.18 test -v 38 | 39 | - name: Run tests with foundry 40 | run: forge test -vvv 41 | 42 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/lib/ds-test/.gitignore: -------------------------------------------------------------------------------- 1 | /.dapple 2 | /build 3 | /out 4 | /cache/ 5 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/lib/ds-test/Makefile: -------------------------------------------------------------------------------- 1 | all:; dapp build 2 | 3 | test: 4 | -dapp --use solc:0.4.23 build 5 | -dapp --use solc:0.4.26 build 6 | -dapp --use solc:0.5.17 build 7 | -dapp --use solc:0.6.12 build 8 | -dapp --use solc:0.7.5 build 9 | 10 | demo: 11 | DAPP_SRC=demo dapp --use solc:0.7.5 build 12 | -hevm dapp-test --verbose 3 13 | 14 | .PHONY: test demo 15 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/lib/ds-test/default.nix: -------------------------------------------------------------------------------- 1 | { solidityPackage, dappsys }: solidityPackage { 2 | name = "ds-test"; 3 | src = ./src; 4 | } 5 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/lib/ds-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ds-test", 3 | "version": "1.0.0", 4 | "description": "Assertions, equality checks and other test helpers ", 5 | "bugs": "https://github.com/dapphub/ds-test/issues", 6 | "license": "GPL-3.0", 7 | "author": "Contributors to ds-test", 8 | "files": [ 9 | "src/*" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/dapphub/ds-test.git" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forge-std", 3 | "version": "1.7.1", 4 | "description": "Forge Standard Library is a collection of helpful contracts and libraries for use with Forge and Foundry.", 5 | "homepage": "https://book.getfoundry.sh/forge/forge-std", 6 | "bugs": "https://github.com/foundry-rs/forge-std/issues", 7 | "license": "(Apache-2.0 OR MIT)", 8 | "author": "Contributors to Forge Standard Library", 9 | "files": [ 10 | "src/**/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/foundry-rs/forge-std.git" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import {StdStorage} from "./StdStorage.sol"; 5 | import {Vm, VmSafe} from "./Vm.sol"; 6 | 7 | abstract contract CommonBase { 8 | // Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. 9 | address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); 10 | // console.sol and console2.sol work by executing a staticcall to this address. 11 | address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67; 12 | // Used when deploying with create2, https://github.com/Arachnid/deterministic-deployment-proxy. 13 | address internal constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; 14 | // Default address for tx.origin and msg.sender, 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38. 15 | address internal constant DEFAULT_SENDER = address(uint160(uint256(keccak256("foundry default caller")))); 16 | // Address of the test contract, deployed by the DEFAULT_SENDER. 17 | address internal constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; 18 | // Deterministic deployment address of the Multicall3 contract. 19 | address internal constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11; 20 | // The order of the secp256k1 curve. 21 | uint256 internal constant SECP256K1_ORDER = 22 | 115792089237316195423570985008687907852837564279074904382605163141518161494337; 23 | 24 | uint256 internal constant UINT256_MAX = 25 | 115792089237316195423570985008687907853269984665640564039457584007913129639935; 26 | 27 | Vm internal constant vm = Vm(VM_ADDRESS); 28 | StdStorage internal stdstore; 29 | } 30 | 31 | abstract contract TestBase is CommonBase {} 32 | 33 | abstract contract ScriptBase is CommonBase { 34 | VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); 35 | } 36 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/Script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | // 💬 ABOUT 5 | // Forge Std's default Script. 6 | 7 | // 🧩 MODULES 8 | import {console} from "./console.sol"; 9 | import {console2} from "./console2.sol"; 10 | import {safeconsole} from "./safeconsole.sol"; 11 | import {StdChains} from "./StdChains.sol"; 12 | import {StdCheatsSafe} from "./StdCheats.sol"; 13 | import {stdJson} from "./StdJson.sol"; 14 | import {stdMath} from "./StdMath.sol"; 15 | import {StdStorage, stdStorageSafe} from "./StdStorage.sol"; 16 | import {StdStyle} from "./StdStyle.sol"; 17 | import {StdUtils} from "./StdUtils.sol"; 18 | import {VmSafe} from "./Vm.sol"; 19 | 20 | // 📦 BOILERPLATE 21 | import {ScriptBase} from "./Base.sol"; 22 | 23 | // ⭐️ SCRIPT 24 | abstract contract Script is ScriptBase, StdChains, StdCheatsSafe, StdUtils { 25 | // Note: IS_SCRIPT() must return true. 26 | bool public IS_SCRIPT = true; 27 | } 28 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/StdError.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Panics work for versions >=0.8.0, but we lowered the pragma to make this compatible with Test 3 | pragma solidity >=0.6.2 <0.9.0; 4 | 5 | library stdError { 6 | bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); 7 | bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); 8 | bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); 9 | bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); 10 | bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); 11 | bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); 12 | bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); 13 | bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); 14 | bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); 15 | } 16 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/StdInvariant.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | abstract contract StdInvariant { 7 | struct FuzzSelector { 8 | address addr; 9 | bytes4[] selectors; 10 | } 11 | 12 | struct FuzzInterface { 13 | address addr; 14 | string[] artifacts; 15 | } 16 | 17 | address[] private _excludedContracts; 18 | address[] private _excludedSenders; 19 | address[] private _targetedContracts; 20 | address[] private _targetedSenders; 21 | 22 | string[] private _excludedArtifacts; 23 | string[] private _targetedArtifacts; 24 | 25 | FuzzSelector[] private _targetedArtifactSelectors; 26 | FuzzSelector[] private _targetedSelectors; 27 | 28 | FuzzInterface[] private _targetedInterfaces; 29 | 30 | // Functions for users: 31 | // These are intended to be called in tests. 32 | 33 | function excludeContract(address newExcludedContract_) internal { 34 | _excludedContracts.push(newExcludedContract_); 35 | } 36 | 37 | function excludeSender(address newExcludedSender_) internal { 38 | _excludedSenders.push(newExcludedSender_); 39 | } 40 | 41 | function excludeArtifact(string memory newExcludedArtifact_) internal { 42 | _excludedArtifacts.push(newExcludedArtifact_); 43 | } 44 | 45 | function targetArtifact(string memory newTargetedArtifact_) internal { 46 | _targetedArtifacts.push(newTargetedArtifact_); 47 | } 48 | 49 | function targetArtifactSelector(FuzzSelector memory newTargetedArtifactSelector_) internal { 50 | _targetedArtifactSelectors.push(newTargetedArtifactSelector_); 51 | } 52 | 53 | function targetContract(address newTargetedContract_) internal { 54 | _targetedContracts.push(newTargetedContract_); 55 | } 56 | 57 | function targetSelector(FuzzSelector memory newTargetedSelector_) internal { 58 | _targetedSelectors.push(newTargetedSelector_); 59 | } 60 | 61 | function targetSender(address newTargetedSender_) internal { 62 | _targetedSenders.push(newTargetedSender_); 63 | } 64 | 65 | function targetInterface(FuzzInterface memory newTargetedInterface_) internal { 66 | _targetedInterfaces.push(newTargetedInterface_); 67 | } 68 | 69 | // Functions for forge: 70 | // These are called by forge to run invariant tests and don't need to be called in tests. 71 | 72 | function excludeArtifacts() public view returns (string[] memory excludedArtifacts_) { 73 | excludedArtifacts_ = _excludedArtifacts; 74 | } 75 | 76 | function excludeContracts() public view returns (address[] memory excludedContracts_) { 77 | excludedContracts_ = _excludedContracts; 78 | } 79 | 80 | function excludeSenders() public view returns (address[] memory excludedSenders_) { 81 | excludedSenders_ = _excludedSenders; 82 | } 83 | 84 | function targetArtifacts() public view returns (string[] memory targetedArtifacts_) { 85 | targetedArtifacts_ = _targetedArtifacts; 86 | } 87 | 88 | function targetArtifactSelectors() public view returns (FuzzSelector[] memory targetedArtifactSelectors_) { 89 | targetedArtifactSelectors_ = _targetedArtifactSelectors; 90 | } 91 | 92 | function targetContracts() public view returns (address[] memory targetedContracts_) { 93 | targetedContracts_ = _targetedContracts; 94 | } 95 | 96 | function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) { 97 | targetedSelectors_ = _targetedSelectors; 98 | } 99 | 100 | function targetSenders() public view returns (address[] memory targetedSenders_) { 101 | targetedSenders_ = _targetedSenders; 102 | } 103 | 104 | function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces_) { 105 | targetedInterfaces_ = _targetedInterfaces; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/StdJson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import {VmSafe} from "./Vm.sol"; 7 | 8 | // Helpers for parsing and writing JSON files 9 | // To parse: 10 | // ``` 11 | // using stdJson for string; 12 | // string memory json = vm.readFile("some_peth"); 13 | // json.parseUint(""); 14 | // ``` 15 | // To write: 16 | // ``` 17 | // using stdJson for string; 18 | // string memory json = "deploymentArtifact"; 19 | // Contract contract = new Contract(); 20 | // json.serialize("contractAddress", address(contract)); 21 | // json = json.serialize("deploymentTimes", uint(1)); 22 | // // store the stringified JSON to the 'json' variable we have been using as a key 23 | // // as we won't need it any longer 24 | // string memory json2 = "finalArtifact"; 25 | // string memory final = json2.serialize("depArtifact", json); 26 | // final.write(""); 27 | // ``` 28 | 29 | library stdJson { 30 | VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); 31 | 32 | function parseRaw(string memory json, string memory key) internal pure returns (bytes memory) { 33 | return vm.parseJson(json, key); 34 | } 35 | 36 | function readUint(string memory json, string memory key) internal pure returns (uint256) { 37 | return vm.parseJsonUint(json, key); 38 | } 39 | 40 | function readUintArray(string memory json, string memory key) internal pure returns (uint256[] memory) { 41 | return vm.parseJsonUintArray(json, key); 42 | } 43 | 44 | function readInt(string memory json, string memory key) internal pure returns (int256) { 45 | return vm.parseJsonInt(json, key); 46 | } 47 | 48 | function readIntArray(string memory json, string memory key) internal pure returns (int256[] memory) { 49 | return vm.parseJsonIntArray(json, key); 50 | } 51 | 52 | function readBytes32(string memory json, string memory key) internal pure returns (bytes32) { 53 | return vm.parseJsonBytes32(json, key); 54 | } 55 | 56 | function readBytes32Array(string memory json, string memory key) internal pure returns (bytes32[] memory) { 57 | return vm.parseJsonBytes32Array(json, key); 58 | } 59 | 60 | function readString(string memory json, string memory key) internal pure returns (string memory) { 61 | return vm.parseJsonString(json, key); 62 | } 63 | 64 | function readStringArray(string memory json, string memory key) internal pure returns (string[] memory) { 65 | return vm.parseJsonStringArray(json, key); 66 | } 67 | 68 | function readAddress(string memory json, string memory key) internal pure returns (address) { 69 | return vm.parseJsonAddress(json, key); 70 | } 71 | 72 | function readAddressArray(string memory json, string memory key) internal pure returns (address[] memory) { 73 | return vm.parseJsonAddressArray(json, key); 74 | } 75 | 76 | function readBool(string memory json, string memory key) internal pure returns (bool) { 77 | return vm.parseJsonBool(json, key); 78 | } 79 | 80 | function readBoolArray(string memory json, string memory key) internal pure returns (bool[] memory) { 81 | return vm.parseJsonBoolArray(json, key); 82 | } 83 | 84 | function readBytes(string memory json, string memory key) internal pure returns (bytes memory) { 85 | return vm.parseJsonBytes(json, key); 86 | } 87 | 88 | function readBytesArray(string memory json, string memory key) internal pure returns (bytes[] memory) { 89 | return vm.parseJsonBytesArray(json, key); 90 | } 91 | 92 | function serialize(string memory jsonKey, string memory rootObject) internal returns (string memory) { 93 | return vm.serializeJson(jsonKey, rootObject); 94 | } 95 | 96 | function serialize(string memory jsonKey, string memory key, bool value) internal returns (string memory) { 97 | return vm.serializeBool(jsonKey, key, value); 98 | } 99 | 100 | function serialize(string memory jsonKey, string memory key, bool[] memory value) 101 | internal 102 | returns (string memory) 103 | { 104 | return vm.serializeBool(jsonKey, key, value); 105 | } 106 | 107 | function serialize(string memory jsonKey, string memory key, uint256 value) internal returns (string memory) { 108 | return vm.serializeUint(jsonKey, key, value); 109 | } 110 | 111 | function serialize(string memory jsonKey, string memory key, uint256[] memory value) 112 | internal 113 | returns (string memory) 114 | { 115 | return vm.serializeUint(jsonKey, key, value); 116 | } 117 | 118 | function serialize(string memory jsonKey, string memory key, int256 value) internal returns (string memory) { 119 | return vm.serializeInt(jsonKey, key, value); 120 | } 121 | 122 | function serialize(string memory jsonKey, string memory key, int256[] memory value) 123 | internal 124 | returns (string memory) 125 | { 126 | return vm.serializeInt(jsonKey, key, value); 127 | } 128 | 129 | function serialize(string memory jsonKey, string memory key, address value) internal returns (string memory) { 130 | return vm.serializeAddress(jsonKey, key, value); 131 | } 132 | 133 | function serialize(string memory jsonKey, string memory key, address[] memory value) 134 | internal 135 | returns (string memory) 136 | { 137 | return vm.serializeAddress(jsonKey, key, value); 138 | } 139 | 140 | function serialize(string memory jsonKey, string memory key, bytes32 value) internal returns (string memory) { 141 | return vm.serializeBytes32(jsonKey, key, value); 142 | } 143 | 144 | function serialize(string memory jsonKey, string memory key, bytes32[] memory value) 145 | internal 146 | returns (string memory) 147 | { 148 | return vm.serializeBytes32(jsonKey, key, value); 149 | } 150 | 151 | function serialize(string memory jsonKey, string memory key, bytes memory value) internal returns (string memory) { 152 | return vm.serializeBytes(jsonKey, key, value); 153 | } 154 | 155 | function serialize(string memory jsonKey, string memory key, bytes[] memory value) 156 | internal 157 | returns (string memory) 158 | { 159 | return vm.serializeBytes(jsonKey, key, value); 160 | } 161 | 162 | function serialize(string memory jsonKey, string memory key, string memory value) 163 | internal 164 | returns (string memory) 165 | { 166 | return vm.serializeString(jsonKey, key, value); 167 | } 168 | 169 | function serialize(string memory jsonKey, string memory key, string[] memory value) 170 | internal 171 | returns (string memory) 172 | { 173 | return vm.serializeString(jsonKey, key, value); 174 | } 175 | 176 | function write(string memory jsonKey, string memory path) internal { 177 | vm.writeJson(jsonKey, path); 178 | } 179 | 180 | function write(string memory jsonKey, string memory path, string memory valueKey) internal { 181 | vm.writeJson(jsonKey, path, valueKey); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/StdMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | library stdMath { 5 | int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968; 6 | 7 | function abs(int256 a) internal pure returns (uint256) { 8 | // Required or it will fail when `a = type(int256).min` 9 | if (a == INT256_MIN) { 10 | return 57896044618658097711785492504343953926634992332820282019728792003956564819968; 11 | } 12 | 13 | return uint256(a > 0 ? a : -a); 14 | } 15 | 16 | function delta(uint256 a, uint256 b) internal pure returns (uint256) { 17 | return a > b ? a - b : b - a; 18 | } 19 | 20 | function delta(int256 a, int256 b) internal pure returns (uint256) { 21 | // a and b are of the same sign 22 | // this works thanks to two's complement, the left-most bit is the sign bit 23 | if ((a ^ b) > -1) { 24 | return delta(abs(a), abs(b)); 25 | } 26 | 27 | // a and b are of opposite signs 28 | return abs(a) + abs(b); 29 | } 30 | 31 | function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) { 32 | uint256 absDelta = delta(a, b); 33 | 34 | return absDelta * 1e18 / b; 35 | } 36 | 37 | function percentDelta(int256 a, int256 b) internal pure returns (uint256) { 38 | uint256 absDelta = delta(a, b); 39 | uint256 absB = abs(b); 40 | 41 | return absDelta * 1e18 / absB; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | // 💬 ABOUT 7 | // Forge Std's default Test. 8 | 9 | // 🧩 MODULES 10 | import {console} from "./console.sol"; 11 | import {console2} from "./console2.sol"; 12 | import {safeconsole} from "./safeconsole.sol"; 13 | import {StdAssertions} from "./StdAssertions.sol"; 14 | import {StdChains} from "./StdChains.sol"; 15 | import {StdCheats} from "./StdCheats.sol"; 16 | import {stdError} from "./StdError.sol"; 17 | import {StdInvariant} from "./StdInvariant.sol"; 18 | import {stdJson} from "./StdJson.sol"; 19 | import {stdMath} from "./StdMath.sol"; 20 | import {StdStorage, stdStorage} from "./StdStorage.sol"; 21 | import {StdStyle} from "./StdStyle.sol"; 22 | import {StdUtils} from "./StdUtils.sol"; 23 | import {Vm} from "./Vm.sol"; 24 | 25 | // 📦 BOILERPLATE 26 | import {TestBase} from "./Base.sol"; 27 | import {DSTest} from "ds-test/test.sol"; 28 | 29 | // ⭐️ TEST 30 | abstract contract Test is TestBase, DSTest, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { 31 | // Note: IS_TEST() must return true. 32 | // Note: Must have failure system, https://github.com/dapphub/ds-test/blob/cd98eff28324bfac652e63a239a60632a761790b/src/test.sol#L39-L76. 33 | } 34 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/interfaces/IERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | import "./IERC165.sol"; 5 | 6 | /// @title ERC-1155 Multi Token Standard 7 | /// @dev See https://eips.ethereum.org/EIPS/eip-1155 8 | /// Note: The ERC-165 identifier for this interface is 0xd9b67a26. 9 | interface IERC1155 is IERC165 { 10 | /// @dev 11 | /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 12 | /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 13 | /// - The `_from` argument MUST be the address of the holder whose balance is decreased. 14 | /// - The `_to` argument MUST be the address of the recipient whose balance is increased. 15 | /// - The `_id` argument MUST be the token type being transferred. 16 | /// - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. 17 | /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 18 | /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 19 | event TransferSingle( 20 | address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value 21 | ); 22 | 23 | /// @dev 24 | /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 25 | /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 26 | /// - The `_from` argument MUST be the address of the holder whose balance is decreased. 27 | /// - The `_to` argument MUST be the address of the recipient whose balance is increased. 28 | /// - The `_ids` argument MUST be the list of tokens being transferred. 29 | /// - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. 30 | /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 31 | /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 32 | event TransferBatch( 33 | address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values 34 | ); 35 | 36 | /// @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). 37 | event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 38 | 39 | /// @dev MUST emit when the URI is updated for a token ID. URIs are defined in RFC 3986. 40 | /// The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". 41 | event URI(string _value, uint256 indexed _id); 42 | 43 | /// @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). 44 | /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 45 | /// - MUST revert if `_to` is the zero address. 46 | /// - MUST revert if balance of holder for token `_id` is lower than the `_value` sent. 47 | /// - MUST revert on any other error. 48 | /// - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). 49 | /// - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 50 | /// @param _from Source address 51 | /// @param _to Target address 52 | /// @param _id ID of the token type 53 | /// @param _value Transfer amount 54 | /// @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` 55 | function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; 56 | 57 | /// @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). 58 | /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 59 | /// - MUST revert if `_to` is the zero address. 60 | /// - MUST revert if length of `_ids` is not the same as length of `_values`. 61 | /// - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. 62 | /// - MUST revert on any other error. 63 | /// - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). 64 | /// - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). 65 | /// - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 66 | /// @param _from Source address 67 | /// @param _to Target address 68 | /// @param _ids IDs of each token type (order and length must match _values array) 69 | /// @param _values Transfer amounts per token type (order and length must match _ids array) 70 | /// @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` 71 | function safeBatchTransferFrom( 72 | address _from, 73 | address _to, 74 | uint256[] calldata _ids, 75 | uint256[] calldata _values, 76 | bytes calldata _data 77 | ) external; 78 | 79 | /// @notice Get the balance of an account's tokens. 80 | /// @param _owner The address of the token holder 81 | /// @param _id ID of the token 82 | /// @return The _owner's balance of the token type requested 83 | function balanceOf(address _owner, uint256 _id) external view returns (uint256); 84 | 85 | /// @notice Get the balance of multiple account/token pairs 86 | /// @param _owners The addresses of the token holders 87 | /// @param _ids ID of the tokens 88 | /// @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) 89 | function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) 90 | external 91 | view 92 | returns (uint256[] memory); 93 | 94 | /// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. 95 | /// @dev MUST emit the ApprovalForAll event on success. 96 | /// @param _operator Address to add to the set of authorized operators 97 | /// @param _approved True if the operator is approved, false to revoke approval 98 | function setApprovalForAll(address _operator, bool _approved) external; 99 | 100 | /// @notice Queries the approval status of an operator for a given owner. 101 | /// @param _owner The owner of the tokens 102 | /// @param _operator Address of authorized operator 103 | /// @return True if the operator is approved, false if not 104 | function isApprovedForAll(address _owner, address _operator) external view returns (bool); 105 | } 106 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceID The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | /// @dev Interface of the ERC20 standard as defined in the EIP. 5 | /// @dev This includes the optional name, symbol, and decimals metadata. 6 | interface IERC20 { 7 | /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`). 8 | event Transfer(address indexed from, address indexed to, uint256 value); 9 | 10 | /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value` 11 | /// is the new allowance. 12 | event Approval(address indexed owner, address indexed spender, uint256 value); 13 | 14 | /// @notice Returns the amount of tokens in existence. 15 | function totalSupply() external view returns (uint256); 16 | 17 | /// @notice Returns the amount of tokens owned by `account`. 18 | function balanceOf(address account) external view returns (uint256); 19 | 20 | /// @notice Moves `amount` tokens from the caller's account to `to`. 21 | function transfer(address to, uint256 amount) external returns (bool); 22 | 23 | /// @notice Returns the remaining number of tokens that `spender` is allowed 24 | /// to spend on behalf of `owner` 25 | function allowance(address owner, address spender) external view returns (uint256); 26 | 27 | /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens. 28 | /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 29 | function approve(address spender, uint256 amount) external returns (bool); 30 | 31 | /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. 32 | /// `amount` is then deducted from the caller's allowance. 33 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 34 | 35 | /// @notice Returns the name of the token. 36 | function name() external view returns (string memory); 37 | 38 | /// @notice Returns the symbol of the token. 39 | function symbol() external view returns (string memory); 40 | 41 | /// @notice Returns the decimals places of the token. 42 | function decimals() external view returns (uint8); 43 | } 44 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/src/interfaces/IMulticall3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | interface IMulticall3 { 7 | struct Call { 8 | address target; 9 | bytes callData; 10 | } 11 | 12 | struct Call3 { 13 | address target; 14 | bool allowFailure; 15 | bytes callData; 16 | } 17 | 18 | struct Call3Value { 19 | address target; 20 | bool allowFailure; 21 | uint256 value; 22 | bytes callData; 23 | } 24 | 25 | struct Result { 26 | bool success; 27 | bytes returnData; 28 | } 29 | 30 | function aggregate(Call[] calldata calls) 31 | external 32 | payable 33 | returns (uint256 blockNumber, bytes[] memory returnData); 34 | 35 | function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData); 36 | 37 | function aggregate3Value(Call3Value[] calldata calls) external payable returns (Result[] memory returnData); 38 | 39 | function blockAndAggregate(Call[] calldata calls) 40 | external 41 | payable 42 | returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); 43 | 44 | function getBasefee() external view returns (uint256 basefee); 45 | 46 | function getBlockHash(uint256 blockNumber) external view returns (bytes32 blockHash); 47 | 48 | function getBlockNumber() external view returns (uint256 blockNumber); 49 | 50 | function getChainId() external view returns (uint256 chainid); 51 | 52 | function getCurrentBlockCoinbase() external view returns (address coinbase); 53 | 54 | function getCurrentBlockDifficulty() external view returns (uint256 difficulty); 55 | 56 | function getCurrentBlockGasLimit() external view returns (uint256 gaslimit); 57 | 58 | function getCurrentBlockTimestamp() external view returns (uint256 timestamp); 59 | 60 | function getEthBalance(address addr) external view returns (uint256 balance); 61 | 62 | function getLastBlockHash() external view returns (bytes32 blockHash); 63 | 64 | function tryAggregate(bool requireSuccess, Call[] calldata calls) 65 | external 66 | payable 67 | returns (Result[] memory returnData); 68 | 69 | function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) 70 | external 71 | payable 72 | returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); 73 | } 74 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/StdError.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/StdError.sol"; 5 | import "../src/Test.sol"; 6 | 7 | contract StdErrorsTest is Test { 8 | ErrorsTest test; 9 | 10 | function setUp() public { 11 | test = new ErrorsTest(); 12 | } 13 | 14 | function test_ExpectAssertion() public { 15 | vm.expectRevert(stdError.assertionError); 16 | test.assertionError(); 17 | } 18 | 19 | function test_ExpectArithmetic() public { 20 | vm.expectRevert(stdError.arithmeticError); 21 | test.arithmeticError(10); 22 | } 23 | 24 | function test_ExpectDiv() public { 25 | vm.expectRevert(stdError.divisionError); 26 | test.divError(0); 27 | } 28 | 29 | function test_ExpectMod() public { 30 | vm.expectRevert(stdError.divisionError); 31 | test.modError(0); 32 | } 33 | 34 | function test_ExpectEnum() public { 35 | vm.expectRevert(stdError.enumConversionError); 36 | test.enumConversion(1); 37 | } 38 | 39 | function test_ExpectEncodeStg() public { 40 | vm.expectRevert(stdError.encodeStorageError); 41 | test.encodeStgError(); 42 | } 43 | 44 | function test_ExpectPop() public { 45 | vm.expectRevert(stdError.popError); 46 | test.pop(); 47 | } 48 | 49 | function test_ExpectOOB() public { 50 | vm.expectRevert(stdError.indexOOBError); 51 | test.indexOOBError(1); 52 | } 53 | 54 | function test_ExpectMem() public { 55 | vm.expectRevert(stdError.memOverflowError); 56 | test.mem(); 57 | } 58 | 59 | function test_ExpectIntern() public { 60 | vm.expectRevert(stdError.zeroVarError); 61 | test.intern(); 62 | } 63 | } 64 | 65 | contract ErrorsTest { 66 | enum T {T1} 67 | 68 | uint256[] public someArr; 69 | bytes someBytes; 70 | 71 | function assertionError() public pure { 72 | assert(false); 73 | } 74 | 75 | function arithmeticError(uint256 a) public pure { 76 | a -= 100; 77 | } 78 | 79 | function divError(uint256 a) public pure { 80 | 100 / a; 81 | } 82 | 83 | function modError(uint256 a) public pure { 84 | 100 % a; 85 | } 86 | 87 | function enumConversion(uint256 a) public pure { 88 | T(a); 89 | } 90 | 91 | function encodeStgError() public { 92 | /// @solidity memory-safe-assembly 93 | assembly { 94 | sstore(someBytes.slot, 1) 95 | } 96 | keccak256(someBytes); 97 | } 98 | 99 | function pop() public { 100 | someArr.pop(); 101 | } 102 | 103 | function indexOOBError(uint256 a) public pure { 104 | uint256[] memory t = new uint256[](0); 105 | t[a]; 106 | } 107 | 108 | function mem() public pure { 109 | uint256 l = 2 ** 256 / 32; 110 | new uint256[](l); 111 | } 112 | 113 | function intern() public returns (uint256) { 114 | function(uint256) internal returns (uint256) x; 115 | x(2); 116 | return 7; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/StdStyle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.7.0 <0.9.0; 3 | 4 | import "../src/Test.sol"; 5 | 6 | contract StdStyleTest is Test { 7 | function test_StyleColor() public pure { 8 | console2.log(StdStyle.red("StdStyle.red String Test")); 9 | console2.log(StdStyle.red(uint256(10e18))); 10 | console2.log(StdStyle.red(int256(-10e18))); 11 | console2.log(StdStyle.red(true)); 12 | console2.log(StdStyle.red(address(0))); 13 | console2.log(StdStyle.redBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 14 | console2.log(StdStyle.redBytes32("StdStyle.redBytes32")); 15 | console2.log(StdStyle.green("StdStyle.green String Test")); 16 | console2.log(StdStyle.green(uint256(10e18))); 17 | console2.log(StdStyle.green(int256(-10e18))); 18 | console2.log(StdStyle.green(true)); 19 | console2.log(StdStyle.green(address(0))); 20 | console2.log(StdStyle.greenBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 21 | console2.log(StdStyle.greenBytes32("StdStyle.greenBytes32")); 22 | console2.log(StdStyle.yellow("StdStyle.yellow String Test")); 23 | console2.log(StdStyle.yellow(uint256(10e18))); 24 | console2.log(StdStyle.yellow(int256(-10e18))); 25 | console2.log(StdStyle.yellow(true)); 26 | console2.log(StdStyle.yellow(address(0))); 27 | console2.log(StdStyle.yellowBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 28 | console2.log(StdStyle.yellowBytes32("StdStyle.yellowBytes32")); 29 | console2.log(StdStyle.blue("StdStyle.blue String Test")); 30 | console2.log(StdStyle.blue(uint256(10e18))); 31 | console2.log(StdStyle.blue(int256(-10e18))); 32 | console2.log(StdStyle.blue(true)); 33 | console2.log(StdStyle.blue(address(0))); 34 | console2.log(StdStyle.blueBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 35 | console2.log(StdStyle.blueBytes32("StdStyle.blueBytes32")); 36 | console2.log(StdStyle.magenta("StdStyle.magenta String Test")); 37 | console2.log(StdStyle.magenta(uint256(10e18))); 38 | console2.log(StdStyle.magenta(int256(-10e18))); 39 | console2.log(StdStyle.magenta(true)); 40 | console2.log(StdStyle.magenta(address(0))); 41 | console2.log(StdStyle.magentaBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 42 | console2.log(StdStyle.magentaBytes32("StdStyle.magentaBytes32")); 43 | console2.log(StdStyle.cyan("StdStyle.cyan String Test")); 44 | console2.log(StdStyle.cyan(uint256(10e18))); 45 | console2.log(StdStyle.cyan(int256(-10e18))); 46 | console2.log(StdStyle.cyan(true)); 47 | console2.log(StdStyle.cyan(address(0))); 48 | console2.log(StdStyle.cyanBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 49 | console2.log(StdStyle.cyanBytes32("StdStyle.cyanBytes32")); 50 | } 51 | 52 | function test_StyleFontWeight() public pure { 53 | console2.log(StdStyle.bold("StdStyle.bold String Test")); 54 | console2.log(StdStyle.bold(uint256(10e18))); 55 | console2.log(StdStyle.bold(int256(-10e18))); 56 | console2.log(StdStyle.bold(address(0))); 57 | console2.log(StdStyle.bold(true)); 58 | console2.log(StdStyle.boldBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 59 | console2.log(StdStyle.boldBytes32("StdStyle.boldBytes32")); 60 | console2.log(StdStyle.dim("StdStyle.dim String Test")); 61 | console2.log(StdStyle.dim(uint256(10e18))); 62 | console2.log(StdStyle.dim(int256(-10e18))); 63 | console2.log(StdStyle.dim(address(0))); 64 | console2.log(StdStyle.dim(true)); 65 | console2.log(StdStyle.dimBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 66 | console2.log(StdStyle.dimBytes32("StdStyle.dimBytes32")); 67 | console2.log(StdStyle.italic("StdStyle.italic String Test")); 68 | console2.log(StdStyle.italic(uint256(10e18))); 69 | console2.log(StdStyle.italic(int256(-10e18))); 70 | console2.log(StdStyle.italic(address(0))); 71 | console2.log(StdStyle.italic(true)); 72 | console2.log(StdStyle.italicBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 73 | console2.log(StdStyle.italicBytes32("StdStyle.italicBytes32")); 74 | console2.log(StdStyle.underline("StdStyle.underline String Test")); 75 | console2.log(StdStyle.underline(uint256(10e18))); 76 | console2.log(StdStyle.underline(int256(-10e18))); 77 | console2.log(StdStyle.underline(address(0))); 78 | console2.log(StdStyle.underline(true)); 79 | console2.log(StdStyle.underlineBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 80 | console2.log(StdStyle.underlineBytes32("StdStyle.underlineBytes32")); 81 | console2.log(StdStyle.inverse("StdStyle.inverse String Test")); 82 | console2.log(StdStyle.inverse(uint256(10e18))); 83 | console2.log(StdStyle.inverse(int256(-10e18))); 84 | console2.log(StdStyle.inverse(address(0))); 85 | console2.log(StdStyle.inverse(true)); 86 | console2.log(StdStyle.inverseBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); 87 | console2.log(StdStyle.inverseBytes32("StdStyle.inverseBytes32")); 88 | } 89 | 90 | function test_StyleCombined() public pure { 91 | console2.log(StdStyle.red(StdStyle.bold("Red Bold String Test"))); 92 | console2.log(StdStyle.green(StdStyle.dim(uint256(10e18)))); 93 | console2.log(StdStyle.yellow(StdStyle.italic(int256(-10e18)))); 94 | console2.log(StdStyle.blue(StdStyle.underline(address(0)))); 95 | console2.log(StdStyle.magenta(StdStyle.inverse(true))); 96 | } 97 | 98 | function test_StyleCustom() public pure { 99 | console2.log(h1("Custom Style 1")); 100 | console2.log(h2("Custom Style 2")); 101 | } 102 | 103 | function h1(string memory a) private pure returns (string memory) { 104 | return StdStyle.cyan(StdStyle.inverse(StdStyle.bold(a))); 105 | } 106 | 107 | function h2(string memory a) private pure returns (string memory) { 108 | return StdStyle.magenta(StdStyle.bold(StdStyle.underline(a))); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/Vm.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {Test} from "../src/Test.sol"; 5 | import {Vm, VmSafe} from "../src/Vm.sol"; 6 | 7 | contract VmTest is Test { 8 | // This test ensures that functions are never accidentally removed from a Vm interface, or 9 | // inadvertently moved between Vm and VmSafe. This test must be updated each time a function is 10 | // added to or removed from Vm or VmSafe. 11 | function test_interfaceId() public { 12 | assertEq(type(VmSafe).interfaceId, bytes4(0x329f5e71), "VmSafe"); 13 | assertEq(type(Vm).interfaceId, bytes4(0x82ccbb14), "Vm"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/compilation/CompilationScript.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Script.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationScript is Script {} 11 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/compilation/CompilationScriptBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Script.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationScriptBase is ScriptBase {} 11 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/compilation/CompilationTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Test.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationTest is Test {} 11 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/lib/forge-std/test/compilation/CompilationTestBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Test.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationTestBase is TestBase {} 11 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | forge-ctf/=lib/forge-ctf/src/ 4 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-ctf/CTFDeployer.sol"; 5 | 6 | import "src/Challenge.sol"; 7 | 8 | contract Deploy is CTFDeployer { 9 | function deploy(address system, address player) internal override returns (address challenge) { 10 | vm.startBroadcast(system); 11 | 12 | challenge = address(new Challenge(player)); 13 | 14 | vm.stopBroadcast(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/script/Solve.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-ctf/CTFSolver.sol"; 5 | 6 | import "script/exploit/Exploit.sol"; 7 | 8 | contract Solve is CTFSolver { 9 | function solve(address challengeAddress, address) internal override { 10 | Challenge challenge = Challenge(challengeAddress); 11 | Exploit exploit = new Exploit(challenge); 12 | exploit.exploit(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/script/exploit/Exploit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "src/Challenge.sol"; 5 | 6 | contract Exploit { 7 | Challenge private immutable CHALLENGE; 8 | 9 | constructor(Challenge challenge) { 10 | CHALLENGE = challenge; 11 | } 12 | 13 | function exploit() external { 14 | CHALLENGE.solve(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/project/src/Challenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | contract Challenge { 5 | address public immutable PLAYER; 6 | 7 | bool private solved; 8 | 9 | constructor(address player) { 10 | PLAYER = player; 11 | } 12 | 13 | function solve() external { 14 | solved = true; 15 | } 16 | 17 | function isSolved() external view returns (bool) { 18 | return solved; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/eth-pwn/challenge/solve.py: -------------------------------------------------------------------------------- 1 | from ctf_solvers.pwn_solver import PwnChallengeSolver 2 | 3 | PwnChallengeSolver().start() 4 | --------------------------------------------------------------------------------