diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml new file mode 100644 index 00000000..33299093 --- /dev/null +++ b/.github/workflows/deploy-testnet.yml @@ -0,0 +1,158 @@ +name: Deploy All Contracts to Testnet + +on: + push: + branches: + - dev + - staging + pull_request: + branches: + - dev + - staging + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Build contracts + run: forge build + + - name: Run tests (with fork) + if: ${{ secrets.TESTNET_RPC_URL != '' }} + run: forge test --fork-url ${{ secrets.TESTNET_RPC_URL }} -vvvv + + - name: Run tests (without fork) + if: ${{ secrets.TESTNET_RPC_URL == '' }} + run: forge test -vvvv + + - name: Initialize deployment tracking + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + run: | + echo '{"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'", "network": "testnet", "contracts": {}}' > deployment.json + + - name: Deploy ButteredBread + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + env: + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} + TESTNET_PRIVATE_KEY: ${{ secrets.TESTNET_PRIVATE_KEY }} + run: | + forge script script/deploy/DeployButteredBread.s.sol:DeployButteredBread --rpc-url "$TESTNET_RPC_URL" --broadcast --private-key "$TESTNET_PRIVATE_KEY" --json > butteredbread_deploy.json + ADDRESS=$(jq -r '.transactions[0].contractAddress // empty' butteredbread_deploy.json) + if [ -n "$ADDRESS" ] && [ "$ADDRESS" != "null" ]; then + jq --arg addr "$ADDRESS" '.contracts.ButteredBread = $addr' deployment.json > temp.json && mv temp.json deployment.json + echo "ButteredBread deployed to: $ADDRESS" + fi + + - name: Deploy YieldDistributor + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + env: + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} + TESTNET_PRIVATE_KEY: ${{ secrets.TESTNET_PRIVATE_KEY }} + run: | + forge script script/deploy/DeployYieldDistributor.s.sol:DeployYieldDistributor --rpc-url "$TESTNET_RPC_URL" --broadcast --private-key "$TESTNET_PRIVATE_KEY" --json > yielddistributor_deploy.json + ADDRESS=$(jq -r '.transactions[0].contractAddress // empty' yielddistributor_deploy.json) + if [ -n "$ADDRESS" ] && [ "$ADDRESS" != "null" ]; then + jq --arg addr "$ADDRESS" '.contracts.YieldDistributor = $addr' deployment.json > temp.json && mv temp.json deployment.json + echo "YieldDistributor deployed to: $ADDRESS" + fi + + - name: Deploy NFTMultiplier + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + env: + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} + TESTNET_PRIVATE_KEY: ${{ secrets.TESTNET_PRIVATE_KEY }} + run: | + forge script script/deploy/DeployNFTMultiplier.s.sol:DeployNFTMultiplier --rpc-url "$TESTNET_RPC_URL" --broadcast --private-key "$TESTNET_PRIVATE_KEY" --json > nftmultiplier_deploy.json + ADDRESS=$(jq -r '.transactions[0].contractAddress // empty' nftmultiplier_deploy.json) + if [ -n "$ADDRESS" ] && [ "$ADDRESS" != "null" ]; then + jq --arg addr "$ADDRESS" '.contracts.NFTMultiplier = $addr' deployment.json > temp.json && mv temp.json deployment.json + echo "NFTMultiplier deployed to: $ADDRESS" + fi + + - name: Deploy VotingStreakMultiplier + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + env: + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} + TESTNET_PRIVATE_KEY: ${{ secrets.TESTNET_PRIVATE_KEY }} + run: | + forge script script/deploy/DeployVotingStreakMultiplier.s.sol:DeployVotingStreakMultiplier --rpc-url "$TESTNET_RPC_URL" --broadcast --private-key "$TESTNET_PRIVATE_KEY" --json > votingstreakMultiplier_deploy.json + ADDRESS=$(jq -r '.transactions[0].contractAddress // empty' votingstreakMultiplier_deploy.json) + if [ -n "$ADDRESS" ] && [ "$ADDRESS" != "null" ]; then + jq --arg addr "$ADDRESS" '.contracts.VotingStreakMultiplier = $addr' deployment.json > temp.json && mv temp.json deployment.json + echo "VotingStreakMultiplier deployed to: $ADDRESS" + fi + + - name: Verify contracts on block explorer + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.ETHERSCAN_API_KEY != '' }} + env: + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + run: | + echo "Starting contract verification..." + VERIFICATION_FAILED=false + + # Verify ButteredBread + BUTTEREDBREAD_ADDR=$(jq -r '.contracts.ButteredBread // empty' deployment.json) + if [ -n "$BUTTEREDBREAD_ADDR" ] && [ "$BUTTEREDBREAD_ADDR" != "null" ]; then + echo "Verifying ButteredBread at $BUTTEREDBREAD_ADDR..." + forge verify-contract "$BUTTEREDBREAD_ADDR" src/ButteredBread.sol:ButteredBread --rpc-url "$TESTNET_RPC_URL" --etherscan-api-key "$ETHERSCAN_API_KEY" || VERIFICATION_FAILED=true + fi + + # Verify YieldDistributor + YIELDDISTRIBUTOR_ADDR=$(jq -r '.contracts.YieldDistributor // empty' deployment.json) + if [ -n "$YIELDDISTRIBUTOR_ADDR" ] && [ "$YIELDDISTRIBUTOR_ADDR" != "null" ]; then + echo "Verifying YieldDistributor at $YIELDDISTRIBUTOR_ADDR..." + forge verify-contract "$YIELDDISTRIBUTOR_ADDR" src/YieldDistributor.sol:YieldDistributor --rpc-url "$TESTNET_RPC_URL" --etherscan-api-key "$ETHERSCAN_API_KEY" || VERIFICATION_FAILED=true + fi + + # Verify NFTMultiplier + NFTMULTIPLIER_ADDR=$(jq -r '.contracts.NFTMultiplier // empty' deployment.json) + if [ -n "$NFTMULTIPLIER_ADDR" ] && [ "$NFTMULTIPLIER_ADDR" != "null" ]; then + echo "Verifying NFTMultiplier at $NFTMULTIPLIER_ADDR..." + forge verify-contract "$NFTMULTIPLIER_ADDR" src/NFTMultiplier.sol:NFTMultiplier --rpc-url "$TESTNET_RPC_URL" --etherscan-api-key "$ETHERSCAN_API_KEY" || VERIFICATION_FAILED=true + fi + + # Verify VotingStreakMultiplier + VOTINGSTREAKMULTIPLIER_ADDR=$(jq -r '.contracts.VotingStreakMultiplier // empty' deployment.json) + if [ -n "$VOTINGSTREAKMULTIPLIER_ADDR" ] && [ "$VOTINGSTREAKMULTIPLIER_ADDR" != "null" ]; then + echo "Verifying VotingStreakMultiplier at $VOTINGSTREAKMULTIPLIER_ADDR..." + forge verify-contract "$VOTINGSTREAKMULTIPLIER_ADDR" src/VotingStreakMultiplier.sol:VotingStreakMultiplier --rpc-url "$TESTNET_RPC_URL" --etherscan-api-key "$ETHERSCAN_API_KEY" || VERIFICATION_FAILED=true + fi + + # Update deployment.json with verification status + if [ "$VERIFICATION_FAILED" = "true" ]; then + jq '.verification_status = "partial_failure"' deployment.json > temp.json && mv temp.json deployment.json + echo "⚠️ Some contract verifications failed. Check logs above for details." + else + jq '.verification_status = "success"' deployment.json > temp.json && mv temp.json deployment.json + echo "✅ All contracts verified successfully!" + fi + + - name: Upload deployment artifacts + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + uses: actions/upload-artifact@v4 + with: + name: deployment-artifacts + path: | + deployment.json + *_deploy.json + retention-days: 30 + + - name: Display deployment summary + if: ${{ secrets.TESTNET_RPC_URL != '' && secrets.TESTNET_PRIVATE_KEY != '' }} + run: | + echo "📋 Deployment Summary" + echo "====================" + cat deployment.json | jq '.' + echo "" + echo "🔗 Contract Addresses:" + jq -r '.contracts | to_entries[] | "- \(.key): \(.value)"' deployment.json \ No newline at end of file diff --git a/TESTNET_DEPLOYMENT.md b/TESTNET_DEPLOYMENT.md new file mode 100644 index 00000000..235f9950 --- /dev/null +++ b/TESTNET_DEPLOYMENT.md @@ -0,0 +1,115 @@ +# Testnet Deployment Setup + +This repository includes automated testnet deployment via GitHub Actions. The workflow automatically deploys all smart contracts to testnet when code is pushed to the `dev` or `staging` branches. + +## Required GitHub Secrets + +Before the deployment workflow can run successfully, you must configure the following secrets in your GitHub repository settings: + +### 1. TESTNET_RPC_URL +- **Description**: The RPC endpoint URL for your target testnet +- **Example**: `https://rpc.gnosischain.com` (for Gnosis Chain testnet) +- **Format**: Full HTTPS URL including protocol + +### 2. TESTNET_PRIVATE_KEY +- **Description**: Private key for the wallet that will deploy the contracts +- **Format**: 64-character hexadecimal string (with or without `0x` prefix) +- **Security**: This wallet should be used ONLY for testnet deployments and contain only testnet tokens + +### 3. ETHERSCAN_API_KEY +- **Description**: API key for contract verification on block explorer (e.g., Etherscan, Gnosisscan) +- **Format**: Alphanumeric string provided by the block explorer service +- **Purpose**: Enables automatic contract source code verification after deployment +- **Optional**: Verification will be skipped if this secret is not provided + +## Setting Up GitHub Secrets + +1. Navigate to your GitHub repository +2. Go to **Settings** > **Secrets and variables** > **Actions** +3. Click **New repository secret** +4. Add each secret with the exact names listed above + +## Deployed Contracts + +The workflow deploys the following contracts in sequence: + +1. **ButteredBread** - `script/deploy/DeployButteredBread.s.sol` +2. **YieldDistributor** - `script/deploy/DeployYieldDistributor.s.sol` +3. **NFTMultiplier** - `script/deploy/DeployNFTMultiplier.s.sol` +4. **VotingStreakMultiplier** - `script/deploy/DeployVotingStreakMultiplier.s.sol` + +## Workflow Triggers + +The deployment workflow runs automatically on: +- Push to `dev` branch (production testnet deployment) +- Push to `staging` branch (testing/validation deployment) + +## Workflow Steps + +1. **Build**: Compiles all contracts using `forge build` +2. **Test**: Runs tests with fork testing using the testnet RPC +3. **Deploy**: Executes each deployment script in sequence, capturing contract addresses +4. **Verify**: Attempts to verify each deployed contract on the block explorer +5. **Artifact Generation**: Creates `deployment.json` with all contract addresses and verification status +6. **Logging**: Contract addresses and transaction hashes are logged in the workflow output + +## Deployment Artifacts + +After each deployment, the workflow generates several artifacts: + +### deployment.json +Contains the complete deployment information: +```json +{ + "timestamp": "2024-01-01T12:00:00Z", + "network": "testnet", + "contracts": { + "ButteredBread": "0x1234...", + "YieldDistributor": "0x5678...", + "NFTMultiplier": "0x9abc...", + "VotingStreakMultiplier": "0xdef0..." + }, + "verification_status": "success" +} +``` + +### Individual deployment files +- `butteredbread_deploy.json` +- `yielddistributor_deploy.json` +- `nftmultiplier_deploy.json` +- `votingstreakMultiplier_deploy.json` + +These artifacts are automatically uploaded and retained for 30 days. + +## Monitoring Deployments + +- View deployment logs in the **Actions** tab of your GitHub repository +- Each deployment step will show the deployed contract addresses +- Failed deployments will stop the workflow and show error details + +## Security Considerations + +- Never use mainnet private keys for testnet deployments +- Testnet private keys should be separate from any production keys +- Monitor the testnet wallet balance to ensure sufficient gas funds +- Regularly rotate testnet private keys for security + +## Troubleshooting + +### Common Issues + +1. **Insufficient Gas**: Ensure the deployment wallet has enough testnet tokens +2. **RPC Errors**: Verify the TESTNET_RPC_URL is correct and accessible +3. **Private Key Format**: Ensure the private key is properly formatted (64 hex characters) +4. **Contract Dependencies**: Some contracts may depend on others being deployed first +5. **Verification Failures**: + - Check that ETHERSCAN_API_KEY is valid and has sufficient rate limits + - Ensure the block explorer supports the target network + - Verification may fail if contracts are not yet indexed (try again later) +6. **Missing Contract Addresses**: If deployment.json shows null addresses, check individual deployment logs for errors + +### Getting Help + +- Check the GitHub Actions logs for detailed error messages +- Verify all secrets are properly set in repository settings +- Ensure the target testnet is operational and accessible \ No newline at end of file