Guider/Blockchain/BlockchainDevGuide0004
Blockchain#04

BlockchainDevGuide0004

개발 환경과 배포 완전 정복

⟠ BLOCKCHAIN DEV GUIDE

BlockchainDevGuide0004

개발 환경과 배포 완전 정복 A-Z

Hardhat · Foundry · 테스트넷 배포 · IPFS · CI/CD — 현업 블록체인 개발자의 필수 워크플로우

⏱ 예상 학습: 28~35시간 📚 난이도: 중급 🎯 목표: 실제 메인넷 배포 역량

🗺️ BlockchainDevGuide 시리즈 진행 현황

✅ Guide0001
블록체인 개념 이해
✅ Guide0002
JS 프로그래밍 기초
✅ Guide0003
이더리움·스마트 컨트랙트
🔥 Guide0004
개발환경·배포
⏳ Guide0005
NFT·DeFi 심화
⏳ Guide0006
보안·감사
⏳ Guide0007
실전 프로젝트

📋 학습 목차 (10 Chapters)

챕터 주제 핵심 기술
01 개발 환경 완전 세팅 Node.js, nvm, VSCode 확장, Git 설정
02 Hardhat 프로젝트 완전 정복 컴파일·테스트·디버깅·Gas 리포트
03 Foundry — 차세대 개발 프레임워크 forge·cast·anvil·chisel, Fuzz 테스트
04 테스트넷 완전 가이드 Sepolia·Mumbai·Faucet·RPC·환경변수
05 Hardhat Ignition 배포 시스템 모듈·배포 계획·재현 가능 배포
06 Etherscan & 컨트랙트 검증 소스 공개·ABI·읽기·쓰기 인터페이스
07 IPFS & 분산 스토리지 Pinata·NFT.storage·Filecoin·메타데이터
08 업그레이더블 컨트랙트 배포 Proxy 패턴·UUPS·Transparent·Beacon
09 CI/CD 파이프라인 구축 GitHub Actions·자동 테스트·자동 배포
10 현업 면접 Q&A TOP 15 배포·환경·보안 실전 면접 완전 대비

🖥️ Chapter 01. 개발 환경 완전 세팅

블록체인 개발을 시작하기 전 — 완벽한 환경 구성 A-Z

🛠️ 1.1 필수 도구 설치 순서

순서 도구 버전 용도 설치 확인
1 nvm 최신 Node.js 버전 관리자 nvm --version
2 Node.js 18.x LTS JS 런타임 node --version
3 Git 최신 소스 버전 관리 git --version
4 VSCode 최신 코드 에디터 앱 실행 확인
5 Foundry 최신 Solidity 개발 툴킷 forge --version
6 MetaMask 최신 브라우저 지갑 크롬 확장 설치
7 Hardhat 최신 이더리움 개발 환경 npx hardhat --version
# ===== macOS / Linux 설치 스크립트 =====

# 1. nvm 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc  # 또는 source ~/.zshrc

# 2. Node.js 18 LTS 설치
nvm install 18
nvm use 18
nvm alias default 18
node --version   # v18.x.x 확인

# 3. Foundry 설치 (forge, cast, anvil, chisel)
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge --version  # forge 0.2.x 확인

# 4. Hardhat 글로벌 설치 (선택)
npm install -g hardhat

# ===== Windows (PowerShell) =====
# nvm-windows 다운로드: https://github.com/coreybutler/nvm-windows
nvm install 18.17.0
nvm use 18.17.0

🔧 1.2 VSCode 필수 확장 프로그램

🔵 Solidity (Nomic)
신택스 하이라이팅, 컴파일 오류 표시, 호버 문서. 가장 중요한 확장!
🟠 Hardhat VSCode
Hardhat 프로젝트 통합. console.log 지원, 태스크 실행.
🟢 Prettier
Solidity 코드 자동 포맷팅. prettier-plugin-solidity 설치 필요.
🟣 GitLens
Git 히스토리 시각화, blame 정보. 팀 개발 필수.
🔴 DotENV
.env 파일 신택스 하이라이팅. 환경변수 관리 시 가독성 향상.
🔵 Solidity Visual Dev
컨트랙트 구조 시각화, 함수 플로우 다이어그램.
// .vscode/settings.json — 권장 설정
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[solidity]": {
    "editor.defaultFormatter": "NomicFoundation.hardhat-solidity"
  },
  "solidity.compileUsingRemoteVersion": "0.8.20",
  "solidity.formatter": "prettier",
  "files.exclude": {
    "**/node_modules": true,
    "**/.git": true
  }
}

🔐 1.3 환경변수(.env) 보안 관리

개인키와 API 키는 절대 코드에 직접 작성하면 안 됩니다. .env 파일로 분리하고 .gitignore에 추가하세요.

# .env 파일 (절대 깃허브에 올리지 마세요!)
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY
POLYGONSCAN_API_KEY=YOUR_POLYGONSCAN_API_KEY
COINMARKETCAP_API_KEY=YOUR_CMC_API_KEY  # Gas Reporter USD 환산

# .gitignore — 반드시 추가
.env
.env.local
node_modules/
artifacts/
cache/
typechain-types/
coverage/

⛔ 절대 하지 말아야 할 것

개인키를 코드에 직접 작성 → GitHub에 올리면 즉시 해킹
.env를 .gitignore에 추가 안 함 → 수백만 원 피해 사례 다수
테스트넷 키와 메인넷 키 동일 사용 → 실수로 메인넷 배포 위험
API 키 공유 → 요금 폭탄 및 레이트 리밋

⚒️ Chapter 02. Hardhat 프로젝트 완전 정복

컴파일 · 테스트 · 디버깅 · Gas 리포트 — 실무 Hardhat 워크플로우

📁 2.1 Hardhat 프로젝트 구조 완전 이해

# Hardhat 프로젝트 초기화
mkdir blockchain-project && cd blockchain-project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
npm install @openzeppelin/contracts

npx hardhat init
# → "Create a JavaScript project" 선택

# 최종 프로젝트 구조
blockchain-project/
├── contracts/                 # Solidity 소스
│   ├── Token.sol
│   ├── NFT.sol
│   └── interfaces/
│       └── IToken.sol
├── scripts/                   # 배포/상호작용 스크립트
│   ├── deploy.js
│   └── interact.js
├── test/                      # 테스트 파일
│   ├── Token.test.js
│   └── NFT.test.js
├── ignition/                  # Hardhat Ignition 모듈
│   └── modules/
│       └── Token.js
├── hardhat.config.js          # 핵심 설정 파일
├── .env                       # 환경변수 (gitignore)
├── .gitignore
└── package.json

⚙️ 2.2 hardhat.config.js 완전 가이드

require("@nomicfoundation/hardhat-toolbox");
require("@openzeppelin/hardhat-upgrades");  // 업그레이더블
require("hardhat-gas-reporter");             // Gas 리포트
require("solidity-coverage");               // 코드 커버리지
require("dotenv").config();

const { PRIVATE_KEY, SEPOLIA_RPC_URL, MAINNET_RPC_URL,
        ETHERSCAN_API_KEY, COINMARKETCAP_API_KEY } = process.env;

module.exports = {
  solidity: {
    compilers: [
      {
        version: "0.8.20",
        settings: {
          optimizer: { enabled: true, runs: 200 },
          viaIR: true  // 대형 컨트랙트 컴파일 최적화
        }
      }
    ]
  },
  networks: {
    hardhat: {
      chainId: 31337,
      forking: {                          // 메인넷 포크 (선택)
        url: MAINNET_RPC_URL,
        blockNumber: 19000000
      }
    },
    localhost: { url: "http://127.0.0.1:8545" },
    sepolia: {
      url: SEPOLIA_RPC_URL || "",
      accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
      chainId: 11155111,
      gasPrice: "auto"
    },
    polygon: {
      url: "https://polygon-rpc.com",
      accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
      chainId: 137
    },
    arbitrum: {
      url: "https://arb1.arbitrum.io/rpc",
      accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
      chainId: 42161
    }
  },
  etherscan: {
    apiKey: {
      mainnet:  ETHERSCAN_API_KEY,
      sepolia:  ETHERSCAN_API_KEY,
      polygon:  process.env.POLYGONSCAN_API_KEY,
      arbitrumOne: process.env.ARBISCAN_API_KEY
    }
  },
  gasReporter: {
    enabled:         process.env.REPORT_GAS === "true",
    currency:        "USD",
    coinmarketcap:   COINMARKETCAP_API_KEY,
    outputFile:      "gas-report.txt",
    noColors:        true
  },
  paths: {
    sources:   "./contracts",
    tests:     "./test",
    cache:     "./cache",
    artifacts: "./artifacts"
  },
  mocha: { timeout: 60000 }  // 느린 테스트 타임아웃
};

🧪 2.3 Hardhat 테스트 심화 — Fixtures & Snapshot

// test/Token.test.js — 실전 테스트 패턴
const { expect }              = require("chai");
const { ethers, network }     = require("hardhat");
const { loadFixture, time }   = require("@nomicfoundation/hardhat-network-helpers");

// Fixture: 재사용 가능한 초기 상태 스냅샷
async function deployTokenFixture() {
  const [owner, alice, bob, carol] = await ethers.getSigners();
  const Token = await ethers.getContractFactory("BlockToken");
  const token = await Token.deploy(owner.address);
  await token.waitForDeployment();

  // 초기 세팅
  await token.transfer(alice.address, ethers.parseEther("1000"));
  await token.transfer(bob.address,   ethers.parseEther("500"));

  return { token, owner, alice, bob, carol };
}

describe("BlockToken — 완전한 테스트 스위트", function() {

  describe("Deployment", function() {
    it("초기 공급량 확인", async function() {
      const { token, owner } = await loadFixture(deployTokenFixture);
      const total = await token.totalSupply();
      expect(total).to.equal(ethers.parseEther("10000000"));
    });
  });

  describe("Transfers", function() {
    it("정상 전송", async function() {
      const { token, alice, bob } = await loadFixture(deployTokenFixture);
      await token.connect(alice).transfer(bob.address, ethers.parseEther("100"));
      expect(await token.balanceOf(bob.address)).to.equal(ethers.parseEther("600"));
    });

    it("잔액 부족 revert", async function() {
      const { token, carol } = await loadFixture(deployTokenFixture);
      await expect(
        token.connect(carol).transfer(carol.address, 1)
      ).to.be.reverted;
    });
  });

  describe("Time Travel — 시간 조작 테스트", function() {
    it("1년 후 스테이킹 보상 확인", async function() {
      const { token, alice } = await loadFixture(deployTokenFixture);
      // 1년 뒤로 시간 이동
      await time.increase(365 * 24 * 60 * 60);
      // 보상 계산 로직 테스트...
    });
  });

  describe("Impersonation — 임의 주소 사칭 테스트", function() {
    it("고래 지갑 행동 시뮬레이션", async function() {
      const whaleAddress = "0x28C6c06298d514Db089934071355E5743bf21d60";
      await network.provider.request({
        method: "hardhat_impersonateAccount",
        params: [whaleAddress]
      });
      const whale = await ethers.getSigner(whaleAddress);
      // whale 계정으로 트랜잭션 실행...
    });
  });
});

📊 2.4 Gas 리포트 분석

Gas Reporter 출력 예시

REPORT_GAS=true npx hardhat test

·-------------------------------------|----------------------------|-------------|-----------------------------·
|         Solc version: 0.8.20        ·  Optimizer enabled: true  ·  Runs: 200  ·  Block limit: 30000000 gas  │
······································|····························|·············|······························
|  Methods                                                                                                     │
·············|·······················|············|············|·············|·············|···················
|  Contract  ·  Method               ·  Min       ·  Max       ·  Avg        ·  # calls    ·  usd (avg)       │
·············|·······················|············|············|·············|·············|···················
|  BlockToken·  approve              ·     46,213 ·     46,225 ·     46,221  ·          8  ·          $0.14   │
|  BlockToken·  transfer             ·     51,452 ·     68,652 ·     60,052  ·         15  ·          $0.18   │
|  BlockToken·  mint                 ·     68,924 ·     68,924 ·     68,924  ·          3  ·          $0.21   │
·············|·······················|············|············|·············|·············|···················
|  Deployments                        ·                                       ·  % of limit ·                  │
······································|············|············|·············|·············|···················
|  BlockToken                         ·          - ·          - ·    1,234,567·      4.1 %  ·          $3.71   │
·-------------------------------------|------------|------------|-------------|-------------|-------------------·

🔨 Chapter 03. Foundry — 차세대 개발 프레임워크

Rust로 만든 초고속 도구 — forge · cast · anvil · chisel

📚 3.1 Foundry vs Hardhat 비교

항목 Hardhat Foundry
언어 JavaScript Solidity (테스트도!)
속도 보통 ⚡ 10~100배 빠름
Fuzz 테스트 별도 도구 필요 ✅ 내장
Invariant 테스트 없음 ✅ 내장
생태계 더 넓음 (플러그인) 성장 중
추천 사용 DApp 개발, 스크립트 보안 감사, 프로토콜
# ===== Foundry 프로젝트 세팅 =====
forge init my-foundry-project
cd my-foundry-project

# OpenZeppelin 라이브러리 추가 (forge install)
forge install OpenZeppelin/openzeppelin-contracts
forge install foundry-rs/forge-std

# foundry.toml 설정
[profile.default]
src     = "src"
out     = "out"
libs    = ["lib"]
solc    = "0.8.20"
optimizer       = true
optimizer-runs  = 200
fuzz.runs       = 1000     # Fuzz 테스트 횟수
invariant.runs  = 256

# 빌드 / 테스트 / 가스 스냅샷
forge build
forge test -vvvv           # -v 많을수록 상세 로그
forge snapshot             # gas snapshot 저장
forge coverage             # 커버리지 리포트

💻 3.2 Foundry 테스트 작성 (Solidity로!)

// test/Token.t.sol — Foundry 테스트는 Solidity로 작성!
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/BlockToken.sol";

contract BlockTokenTest is Test {
    BlockToken public token;
    address public owner   = address(1);
    address public alice   = address(2);
    address public bob     = address(3);

    function setUp() public {
        vm.startPrank(owner);         // owner로 tx 발생
        token = new BlockToken(owner);
        token.transfer(alice, 1000 ether);
        vm.stopPrank();
    }

    // === 기본 테스트 ===
    function test_Transfer() public {
        vm.prank(alice);
        token.transfer(bob, 100 ether);
        assertEq(token.balanceOf(bob), 100 ether);
    }

    function test_RevertWhen_InsufficientBalance() public {
        vm.prank(bob);
        vm.expectRevert();            // revert 예상
        token.transfer(alice, 1);
    }

    // === Fuzz 테스트 — 자동으로 랜덤 입력값 생성! ===
    function testFuzz_Transfer(uint256 amount) public {
        amount = bound(amount, 1, 1000 ether);  // 범위 제한
        vm.prank(alice);
        token.transfer(bob, amount);
        assertEq(token.balanceOf(bob), amount);
        assertEq(token.balanceOf(alice), 1000 ether - amount);
    }

    // === Invariant 테스트 — 불변식 검증 ===
    function invariant_TotalSupplyConstant() public view {
        // 어떤 트랜잭션 후에도 totalSupply는 변하지 않아야 함
        assertEq(token.totalSupply(), 10_000_000 ether);
    }

    // === 시간 조작 ===
    function test_TimeWarp() public {
        uint256 future = block.timestamp + 365 days;
        vm.warp(future);              // 블록 타임스탬프 변경
        vm.roll(block.number + 1000); // 블록 번호 변경
    }

    // === ETH 잔액 설정 ===
    function test_EthBalance() public {
        vm.deal(alice, 10 ether);      // alice에게 ETH 10 부여
        assertEq(alice.balance, 10 ether);
    }
}

🔧 3.3 cast & anvil — 터미널 블록체인 도구

# ===== anvil: 로컬 EVM 노드 (Hardhat Node 대체) =====
anvil                        # 기본 실행 (20개 테스트 계정)
anvil --fork-url $MAINNET_RPC_URL --fork-block-number 19000000
anvil --chain-id 1337 --block-time 2  # 2초마다 블록 생성

# ===== cast: 터미널에서 RPC 호출 =====

# 잔액 조회
cast balance 0x742d35Cc6634C0532925a3b8D4C9b5c8b4a6F9F4 --rpc-url $SEPOLIA_RPC_URL

# 컨트랙트 함수 호출 (읽기)
cast call 0xContractAddress "balanceOf(address)(uint256)" 0xAddress --rpc-url $SEPOLIA_RPC_URL

# 컨트랙트 함수 호출 (쓰기 — 트랜잭션)
cast send 0xContractAddress "transfer(address,uint256)" 0xToAddress 1000000000000000000   --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL

# ABI 인코딩/디코딩
cast abi-encode "transfer(address,uint256)" 0xAddress 1000
cast decode-calldata "transfer(address,uint256)" 0xa9059cbb...

# 트랜잭션 정보 조회
cast tx 0xTxHash --rpc-url $MAINNET_RPC_URL

# ETH 단위 변환
cast to-unit 1000000000000000000 ether   # → 1.000000000000000000
cast from-wei 1000000000 gwei            # → 1.000000000

# 블록 정보
cast block latest --rpc-url $MAINNET_RPC_URL

# ===== chisel: Solidity REPL (실시간 Solidity 실행) =====
chisel
# > uint256 x = 100 * 1e18;
# > keccak256(abi.encodePacked("hello"))

🌐 Chapter 04. 테스트넷 완전 가이드

Sepolia · Mumbai · Faucet · RPC 엔드포인트 · 환경 설정

📡 4.1 주요 테스트넷 현황

네트워크 Chain ID 통화 Faucet 추천도
Sepolia 11155111 ETH sepoliafaucet.com ⭐⭐⭐ 이더리움 메인
Mumbai 80001 MATIC faucet.polygon.technology ⭐⭐ Polygon 개발용
Arbitrum Sepolia 421614 ETH faucet.quicknode.com ⭐⭐ L2 개발용
Base Sepolia 84532 ETH base-faucet.vercel.app ⭐⭐ Base L2
Hardhat Local 31337 ETH 내장 계정 자동 지급 ⭐⭐⭐ 로컬 개발

🔌 4.2 RPC 엔드포인트 제공업체 비교

🔵 Alchemy
• 무료: 300M CU/월
• Enhanced API (NFT, 트랜잭션 히스토리)
• WebSocket 지원
• 최고의 신뢰성
✅ 추천: 메인 개발
🟠 Infura
• 무료: 100,000 req/일
• 가장 오래된 서비스
• IPFS 게이트웨이
• MetaMask 기반
✅ 추천: 안정성 중시
🟢 QuickNode
• 무료: 15M credits/월
• 멀티체인 지원
• 가장 빠른 응답
• 스트림 기능
✅ 추천: 성능 중시
🟣 Ankr
• 무료 공개 엔드포인트
• 50+ 체인 지원
• 레이트 리밋 있음
• 테스트용 적합
→ 테스트/개발 시작용
# ===== MetaMask에 테스트넷 추가 방법 =====

# Sepolia 설정 (가장 많이 사용)
Network Name: Sepolia Testnet
RPC URL: https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
Chain ID: 11155111
Symbol: ETH
Block Explorer: https://sepolia.etherscan.io

# Hardhat 로컬 네트워크 설정 (개발용)
Network Name: Hardhat Local
RPC URL: http://127.0.0.1:8545
Chain ID: 31337
Symbol: ETH
Block Explorer: (없음)

# ===== Faucet으로 테스트 ETH 받기 =====
# 1. Alchemy Sepolia Faucet: https://sepoliafaucet.com
# 2. Chainlink Faucet: https://faucets.chain.link/sepolia
# 3. QuickNode Faucet: https://faucet.quicknode.com/ethereum/sepolia
# → 하루 0.1~0.5 SEP ETH 지급 (소셜 인증 필요할 수 있음)

🚀 Chapter 05. Hardhat Ignition 배포 시스템

재현 가능한 선언적 배포 — 현업 표준 배포 방식

📚 5.1 Ignition이란?

Hardhat Ignition은 기존 scripts/deploy.js 방식을 대체하는 선언적 배포 시스템입니다. 배포 상태를 추적하고, 실패 시 중단 지점부터 재시도하며, 완전히 재현 가능합니다.

// ignition/modules/Token.js — Ignition 배포 모듈
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

const TokenModule = buildModule("TokenModule", (m) => {
  // 배포 파라미터 (--parameters로 주입 가능)
  const initialOwner = m.getParameter("initialOwner");

  // 컨트랙트 배포
  const token = m.contract("BlockToken", [initialOwner]);

  // 배포 후 초기화 (선택)
  m.call(token, "mint", [initialOwner, ethers.parseEther("1000000")]);

  return { token };
});

module.exports = TokenModule;
# ===== Ignition 배포 명령어 =====

# 로컬 배포
npx hardhat ignition deploy ignition/modules/Token.js --network localhost

# Sepolia 배포 (파라미터 지정)
npx hardhat ignition deploy ignition/modules/Token.js   --network sepolia   --parameters '{"TokenModule":{"initialOwner":"0xYourAddress"}}'

# 배포 상태 확인
npx hardhat ignition status ignition/deployments/chain-11155111

# 검증 포함 배포
npx hardhat ignition deploy ignition/modules/Token.js   --network sepolia   --verify

# 배포 결과 (자동 생성)
# ignition/deployments/chain-11155111/deployed_addresses.json
{
  "TokenModule#BlockToken": "0xDeployedContractAddress..."
}

🔗 5.2 멀티 컨트랙트 의존성 배포

// ignition/modules/DeFiProtocol.js
const DeFiModule = buildModule("DeFiModule", (m) => {
  const owner = m.getParameter("owner");

  // 1. TokenA 배포
  const tokenA = m.contract("BlockToken", [owner], { id: "TokenA" });

  // 2. TokenB 배포
  const tokenB = m.contract("BlockToken", [owner], { id: "TokenB" });

  // 3. AMM 풀 배포 (TokenA, TokenB 주소 주입)
  const amm = m.contract("SimpleAMM", [tokenA, tokenB]);

  // 4. 초기 유동성 공급 (AMM 배포 후 실행)
  const approveA = m.call(tokenA, "approve", [amm, ethers.parseEther("100000")], {
    from: owner,
    after: [amm]
  });
  m.call(amm, "addLiquidity", [
    ethers.parseEther("10000"),
    ethers.parseEther("10000")
  ], { after: [approveA] });

  return { tokenA, tokenB, amm };
});

🔍 Chapter 06. Etherscan & 컨트랙트 검증

소스코드 공개 · ABI 인터페이스 · 읽기/쓰기 UI

📚 6.1 Etherscan 검증이 중요한 이유

배포된 컨트랙트의 소스코드를 Etherscan에 공개하면: ①사용자 신뢰도 향상 ②Read/Write 탭으로 직접 상호작용 ③버그 신고 및 감사 편의성 ④보안 커뮤니티 검토. 미검증 컨트랙트는 "불투명한 블랙박스"로 인식됩니다.

# ===== 방법 1: Hardhat verify 플러그인 (권장) =====

# 배포 후 자동 검증
npx hardhat verify --network sepolia 0xDeployedAddress "0xConstructorArg1" "arg2"

# 생성자 인자가 복잡한 경우 (배열, 구조체)
# scripts/verify-args.js 파일 생성
module.exports = [
  "BlockToken",
  "BTKN",
  ethers.parseEther("10000000")
];

npx hardhat verify --network sepolia 0xAddress --constructor-args scripts/verify-args.js

# ===== 방법 2: Etherscan 웹사이트 직접 업로드 =====
# 1. etherscan.io/address/0xAddress → Contract 탭
# 2. "Verify and Publish" 클릭
# 3. Compiler Type: Solidity (Single File / Multi-Part)
# 4. Compiler Version: 0.8.20
# 5. Optimization: Yes, 200 runs
# 6. 소스코드 붙여넣기 → Verify

# ===== 방법 3: Foundry verify =====
forge verify-contract 0xDeployedAddress src/BlockToken.sol:BlockToken   --chain sepolia   --etherscan-api-key $ETHERSCAN_API_KEY   --constructor-args $(cast abi-encode "constructor(address)" 0xOwner)

🔍 6.2 Etherscan에서 컨트랙트 직접 사용하기

기능 MetaMask 필요
Code 검증된 소스코드 · ABI · 바이트코드 확인
Read Contract view/pure 함수 무료 호출 (잔액, 이름 등)
Write Contract 상태 변경 함수 트랜잭션 실행 ✅ 필요
Events 발생한 이벤트 로그 확인
Internal Txns 내부 ETH 전송 추적

📦 Chapter 07. IPFS & 분산 스토리지

NFT 메타데이터 · 이미지 · 파일을 탈중앙화 저장소에

📚 7.1 IPFS란? 왜 필요한가?

IPFS(InterPlanetary File System)는 콘텐츠 기반 주소 지정을 사용하는 분산 파일 시스템입니다. NFT 이미지를 중앙 서버에 저장하면 서버가 다운될 때 "이미지를 찾을 수 없음"이 되지만, IPFS는 파일 내용의 해시가 주소이므로 변조 불가능합니다.

서비스 무료 한도 특징 추천
Pinata 1GB / 100파일 가장 유명, SDK 완비 ⭐⭐⭐
NFT.storage 무제한 (NFT 한정) Filecoin 백업, NFT 전용 ⭐⭐⭐
web3.storage 5GB Protocol Labs 운영 ⭐⭐
Infura IPFS 5GB Infura 생태계 통합 ⭐⭐

💻 7.2 Pinata + NFT 메타데이터 완전 구현

// scripts/upload-to-ipfs.js
const { PinataSDK } = require("pinata");
const fs             = require("fs");
const path           = require("path");
require("dotenv").config();

const pinata = new PinataSDK({
  pinataJwt:     process.env.PINATA_JWT,
  pinataGateway: process.env.PINATA_GATEWAY_URL
});

async function uploadNFTCollection(imageDir, count) {
  const imageHashes   = [];
  const metadataHashes = [];

  // 1단계: 이미지 일괄 업로드
  console.log("📸 이미지 업로드 중...");
  for (let i = 1; i <= count; i++) {
    const imgPath = path.join(imageDir, `${i}.png`);
    const file = new File(
      [fs.readFileSync(imgPath)],
      `${i}.png`,
      { type: "image/png" }
    );
    const result = await pinata.upload.file(file);
    imageHashes.push(result.IpfsHash);
    console.log(`  ✅ ${i}.png → ipfs://${result.IpfsHash}`);
  }

  // 2단계: 메타데이터 JSON 생성 및 업로드
  console.log("📝 메타데이터 업로드 중...");
  for (let i = 1; i <= count; i++) {
    const metadata = {
      name:         `BlockchainNFT #${i}`,
      description:  "A unique blockchain developer collectible",
      image:        `ipfs://${imageHashes[i-1]}`,
      external_url: `https://myproject.io/nft/${i}`,
      attributes: [
        { trait_type: "Rarity",   value: getRarity(i)    },
        { trait_type: "Level",     value: getLevel(i)     },
        { trait_type: "Token ID",  value: i               }
      ]
    };
    const result = await pinata.upload.json(metadata);
    metadataHashes.push(result.IpfsHash);
    console.log(`  ✅ Metadata #${i} → ipfs://${result.IpfsHash}`);
  }

  // 3단계: 결과 저장
  fs.writeFileSync("./ipfs-hashes.json", JSON.stringify({ imageHashes, metadataHashes }, null, 2));
  console.log("✨ 업로드 완료! ipfs-hashes.json 저장됨");
  return metadataHashes;
}

function getRarity(id) {
  if (id <= 10)  return "Legendary";
  if (id <= 50)  return "Epic";
  if (id <= 200) return "Rare";
  return "Common";
}
function getLevel(id) { return Math.floor(Math.random() * 100) + 1; }

uploadNFTCollection("./images", 1000).catch(console.error);

⬆️ Chapter 08. 업그레이더블 컨트랙트 배포

배포 후 수정 불가? — Proxy 패턴으로 해결

📚 8.1 Proxy 패턴 완전 이해

Transparent Proxy (EIP-1967)
Admin과 사용자 역할 구분. Admin만 업그레이드 가능. ProxyAdmin 컨트랙트 별도 배포.
OpenZeppelin 기본 제공
UUPS (EIP-1822)
업그레이드 로직이 구현 컨트랙트에 있음. Gas 효율적. 더 단순한 구조. 현재 권장 방식.
✅ 현업 권장
Beacon Proxy
여러 프록시가 하나의 Beacon을 참조. 한 번에 모든 인스턴스 업그레이드 가능.
다수 인스턴스 관리 시
// npm install @openzeppelin/hardhat-upgrades @openzeppelin/contracts-upgradeable

// contracts/TokenV1.sol — UUPS 업그레이더블 토큰
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract TokenV1 is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
    // ⚠️ constructor 대신 initialize 사용!
    function initialize(address initialOwner) public initializer {
        __ERC20_init("BlockToken", "BTKN");
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();
        _mint(initialOwner, 10_000_000 ether);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}
}

// contracts/TokenV2.sol — 새 기능 추가 버전
contract TokenV2 is TokenV1 {
    uint256 public version;

    function initializeV2() public reinitializer(2) {
        version = 2;
    }

    // 새로운 기능 추가
    function newFeature() external pure returns (string memory) {
        return "V2 Feature!";
    }
}
// scripts/deploy-upgradeable.js
const { ethers, upgrades } = require("hardhat");

async function main() {
  const [deployer] = await ethers.getSigners();

  // V1 배포 (UUPS 프록시와 함께)
  const TokenV1 = await ethers.getContractFactory("TokenV1");
  const proxy = await upgrades.deployProxy(TokenV1, [deployer.address], {
    initializer: "initialize",
    kind: "uups"
  });
  await proxy.waitForDeployment();
  console.log("Proxy 주소 (변하지 않음):", await proxy.getAddress());

  // V2로 업그레이드
  const TokenV2 = await ethers.getContractFactory("TokenV2");
  const upgraded = await upgrades.upgradeProxy(proxy, TokenV2);
  await upgraded.initializeV2();
  console.log("✅ V2로 업그레이드 완료! 주소 동일", await upgraded.getAddress());
}

main().catch(console.error);

🔄 Chapter 09. CI/CD 파이프라인 구축

GitHub Actions로 자동 테스트 · 린팅 · 배포 파이프라인

📚 9.1 블록체인 CI/CD 파이프라인 구조

스마트 컨트랙트는 배포 후 수정이 어렵습니다. GitHub Actions로 PR마다 자동 테스트 → 보안 스캔 → 테스트넷 배포 파이프라인을 구축하면 배포 전 버그를 99% 차단합니다.

단계 도구 내용 트리거
1. Lint solhint, prettier 코드 스타일 검사 모든 push/PR
2. Compile hardhat compile 컴파일 오류 확인 모든 push/PR
3. Test hardhat test / forge 유닛·통합·Fuzz 테스트 모든 push/PR
4. Coverage solidity-coverage 커버리지 80%+ 강제 모든 push/PR
5. Security Slither, Mythril 취약점 정적 분석 main 브랜치 PR
6. Deploy hardhat ignition 테스트넷 자동 배포 develop 머지

🔒 9.2 GitHub Secrets 설정 방법

Settings → Secrets and variables → Actions → New repository secret 에서 아래 값을 등록하세요:

Secret 이름 용도
PRIVATE_KEY 0x로 시작하는 개인키 배포 서명
SEPOLIA_RPC_URL Alchemy/Infura URL 테스트넷 연결
ETHERSCAN_API_KEY Etherscan API Key 소스코드 검증
COINMARKETCAP_API_KEY CMC API Key Gas USD 환산
// .solhint.json — 린팅 규칙
{
  "extends": "solhint:recommended",
  "rules": {
    "compiler-version": ["error", "^0.8.20"],
    "reentrancy":       "error",
    "avoid-call-value": "error",
    "max-line-length":  ["warn", 120]
  }
}

// package.json scripts
{
  "scripts": {
    "test":     "hardhat test",
    "coverage": "hardhat coverage",
    "lint":     "solhint contracts/**/*.sol",
    "gas":      "REPORT_GAS=true hardhat test",
    "deploy:sepolia": "hardhat ignition deploy ignition/modules/Token.js --network sepolia --verify"
  }
}

🎤 Chapter 10. 현업 면접 Q&A TOP 15

개발환경·배포·인프라 면접 실전 완전 대비

Q1 Hardhat과 Foundry의 차이점을 설명하고 언제 어떤 것을 사용하나요?

A: Hardhat은 JavaScript 기반으로 생태계가 넓고 DApp 개발·스크립트 작성에 적합합니다. Foundry는 Rust 기반으로 10~100배 빠르고, 테스트를 Solidity로 작성하며 Fuzz/Invariant 테스트가 내장되어 보안 감사와 프로토콜 개발에 적합합니다. 실무에서는 둘을 함께 사용하는 경우도 많습니다.

Q2 Hardhat Ignition과 기존 scripts/deploy.js 방식의 차이는?

A: 기존 deploy.js는 명령형(imperative) — 매번 재실행하면 컨트랙트가 중복 배포될 수 있습니다. Ignition은 선언형(declarative) — 배포 상태를 JSON으로 추적하고, 중간에 실패해도 완료된 부분은 건너뛰고 재시도합니다. 또한 배포 결과가 자동으로 저장되어 재현 가능하며 팀 공유도 쉽습니다.

Q3 스마트 컨트랙트 배포 시 개인키를 안전하게 관리하는 방법은?

A: ①.env 파일 + dotenv 라이브러리 + .gitignore 등록 ②GitHub Actions에서 Secrets 사용 (절대 코드에 하드코딩 금지) ③테스트넷 배포용 별도 지갑 사용 ④메인넷은 Ledger/Trezor 하드웨어 지갑 또는 Safe(멀티시그)로 배포 ⑤AWS Secrets Manager, HashiCorp Vault 같은 비밀 관리 서비스 활용.

Q4 Etherscan 소스코드 검증이 실패하는 흔한 원인은?

A: ①컴파일러 버전 불일치 (hardhat.config.js와 Etherscan 설정이 다른 경우) ②Optimizer 설정 불일치 (runs 수가 다를 때) ③라이브러리 링크 누락 (library 주소 불일치) ④생성자 인자 ABI 인코딩 오류 ⑤멀티파트 컨트랙트에서 import 경로 불일치. --verify 플래그 사용 시 자동으로 올바른 설정이 전달됩니다.

Q5 IPFS의 콘텐츠 주소 지정 방식이란? HTTP URL과 어떻게 다른가요?

A: HTTP URL은 위치 기반(서버:포트/경로). IPFS는 콘텐츠 기반 — 파일 내용의 SHA-256 해시가 주소(CID). 동일한 내용 → 동일한 CID → 변조 불가. 단점: 파일을 아무도 핀(pin)하지 않으면 가비지 컬렉션될 수 있어 Pinata 같은 핀닝 서비스가 필요합니다. NFT tokenURI에 ipfs://CID 형식을 사용합니다.

Q6 UUPS와 Transparent Proxy 패턴의 차이점과 선택 기준은?

A: Transparent Proxy는 ProxyAdmin 컨트랙트가 별도로 필요하고 모든 호출에서 admin 체크 (Gas 비쌈). UUPS는 업그레이드 로직이 구현 컨트랙트에 있어 Gas 효율적이고 구조가 단순합니다. 단, UUPS는 업그레이드 함수를 구현 컨트랙트에서 제거하면 영구 동결 위험. 현재 OpenZeppelin은 UUPS를 권장합니다.

Q7 Fuzz 테스트란? 일반 단위 테스트와 어떻게 다른가요?

A: 단위 테스트는 미리 정한 입력값으로만 검증. Fuzz 테스트는 무작위 입력값 수천 개를 자동 생성하여 예상치 못한 엣지 케이스를 발견합니다. Foundry는 testFuzz_ 접두사 함수에 자동으로 무작위 파라미터를 주입합니다. bound()로 유효 범위를 제한하는 것이 핵심. 오버플로우, 언더플로우, 특수값(0, max) 버그를 효과적으로 탐지합니다.

Q8 메인넷 포킹(Mainnet Forking)이란? 어떤 상황에서 사용하나요?

A: 메인넷의 특정 블록 상태를 로컬에 복제하여 테스트하는 방법. hardhat.config.js의 forking 설정 또는 anvil --fork-url로 사용. 실제 Uniswap, Aave 등 프로토콜과 상호작용 테스트가 가능합니다. ①기존 DeFi 프로토콜과 통합 테스트 ②거래소 유동성이 있는 환경에서 스왑 테스트 ③실제 고래 주소를 impersonate하여 테스트할 때 필수입니다.

Q9 스마트 컨트랙트 코드 커버리지 100%가 의미하는 바와 한계점은?

A: 커버리지 100%는 모든 코드 라인이 테스트에서 실행되었다는 의미이지만, "모든 시나리오가 검증되었다"는 의미는 아닙니다. 한계: ①특수 입력값(0, max, type(uint256).max)이 빠질 수 있음 ②경쟁 조건(reentrancy) 미탐지 ③Gas 경계값 미테스트. 커버리지 + Fuzz 테스트 + Invariant 테스트 + 정적 분석을 조합해야 합니다.

Q10 다중 체인(Multi-chain) 배포 시 고려해야 할 사항은?

A: ①chain ID 별 config 분리 ②각 체인별 block.timestamp, block.number 동작 차이 ③L2 체인은 Gas 모델이 다름 (Arbitrum: L1 data fee + L2 execution fee) ④주소 재사용 (CREATE2로 동일 주소 배포 가능) ⑤브릿지 컨트랙트와의 연동 ⑥체인별 Etherscan 등가 서비스 (Polygonscan, Arbiscan) 검증. ignition/deployments에 체인별 폴더가 자동 생성됩니다.

Q11 Slither로 발견할 수 있는 취약점 종류를 설명하세요.

A: Slither는 Trail of Bits가 만든 Python 정적 분석 도구입니다. ①Reentrancy (재진입 공격) ②Uninitialized variables ③Incorrect ERC20/721 구현 ④Arbitrary send (임의 ETH 전송) ⑤Weak PRNG (예측 가능한 랜덤) ⑥Missing zero-address check ⑦tx.origin 사용 ⑧Divide before multiply (정밀도 손실). 슬리더는 80여 개 검사기를 가지고 있습니다.

Q12 배포 후 컨트랙트에서 버그를 발견했습니다. 어떻게 대응하나요?

A: ①즉시 Pause (일시정지 기능이 있다면) ②버그의 심각도 파악 (자금 탈취 가능 여부) ③Upgradeable이면 새 로직 컨트랙트 준비 ④커뮤니티/사용자에게 투명하게 공지 ⑤Safe(멀티시그) 티임사이너 모두에게 업그레이드 승인 요청 ⑥업그레이드 후 검증. 업그레이드 불가능한 컨트랙트라면 새 버전 배포 + 마이그레이션 가이드 제공이 유일한 방법입니다.

Q13 업그레이더블 컨트랙트 개발 시 스토리지 충돌(Storage Collision)을 방지하는 방법은?

A: V1에서 V2로 업그레이드 시 새 변수를 기존 변수 중간에 삽입하면 스토리지 슬롯 재배열로 데이터 손상. 방어법: ①기존 변수 순서를 절대 바꾸지 않음 ②새 변수는 반드시 끝에 추가 ③__gap[50] 예약 슬롯 패턴 (상속 체인 중간 컨트랙트에 예비 슬롯 확보) ④@openzeppelin/hardhat-upgrades의 validateUpgrade()로 업그레이드 전 자동 검증.

Q14 nvm을 사용하는 이유와 Node.js 버전 관리의 중요성은?

A: 프로젝트마다 호환되는 Node.js 버전이 다를 수 있습니다. nvm을 쓰면 디렉토리별 .nvmrc 파일로 버전 자동 전환 가능. 예: Hardhat 최신 버전은 Node 18+ 요구, 오래된 프로젝트는 Node 16 요구. CI/CD에서도 actions/setup-node의 node-version을 .nvmrc와 동기화하면 "내 컴퓨터에서는 되는데..." 문제를 방지합니다.

Q15 Gas 최적화를 위한 배포 설정은 어떻게 하나요?

A: ①optimizer: true, runs: 200 (200회 호출 기준 최적화. 라이브러리처럼 많이 호출되면 runs: 1000 권장) ②viaIR: true (대형 컨트랙트 IR 기반 최적화, 컴파일 느림) ③REPORT_GAS=true npx hardhat test로 함수별 Gas 측정 ④forge snapshot으로 PR 전후 Gas 비교 자동화 ⑤storage 접근 최소화, mapping 활용, calldata 파라미터 사용이 핵심.

✅ 학습 체크리스트

🛠️ 개발 환경

☑ nvm + Node.js 18 LTS 설치

☑ Foundry (forge/cast/anvil) 설치

☑ VSCode Solidity 확장 설정

☑ .env + .gitignore 보안 설정

⚒️ 빌드 & 테스트

☑ Hardhat Fixture + 시간 조작 테스트

☑ Foundry Fuzz + Invariant 테스트

☑ Gas 리포트 분석 및 최적화

☑ cast/anvil 터미널 도구 활용

🚀 배포 & 인프라

☑ Ignition 모듈로 재현 가능한 배포

☑ Sepolia 테스트넷 배포 + 검증

☑ IPFS Pinata NFT 메타데이터 업로드

☑ UUPS 업그레이더블 컨트랙트 배포

🔄 CI/CD

☑ GitHub Actions 자동 테스트 구축

☑ Slither 보안 스캔 파이프라인

☑ solhint 린팅 규칙 설정

☑ GitHub Secrets 안전 관리

🎊

BlockchainDevGuide0004 완료!

개발 환경 세팅 · Hardhat · Foundry · 테스트넷 배포
Etherscan 검증 · IPFS · 업그레이더블 컨트랙트 · CI/CD까지 완전 정복!

✅ Guide0001 ✅ Guide0002 ✅ Guide0003 ✅ Guide0004 ⏳ Guide0005 ⏳ Guide0006

📌 다음 편 예고: BlockchainDevGuide0005

NFT · DeFi 심화 — 실전 프로젝트 완성

NFT 마켓플레이스 · ERC-1155 게임아이템 · Uniswap V3 통합 · Aave 대출 프로토콜 · DAO 거버넌스

반응형