diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml new file mode 100644 index 0000000..6a22e3e --- /dev/null +++ b/.woodpecker/ci.yml @@ -0,0 +1,60 @@ +kind: pipeline +type: docker +name: ci + +trigger: + event: + - push + - pull_request + +steps: + - name: bootstrap-deps + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + commands: + - | + bash -lc ' + set -euo pipefail + git submodule update --init --recursive + yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile + ' + + - name: foundry-suite + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + commands: + - | + bash -lc ' + set -euo pipefail + cd onchain + export PATH=/root/.foundry/bin:$PATH + forge --version + forge build + forge test -vvv + forge snapshot + ' + + - name: node-quality + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + environment: + CI: "true" + commands: + - | + bash -lc ' + set -euo pipefail + npm config set fund false + npm config set audit false + ./scripts/build-kraiken-lib.sh + npm install --prefix landing --no-audit --no-fund + npm run lint --prefix landing + npm run build --prefix landing + npm install --prefix web-app --no-audit --no-fund + npm run lint --prefix web-app + npm run test --prefix web-app -- --run + npm run build --prefix web-app + npm install --prefix services/ponder --no-audit --no-fund + npm run lint --prefix services/ponder + npm run build --prefix services/ponder + npm install --prefix services/txnBot --no-audit --no-fund + npm run lint --prefix services/txnBot + npm run test --prefix services/txnBot -- --runInBand + npm run build --prefix services/txnBot + ' diff --git a/.woodpecker/contracts.yml b/.woodpecker/contracts.yml new file mode 100644 index 0000000..6c368bb --- /dev/null +++ b/.woodpecker/contracts.yml @@ -0,0 +1,74 @@ +kind: pipeline +type: docker +name: contracts-local-fork + +trigger: + event: + - push + - pull_request + +steps: + - name: bootstrap-deps + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + commands: + - | + bash -lc ' + set -euo pipefail + git submodule update --init --recursive + yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile + ' + + - name: forge-suite + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + environment: + HARB_ENV: BASE_SEPOLIA_LOCAL_FORK + commands: + - | + bash -lc ' + set -euo pipefail + cd onchain + export PATH=/root/.foundry/bin:$PATH + forge build + forge test -vv --ffi + forge snapshot + ' + +--- + +kind: pipeline +type: docker +name: contracts-base-sepolia + +trigger: + event: + - push + - pull_request + +steps: + - name: bootstrap-deps + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + commands: + - | + bash -lc ' + set -euo pipefail + git submodule update --init --recursive + yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile + ' + + - name: forge-suite + image: registry.sovraigns.network/harb/node-ci:latest + environment: + HARB_ENV: BASE_SEPOLIA + BASE_SEPOLIA_RPC: + from_secret: base_sepolia_rpc + commands: + - | + bash -lc ' + set -euo pipefail + cd onchain + export BASE_SEPOLIA_RPC="$BASE_SEPOLIA_RPC" + export PATH=/root/.foundry/bin:$PATH + forge build + forge test -vv --ffi + forge snapshot + ' diff --git a/.woodpecker/e2e.yml b/.woodpecker/e2e.yml new file mode 100644 index 0000000..1414848 --- /dev/null +++ b/.woodpecker/e2e.yml @@ -0,0 +1,60 @@ +kind: pipeline +type: docker +name: e2e + +labels: + podman: "true" + +trigger: + event: + - push + - pull_request + +steps: + - name: run-e2e + image: registry.sovraigns.network/harb/playwright-ci:ci-20251013 + privileged: true + environment: + PNPM_HOME: /root/.local/share/pnpm + PATH: /root/.local/share/pnpm:/root/.local/bin:/usr/local/bin:/usr/bin:/bin + HARB_ENV: BASE_SEPOLIA_LOCAL_FORK + SKIP_WATCH: "1" + XDG_RUNTIME_DIR: /tmp/podman-run + commands: + - | + set -eu + set -o pipefail 2>/dev/null || true + mkdir -p "$XDG_RUNTIME_DIR" + git submodule update --init --recursive + yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile + npm config set fund false + npm config set audit false + npm install --prefix kraiken-lib --no-audit --no-fund + ./scripts/build-kraiken-lib.sh + npm install --prefix landing --no-audit --no-fund + npm install --prefix web-app --no-audit --no-fund + npm install --prefix services/ponder --no-audit --no-fund + npm install --prefix services/txnBot --no-audit --no-fund + npm install --no-audit --no-fund + npx playwright install chromium + trap "./scripts/dev.sh stop || true" EXIT + ./scripts/dev.sh start + timeout 240 bash -c 'until curl -sf http://localhost:8081/api/graphql > /dev/null; do sleep 3; done' + npm run test:e2e + + - name: collect-artifacts + image: alpine:3.20 + depends_on: + - run-e2e + when: + status: + - success + - failure + commands: + - | + set -euo pipefail + apk add --no-cache tar gzip + mkdir -p artifacts + if [ -d playwright-report ]; then tar -czf artifacts/playwright-report.tgz playwright-report; fi + if [ -d test-results ]; then tar -czf artifacts/test-results.tgz test-results; fi + if [ -d logs ]; then tar -czf artifacts/stack-logs.tgz logs; fi diff --git a/.woodpecker/fuzz-nightly.yml b/.woodpecker/fuzz-nightly.yml new file mode 100644 index 0000000..93efae6 --- /dev/null +++ b/.woodpecker/fuzz-nightly.yml @@ -0,0 +1,46 @@ +kind: pipeline +type: docker +name: fuzz-nightly + +trigger: + event: + - cron + +steps: + - name: bootstrap-deps + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + commands: + - | + bash -lc ' + set -euo pipefail + git submodule update --init --recursive + yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile + ' + + - name: fuzz + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + commands: + - | + bash -lc ' + set -euo pipefail + if ! command -v bc >/dev/null 2>&1; then + apt-get update + apt-get install -y bc + fi + cd onchain + export PATH=/root/.foundry/bin:$PATH + forge --version + ./analysis/run-fuzzing.sh BullMarketOptimizer runs=75 + ' + + - name: package-results + image: alpine:3.20 + when: + status: + - success + - failure + commands: + - set -e + - apk add --no-cache tar + - mkdir -p artifacts + - if [ -d onchain/analysis ]; then tar -czf artifacts/fuzz-results.tgz onchain/analysis; fi diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml new file mode 100644 index 0000000..8776a6d --- /dev/null +++ b/.woodpecker/release.yml @@ -0,0 +1,178 @@ +kind: pipeline +type: docker +name: release + +labels: + podman: "true" + +when: + event: tag + +steps: + - name: version-check + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + when: + event: tag + commands: + - | + bash -lc ' + set -euo pipefail + git submodule update --init --recursive + corepack enable + yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile + npm config set fund false + npm config set audit false + npm install --prefix kraiken-lib --no-audit --no-fund + ./scripts/build-kraiken-lib.sh + node <<\"NODE\" + import fs from \"fs\"; + + const sol = fs.readFileSync(\"onchain/src/Kraiken.sol\", \"utf8\"); + const lib = fs.readFileSync(\"kraiken-lib/src/version.ts\", \"utf8\"); + + const contractVersionMatch = sol.match(/VERSION\\s*=\\s*(\\d+)/); + if (!contractVersionMatch) { + console.error(\"Unable to find VERSION constant in Kraiken.sol\"); + process.exit(1); + } + const contractVersion = Number(contractVersionMatch[1]); + + const libVersionMatch = lib.match(/KRAIKEN_LIB_VERSION\\s*=\\s*(\\d+)/); + if (!libVersionMatch) { + console.error(\"Unable to find KRAIKEN_LIB_VERSION in kraiken-lib/src/version.ts\"); + process.exit(1); + } + const libVersion = Number(libVersionMatch[1]); + + const compatMatch = lib.match(/COMPATIBLE_CONTRACT_VERSIONS\\s*=\\s*\\[([^\\]]*)\\]/); + if (!compatMatch) { + console.error(\"Unable to find COMPATIBLE_CONTRACT_VERSIONS in kraiken-lib/src/version.ts\"); + process.exit(1); + } + const compatibleVersions = compatMatch[1] + .split(\",\") + .map(v => v.trim()) + .filter(Boolean) + .map(Number); + + if (contractVersion !== libVersion) { + console.error(\"Contract VERSION (\" + contractVersion + \") and KRAIKEN_LIB_VERSION (\" + libVersion + \") differ\"); + process.exit(1); + } + if (!compatibleVersions.includes(contractVersion)) { + console.error(\"Contract VERSION \" + contractVersion + \" missing from COMPATIBLE_CONTRACT_VERSIONS [\" + compatibleVersions.join(\", \") + \"]\"); + process.exit(1); + } + + console.log(\"Version check passed for VERSION \" + contractVersion); + NODE + ' + + - name: build-artifacts + image: registry.sovraigns.network/harb/node-ci:ci-20251013 + depends_on: + - version-check + when: + event: tag + commands: + - | + bash -lc ' + set -euo pipefail + npm config set fund false + npm config set audit false + npm install --prefix kraiken-lib --no-audit --no-fund + ./scripts/build-kraiken-lib.sh + npm install --prefix landing --no-audit --no-fund + npm install --prefix web-app --no-audit --no-fund + npm install --prefix services/ponder --no-audit --no-fund + npm install --prefix services/txnBot --no-audit --no-fund + npm install --no-audit --no-fund + export PATH=/root/.foundry/bin:$PATH + forge --version + (cd onchain && forge build) + npm run build --prefix landing + npm run build --prefix web-app + npm run build --prefix services/ponder + npm run build --prefix services/txnBot + rm -rf release + mkdir -p release/dist + cp -r onchain/out release/dist/abi + cp -r kraiken-lib/dist release/dist/kraiken-lib + cp -r landing/dist release/dist/landing + cp -r web-app/dist release/dist/web-app + cp -r services/txnBot/dist release/dist/txn-bot + if [ -d services/ponder/generated ]; then + cp -r services/ponder/generated release/dist/ponder-generated + fi + tar -czf release-bundle.tgz -C release dist + ' + + - name: podman-publish + image: registry.sovraigns.network/harb/playwright-ci:ci-20251013 + pull: true + privileged: true + depends_on: + - build-artifacts + when: + event: tag + environment: + REGISTRY_SERVER: + from_secret: registry_server + REGISTRY_NAMESPACE: + from_secret: registry_namespace + REGISTRY_USERNAME: + from_secret: registry_username + REGISTRY_PASSWORD: + from_secret: registry_password + commands: + - | + bash -lc ' + set -eo pipefail + if [ -z "${CI_COMMIT_TAG:-}" ]; then + echo "CI_COMMIT_TAG not set" >&2 + exit 1 + fi + if [ -z "${REGISTRY_SERVER:-}" ] || [ -z "${REGISTRY_NAMESPACE:-}" ]; then + echo "Registry server or namespace missing" >&2 + exit 1 + fi + TAG=$(printf '%s' "$CI_COMMIT_TAG" | sed "s#^refs/tags/##") + export TAG + if [ -z "${COMPOSE_PROJECT_NAME:-}" ]; then + COMPOSE_PROJECT_NAME=harb + fi + REGISTRY_ROOT="${REGISTRY_SERVER:-registry.sovraigns.network}" + REGISTRY_NS="${REGISTRY_NAMESPACE:-harb}" + REGISTRY_BASE="$REGISTRY_ROOT/$REGISTRY_NS" + + podman login "$REGISTRY_ROOT" -u "$REGISTRY_USERNAME" -p "$REGISTRY_PASSWORD" + # Build and publish CI base images + node_ci_tmp=harb-node-ci-build + playwright_ci_tmp=harb-playwright-ci-build + + podman build -f docker/Dockerfile.node-ci -t "$node_ci_tmp" . + podman tag "$node_ci_tmp" "$REGISTRY_BASE/node-ci:$TAG" + podman push "$REGISTRY_BASE/node-ci:$TAG" + podman tag "$REGISTRY_BASE/node-ci:$TAG" "$REGISTRY_BASE/node-ci:latest" + podman push "$REGISTRY_BASE/node-ci:latest" + + podman build -f docker/Dockerfile.playwright-ci -t "$playwright_ci_tmp" . + podman tag "$playwright_ci_tmp" "$REGISTRY_BASE/playwright-ci:$TAG" + podman push "$REGISTRY_BASE/playwright-ci:$TAG" + podman tag "$REGISTRY_BASE/playwright-ci:$TAG" "$REGISTRY_BASE/playwright-ci:latest" + podman push "$REGISTRY_BASE/playwright-ci:latest" + + podman-compose build ponder webapp landing txn-bot + for service in ponder webapp landing txn-bot; do + image=$(podman image ls --filter "label=com.docker.compose.project=$COMPOSE_PROJECT_NAME" --filter "label=com.docker.compose.service=$service" --format "{{.Repository}}:{{ .Tag }}" | head -n1) + if [ -z "$image" ]; then + echo "Unable to find built image for $service" >&2 + exit 1 + fi + target="$REGISTRY_BASE/$service" + podman tag "$image" "$target:$TAG" + podman push "$target:$TAG" + podman tag "$target:$TAG" "$target:latest" + podman push "$target:latest" + done + ' diff --git a/docker/Dockerfile.node-ci b/docker/Dockerfile.node-ci new file mode 100644 index 0000000..1a0be83 --- /dev/null +++ b/docker/Dockerfile.node-ci @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1.6 + +FROM node:20-bookworm + +LABEL org.opencontainers.image.source="https://codeberg.org/johba/harb-ci" +LABEL org.opencontainers.image.description="Node.js toolchain for Harb Stack CI jobs" + +ENV DEBIAN_FRONTEND=noninteractive \ + PNPM_HOME=/root/.local/share/pnpm \ + PATH=/root/.local/share/pnpm:/root/.local/bin:/root/.foundry/bin:$PATH + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + git \ + ca-certificates \ + build-essential \ + pkg-config \ + libssl-dev \ + python3 \ + python3-pip \ + bc \ + jq \ + curl && \ + rm -rf /var/lib/apt/lists/* + +# Enable corepack-managed package managers and pin the versions we expect in CI. +RUN corepack enable && \ + corepack prepare pnpm@8.15.4 --activate && \ + corepack prepare yarn@1.22.19 --activate + +# Install Foundry once so downstream jobs skip the bootstrap step. +RUN curl -L https://foundry.paradigm.xyz | bash && \ + ~/.foundry/bin/foundryup --version && \ + ~/.foundry/bin/foundryup + +WORKDIR /workspace + +CMD ["bash"] diff --git a/docker/Dockerfile.playwright-ci b/docker/Dockerfile.playwright-ci new file mode 100644 index 0000000..fdf2966 --- /dev/null +++ b/docker/Dockerfile.playwright-ci @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1.6 + +FROM mcr.microsoft.com/playwright:v1.56.0-jammy + +LABEL org.opencontainers.image.source="https://codeberg.org/johba/harb-ci" +LABEL org.opencontainers.image.description="Playwright + Podman image for Harb Stack end-to-end CI" + +ENV DEBIAN_FRONTEND=noninteractive \ + PNPM_HOME=/root/.local/share/pnpm \ + PATH=/root/.local/share/pnpm:/root/.local/bin:$PATH + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + podman \ + slirp4netns \ + uidmap \ + iptables \ + git \ + ca-certificates \ + python3-pip \ + jq \ + curl && \ + rm -rf /var/lib/apt/lists/* + +RUN python3 -m pip install --no-cache-dir podman-compose && \ + corepack enable && \ + corepack prepare pnpm@8.15.4 --activate && \ + corepack prepare yarn@1.22.19 --activate + +WORKDIR /workspace + +CMD ["bash"] diff --git a/onchain/foundry.toml b/onchain/foundry.toml index 0b6aff8..6566c8d 100644 --- a/onchain/foundry.toml +++ b/onchain/foundry.toml @@ -7,6 +7,10 @@ gas_limit = 1_000_000_000 gas_price = 0 optimizer = true optimizer_runs = 200 +bytecode_size_limit = 0 + +[profile.maxperf] +bytecode_size_limit = 0 # See more config options https://github.com/foundry-rs/foundry/tree/master/config [rpc_endpoints] diff --git a/onchain/test/helpers/TestBase.sol b/onchain/test/helpers/TestBase.sol index 7fd51b3..0a2d50d 100644 --- a/onchain/test/helpers/TestBase.sol +++ b/onchain/test/helpers/TestBase.sol @@ -72,17 +72,17 @@ contract TestEnvironment is TestConstants { using UniswapHelpers for IUniswapV3Pool; // Core contracts - IUniswapV3Factory public factory; - IUniswapV3Pool public pool; - IWETH9 public weth; - Kraiken public harberg; - Stake public stake; - LiquidityManager public lm; - Optimizer public optimizer; + IUniswapV3Factory internal factory; + IUniswapV3Pool internal pool; + IWETH9 internal weth; + Kraiken internal harberg; + Stake internal stake; + LiquidityManager internal lm; + Optimizer internal optimizer; // State variables - bool public token0isWeth; - address public feeDestination; + bool internal token0isWeth; + address internal feeDestination; constructor(address _feeDestination) { feeDestination = _feeDestination; @@ -314,17 +314,4 @@ contract TestEnvironment is TestConstants { return (factory, pool, weth, harberg, stake, lm, optimizer, token0isWeth); } - /** - * @notice Perform recenter with proper time warp and oracle updates - * @param liquidityManager The LiquidityManager instance to recenter - * @param caller The address that will call recenter - */ - function performRecenter(LiquidityManager liquidityManager, address caller) external { - // Update oracle time - vm.warp(block.timestamp + ORACLE_UPDATE_INTERVAL); - - // Perform recenter - vm.prank(caller); - liquidityManager.recenter(); - } } diff --git a/web-app/env.d.ts b/web-app/src/env.d.ts similarity index 81% rename from web-app/env.d.ts rename to web-app/src/env.d.ts index 1a00442..87b9490 100644 --- a/web-app/env.d.ts +++ b/web-app/src/env.d.ts @@ -6,8 +6,8 @@ declare global { interface Window { ethereum?: EIP1193Provider; } + + const __APP_VERSION__: string; } -declare const __APP_VERSION__: string; - export {}; diff --git a/web-app/tsconfig.app.json b/web-app/tsconfig.app.json index 3272419..3a76366 100644 --- a/web-app/tsconfig.app.json +++ b/web-app/tsconfig.app.json @@ -1,6 +1,6 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", - "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "include": ["src/env.d.ts", "src/**/*", "src/**/*.vue"], "exclude": ["src/**/__tests__/*"], "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",