diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..be07daa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +# Git files +.git +.gitignore + +# Docker files +Dockerfile* +docker-compose.yml +.dockerignore + +# Build artifacts +out/ +cache/ +target/ + +# Environment files (we'll pass these as environment variables) +.env +.env.* + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Documentation (we'll generate this fresh) +docs/ + +# Large library files (we'll install these in the container) +lib/ + +# Logs +*.log diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 06e9c74..c3dd45b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -15,5 +15,11 @@ jobs: fetch-depth: 0 - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Lint Solidity code + run: forge lint + - name: Deployment Dry Run + run: | + export MANAGER_ADDRESS=0x1234567890123456789012345678901234567890 + script/deploy.sh --dry-run - name: Run contract tests - run: forge test -vvv \ No newline at end of file + run: forge test -vvv --summary \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7cd957e..e87447d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,9 @@ out/ # Docs docs/ -# Dotenv file +# Dotenv files .env +docker/docker.env # Lock files foundry.lock diff --git a/docker/DOCKER.md b/docker/DOCKER.md new file mode 100644 index 0000000..2a75ab6 --- /dev/null +++ b/docker/DOCKER.md @@ -0,0 +1,42 @@ +# Docker Setup + +Run everything in Docker containers. + +All commands need `cd docker` first. + +## Setup + +```bash +cd docker +cp docker.env.example docker.env +./build-docker-images.sh +``` + +## Usage + +```bash +cd docker + +# start blockchain +docker-compose up -d anvil + +# deploy +docker-compose run --rm dev script/deploy.sh + +# test +docker-compose run --rm dev forge test + +# interactive shell +docker-compose run --rm dev bash + +# stop +docker-compose down +``` + +## Troubleshooting + +- **"exec format error"**: Use `docker-compose run` not `exec` +- **"Port 8545 in use"**: `pkill anvil` +- **"Build failed"**: `./build-docker-images.sh clean` + + diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev new file mode 100644 index 0000000..f1ff4d4 --- /dev/null +++ b/docker/Dockerfile.dev @@ -0,0 +1,41 @@ +# Development environment for Timeboost Contracts +# Use Ubuntu as base +FROM ubuntu:22.04 + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + bash \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install Foundry +RUN curl -L https://foundry.paradigm.xyz | bash +RUN /root/.foundry/bin/foundryup + +# Verify installation +RUN /root/.foundry/bin/forge --version + +# Add Foundry to PATH +ENV PATH="/root/.foundry/bin:${PATH}" + +# Set working directory +WORKDIR /app + +# Copy project files +COPY . . + +# Initialize git (needed for forge install) +RUN git init + +# Install dependencies +RUN forge install + +# Make scripts executable +RUN chmod +x script/deploy.sh + +# Set default command +CMD ["bash"] + + diff --git a/docker/build-docker-images.sh b/docker/build-docker-images.sh new file mode 100755 index 0000000..43e08fc --- /dev/null +++ b/docker/build-docker-images.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Build script for Timeboost Contracts Docker images +# Supports multi-architecture builds for CI and local development + +usage() { + >&2 cat <<"EOF" +Usage: + + build-docker-images [--image image] [--platform platform] + build-docker-images clean + +Build timeboost-contracts docker images for multiple architectures. + +- The script supports building all images (the default) or one specific image +- By default builds for both linux/amd64 and linux/arm64 (multi-arch) +- Use --platform to build for specific architecture only + +Examples: + + # build for all supported platforms + build-docker-images + + # build for specific platform (CI) + build-docker-images --platform linux/amd64 + + # build for Apple Silicon only + build-docker-images --platform linux/arm64 + + # clean the build artifacts + build-docker-images clean +EOF +} + +# Default values +docker_build_args="" +image="" +platform="" +clean="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -i|--image) + if [[ -n "${image:-}" ]]; then + >&2 echo "Error: --image option specified multiple times" + >&2 echo "" + usage + exit 1 + fi + image="$2" + shift 2 + ;; + -p|--platform) + if [[ -n "${platform:-}" ]]; then + >&2 echo "Error: --platform option specified multiple times" + >&2 echo "" + usage + exit 1 + fi + platform="$2" + shift 2 + ;; + clean) + clean="true" + shift + ;; + *) + >&2 echo "Error: Unknown option '$1'" + >&2 echo "" + usage + exit 1 + ;; + esac +done + +# Handle clean command +if [[ "${clean:-}" == "true" ]]; then + echo "Cleaning Docker build artifacts..." + docker system prune -f + docker builder prune -f + echo "Clean complete!" + exit 0 +fi + +# Set default platform if not specified +if [[ -z "${platform:-}" ]]; then + platform="linux/amd64,linux/arm64" +fi + +# Set default image if not specified +if [[ -z "${image:-}" ]]; then + image="all" +fi + +echo "Building Docker images..." +echo "Image: ${image}" +echo "Platform: ${platform}" +echo "" + +build_base_image() { + echo "Building base image..." + + if [[ "$platform" == *","* ]]; then + echo "Multi-platform build for: ${platform}" + docker buildx build \ + --platform "${platform}" \ + --file "Dockerfile.dev" \ + --tag "timeboost-contracts:latest" \ + --load \ + .. + else + echo "Single platform build for: ${platform}" + docker buildx build \ + --platform "${platform}" \ + --file "Dockerfile.dev" \ + --tag "timeboost-contracts:latest" \ + --load \ + .. + fi + + echo "Base image built successfully" + echo "" +} + +# Build the single image (all services use the same image) +build_base_image + +echo "Build complete!" +echo "" +echo "To test the images:" +echo " docker-compose up -d anvil" +echo " docker-compose run --rm dev script/deploy.sh" diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..61c1ed8 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,56 @@ +services: + # Our main development environment + dev: + # Build the image directly + build: + context: .. + dockerfile: docker/Dockerfile.dev + image: timeboost-contracts:latest + + volumes: + - ../src:/app/src # Source code + - ../script:/app/script # Deployment scripts + - ../test:/app/test # Test files + - ../lib:/app/lib # Dependencies (forge-std, openzeppelin, etc.) + - ../broadcast:/app/broadcast # Deployment transaction logs + - ../foundry.toml:/app/foundry.toml # Foundry configuration + - ../README.md:/app/README.md # Documentation + + env_file: + - docker.env + + # Set the working directory + working_dir: /app + + # Keep the container running and allow interaction + stdin_open: true + tty: true + + command: bash + + depends_on: + - anvil + + # Local Ethereum blockchain for testing + anvil: + # Use the same base image + image: timeboost-contracts:latest + + # Command to run when container starts + command: anvil --host 0.0.0.0 --port 8545 + + # Expose port 8545 to your computer + ports: + - "8545:8545" + + # Health check for anvil + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8545"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + # Keep container running + stdin_open: true + tty: true diff --git a/docker/docker.env.example b/docker/docker.env.example new file mode 100644 index 0000000..6e17d00 --- /dev/null +++ b/docker/docker.env.example @@ -0,0 +1,6 @@ +# Docker environment variables for development +# Copy this file to docker.env and customize as needed +MANAGER_ADDRESS=0x1234567890123456789012345678901234567890 +RPC_URL=http://anvil:8545 +MNEMONIC=test test test test test test test test test test test junk +ACCOUNT_INDEX=0 diff --git a/script/deploy.sh b/script/deploy.sh index 43699d3..cf9982c 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -1,78 +1,105 @@ #!/bin/bash - -# Simple deployment script for KeyManager contract +# Bash script to assist with deploying the KeyManager contract set -e -# Load environment variables if .env file exists +# Script options +DRY_RUN=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + -h|--help) + echo "Usage: $0 [--dry-run]" + echo " --dry-run test deployment" + echo "" + echo "Set MANAGER_ADDRESS env var" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac +done + +# load .env if [ -f ".env" ]; then - echo "Loading environment from .env file..." - # Use source to properly handle quoted values + echo "Loading .env file..." set -a source .env set +a +else + echo "Using environment variables" fi # Check if required environment variables are set if [ -z "$MANAGER_ADDRESS" ]; then - echo "Error: MANAGER_ADDRESS is required" - echo "Please set MANAGER_ADDRESS in your .env file" - echo "" - echo "Example .env file:" - echo "MANAGER_ADDRESS=0x1234567890123456789012345678901234567890" - echo "RPC_URL=http://localhost:8545" - echo "MNEMONIC=\"your twelve word mnemonic phrase here\"" - echo "ACCOUNT_INDEX=0" + echo "Need MANAGER_ADDRESS env var" exit 1 fi -# Use environment variables with defaults +# defaults RPC_URL=${RPC_URL:-"http://localhost:8545"} ACCOUNT_INDEX=${ACCOUNT_INDEX:-0} MNEMONIC=${MNEMONIC:-"test test test test test test test test test test test junk"} -echo "Deploying KeyManager contract..." -echo "Manager address: $MANAGER_ADDRESS" -echo "RPC URL: $RPC_URL" -echo "Account index: $ACCOUNT_INDEX" +echo "Deploying KeyManager..." +echo "Manager: $MANAGER_ADDRESS" +echo "RPC: $RPC_URL" echo # Set environment variable for the script export MANAGER_ADDRESS=$MANAGER_ADDRESS -# Build forge command -FORGE_CMD="forge script script/DeployKeyManager.s.sol:DeployKeyManager --rpc-url $RPC_URL --broadcast --mnemonics \"$MNEMONIC\" --mnemonic-indexes $ACCOUNT_INDEX" +# Build forge command with dry-run support +FORGE_CMD="forge script script/DeployKeyManager.s.sol:DeployKeyManager" +if [ "$DRY_RUN" = true ]; then + echo "Running simulation (dry run)" + FORGE_CMD="$FORGE_CMD -- --dry-run" +else + echo "Testing blockchain network connection..." + if ! curl -s -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' "$RPC_URL" > /dev/null; then + echo "Can't connect to $RPC_URL" + exit 1 + fi + echo "Network is reachable. Running deployment..." + FORGE_CMD="$FORGE_CMD --rpc-url $RPC_URL --broadcast" +fi -echo "Executing Forge command" -echo +# fix mnemonic quotes +MNEMONIC_TO_USE="$MNEMONIC" +if [[ "$MNEMONIC" == \"*\" ]]; then + MNEMONIC_TO_USE="${MNEMONIC#\"}" + MNEMONIC_TO_USE="${MNEMONIC_TO_USE%\"}" +fi -# Deploy the contract and capture output -DEPLOYMENT_OUTPUT=$(eval $FORGE_CMD 2>&1) +# Execute the forge command +timeout 60 $FORGE_CMD --mnemonics "$MNEMONIC_TO_USE" --mnemonic-indexes "$ACCOUNT_INDEX" DEPLOYMENT_EXIT_CODE=$? -if [ $? -eq 124 ]; then - echo "Forge command timed out - likely invalid flags or connection issue" +if [ $DEPLOYMENT_EXIT_CODE -eq 124 ]; then + echo "Command timed out - check RPC connection" exit 1 fi -echo "Forge command completed with exit code: $DEPLOYMENT_EXIT_CODE" - -# Display the deployment output -echo "$DEPLOYMENT_OUTPUT" - -# Check if deployment was successful if [ $DEPLOYMENT_EXIT_CODE -eq 0 ]; then - echo - echo "Deployment complete!" - echo "Check the output above for contract addresses." - - # Try to extract addresses from the output (optional enhancement) - echo - echo "Extracted contract addresses:" - echo "$DEPLOYMENT_OUTPUT" | grep -E "(KeyManager implementation deployed at:|ERC1967Proxy deployed at:)" || echo "Could not extract addresses from output" + if [ "$DRY_RUN" = true ]; then + echo "Looks good. Run without --dry-run to deploy." + else + echo "Done. Check broadcast/ for details." + fi else - echo - echo "Deployment failed with exit code $DEPLOYMENT_EXIT_CODE" + if [ "$DRY_RUN" = true ]; then + echo "Simulation failed (exit $DEPLOYMENT_EXIT_CODE)" + else + echo "Deployment failed (exit $DEPLOYMENT_EXIT_CODE)" + fi exit $DEPLOYMENT_EXIT_CODE fi diff --git a/test/KeyManager.t.sol b/test/KeyManager.t.sol index 3b1eb7a..89deb8f 100644 --- a/test/KeyManager.t.sol +++ b/test/KeyManager.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test, console} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {KeyManager} from "../src/KeyManager.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";