diff --git a/.env.example b/.env.example index 00dbd476..debe0c40 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ -# RPC URLs (replace with your own - get from Alchemy/Infura) -SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY - -# Private key for deployments (NEVER commit actual key!) -PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 +# RPC URLs (replace with your own - get from Alchemy/Infura) +SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY + +# Private key for deployments (NEVER commit actual key!) +PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 diff --git a/.github/ISSUE_TEMPLATE/help-request.md b/.github/ISSUE_TEMPLATE/help-request.md index e39b90b9..06876bcb 100644 --- a/.github/ISSUE_TEMPLATE/help-request.md +++ b/.github/ISSUE_TEMPLATE/help-request.md @@ -1,85 +1,85 @@ ---- -name: 도움 요청 -about: 과제 수행 중 어려운 점이 있을 때 사용하세요 -title: "[Week X] 질문 제목" -labels: question, help-wanted -assignees: '' ---- - -## 기본 정보 - -**주차:** Week - -**과제 유형:** -- [ ] 이론 (Theory) -- [ ] 개발 (Dev) - ---- - -## 문제 상황 - -### 무엇을 하려고 했나요? - - - - - -### 어떤 문제가 발생했나요? - - - - - ---- - -## 시도한 방법 - -### 이미 시도해본 것들 - - - -1. -2. -3. - -### 참고한 자료 - - - -- - ---- - -## 에러 메시지 / 스크린샷 - -### 에러 메시지 (있는 경우) - -``` - - -``` - -### 스크린샷 (있는 경우) - - - ---- - -## 관련 코드 - -### 코드 스니펫 (있는 경우) - -```solidity -// 문제가 발생한 코드 부분을 여기에 붙여넣기 - -``` - ---- - -## 추가 정보 - - - -- OS: -- Foundry 버전: -- 기타: +--- +name: 도움 요청 +about: 과제 수행 중 어려운 점이 있을 때 사용하세요 +title: "[Week X] 질문 제목" +labels: question, help-wanted +assignees: '' +--- + +## 기본 정보 + +**주차:** Week + +**과제 유형:** +- [ ] 이론 (Theory) +- [ ] 개발 (Dev) + +--- + +## 문제 상황 + +### 무엇을 하려고 했나요? + + + + + +### 어떤 문제가 발생했나요? + + + + + +--- + +## 시도한 방법 + +### 이미 시도해본 것들 + + + +1. +2. +3. + +### 참고한 자료 + + + +- + +--- + +## 에러 메시지 / 스크린샷 + +### 에러 메시지 (있는 경우) + +``` + + +``` + +### 스크린샷 (있는 경우) + + + +--- + +## 관련 코드 + +### 코드 스니펫 (있는 경우) + +```solidity +// 문제가 발생한 코드 부분을 여기에 붙여넣기 + +``` + +--- + +## 추가 정보 + + + +- OS: +- Foundry 버전: +- 기타: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index efb8dcb8..e2e4070f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,69 +1,69 @@ -## 과제 제출 정보 - -**주차:** Week - -**과제 유형:** -- [ ] 이론 (Theory Quiz) -- [ ] 개발 (Dev Assignment) - ---- - -## 구현 내용 - - - -- -- -- - ---- - -## 배운 점 (What I Learned) - -### 이번 주에 배운 것 (2-3가지) - - - -1. -2. -3. - -### 어려웠던 점과 해결 방법 - - - -**어려웠던 점:** - - -**해결 방법:** - - -### 질문 사항 - - - -- - ---- - -## 체크리스트 - -### 테스트 - -- [ ] `forge build` 성공 -- [ ] `forge test` 모든 테스트 통과 - -### 제출 규칙 - -- [ ] 브랜치명이 `{username}/week-{XX}` 형식 -- [ ] `.env` 파일이 커밋에 포함되지 않음 -- [ ] 커밋 메시지가 규칙을 따름 - ---- - - +## 과제 제출 정보 + +**주차:** Week + +**과제 유형:** +- [ ] 이론 (Theory Quiz) +- [ ] 개발 (Dev Assignment) + +--- + +## 구현 내용 + + + +- +- +- + +--- + +## 배운 점 (What I Learned) + +### 이번 주에 배운 것 (2-3가지) + + + +1. +2. +3. + +### 어려웠던 점과 해결 방법 + + + +**어려웠던 점:** + + +**해결 방법:** + + +### 질문 사항 + + + +- + +--- + +## 체크리스트 + +### 테스트 + +- [ ] `forge build` 성공 +- [ ] `forge test` 모든 테스트 통과 + +### 제출 규칙 + +- [ ] 브랜치명이 `{username}/week-{XX}` 형식 +- [ ] `.env` 파일이 커밋에 포함되지 않음 +- [ ] 커밋 메시지가 규칙을 따름 + +--- + + diff --git a/.github/PULL_REQUEST_TEMPLATE/quiz_submission.md b/.github/PULL_REQUEST_TEMPLATE/quiz_submission.md index bef00607..52010180 100644 --- a/.github/PULL_REQUEST_TEMPLATE/quiz_submission.md +++ b/.github/PULL_REQUEST_TEMPLATE/quiz_submission.md @@ -1,69 +1,69 @@ -## 퀴즈 제출 정보 - -**주차:** Week - -**퀴즈 파일:** -- [ ] `quiz-XX-solution.md` 파일 포함 -- [ ] 모든 문제에 답변 작성 완료 - ---- - -## 자기 평가 - -### 확실하게 이해한 개념 (3개) - -이번 주차를 공부하면서 확실히 이해한 개념 3가지를 작성하세요: - -1. -2. -3. - -### 어려웠던 문제와 이유 - -**어려웠던 문제 번호:** # - -**왜 어려웠는지:** - - - -**어떻게 접근했는지:** - - - ---- - -## 리뷰어에게 - -### 질문하고 싶은 내용 - -퀴즈를 풀면서 궁금했던 점이나 확인받고 싶은 내용을 작성하세요: - -- -- - -### 추가로 학습하고 싶은 주제 - -이번 주차와 관련해서 더 깊이 알고 싶은 주제가 있다면 작성하세요: - -- - ---- - -## 체크리스트 - -제출 전 확인사항: - -- [ ] 모든 답변에 "왜"에 대한 설명 포함 -- [ ] 코드 문제는 실행 가능한 코드로 작성 -- [ ] 단답형은 2-3문장 이상으로 설명 -- [ ] 객관식은 선택 이유 포함 - ---- - - +## 퀴즈 제출 정보 + +**주차:** Week + +**퀴즈 파일:** +- [ ] `quiz-XX-solution.md` 파일 포함 +- [ ] 모든 문제에 답변 작성 완료 + +--- + +## 자기 평가 + +### 확실하게 이해한 개념 (3개) + +이번 주차를 공부하면서 확실히 이해한 개념 3가지를 작성하세요: + +1. +2. +3. + +### 어려웠던 문제와 이유 + +**어려웠던 문제 번호:** # + +**왜 어려웠는지:** + + + +**어떻게 접근했는지:** + + + +--- + +## 리뷰어에게 + +### 질문하고 싶은 내용 + +퀴즈를 풀면서 궁금했던 점이나 확인받고 싶은 내용을 작성하세요: + +- +- + +### 추가로 학습하고 싶은 주제 + +이번 주차와 관련해서 더 깊이 알고 싶은 주제가 있다면 작성하세요: + +- + +--- + +## 체크리스트 + +제출 전 확인사항: + +- [ ] 모든 답변에 "왜"에 대한 설명 포함 +- [ ] 코드 문제는 실행 가능한 코드로 작성 +- [ ] 단답형은 2-3문장 이상으로 설명 +- [ ] 객관식은 선택 이유 포함 + +--- + + diff --git a/.github/labeler.yml b/.github/labeler.yml index 51a425ba..108f5194 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,38 +1,38 @@ -# PR Auto-Labeling Configuration -# Used by actions/labeler@v5 to automatically add labels based on changed files -# See: https://github.com/actions/labeler - -# Week labels (applies when PR changes files in week-N folder) -# Note: Folder names use zero-padded format (week-01), labels use single digit (week-1) -week-1: - - changed-files: - - any-glob-to-any-file: 'week-01/**/*' - -week-2: - - changed-files: - - any-glob-to-any-file: 'week-02/**/*' - -week-3: - - changed-files: - - any-glob-to-any-file: 'week-03/**/*' - -week-4: - - changed-files: - - any-glob-to-any-file: 'week-04/**/*' - -week-5: - - changed-files: - - any-glob-to-any-file: 'week-05/**/*' - -week-6: - - changed-files: - - any-glob-to-any-file: 'week-06/**/*' - -# Type labels (applies based on subfolder) -theory: - - changed-files: - - any-glob-to-any-file: '**/theory/**/*' - -dev: - - changed-files: - - any-glob-to-any-file: '**/dev/**/*' +# PR Auto-Labeling Configuration +# Used by actions/labeler@v5 to automatically add labels based on changed files +# See: https://github.com/actions/labeler + +# Week labels (applies when PR changes files in week-N folder) +# Note: Folder names use zero-padded format (week-01), labels use single digit (week-1) +week-1: + - changed-files: + - any-glob-to-any-file: 'week-01/**/*' + +week-2: + - changed-files: + - any-glob-to-any-file: 'week-02/**/*' + +week-3: + - changed-files: + - any-glob-to-any-file: 'week-03/**/*' + +week-4: + - changed-files: + - any-glob-to-any-file: 'week-04/**/*' + +week-5: + - changed-files: + - any-glob-to-any-file: 'week-05/**/*' + +week-6: + - changed-files: + - any-glob-to-any-file: 'week-06/**/*' + +# Type labels (applies based on subfolder) +theory: + - changed-files: + - any-glob-to-any-file: '**/theory/**/*' + +dev: + - changed-files: + - any-glob-to-any-file: '**/dev/**/*' diff --git a/.github/scripts/create-labels.sh b/.github/scripts/create-labels.sh index 97df2b5e..d56ce3b1 100755 --- a/.github/scripts/create-labels.sh +++ b/.github/scripts/create-labels.sh @@ -1,35 +1,35 @@ -#!/usr/bin/env bash -# Create GitHub labels for eth-homework repository -# Run this after pushing to GitHub: bash .github/scripts/create-labels.sh -# -# Label color taxonomy per CONTEXT.md: -# - Week labels: Blue family (파란 계열) for structural categorization -# - Type labels: Green family (초록 계열) for content type -# - Status labels: Yellow (needs-review), Blue (in-review), Green (approved) - -set -euo pipefail - -echo "Creating GitHub labels for eth-homework..." - -# Week labels - blue family (파란 계열) -# Gradient from dark to light blue for visual week progression -gh label create "week-1" --color "0052CC" --description "Week 1 assignment" --force -gh label create "week-2" --color "0066FF" --description "Week 2 assignment" --force -gh label create "week-3" --color "1A7FFF" --description "Week 3 assignment" --force -gh label create "week-4" --color "3399FF" --description "Week 4 assignment" --force -gh label create "week-5" --color "4DB3FF" --description "Week 5 assignment" --force -gh label create "week-6" --color "66CCFF" --description "Week 6 assignment" --force - -# Type labels - green family (초록 계열) -gh label create "theory" --color "0E8A16" --description "Theory/quiz submission" --force -gh label create "dev" --color "2CBE4E" --description "Development assignment" --force - -# Review status labels (per CONTEXT.md: 노란/파란/초록) -gh label create "needs-review" --color "FBCA04" --description "Ready for reviewer" --force -gh label create "in-review" --color "0052CC" --description "Under review" --force -gh label create "approved" --color "0E8A16" --description "Review approved" --force - -echo "Done! Created 11 labels:" -echo " - 6 week labels (week-1 through week-6)" -echo " - 2 type labels (theory, dev)" -echo " - 3 status labels (needs-review, in-review, approved)" +#!/usr/bin/env bash +# Create GitHub labels for eth-homework repository +# Run this after pushing to GitHub: bash .github/scripts/create-labels.sh +# +# Label color taxonomy per CONTEXT.md: +# - Week labels: Blue family (파란 계열) for structural categorization +# - Type labels: Green family (초록 계열) for content type +# - Status labels: Yellow (needs-review), Blue (in-review), Green (approved) + +set -euo pipefail + +echo "Creating GitHub labels for eth-homework..." + +# Week labels - blue family (파란 계열) +# Gradient from dark to light blue for visual week progression +gh label create "week-1" --color "0052CC" --description "Week 1 assignment" --force +gh label create "week-2" --color "0066FF" --description "Week 2 assignment" --force +gh label create "week-3" --color "1A7FFF" --description "Week 3 assignment" --force +gh label create "week-4" --color "3399FF" --description "Week 4 assignment" --force +gh label create "week-5" --color "4DB3FF" --description "Week 5 assignment" --force +gh label create "week-6" --color "66CCFF" --description "Week 6 assignment" --force + +# Type labels - green family (초록 계열) +gh label create "theory" --color "0E8A16" --description "Theory/quiz submission" --force +gh label create "dev" --color "2CBE4E" --description "Development assignment" --force + +# Review status labels (per CONTEXT.md: 노란/파란/초록) +gh label create "needs-review" --color "FBCA04" --description "Ready for reviewer" --force +gh label create "in-review" --color "0052CC" --description "Under review" --force +gh label create "approved" --color "0E8A16" --description "Review approved" --force + +echo "Done! Created 11 labels:" +echo " - 6 week labels (week-1 through week-6)" +echo " - 2 type labels (theory, dev)" +echo " - 3 status labels (needs-review, in-review, approved)" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97c0c8c9..8505094e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,138 +1,138 @@ -name: Homework CI - -on: - pull_request: - types: [opened, synchronize, reopened] - -permissions: - contents: read - pull-requests: write - checks: write - security-events: write - -env: - FOUNDRY_PROFILE: ci - -jobs: - # Job 1: Auto-label based on file paths (CI-04) - label: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@v4 - - uses: actions/labeler@v5 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - # Job 2: Solidity linting (CI-01) - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - run: npm ci - - name: Run solhint - id: solhint - run: | - npx solhint 'week-*/dev/src/**/*.sol' -f sarif -o solhint.sarif || true - npx solhint 'week-*/dev/src/**/*.sol' -f stylish - continue-on-error: true - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: solhint.sarif - category: solhint - - # Job 3: Format check (CI-02) - format: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - run: npm ci - - name: Check Prettier formatting - id: prettier - run: npx prettier --check 'week-*/dev/src/**/*.sol' - continue-on-error: true - - # Job 4: Foundry tests (CI-03) - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: stable - - name: Run forge build - run: forge build - - name: Run forge tests - id: test - run: forge test -vvv - continue-on-error: true - - # Job 5: Security scan with Slither (CI-05) - security: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install Foundry (for compilation) - uses: foundry-rs/foundry-toolchain@v1 - - name: Run Slither - uses: crytic/slither-action@v0.4.2 - id: slither - with: - sarif: slither.sarif - fail-on: none - slither-args: --exclude-informational --exclude-optimization - continue-on-error: true - - name: Upload Slither SARIF - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: ${{ steps.slither.outputs.sarif }} - category: slither - - # Job 6: Korean feedback summary (CI-06) - feedback: - needs: [lint, format, test, security] - runs-on: ubuntu-latest - if: always() - steps: - - name: Generate Korean feedback summary - run: | - cat << 'EOF' >> $GITHUB_STEP_SUMMARY - ## 숙제 제출 CI 결과 - - | 항목 | 상태 | 설명 | - |------|------|------| - | 린트 검사 | ${{ needs.lint.result == 'success' && '✅ 통과' || '⚠️ 검토 필요' }} | 코드 스타일 및 잠재적 문제 검사 | - | 포맷 검사 | ${{ needs.format.result == 'success' && '✅ 통과' || '⚠️ 수정 필요' }} | 코드 형식 일관성 검사 | - | 테스트 | ${{ needs.test.result == 'success' && '✅ 통과' || '❌ 실패' }} | 스마트 컨트랙트 기능 테스트 | - | 보안 검토 | ${{ needs.security.result == 'success' && '✅ 이상 없음' || '🛡️ 검토 필요' }} | 보안 취약점 분석 | - - --- - - ### 다음 단계 - - - **모두 통과**: PR 리뷰를 기다려주세요 👍 - - **검토 필요**: 위 항목들을 클릭하여 상세 내용을 확인하세요 - - **도움 필요**: Slack #eth-homework 채널에 질문해주세요 - - --- - 자동 생성된 피드백입니다. 문제가 있으면 멘토에게 문의하세요. - EOF +name: Homework CI + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + checks: write + security-events: write + +env: + FOUNDRY_PROFILE: ci + +jobs: + # Job 1: Auto-label based on file paths (CI-04) + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + - uses: actions/labeler@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # Job 2: Solidity linting (CI-01) + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + - run: npm ci + - name: Run solhint + id: solhint + run: | + npx solhint 'week-*/dev/src/**/*.sol' -f sarif -o solhint.sarif || true + npx solhint 'week-*/dev/src/**/*.sol' -f stylish + continue-on-error: true + - name: Upload SARIF + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: solhint.sarif + category: solhint + + # Job 3: Format check (CI-02) + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + - run: npm ci + - name: Check Prettier formatting + id: prettier + run: npx prettier --check 'week-*/dev/src/**/*.sol' + continue-on-error: true + + # Job 4: Foundry tests (CI-03) + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + - name: Run forge build + run: forge build + - name: Run forge tests + id: test + run: forge test -vvv + continue-on-error: true + + # Job 5: Security scan with Slither (CI-05) + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install Foundry (for compilation) + uses: foundry-rs/foundry-toolchain@v1 + - name: Run Slither + uses: crytic/slither-action@v0.4.2 + id: slither + with: + sarif: slither.sarif + fail-on: none + slither-args: --exclude-informational --exclude-optimization + continue-on-error: true + - name: Upload Slither SARIF + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: ${{ steps.slither.outputs.sarif }} + category: slither + + # Job 6: Korean feedback summary (CI-06) + feedback: + needs: [lint, format, test, security] + runs-on: ubuntu-latest + if: always() + steps: + - name: Generate Korean feedback summary + run: | + cat << 'EOF' >> $GITHUB_STEP_SUMMARY + ## 숙제 제출 CI 결과 + + | 항목 | 상태 | 설명 | + |------|------|------| + | 린트 검사 | ${{ needs.lint.result == 'success' && '✅ 통과' || '⚠️ 검토 필요' }} | 코드 스타일 및 잠재적 문제 검사 | + | 포맷 검사 | ${{ needs.format.result == 'success' && '✅ 통과' || '⚠️ 수정 필요' }} | 코드 형식 일관성 검사 | + | 테스트 | ${{ needs.test.result == 'success' && '✅ 통과' || '❌ 실패' }} | 스마트 컨트랙트 기능 테스트 | + | 보안 검토 | ${{ needs.security.result == 'success' && '✅ 이상 없음' || '🛡️ 검토 필요' }} | 보안 취약점 분석 | + + --- + + ### 다음 단계 + + - **모두 통과**: PR 리뷰를 기다려주세요 👍 + - **검토 필요**: 위 항목들을 클릭하여 상세 내용을 확인하세요 + - **도움 필요**: Slack #eth-homework 채널에 질문해주세요 + + --- + 자동 생성된 피드백입니다. 문제가 있으면 멘토에게 문의하세요. + EOF diff --git a/.gitignore b/.gitignore index 9fbaa8f2..329bcc92 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ lcov.info # Config .env +.env.* # IDE .vscode diff --git a/.gitmodules b/.gitmodules index 888d42dc..55075b48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "lib/forge-std"] +[submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std diff --git a/.prettierrc b/.prettierrc index 09422522..92e2479f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,16 +1,16 @@ -{ - "plugins": ["prettier-plugin-solidity"], - "overrides": [ - { - "files": "*.sol", - "options": { - "printWidth": 100, - "tabWidth": 4, - "useTabs": false, - "singleQuote": false, - "bracketSpacing": true, - "explicitTypes": "always" - } - } - ] -} +{ + "plugins": ["prettier-plugin-solidity"], + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": true, + "explicitTypes": "always" + } + } + ] +} diff --git a/.solhint.json b/.solhint.json index 2d700445..56ef7ddd 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,15 +1,15 @@ -{ - "extends": "solhint:recommended", - "plugins": [], - "rules": { - "compiler-version": ["error", "^0.8.0"], - "func-visibility": ["warn", { "ignoreConstructors": true }], - "max-line-length": ["warn", 120], - "no-empty-blocks": "warn", - "no-unused-vars": "error", - "reason-string": ["warn", { "maxLength": 64 }], - "reentrancy": "warn", - "avoid-low-level-calls": "warn", - "avoid-tx-origin": "error" - } -} +{ + "extends": "solhint:recommended", + "plugins": [], + "rules": { + "compiler-version": ["error", "^0.8.0"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "max-line-length": ["warn", 120], + "no-empty-blocks": "warn", + "no-unused-vars": "error", + "reason-string": ["warn", { "maxLength": 64 }], + "reentrancy": "warn", + "avoid-low-level-calls": "warn", + "avoid-tx-origin": "error" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99de2efa..be24a7bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,186 +1,186 @@ -# 과제 제출 가이드 - -이 문서는 이더리움 온보딩 과제의 제출 방법을 상세히 설명합니다. - -## 목차 - -- [브랜치 명명 규칙](#브랜치-명명-규칙) -- [제출 프로세스](#제출-프로세스) -- [제출물 종류](#제출물-종류) -- [커밋 메시지 규칙](#커밋-메시지-규칙) -- [주의사항](#주의사항) - -## 브랜치 명명 규칙 - -### 형식 - -``` -{github-username}/week-{주차번호} -``` - -### 예시 - -| GitHub 사용자명 | 주차 | 브랜치명 | -|----------------|------|----------| -| ahwlsqja | 1주차 | `ahwlsqja/week-01` | -| nubro999 | 3주차 | `nubro999/week-03` | -| student123 | 2주차 | `student123/week-02` | - -### 규칙 - -- 주차 번호는 두 자리 숫자로 작성 (01, 02, ... 06) -- 브랜치명은 모두 소문자 사용 -- GitHub 사용자명은 정확히 입력 - -## 제출 프로세스 - -### Step 1: 저장소 클론 (최초 1회) - -```bash -git clone --recurse-submodules https://github.com/your-org/eth-homework.git -cd eth-homework -``` - -이미 클론했다면 최신 상태로 업데이트: - -```bash -git checkout main -git pull origin main -git submodule update --init --recursive -``` - -### Step 2: 브랜치 생성 - -```bash -git checkout -b {username}/week-{XX} -``` - -예시: -```bash -git checkout -b ahwlsqja/week-01 -``` - -### Step 3: 과제 수행 - -해당 주차의 과제를 완성합니다: -- 이론: `week-XX/theory/` 폴더의 퀴즈 답안 작성 -- 개발: `week-XX/dev/src/` 폴더의 컨트랙트 구현 - -### Step 4: 테스트 실행 - -개발 과제는 반드시 테스트를 통과해야 합니다: - -```bash -# 전체 테스트 -forge test - -# 특정 파일만 테스트 -forge test --match-path "week-01/dev/test/*.sol" - -# 상세 출력 (-vvv) -forge test -vvv -``` - -### Step 5: 커밋 - -```bash -git add . -git commit -m "feat(week-01): complete counter assignment" -``` - -### Step 6: 푸시 - -```bash -git push -u origin {username}/week-{XX} -``` - -### Step 7: Pull Request 생성 - -1. GitHub 저장소 페이지로 이동 -2. "Compare & pull request" 버튼 클릭 -3. PR 템플릿에 따라 내용 작성 - - 구현한 내용 설명 - - 배운 점 기록 - - 어려웠던 점과 해결 방법 -4. "Create pull request" 클릭 - -PR 템플릿은 자동으로 로드됩니다. 성찰적 학습을 위해 빈칸을 모두 채워주세요. - -## 제출물 종류 - -### 이론 과제 (Theory) - -| 항목 | 설명 | -|------|------| -| 위치 | `week-XX/theory/` | -| 형식 | Markdown 파일 | -| 제출 | `quiz-XX-template.md`를 복사하여 `quiz-XX-solution.md`로 저장 | - -### 개발 과제 (Dev) - -| 항목 | 설명 | -|------|------| -| 위치 | `week-XX/dev/src/` | -| 형식 | Solidity 파일 (.sol) | -| 검증 | `forge test` 통과 필수 | - -## 커밋 메시지 규칙 - -### 형식 - -``` -{type}(week-{XX}): {간단한 설명} -``` - -### Type 종류 - -| Type | 용도 | -|------|------| -| `feat` | 새로운 기능 구현 | -| `fix` | 버그 수정 | -| `docs` | 문서 작성/수정 | -| `refactor` | 코드 리팩토링 | -| `test` | 테스트 추가 | - -### 예시 - -```bash -git commit -m "feat(week-01): implement Counter increment function" -git commit -m "fix(week-02): correct token transfer logic" -git commit -m "docs(week-03): add solution for theory quiz" -``` - -## 주의사항 - -### 절대 커밋하면 안 되는 파일 - -다음 파일들은 `.gitignore`에 포함되어 있지만, 실수로 추가하지 않도록 주의하세요: - -| 파일/폴더 | 이유 | -|-----------|------| -| `.env` | 개인 API 키, Private Key 포함 | -| `out/` | 빌드 결과물 (컴파일 시 자동 생성) | -| `cache/` | Foundry 캐시 | -| `broadcast/` | 배포 기록 (테스트넷 제외) | - -### 체크리스트 - -PR 제출 전 확인사항: - -- [ ] 브랜치명이 `{username}/week-{XX}` 형식인가? -- [ ] `forge build`가 에러 없이 완료되는가? -- [ ] `forge test`가 모두 통과하는가? -- [ ] `.env` 파일이 커밋에 포함되지 않았는가? -- [ ] PR 템플릿의 모든 항목을 작성했는가? - -### 도움 요청 - -과제 수행 중 막히는 부분이 있다면: - -1. [Issue 생성](https://github.com/your-org/eth-homework/issues/new?template=help-request.md) -2. 같은 주차 동기들과 토론 -3. PR에 코멘트로 질문 - ---- - -질문이 있으시면 Issue를 통해 문의해주세요! +# 과제 제출 가이드 + +이 문서는 이더리움 온보딩 과제의 제출 방법을 상세히 설명합니다. + +## 목차 + +- [브랜치 명명 규칙](#브랜치-명명-규칙) +- [제출 프로세스](#제출-프로세스) +- [제출물 종류](#제출물-종류) +- [커밋 메시지 규칙](#커밋-메시지-규칙) +- [주의사항](#주의사항) + +## 브랜치 명명 규칙 + +### 형식 + +``` +{github-username}/week-{주차번호} +``` + +### 예시 + +| GitHub 사용자명 | 주차 | 브랜치명 | +|----------------|------|----------| +| ahwlsqja | 1주차 | `ahwlsqja/week-01` | +| nubro999 | 3주차 | `nubro999/week-03` | +| student123 | 2주차 | `student123/week-02` | + +### 규칙 + +- 주차 번호는 두 자리 숫자로 작성 (01, 02, ... 06) +- 브랜치명은 모두 소문자 사용 +- GitHub 사용자명은 정확히 입력 + +## 제출 프로세스 + +### Step 1: 저장소 클론 (최초 1회) + +```bash +git clone --recurse-submodules https://github.com/your-org/eth-homework.git +cd eth-homework +``` + +이미 클론했다면 최신 상태로 업데이트: + +```bash +git checkout main +git pull origin main +git submodule update --init --recursive +``` + +### Step 2: 브랜치 생성 + +```bash +git checkout -b {username}/week-{XX} +``` + +예시: +```bash +git checkout -b ahwlsqja/week-01 +``` + +### Step 3: 과제 수행 + +해당 주차의 과제를 완성합니다: +- 이론: `week-XX/theory/` 폴더의 퀴즈 답안 작성 +- 개발: `week-XX/dev/src/` 폴더의 컨트랙트 구현 + +### Step 4: 테스트 실행 + +개발 과제는 반드시 테스트를 통과해야 합니다: + +```bash +# 전체 테스트 +forge test + +# 특정 파일만 테스트 +forge test --match-path "week-01/dev/test/*.sol" + +# 상세 출력 (-vvv) +forge test -vvv +``` + +### Step 5: 커밋 + +```bash +git add . +git commit -m "feat(week-01): complete counter assignment" +``` + +### Step 6: 푸시 + +```bash +git push -u origin {username}/week-{XX} +``` + +### Step 7: Pull Request 생성 + +1. GitHub 저장소 페이지로 이동 +2. "Compare & pull request" 버튼 클릭 +3. PR 템플릿에 따라 내용 작성 + - 구현한 내용 설명 + - 배운 점 기록 + - 어려웠던 점과 해결 방법 +4. "Create pull request" 클릭 + +PR 템플릿은 자동으로 로드됩니다. 성찰적 학습을 위해 빈칸을 모두 채워주세요. + +## 제출물 종류 + +### 이론 과제 (Theory) + +| 항목 | 설명 | +|------|------| +| 위치 | `week-XX/theory/` | +| 형식 | Markdown 파일 | +| 제출 | `quiz-XX-template.md`를 복사하여 `quiz-XX-solution.md`로 저장 | + +### 개발 과제 (Dev) + +| 항목 | 설명 | +|------|------| +| 위치 | `week-XX/dev/src/` | +| 형식 | Solidity 파일 (.sol) | +| 검증 | `forge test` 통과 필수 | + +## 커밋 메시지 규칙 + +### 형식 + +``` +{type}(week-{XX}): {간단한 설명} +``` + +### Type 종류 + +| Type | 용도 | +|------|------| +| `feat` | 새로운 기능 구현 | +| `fix` | 버그 수정 | +| `docs` | 문서 작성/수정 | +| `refactor` | 코드 리팩토링 | +| `test` | 테스트 추가 | + +### 예시 + +```bash +git commit -m "feat(week-01): implement Counter increment function" +git commit -m "fix(week-02): correct token transfer logic" +git commit -m "docs(week-03): add solution for theory quiz" +``` + +## 주의사항 + +### 절대 커밋하면 안 되는 파일 + +다음 파일들은 `.gitignore`에 포함되어 있지만, 실수로 추가하지 않도록 주의하세요: + +| 파일/폴더 | 이유 | +|-----------|------| +| `.env` | 개인 API 키, Private Key 포함 | +| `out/` | 빌드 결과물 (컴파일 시 자동 생성) | +| `cache/` | Foundry 캐시 | +| `broadcast/` | 배포 기록 (테스트넷 제외) | + +### 체크리스트 + +PR 제출 전 확인사항: + +- [ ] 브랜치명이 `{username}/week-{XX}` 형식인가? +- [ ] `forge build`가 에러 없이 완료되는가? +- [ ] `forge test`가 모두 통과하는가? +- [ ] `.env` 파일이 커밋에 포함되지 않았는가? +- [ ] PR 템플릿의 모든 항목을 작성했는가? + +### 도움 요청 + +과제 수행 중 막히는 부분이 있다면: + +1. [Issue 생성](https://github.com/your-org/eth-homework/issues/new?template=help-request.md) +2. 같은 주차 동기들과 토론 +3. PR에 코멘트로 질문 + +--- + +질문이 있으시면 Issue를 통해 문의해주세요! diff --git a/README.md b/README.md index 51a51071..79d7d830 100644 --- a/README.md +++ b/README.md @@ -1,196 +1,196 @@ -# Bay-17th 이더리움 온보딩 - 과제 제출 - -이더리움 온보딩 프로그램의 과제 제출 저장소입니다. - -## 목차 - -- [빠른 시작](#빠른-시작) -- [주차별 과제](#주차별-과제) -- [제출 방법](#제출-방법) -- [네트워크 연결](#네트워크-연결) -- [도움 받기](#도움-받기) - -## 빠른 시작 - -### 저장소 클론 - -```bash -git clone --recurse-submodules https://github.com/your-org/eth-homework.git -cd eth-homework -``` - -> **Note:** `--recurse-submodules` 옵션을 사용해야 Foundry 라이브러리가 함께 다운로드됩니다. - -### 개발 환경 설정 - -1. [Foundry](https://book.getfoundry.sh/getting-started/installation) 설치: - ```bash - curl -L https://foundry.paradigm.xyz | bash - foundryup - ``` - -2. 의존성 설치: - ```bash - forge install - ``` - - > **Note:** `--recurse-submodules` 옵션 없이 클론한 경우, forge-std 라이브러리를 수동으로 설치해야 합니다: - > ```bash - > forge install foundry-rs/forge-std - > ``` - -3. 빌드 테스트: - ```bash - forge build - ``` - -## 주차별 과제 - -| 주차 | 이론 | 개발 | 주제 | -|:----:|:----:|:----:|------| -| [Week 1](./week-01/) | Quiz | Counter | 블록체인 기초 & Solidity 입문 | -| [Week 2](./week-02/) | Quiz | ERC20 | 이더리움 구조 & 토큰 표준 | -| [Week 3](./week-03/) | Quiz | Vault | 트랜잭션 & 보안 기초 | -| [Week 4](./week-04/) | Quiz | NFT | 스마트 컨트랙트 심화 | -| [Week 5](./week-05/) | Quiz | DEX | DeFi 기초 | -| [Week 6](./week-06/) | - | Final | 최종 프로젝트 | - -각 주차 폴더에는 다음이 포함됩니다: -- `theory/`: 이론 퀴즈 템플릿 -- `dev/`: 개발 과제 (src, test, script) - -## 제출 방법 - -### 1. 브랜치 생성 - -자신의 GitHub 사용자명과 주차를 조합하여 브랜치를 만듭니다: - -```bash -git checkout -b username/week-01 -``` - -### 2. 과제 완성 - -- **이론**: `week-XX/theory/` 폴더의 템플릿을 복사하여 답안 작성 -- **개발**: `week-XX/dev/src/` 폴더의 TODO를 구현 - -### 3. 테스트 확인 - -```bash -# 개발 과제 테스트 -forge test - -# 특정 주차만 테스트 -forge test --match-path "week-01/*" -``` - -### 4. 커밋 & 푸시 - -```bash -git add . -git commit -m "feat(week-01): complete counter assignment" -git push -u origin username/week-01 -``` - -### 5. Pull Request 생성 - -GitHub에서 Pull Request를 생성합니다. PR 템플릿에 따라 배운 점과 어려웠던 점을 작성해주세요. - -자세한 제출 가이드는 [CONTRIBUTING.md](./CONTRIBUTING.md)를 참조하세요. - -## 네트워크 연결 - -Foundry는 로컬 테스트뿐만 아니라 Sepolia, 메인넷 등 실제 네트워크에도 연결할 수 있습니다. - -### Foundry 도구 소개 - -| 도구 | 설명 | -|------|------| -| `forge` | 컴파일, 테스트, 배포 | -| `cast` | 온체인 데이터 조회, 트랜잭션 전송 | -| `anvil` | 로컬 테스트넷 실행 | -| `chisel` | Solidity REPL | - -### 로컬 테스트넷 (Anvil) - -```bash -# 로컬넷 실행 -anvil - -# 다른 터미널에서 배포 -forge script script/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast -``` - -### 테스트넷 연결 (Sepolia) - -1. [Infura](https://infura.io), [Alchemy](https://alchemy.com) 등에서 무료 API 키 발급 - -2. `.env` 파일 설정: - ```bash - cp .env.example .env - # .env 파일에 API 키와 프라이빗 키 입력 - ``` - -3. 배포: - ```bash - forge script script/Deploy.s.sol \ - --rpc-url https://sepolia.infura.io/v3/YOUR_API_KEY \ - --private-key YOUR_PRIVATE_KEY \ - --broadcast - ``` - -4. 컨트랙트 조회: - ```bash - cast call 0x컨트랙트주소 "balanceOf(address)" 0x지갑주소 \ - --rpc-url https://sepolia.infura.io/v3/YOUR_API_KEY - ``` - -### foundry.toml RPC 설정 (선택사항) - -`foundry.toml`에 RPC 엔드포인트를 설정하면 편리합니다: - -```toml -[rpc_endpoints] -sepolia = "https://sepolia.infura.io/v3/${INFURA_API_KEY}" -mainnet = "https://mainnet.infura.io/v3/${INFURA_API_KEY}" -``` - -이후 간단히 사용: -```bash -forge script script/Deploy.s.sol --rpc-url sepolia --broadcast -``` - -## 도움 받기 - -과제 수행 중 어려움이 있다면: - -1. **Issue 생성**: [질문 템플릿](./.github/ISSUE_TEMPLATE/help-request.md)을 사용해 질문을 올려주세요. -2. **동기들과 토론**: 같은 주차를 진행하는 동기들과 함께 고민해보세요. -3. **리뷰어에게 질문**: PR 코멘트로 리뷰어에게 직접 질문할 수 있습니다. - -## 프로젝트 구조 - -``` -eth-homework/ -├── README.md # 이 파일 -├── CONTRIBUTING.md # 상세 제출 가이드 -├── foundry.toml # Foundry 설정 -├── .env.example # 환경변수 예시 -├── lib/ # Foundry 라이브러리 (forge-std) -├── week-01/ -│ ├── theory/ # 이론 퀴즈 -│ │ └── quiz-01-template.md -│ └── dev/ # 개발 과제 -│ ├── src/ # 구현할 컨트랙트 -│ ├── test/ # 제공되는 테스트 -│ └── script/ # 배포 스크립트 -├── week-02/ ... week-06/ # 각 주차별 동일 구조 -└── .github/ - ├── PULL_REQUEST_TEMPLATE.md - └── ISSUE_TEMPLATE/ - └── help-request.md -``` - ---- - -Made with by Bay-17th +# Bay-17th 이더리움 온보딩 - 과제 제출 + +이더리움 온보딩 프로그램의 과제 제출 저장소입니다. + +## 목차 + +- [빠른 시작](#빠른-시작) +- [주차별 과제](#주차별-과제) +- [제출 방법](#제출-방법) +- [네트워크 연결](#네트워크-연결) +- [도움 받기](#도움-받기) + +## 빠른 시작 + +### 저장소 클론 + +```bash +git clone --recurse-submodules https://github.com/your-org/eth-homework.git +cd eth-homework +``` + +> **Note:** `--recurse-submodules` 옵션을 사용해야 Foundry 라이브러리가 함께 다운로드됩니다. + +### 개발 환경 설정 + +1. [Foundry](https://book.getfoundry.sh/getting-started/installation) 설치: + ```bash + curl -L https://foundry.paradigm.xyz | bash + foundryup + ``` + +2. 의존성 설치: + ```bash + forge install + ``` + + > **Note:** `--recurse-submodules` 옵션 없이 클론한 경우, forge-std 라이브러리를 수동으로 설치해야 합니다: + > ```bash + > forge install foundry-rs/forge-std + > ``` + +3. 빌드 테스트: + ```bash + forge build + ``` + +## 주차별 과제 + +| 주차 | 이론 | 개발 | 주제 | +|:----:|:----:|:----:|------| +| [Week 1](./week-01/) | Quiz | Counter | 블록체인 기초 & Solidity 입문 | +| [Week 2](./week-02/) | Quiz | ERC20 | 이더리움 구조 & 토큰 표준 | +| [Week 3](./week-03/) | Quiz | Vault | 트랜잭션 & 보안 기초 | +| [Week 4](./week-04/) | Quiz | NFT | 스마트 컨트랙트 심화 | +| [Week 5](./week-05/) | Quiz | DEX | DeFi 기초 | +| [Week 6](./week-06/) | - | Final | 최종 프로젝트 | + +각 주차 폴더에는 다음이 포함됩니다: +- `theory/`: 이론 퀴즈 템플릿 +- `dev/`: 개발 과제 (src, test, script) + +## 제출 방법 + +### 1. 브랜치 생성 + +자신의 GitHub 사용자명과 주차를 조합하여 브랜치를 만듭니다: + +```bash +git checkout -b username/week-01 +``` + +### 2. 과제 완성 + +- **이론**: `week-XX/theory/` 폴더의 템플릿을 복사하여 답안 작성 +- **개발**: `week-XX/dev/src/` 폴더의 TODO를 구현 + +### 3. 테스트 확인 + +```bash +# 개발 과제 테스트 +forge test + +# 특정 주차만 테스트 +forge test --match-path "week-01/*" +``` + +### 4. 커밋 & 푸시 + +```bash +git add . +git commit -m "feat(week-01): complete counter assignment" +git push -u origin username/week-01 +``` + +### 5. Pull Request 생성 + +GitHub에서 Pull Request를 생성합니다. PR 템플릿에 따라 배운 점과 어려웠던 점을 작성해주세요. + +자세한 제출 가이드는 [CONTRIBUTING.md](./CONTRIBUTING.md)를 참조하세요. + +## 네트워크 연결 + +Foundry는 로컬 테스트뿐만 아니라 Sepolia, 메인넷 등 실제 네트워크에도 연결할 수 있습니다. + +### Foundry 도구 소개 + +| 도구 | 설명 | +|------|------| +| `forge` | 컴파일, 테스트, 배포 | +| `cast` | 온체인 데이터 조회, 트랜잭션 전송 | +| `anvil` | 로컬 테스트넷 실행 | +| `chisel` | Solidity REPL | + +### 로컬 테스트넷 (Anvil) + +```bash +# 로컬넷 실행 +anvil + +# 다른 터미널에서 배포 +forge script script/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast +``` + +### 테스트넷 연결 (Sepolia) + +1. [Infura](https://infura.io), [Alchemy](https://alchemy.com) 등에서 무료 API 키 발급 + +2. `.env` 파일 설정: + ```bash + cp .env.example .env + # .env 파일에 API 키와 프라이빗 키 입력 + ``` + +3. 배포: + ```bash + forge script script/Deploy.s.sol \ + --rpc-url https://sepolia.infura.io/v3/YOUR_API_KEY \ + --private-key YOUR_PRIVATE_KEY \ + --broadcast + ``` + +4. 컨트랙트 조회: + ```bash + cast call 0x컨트랙트주소 "balanceOf(address)" 0x지갑주소 \ + --rpc-url https://sepolia.infura.io/v3/YOUR_API_KEY + ``` + +### foundry.toml RPC 설정 (선택사항) + +`foundry.toml`에 RPC 엔드포인트를 설정하면 편리합니다: + +```toml +[rpc_endpoints] +sepolia = "https://sepolia.infura.io/v3/${INFURA_API_KEY}" +mainnet = "https://mainnet.infura.io/v3/${INFURA_API_KEY}" +``` + +이후 간단히 사용: +```bash +forge script script/Deploy.s.sol --rpc-url sepolia --broadcast +``` + +## 도움 받기 + +과제 수행 중 어려움이 있다면: + +1. **Issue 생성**: [질문 템플릿](./.github/ISSUE_TEMPLATE/help-request.md)을 사용해 질문을 올려주세요. +2. **동기들과 토론**: 같은 주차를 진행하는 동기들과 함께 고민해보세요. +3. **리뷰어에게 질문**: PR 코멘트로 리뷰어에게 직접 질문할 수 있습니다. + +## 프로젝트 구조 + +``` +eth-homework/ +├── README.md # 이 파일 +├── CONTRIBUTING.md # 상세 제출 가이드 +├── foundry.toml # Foundry 설정 +├── .env.example # 환경변수 예시 +├── lib/ # Foundry 라이브러리 (forge-std) +├── week-01/ +│ ├── theory/ # 이론 퀴즈 +│ │ └── quiz-01-template.md +│ └── dev/ # 개발 과제 +│ ├── src/ # 구현할 컨트랙트 +│ ├── test/ # 제공되는 테스트 +│ └── script/ # 배포 스크립트 +├── week-02/ ... week-06/ # 각 주차별 동일 구조 +└── .github/ + ├── PULL_REQUEST_TEMPLATE.md + └── ISSUE_TEMPLATE/ + └── help-request.md +``` + +--- + +Made with by Bay-17th diff --git a/foundry.toml b/foundry.toml index db6a18f8..7bfdb118 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,21 +1,21 @@ -[profile.default] -src = "." -test = "." -out = "out" -libs = ["lib"] -solc = "0.8.26" - -# Optimizer for gas efficiency -optimizer = true -optimizer_runs = 200 - -# Format settings -[fmt] -line_length = 100 -tab_width = 4 -bracket_spacing = true - -# CI profile for GitHub Actions -[profile.ci] -verbosity = 3 -fuzz = { runs = 256 } +[profile.default] +src = "." +test = "." +out = "out" +libs = ["lib"] +solc = "0.8.26" + +# Optimizer for gas efficiency +optimizer = true +optimizer_runs = 200 + +# Format settings +[fmt] +line_length = 100 +tab_width = 4 +bracket_spacing = true + +# CI profile for GitHub Actions +[profile.ci] +verbosity = 3 +fuzz = { runs = 256 } diff --git a/package.json b/package.json index 4ac26373..67185bf6 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ -{ - "name": "eth-homework", - "private": true, - "devDependencies": { - "solhint": "^5.0.0", - "prettier": "^3.0.0", - "prettier-plugin-solidity": "^2.0.0" - }, - "scripts": { - "lint": "solhint 'week-*/dev/src/**/*.sol'", - "lint:fix": "solhint 'week-*/dev/src/**/*.sol' --fix", - "format": "prettier --check 'week-*/dev/src/**/*.sol'", - "format:fix": "prettier --write 'week-*/dev/src/**/*.sol'" - } -} +{ + "name": "eth-homework", + "private": true, + "devDependencies": { + "solhint": "^5.0.0", + "prettier": "^3.0.0", + "prettier-plugin-solidity": "^2.0.0" + }, + "scripts": { + "lint": "solhint 'week-*/dev/src/**/*.sol'", + "lint:fix": "solhint 'week-*/dev/src/**/*.sol' --fix", + "format": "prettier --check 'week-*/dev/src/**/*.sol'", + "format:fix": "prettier --write 'week-*/dev/src/**/*.sol'" + } +} diff --git a/slither.config.json b/slither.config.json index 137e238d..00688cd1 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,7 +1,7 @@ -{ - "detectors_to_exclude": "naming-convention,solc-version,pragma", - "exclude_informational": true, - "exclude_optimization": true, - "filter_paths": "lib/,node_modules/", - "fail_on": "none" -} +{ + "detectors_to_exclude": "naming-convention,solc-version,pragma", + "exclude_informational": true, + "exclude_optimization": true, + "filter_paths": "lib/,node_modules/", + "fail_on": "none" +} diff --git a/templates/REVIEW_CHECKLIST_TEMPLATE.md b/templates/REVIEW_CHECKLIST_TEMPLATE.md index 8bb75180..5f9fee65 100644 --- a/templates/REVIEW_CHECKLIST_TEMPLATE.md +++ b/templates/REVIEW_CHECKLIST_TEMPLATE.md @@ -1,134 +1,134 @@ -# Week [XX] 퀴즈 리뷰 체크리스트 - -> **사용법:** 이 템플릿을 `week-XX/quiz/REVIEW_CHECKLIST.md`로 복사하여 주차별로 수정하세요. - ---- - -## 핵심 개념 확인 - -### [개념 그룹 1] (문제 1-3) - -- [ ] [개념 A]를 이해하는가? -- [ ] [개념 B]와 [개념 C]의 차이를 설명하는가? -- [ ] [실제 사례]와 연결지어 설명하는가? - -### [개념 그룹 2] (문제 4-6) - -- [ ] [패턴/원리]의 작동 원리를 설명하는가? -- [ ] [관련 개념]과의 관계를 이해하는가? -- [ ] [적용 방법]을 올바르게 제시하는가? - -### [개념 그룹 3: 코드 문제] (문제 7-10) - -- [ ] 취약점을 정확히 식별하는가? -- [ ] 왜 문제인지 설명하는가? -- [ ] 수정 방법이 논리적으로 올바른가? (문법 오류 무시 - 개념 이해에 집중) - ---- - -## 평가 가이드 - -### 맞다/틀리다 기준 - -| 상황 | 판정 | -|------|------| -| 핵심 개념을 이해하고 있음 | **맞음** | -| 표현이 다소 다르지만 개념 이해가 보임 | **맞음** | -| 핵심 포인트를 놓쳤지만 부분적으로 맞음 | **부분 정답** (댓글로 피드백) | -| 개념을 잘못 이해하고 있음 | **틀림** (자세한 설명 제공) | - -### 코드 문제 평가 - -``` -// 문법 오류 무시 - 개념 이해에 집중 -// 예: 세미콜론 빠뜨림 → 감점 X -// 예: CEI 순서 잘못됨 → 개념 이해 부족으로 피드백 -``` - -**평가 우선순위:** -1. 취약점/문제를 정확히 식별했는가? -2. 왜 문제인지 설명할 수 있는가? -3. 해결 방법이 논리적으로 맞는가? -4. (낮은 우선순위) 코드 문법이 정확한가? - -### 부분 정답 처리 - -- 리뷰어 재량으로 판단 -- 댓글로 "어떤 부분이 맞았고, 어떤 부분이 부족한지" 설명 -- 추가 질문으로 이해도 확인 가능 - ---- - -## 피드백 작성 가이드 - -### 좋은 피드백 구조 - -1. **잘한 점 먼저 언급** (긍정적으로 시작) - ``` - "CEI 패턴의 순서를 정확히 이해하고 있네요!" - ``` - -2. **개선점은 구체적으로** (무엇이, 왜, 어떻게) - ``` - "문제 3에서 '상태 변경'이 왜 먼저 와야 하는지 설명이 부족해요. - 재진입 공격과 연결해서 다시 생각해 보세요." - ``` - -3. **추가 학습 자료 링크** (관련 자료 제공) - ``` - "eth-materials/week-03/theory/security.md에서 CEI 패턴 부분을 복습하면 도움이 될 거예요." - ``` - -### 피드백 예시 - -#### 좋은 예시 -```markdown -**문제 5 피드백:** - -잘한 점: tx.origin과 msg.sender의 차이를 정확히 구분했어요! - -개선점: 왜 tx.origin이 보안 위험인지 설명이 조금 부족해요. -힌트: 중간에 다른 컨트랙트가 있으면 어떻게 될까요? - -참고: eth-materials/week-03/dev/security-patterns.md의 "피싱 공격" 섹션을 읽어보세요. -``` - -#### 피해야 할 예시 -```markdown -❌ "틀렸습니다." -❌ "다시 공부하세요." -❌ "세미콜론이 빠졌네요." (문법 오류는 낮은 우선순위) -``` - ---- - -## 주차별 참고 자료 - -| 주차 | 이론 자료 | 개발 자료 | -|------|-----------|-----------| -| Week 01 | eth-materials/week-01/theory/ | eth-homework/week-01/dev/ | -| Week 02 | eth-materials/week-02/theory/ | eth-homework/week-02/dev/ | -| Week 03 | eth-materials/week-03/theory/ | eth-homework/week-03/dev/ | -| Week 04 | eth-materials/week-04/theory/ | eth-homework/week-04/dev/ | -| Week 05 | eth-materials/week-05/theory/ | eth-homework/week-05/dev/ | -| Week 06 | eth-materials/week-06/theory/ | eth-homework/week-06/dev/ | - ---- - -## 템플릿 사용 안내 - -1. 이 파일을 `week-XX/quiz/REVIEW_CHECKLIST.md`로 복사 -2. `[XX]`를 해당 주차 번호로 교체 -3. `[개념 그룹]`, `[개념 A]` 등을 주차별 핵심 개념으로 교체 -4. 문제 번호 범위를 실제 퀴즈에 맞게 조정 -5. 주차별 참고 자료 경로 확인 - ---- - - +# Week [XX] 퀴즈 리뷰 체크리스트 + +> **사용법:** 이 템플릿을 `week-XX/quiz/REVIEW_CHECKLIST.md`로 복사하여 주차별로 수정하세요. + +--- + +## 핵심 개념 확인 + +### [개념 그룹 1] (문제 1-3) + +- [ ] [개념 A]를 이해하는가? +- [ ] [개념 B]와 [개념 C]의 차이를 설명하는가? +- [ ] [실제 사례]와 연결지어 설명하는가? + +### [개념 그룹 2] (문제 4-6) + +- [ ] [패턴/원리]의 작동 원리를 설명하는가? +- [ ] [관련 개념]과의 관계를 이해하는가? +- [ ] [적용 방법]을 올바르게 제시하는가? + +### [개념 그룹 3: 코드 문제] (문제 7-10) + +- [ ] 취약점을 정확히 식별하는가? +- [ ] 왜 문제인지 설명하는가? +- [ ] 수정 방법이 논리적으로 올바른가? (문법 오류 무시 - 개념 이해에 집중) + +--- + +## 평가 가이드 + +### 맞다/틀리다 기준 + +| 상황 | 판정 | +|------|------| +| 핵심 개념을 이해하고 있음 | **맞음** | +| 표현이 다소 다르지만 개념 이해가 보임 | **맞음** | +| 핵심 포인트를 놓쳤지만 부분적으로 맞음 | **부분 정답** (댓글로 피드백) | +| 개념을 잘못 이해하고 있음 | **틀림** (자세한 설명 제공) | + +### 코드 문제 평가 + +``` +// 문법 오류 무시 - 개념 이해에 집중 +// 예: 세미콜론 빠뜨림 → 감점 X +// 예: CEI 순서 잘못됨 → 개념 이해 부족으로 피드백 +``` + +**평가 우선순위:** +1. 취약점/문제를 정확히 식별했는가? +2. 왜 문제인지 설명할 수 있는가? +3. 해결 방법이 논리적으로 맞는가? +4. (낮은 우선순위) 코드 문법이 정확한가? + +### 부분 정답 처리 + +- 리뷰어 재량으로 판단 +- 댓글로 "어떤 부분이 맞았고, 어떤 부분이 부족한지" 설명 +- 추가 질문으로 이해도 확인 가능 + +--- + +## 피드백 작성 가이드 + +### 좋은 피드백 구조 + +1. **잘한 점 먼저 언급** (긍정적으로 시작) + ``` + "CEI 패턴의 순서를 정확히 이해하고 있네요!" + ``` + +2. **개선점은 구체적으로** (무엇이, 왜, 어떻게) + ``` + "문제 3에서 '상태 변경'이 왜 먼저 와야 하는지 설명이 부족해요. + 재진입 공격과 연결해서 다시 생각해 보세요." + ``` + +3. **추가 학습 자료 링크** (관련 자료 제공) + ``` + "eth-materials/week-03/theory/security.md에서 CEI 패턴 부분을 복습하면 도움이 될 거예요." + ``` + +### 피드백 예시 + +#### 좋은 예시 +```markdown +**문제 5 피드백:** + +잘한 점: tx.origin과 msg.sender의 차이를 정확히 구분했어요! + +개선점: 왜 tx.origin이 보안 위험인지 설명이 조금 부족해요. +힌트: 중간에 다른 컨트랙트가 있으면 어떻게 될까요? + +참고: eth-materials/week-03/dev/security-patterns.md의 "피싱 공격" 섹션을 읽어보세요. +``` + +#### 피해야 할 예시 +```markdown +❌ "틀렸습니다." +❌ "다시 공부하세요." +❌ "세미콜론이 빠졌네요." (문법 오류는 낮은 우선순위) +``` + +--- + +## 주차별 참고 자료 + +| 주차 | 이론 자료 | 개발 자료 | +|------|-----------|-----------| +| Week 01 | eth-materials/week-01/theory/ | eth-homework/week-01/dev/ | +| Week 02 | eth-materials/week-02/theory/ | eth-homework/week-02/dev/ | +| Week 03 | eth-materials/week-03/theory/ | eth-homework/week-03/dev/ | +| Week 04 | eth-materials/week-04/theory/ | eth-homework/week-04/dev/ | +| Week 05 | eth-materials/week-05/theory/ | eth-homework/week-05/dev/ | +| Week 06 | eth-materials/week-06/theory/ | eth-homework/week-06/dev/ | + +--- + +## 템플릿 사용 안내 + +1. 이 파일을 `week-XX/quiz/REVIEW_CHECKLIST.md`로 복사 +2. `[XX]`를 해당 주차 번호로 교체 +3. `[개념 그룹]`, `[개념 A]` 등을 주차별 핵심 개념으로 교체 +4. 문제 번호 범위를 실제 퀴즈에 맞게 조정 +5. 주차별 참고 자료 경로 확인 + +--- + + diff --git a/templates/quiz-code-problem.md b/templates/quiz-code-problem.md index a7bc4067..3b8ad89a 100644 --- a/templates/quiz-code-problem.md +++ b/templates/quiz-code-problem.md @@ -1,184 +1,184 @@ -# 코드 문제 템플릿 - -> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. -> 코드 문제는 **취약점 발견**과 **빈칸 채우기** 두 가지 형식이 있습니다. - ---- - -## 형식 1: 취약점 찾기 (Bug Finding) - -### 문제 [N]: [카테고리] (취약점 찾기) - -다음 코드에서 보안 취약점을 찾으세요: - -```solidity -// BAD CODE - 취약점 찾기 -contract VulnerableContract { - mapping(address => uint256) public balances; - - function withdraw(uint256 amount) public { - require(balances[msg.sender] >= amount, "Insufficient"); - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Failed"); - balances[msg.sender] -= amount; // ⚠️ 문제 위치 - } -} -``` - -**1) 발견한 취약점:** - - - -**2) 왜 이것이 문제인가:** - - - -**3) 올바른 수정 방법:** -```solidity -// GOOD CODE - 수정된 버전을 작성하세요 -``` - ---- - -## 형식 2: 빈칸 채우기 (Fill-in-the-Blank) - -### 문제 [N]: [카테고리] (빈칸 채우기) - -다음 코드의 빈칸을 채워서 [목표]를 달성하세요: - -```solidity -// TODO: [필요한 import 또는 선언] -_________________________________________ - -contract SecureContract { - // TODO: [필요한 변수 선언] - _________________________________________ - - function safeFunction() public { - // TODO: [핵심 로직 구현] - _________________________________________ - } -} -``` - -**답변:** -```solidity -// 완성된 코드를 여기에 작성하세요 -``` - -**왜 이렇게 작성했나요:** - - - ---- - -## 실제 예시: 재진입 방어 - -### 예시 1: 취약점 찾기 - -```markdown -## 문제 7: 재진입 공격 (취약점 찾기) - -다음 코드에서 보안 취약점을 찾으세요: - -```solidity -// BAD CODE - 취약점 찾기 -contract Vault { - mapping(address => uint256) public balances; - - function deposit() public payable { - balances[msg.sender] += msg.value; - } - - function withdraw(uint256 amount) public { - require(balances[msg.sender] >= amount, "Insufficient balance"); - - // 외부 호출 - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Transfer failed"); - - // 상태 변경 - balances[msg.sender] -= amount; - } -} -``` - -**1) 발견한 취약점:** - - -**2) 왜 이것이 문제인가:** - - -**3) 올바른 수정 방법:** -```solidity -// GOOD CODE - 수정된 버전 -``` -``` - -### 예시 2: 빈칸 채우기 - -```markdown -## 문제 8: ReentrancyGuard 적용 (빈칸 채우기) - -다음 코드의 빈칸을 채워서 재진입 공격을 방어하세요: - -```solidity -// TODO: OpenZeppelin 라이브러리 import -_________________________________________ - -// TODO: 상속 추가 -contract SecureVault _________________ { - mapping(address => uint256) public balances; - - function deposit() public payable { - balances[msg.sender] += msg.value; - } - - // TODO: modifier 추가 - function withdraw(uint256 amount) public _________________ { - require(balances[msg.sender] >= amount, "Insufficient"); - balances[msg.sender] -= amount; - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Failed"); - } -} -``` - -**답변:** -```solidity -// 완성된 코드를 여기에 작성 -``` - -**왜 이렇게 작성했나요:** - -``` - ---- - -## 작성 가이드라인 - -### 취약점 찾기 문제 작성 시 - -1. **// BAD CODE** 주석으로 취약한 코드임을 명시 -2. 취약점은 1-2개로 제한 (너무 많으면 혼란) -3. 취약점이 있는 줄 근처에 ⚠️ 힌트 가능 -4. **// GOOD CODE** 섹션에서 수정 코드 작성 유도 - -### 빈칸 채우기 문제 작성 시 - -1. **// TODO:** 주석으로 채워야 할 부분 표시 -2. **___________** 로 빈칸 표시 -3. // 힌트: 를 통해 방향 제시 가능 -4. 완성된 코드와 **이유 설명** 모두 요구 - ---- - -## 체크리스트 - -답변 작성 시 확인하세요: - -- [ ] 취약점 이름을 정확히 식별했는가? -- [ ] 왜 문제인지 설명했는가? (공격 시나리오) -- [ ] 수정된 코드가 실행 가능한가? (문법 오류는 크게 감점하지 않음) -- [ ] 빈칸 채우기는 논리적으로 올바른가? -- [ ] 왜 그렇게 작성했는지 설명했는가? +# 코드 문제 템플릿 + +> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. +> 코드 문제는 **취약점 발견**과 **빈칸 채우기** 두 가지 형식이 있습니다. + +--- + +## 형식 1: 취약점 찾기 (Bug Finding) + +### 문제 [N]: [카테고리] (취약점 찾기) + +다음 코드에서 보안 취약점을 찾으세요: + +```solidity +// BAD CODE - 취약점 찾기 +contract VulnerableContract { + mapping(address => uint256) public balances; + + function withdraw(uint256 amount) public { + require(balances[msg.sender] >= amount, "Insufficient"); + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Failed"); + balances[msg.sender] -= amount; // ⚠️ 문제 위치 + } +} +``` + +**1) 발견한 취약점:** + + + +**2) 왜 이것이 문제인가:** + + + +**3) 올바른 수정 방법:** +```solidity +// GOOD CODE - 수정된 버전을 작성하세요 +``` + +--- + +## 형식 2: 빈칸 채우기 (Fill-in-the-Blank) + +### 문제 [N]: [카테고리] (빈칸 채우기) + +다음 코드의 빈칸을 채워서 [목표]를 달성하세요: + +```solidity +// TODO: [필요한 import 또는 선언] +_________________________________________ + +contract SecureContract { + // TODO: [필요한 변수 선언] + _________________________________________ + + function safeFunction() public { + // TODO: [핵심 로직 구현] + _________________________________________ + } +} +``` + +**답변:** +```solidity +// 완성된 코드를 여기에 작성하세요 +``` + +**왜 이렇게 작성했나요:** + + + +--- + +## 실제 예시: 재진입 방어 + +### 예시 1: 취약점 찾기 + +```markdown +## 문제 7: 재진입 공격 (취약점 찾기) + +다음 코드에서 보안 취약점을 찾으세요: + +```solidity +// BAD CODE - 취약점 찾기 +contract Vault { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw(uint256 amount) public { + require(balances[msg.sender] >= amount, "Insufficient balance"); + + // 외부 호출 + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + + // 상태 변경 + balances[msg.sender] -= amount; + } +} +``` + +**1) 발견한 취약점:** + + +**2) 왜 이것이 문제인가:** + + +**3) 올바른 수정 방법:** +```solidity +// GOOD CODE - 수정된 버전 +``` +``` + +### 예시 2: 빈칸 채우기 + +```markdown +## 문제 8: ReentrancyGuard 적용 (빈칸 채우기) + +다음 코드의 빈칸을 채워서 재진입 공격을 방어하세요: + +```solidity +// TODO: OpenZeppelin 라이브러리 import +_________________________________________ + +// TODO: 상속 추가 +contract SecureVault _________________ { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + // TODO: modifier 추가 + function withdraw(uint256 amount) public _________________ { + require(balances[msg.sender] >= amount, "Insufficient"); + balances[msg.sender] -= amount; + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Failed"); + } +} +``` + +**답변:** +```solidity +// 완성된 코드를 여기에 작성 +``` + +**왜 이렇게 작성했나요:** + +``` + +--- + +## 작성 가이드라인 + +### 취약점 찾기 문제 작성 시 + +1. **// BAD CODE** 주석으로 취약한 코드임을 명시 +2. 취약점은 1-2개로 제한 (너무 많으면 혼란) +3. 취약점이 있는 줄 근처에 ⚠️ 힌트 가능 +4. **// GOOD CODE** 섹션에서 수정 코드 작성 유도 + +### 빈칸 채우기 문제 작성 시 + +1. **// TODO:** 주석으로 채워야 할 부분 표시 +2. **___________** 로 빈칸 표시 +3. // 힌트: 를 통해 방향 제시 가능 +4. 완성된 코드와 **이유 설명** 모두 요구 + +--- + +## 체크리스트 + +답변 작성 시 확인하세요: + +- [ ] 취약점 이름을 정확히 식별했는가? +- [ ] 왜 문제인지 설명했는가? (공격 시나리오) +- [ ] 수정된 코드가 실행 가능한가? (문법 오류는 크게 감점하지 않음) +- [ ] 빈칸 채우기는 논리적으로 올바른가? +- [ ] 왜 그렇게 작성했는지 설명했는가? diff --git a/templates/quiz-diagram.md b/templates/quiz-diagram.md index abb61ffc..b75802f2 100644 --- a/templates/quiz-diagram.md +++ b/templates/quiz-diagram.md @@ -1,158 +1,158 @@ -# 다이어그램 해석 문제 템플릿 - -> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. -> 다이어그램 문제는 **시각적 이해력**과 **흐름 분석 능력**을 테스트합니다. - ---- - -## 문제 [N]: [카테고리] (다이어그램 해석) - -다음 다이어그램은 [상황/흐름]을 보여줍니다: - -```mermaid -sequenceDiagram - participant A as [참여자 A] - participant B as [참여자 B] - - A->>B: [액션 1] - B->>B: [내부 처리] - B->>A: [응답] -``` - -또는 - -```mermaid -flowchart TD - A[시작] --> B{조건 확인} - B -->|참| C[처리 A] - B -->|거짓| D[처리 B] - C --> E[종료] - D --> E -``` - -**질문:** - -1) [흐름/상태에 대한 질문 - "왜" 이런 일이 일어나는지] - - -2) [문제점/개선점에 대한 질문 - "어떻게" 수정할 수 있는지] - - -3) [변경된 다이어그램 질문 - 수정 후 어떻게 바뀌는지] (선택) - - ---- - -## 실제 예시: 재진입 공격 흐름 - -```markdown -## 문제 10: 재진입 공격 흐름 (다이어그램 해석) - -다음 다이어그램은 재진입 공격의 흐름을 보여줍니다: - -```mermaid -sequenceDiagram - participant A as Attacker - participant V as Vault - - A->>V: withdraw(1 ETH) - V->>V: Check balance (OK) - V->>A: Send 1 ETH - A->>A: receive() triggered - A->>V: withdraw(1 ETH) again! - V->>V: Check balance (still OK!) - V->>A: Send 1 ETH again - V->>V: Update balance (too late!) -``` - -**질문:** - -1) 왜 두 번째 `Check balance`가 통과하나요? - - -2) 이 문제를 막으려면 다이어그램의 어느 단계를 어디로 옮겨야 하나요? - - -3) CEI 패턴을 적용하면 수정된 흐름은 어떻게 바뀌나요? (간단히 설명) - -``` - ---- - -## 다이어그램 유형별 예시 - -### 1. Sequence Diagram (시퀀스 다이어그램) -트랜잭션 흐름, 컨트랙트 간 호출, 공격 시나리오에 사용 - -```mermaid -sequenceDiagram - participant User - participant Contract - participant Token - - User->>Contract: deposit(100) - Contract->>Token: transferFrom(user, contract, 100) - Token-->>Contract: success - Contract-->>User: Deposit complete -``` - -### 2. Flowchart (플로우차트) -의사결정, 상태 전이, 조건 분기에 사용 - -```mermaid -flowchart TD - A[Transaction Received] --> B{Gas >= Required?} - B -->|Yes| C{Valid Signature?} - B -->|No| D[Revert: Out of Gas] - C -->|Yes| E[Execute] - C -->|No| F[Revert: Invalid Sig] - E --> G{Success?} - G -->|Yes| H[Commit State] - G -->|No| I[Revert Changes] -``` - -### 3. State Diagram (상태 다이어그램) -컨트랙트 상태, 라이프사이클에 사용 - -```mermaid -stateDiagram-v2 - [*] --> Pending: createProposal() - Pending --> Active: startVoting() - Active --> Succeeded: quorum reached - Active --> Defeated: voting ended - Succeeded --> Executed: execute() - Defeated --> [*] - Executed --> [*] -``` - ---- - -## 작성 가이드라인 - -### 질문 작성 시 - -1. **1번 질문**: 현재 상태/흐름 이해 (왜 이런 일이 일어나는가?) -2. **2번 질문**: 문제점 식별 및 해결책 (어떻게 고칠 수 있는가?) -3. **3번 질문**: 변경 후 결과 예측 (수정하면 어떻게 바뀌는가?) - -### 다이어그램 선택 기준 - -| 주제 | 권장 다이어그램 | -|------|-----------------| -| 트랜잭션 흐름 | sequenceDiagram | -| 공격 시나리오 | sequenceDiagram | -| 조건 분기 로직 | flowchart | -| 컨트랙트 상태 | stateDiagram-v2 | -| 아키텍처 구조 | flowchart | - ---- - -## 체크리스트 - -답변 작성 시 확인하세요: - -- [ ] 다이어그램의 각 단계를 이해했는가? -- [ ] "왜" 질문에 대한 논리적 설명을 했는가? -- [ ] 문제점을 정확히 식별했는가? -- [ ] 해결책이 실제로 문제를 해결하는가? -- [ ] 수정된 흐름을 설명할 수 있는가? +# 다이어그램 해석 문제 템플릿 + +> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. +> 다이어그램 문제는 **시각적 이해력**과 **흐름 분석 능력**을 테스트합니다. + +--- + +## 문제 [N]: [카테고리] (다이어그램 해석) + +다음 다이어그램은 [상황/흐름]을 보여줍니다: + +```mermaid +sequenceDiagram + participant A as [참여자 A] + participant B as [참여자 B] + + A->>B: [액션 1] + B->>B: [내부 처리] + B->>A: [응답] +``` + +또는 + +```mermaid +flowchart TD + A[시작] --> B{조건 확인} + B -->|참| C[처리 A] + B -->|거짓| D[처리 B] + C --> E[종료] + D --> E +``` + +**질문:** + +1) [흐름/상태에 대한 질문 - "왜" 이런 일이 일어나는지] + + +2) [문제점/개선점에 대한 질문 - "어떻게" 수정할 수 있는지] + + +3) [변경된 다이어그램 질문 - 수정 후 어떻게 바뀌는지] (선택) + + +--- + +## 실제 예시: 재진입 공격 흐름 + +```markdown +## 문제 10: 재진입 공격 흐름 (다이어그램 해석) + +다음 다이어그램은 재진입 공격의 흐름을 보여줍니다: + +```mermaid +sequenceDiagram + participant A as Attacker + participant V as Vault + + A->>V: withdraw(1 ETH) + V->>V: Check balance (OK) + V->>A: Send 1 ETH + A->>A: receive() triggered + A->>V: withdraw(1 ETH) again! + V->>V: Check balance (still OK!) + V->>A: Send 1 ETH again + V->>V: Update balance (too late!) +``` + +**질문:** + +1) 왜 두 번째 `Check balance`가 통과하나요? + + +2) 이 문제를 막으려면 다이어그램의 어느 단계를 어디로 옮겨야 하나요? + + +3) CEI 패턴을 적용하면 수정된 흐름은 어떻게 바뀌나요? (간단히 설명) + +``` + +--- + +## 다이어그램 유형별 예시 + +### 1. Sequence Diagram (시퀀스 다이어그램) +트랜잭션 흐름, 컨트랙트 간 호출, 공격 시나리오에 사용 + +```mermaid +sequenceDiagram + participant User + participant Contract + participant Token + + User->>Contract: deposit(100) + Contract->>Token: transferFrom(user, contract, 100) + Token-->>Contract: success + Contract-->>User: Deposit complete +``` + +### 2. Flowchart (플로우차트) +의사결정, 상태 전이, 조건 분기에 사용 + +```mermaid +flowchart TD + A[Transaction Received] --> B{Gas >= Required?} + B -->|Yes| C{Valid Signature?} + B -->|No| D[Revert: Out of Gas] + C -->|Yes| E[Execute] + C -->|No| F[Revert: Invalid Sig] + E --> G{Success?} + G -->|Yes| H[Commit State] + G -->|No| I[Revert Changes] +``` + +### 3. State Diagram (상태 다이어그램) +컨트랙트 상태, 라이프사이클에 사용 + +```mermaid +stateDiagram-v2 + [*] --> Pending: createProposal() + Pending --> Active: startVoting() + Active --> Succeeded: quorum reached + Active --> Defeated: voting ended + Succeeded --> Executed: execute() + Defeated --> [*] + Executed --> [*] +``` + +--- + +## 작성 가이드라인 + +### 질문 작성 시 + +1. **1번 질문**: 현재 상태/흐름 이해 (왜 이런 일이 일어나는가?) +2. **2번 질문**: 문제점 식별 및 해결책 (어떻게 고칠 수 있는가?) +3. **3번 질문**: 변경 후 결과 예측 (수정하면 어떻게 바뀌는가?) + +### 다이어그램 선택 기준 + +| 주제 | 권장 다이어그램 | +|------|-----------------| +| 트랜잭션 흐름 | sequenceDiagram | +| 공격 시나리오 | sequenceDiagram | +| 조건 분기 로직 | flowchart | +| 컨트랙트 상태 | stateDiagram-v2 | +| 아키텍처 구조 | flowchart | + +--- + +## 체크리스트 + +답변 작성 시 확인하세요: + +- [ ] 다이어그램의 각 단계를 이해했는가? +- [ ] "왜" 질문에 대한 논리적 설명을 했는가? +- [ ] 문제점을 정확히 식별했는가? +- [ ] 해결책이 실제로 문제를 해결하는가? +- [ ] 수정된 흐름을 설명할 수 있는가? diff --git a/templates/quiz-multiple-choice.md b/templates/quiz-multiple-choice.md index 3480278e..718e7739 100644 --- a/templates/quiz-multiple-choice.md +++ b/templates/quiz-multiple-choice.md @@ -1,78 +1,78 @@ -# 객관식 문제 템플릿 - -> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. -> 객관식 문제는 단순 암기가 아닌 **개념 이해**를 테스트합니다. - ---- - -## 문제 [N]: [카테고리] (객관식) - -[시나리오 기반 질문을 작성합니다] - -다음 상황을 고려하세요: - -```solidity -// 상황 설명 코드 (필요한 경우) -``` - -[구체적인 질문 - "왜", "어떻게", "무엇이 일어나는지"를 묻습니다] - -**보기:** -A) [선택지 1 - 명확하고 구체적으로] -B) [선택지 2] -C) [선택지 3] -D) [선택지 4] - -**답변:** - - - ---- - -## 작성 가이드라인 - -### 좋은 질문 예시 - -```markdown -## 문제 1: 재진입 공격 (객관식) - -다음 상황을 고려하세요: - -```solidity -function withdraw(uint256 amount) public { - require(balances[msg.sender] >= amount, "Insufficient"); - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Failed"); - balances[msg.sender] -= amount; -} -``` - -공격자가 `receive()` 함수에서 다시 `withdraw()`를 호출하면 어떻게 되나요? - -**보기:** -A) `balances` 체크에서 실패해서 revert된다 -B) `balances`가 아직 차감되지 않아서 반복 인출이 가능하다 -C) Solidity가 자동으로 재진입을 방지한다 -D) 가스 한도에 걸려서 자동으로 멈춘다 - -**답변:** - -``` - -### 피해야 할 질문 (암기 위주) - -- 나쁜 예: "`view` 키워드의 의미는?" -- 좋은 예: "왜 `view` 함수는 상태를 수정할 수 없나요? 시도하면 무슨 일이 일어나나요?" - ---- - -## 체크리스트 - -답변 작성 시 확인하세요: - -- [ ] 정답 알파벳만이 아닌 **이유**까지 설명했는가? -- [ ] 다른 보기가 왜 틀린지 이해하고 있는가? -- [ ] 실제 상황에서 어떻게 적용되는지 설명할 수 있는가? +# 객관식 문제 템플릿 + +> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. +> 객관식 문제는 단순 암기가 아닌 **개념 이해**를 테스트합니다. + +--- + +## 문제 [N]: [카테고리] (객관식) + +[시나리오 기반 질문을 작성합니다] + +다음 상황을 고려하세요: + +```solidity +// 상황 설명 코드 (필요한 경우) +``` + +[구체적인 질문 - "왜", "어떻게", "무엇이 일어나는지"를 묻습니다] + +**보기:** +A) [선택지 1 - 명확하고 구체적으로] +B) [선택지 2] +C) [선택지 3] +D) [선택지 4] + +**답변:** + + + +--- + +## 작성 가이드라인 + +### 좋은 질문 예시 + +```markdown +## 문제 1: 재진입 공격 (객관식) + +다음 상황을 고려하세요: + +```solidity +function withdraw(uint256 amount) public { + require(balances[msg.sender] >= amount, "Insufficient"); + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Failed"); + balances[msg.sender] -= amount; +} +``` + +공격자가 `receive()` 함수에서 다시 `withdraw()`를 호출하면 어떻게 되나요? + +**보기:** +A) `balances` 체크에서 실패해서 revert된다 +B) `balances`가 아직 차감되지 않아서 반복 인출이 가능하다 +C) Solidity가 자동으로 재진입을 방지한다 +D) 가스 한도에 걸려서 자동으로 멈춘다 + +**답변:** + +``` + +### 피해야 할 질문 (암기 위주) + +- 나쁜 예: "`view` 키워드의 의미는?" +- 좋은 예: "왜 `view` 함수는 상태를 수정할 수 없나요? 시도하면 무슨 일이 일어나나요?" + +--- + +## 체크리스트 + +답변 작성 시 확인하세요: + +- [ ] 정답 알파벳만이 아닌 **이유**까지 설명했는가? +- [ ] 다른 보기가 왜 틀린지 이해하고 있는가? +- [ ] 실제 상황에서 어떻게 적용되는지 설명할 수 있는가? diff --git a/templates/quiz-short-answer.md b/templates/quiz-short-answer.md index 252cd724..b980d536 100644 --- a/templates/quiz-short-answer.md +++ b/templates/quiz-short-answer.md @@ -1,88 +1,88 @@ -# 단답형 문제 템플릿 - -> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. -> 단답형 문제는 **"왜"와 "어떻게"**를 묻습니다. 단순 정의가 아닌 이해를 설명하세요. - ---- - -## 문제 [N]: [카테고리] (단답형) - -[개념 이해를 묻는 질문을 작성합니다] - -**왜** [현상/패턴]이 [결과]를 가져오나요? 구체적인 예시와 함께 설명하세요. - -또는 - -**어떻게** [메커니즘]이 작동하나요? 단계별로 설명하세요. - -**답변:** - - - ---- - -## 작성 가이드라인 - -### 좋은 질문 예시 - -```markdown -## 문제 1: CEI 패턴 (단답형) - -Checks-Effects-Interactions (CEI) 패턴에서 **왜** "Effects"가 "Interactions"보다 먼저 와야 하나요? -구체적인 예시와 함께 설명하세요. - -**답변:** - -``` - -```markdown -## 문제 2: Gas 개념 (단답형) - -이더리움에서 Gas Fee가 높아지면 네트워크에 **어떻게** 영향을 미치나요? -사용자와 채굴자/검증자 관점에서 각각 설명하세요. - -**답변:** - -``` - -### 피해야 할 질문 - -- 나쁜 예: "Gas란 무엇인가요?" -- 좋은 예: "왜 이더리움은 Gas 시스템을 사용하나요? 다른 방식과 비교하면 어떤 장점이 있나요?" - ---- - -## 답변 작성 팁 - -### 좋은 답변 구조 - -1. **핵심 답변** (1문장): 질문에 대한 직접적인 답 -2. **이유 설명** (1-2문장): 왜 그런지 설명 -3. **예시/적용** (선택사항): 구체적인 예시나 실제 적용 사례 - -### 예시 - -**질문:** CEI 패턴에서 왜 "Effects"가 "Interactions"보다 먼저 와야 하나요? - -**답변:** -``` -Effects(상태 변경)가 Interactions(외부 호출) 전에 와야 재진입 공격을 방지할 수 있습니다. -외부 호출 시 상대방 컨트랙트가 다시 호출할 수 있는데, 이때 상태가 이미 변경되어 있으면 -중복 실행이 불가능합니다. 예를 들어, withdraw() 함수에서 잔액을 먼저 0으로 만들면 -재진입 시 잔액 체크에서 실패합니다. -``` - ---- - -## 체크리스트 - -답변 작성 시 확인하세요: - -- [ ] "왜" 또는 "어떻게"에 대한 설명이 포함되어 있는가? -- [ ] 2-3문장 이상으로 충분히 설명했는가? -- [ ] 정의만 나열하지 않고 이해를 보여주는가? -- [ ] 예시나 비유를 활용했는가? (권장) +# 단답형 문제 템플릿 + +> **참고:** 이 템플릿을 복사하여 주차별 퀴즈에 사용하세요. +> 단답형 문제는 **"왜"와 "어떻게"**를 묻습니다. 단순 정의가 아닌 이해를 설명하세요. + +--- + +## 문제 [N]: [카테고리] (단답형) + +[개념 이해를 묻는 질문을 작성합니다] + +**왜** [현상/패턴]이 [결과]를 가져오나요? 구체적인 예시와 함께 설명하세요. + +또는 + +**어떻게** [메커니즘]이 작동하나요? 단계별로 설명하세요. + +**답변:** + + + +--- + +## 작성 가이드라인 + +### 좋은 질문 예시 + +```markdown +## 문제 1: CEI 패턴 (단답형) + +Checks-Effects-Interactions (CEI) 패턴에서 **왜** "Effects"가 "Interactions"보다 먼저 와야 하나요? +구체적인 예시와 함께 설명하세요. + +**답변:** + +``` + +```markdown +## 문제 2: Gas 개념 (단답형) + +이더리움에서 Gas Fee가 높아지면 네트워크에 **어떻게** 영향을 미치나요? +사용자와 채굴자/검증자 관점에서 각각 설명하세요. + +**답변:** + +``` + +### 피해야 할 질문 + +- 나쁜 예: "Gas란 무엇인가요?" +- 좋은 예: "왜 이더리움은 Gas 시스템을 사용하나요? 다른 방식과 비교하면 어떤 장점이 있나요?" + +--- + +## 답변 작성 팁 + +### 좋은 답변 구조 + +1. **핵심 답변** (1문장): 질문에 대한 직접적인 답 +2. **이유 설명** (1-2문장): 왜 그런지 설명 +3. **예시/적용** (선택사항): 구체적인 예시나 실제 적용 사례 + +### 예시 + +**질문:** CEI 패턴에서 왜 "Effects"가 "Interactions"보다 먼저 와야 하나요? + +**답변:** +``` +Effects(상태 변경)가 Interactions(외부 호출) 전에 와야 재진입 공격을 방지할 수 있습니다. +외부 호출 시 상대방 컨트랙트가 다시 호출할 수 있는데, 이때 상태가 이미 변경되어 있으면 +중복 실행이 불가능합니다. 예를 들어, withdraw() 함수에서 잔액을 먼저 0으로 만들면 +재진입 시 잔액 체크에서 실패합니다. +``` + +--- + +## 체크리스트 + +답변 작성 시 확인하세요: + +- [ ] "왜" 또는 "어떻게"에 대한 설명이 포함되어 있는가? +- [ ] 2-3문장 이상으로 충분히 설명했는가? +- [ ] 정의만 나열하지 않고 이해를 보여주는가? +- [ ] 예시나 비유를 활용했는가? (권장) diff --git a/week-01/dev/README.md b/week-01/dev/README.md index 6f6f80ae..b2f6567a 100644 --- a/week-01/dev/README.md +++ b/week-01/dev/README.md @@ -1,105 +1,105 @@ -# Week 1: Solidity 기초 - -> Counter 컨트랙트를 완성하여 Solidity의 기본 문법을 학습합니다. - ---- - -## 학습 목표 - -이 과제를 완료하면 다음을 이해하게 됩니다: - -- 상태 변수(state variable) 선언과 사용 -- `public`, `view` 함수의 차이점 -- `require`를 사용한 조건 검사 -- Foundry 테스트 실행 방법 - ---- - -## 파일 구조 - -``` -week-01/dev/ -├── src/ -│ └── Counter.sol # 과제 파일 (TODO를 구현하세요) -├── test/ -│ └── Counter.t.sol # 테스트 파일 (수정하지 마세요) -├── script/ -│ └── .gitkeep -└── README.md # 이 파일 -``` - ---- - -## 과제 안내 - -### 구현할 함수 (Counter.sol) - -| 함수 | 설명 | 힌트 | -|------|------|------| -| `increment()` | count를 1 증가 | `count += 1;` | -| `decrement()` | count를 1 감소 (0이면 revert) | `require(count > 0, ...)` | -| `reset()` | count를 0으로 초기화 | `count = 0;` | - -### TODO 완성 방법 - -1. `src/Counter.sol` 파일을 엽니다 -2. `// TODO:` 주석이 있는 부분을 찾습니다 -3. 힌트를 참고하여 로직을 구현합니다 -4. 테스트를 실행하여 확인합니다 - ---- - -## 테스트 실행 - -### 프로젝트 루트에서 실행 - -```bash -# eth-homework 디렉토리에서 -cd eth-homework - -# Week 1 테스트만 실행 -forge test --match-path week-01/dev/test/Counter.t.sol -vv - -# 모든 테스트 실행 -forge test -vv -``` - -### 테스트 출력 해석 - -``` -[PASS] test_InitialCount_IsZero() # 초기값 0 확인 -[PASS] test_Increment_IncreasesCount() # increment 동작 확인 -[PASS] test_Decrement_DecreasesCount() # decrement 동작 확인 -[PASS] test_RevertWhen_DecrementBelowZero() # 0에서 감소 시 revert -[PASS] test_Reset_SetsCountToZero() # reset 동작 확인 -``` - -모든 테스트가 `[PASS]`로 표시되면 과제 완료입니다! - ---- - -## 통과 기준 - -- [ ] 5개 테스트 모두 통과 -- [ ] `forge build` 시 컴파일 에러 없음 -- [ ] PR 제출 시 CI 통과 - ---- - -## 참고 자료 - -- [Solidity 기초 문법 가이드](../../../eth-materials/week-01/dev/solidity-basics.md) -- [Solidity 공식 문서](https://docs.soliditylang.org/) -- [Foundry Book](https://book.getfoundry.sh/) - ---- - -## 도움이 필요하면 - -1. `solidity-basics.md` 가이드를 먼저 읽어보세요 -2. 테스트 파일의 주석을 확인하세요 (기대하는 동작이 설명되어 있습니다) -3. Slack에서 질문하세요 - ---- - -*[메인 README로 돌아가기](../../README.md)* +# Week 1: Solidity 기초 + +> Counter 컨트랙트를 완성하여 Solidity의 기본 문법을 학습합니다. + +--- + +## 학습 목표 + +이 과제를 완료하면 다음을 이해하게 됩니다: + +- 상태 변수(state variable) 선언과 사용 +- `public`, `view` 함수의 차이점 +- `require`를 사용한 조건 검사 +- Foundry 테스트 실행 방법 + +--- + +## 파일 구조 + +``` +week-01/dev/ +├── src/ +│ └── Counter.sol # 과제 파일 (TODO를 구현하세요) +├── test/ +│ └── Counter.t.sol # 테스트 파일 (수정하지 마세요) +├── script/ +│ └── .gitkeep +└── README.md # 이 파일 +``` + +--- + +## 과제 안내 + +### 구현할 함수 (Counter.sol) + +| 함수 | 설명 | 힌트 | +|------|------|------| +| `increment()` | count를 1 증가 | `count += 1;` | +| `decrement()` | count를 1 감소 (0이면 revert) | `require(count > 0, ...)` | +| `reset()` | count를 0으로 초기화 | `count = 0;` | + +### TODO 완성 방법 + +1. `src/Counter.sol` 파일을 엽니다 +2. `// TODO:` 주석이 있는 부분을 찾습니다 +3. 힌트를 참고하여 로직을 구현합니다 +4. 테스트를 실행하여 확인합니다 + +--- + +## 테스트 실행 + +### 프로젝트 루트에서 실행 + +```bash +# eth-homework 디렉토리에서 +cd eth-homework + +# Week 1 테스트만 실행 +forge test --match-path week-01/dev/test/Counter.t.sol -vv + +# 모든 테스트 실행 +forge test -vv +``` + +### 테스트 출력 해석 + +``` +[PASS] test_InitialCount_IsZero() # 초기값 0 확인 +[PASS] test_Increment_IncreasesCount() # increment 동작 확인 +[PASS] test_Decrement_DecreasesCount() # decrement 동작 확인 +[PASS] test_RevertWhen_DecrementBelowZero() # 0에서 감소 시 revert +[PASS] test_Reset_SetsCountToZero() # reset 동작 확인 +``` + +모든 테스트가 `[PASS]`로 표시되면 과제 완료입니다! + +--- + +## 통과 기준 + +- [ ] 5개 테스트 모두 통과 +- [ ] `forge build` 시 컴파일 에러 없음 +- [ ] PR 제출 시 CI 통과 + +--- + +## 참고 자료 + +- [Solidity 기초 문법 가이드](../../../eth-materials/week-01/dev/solidity-basics.md) +- [Solidity 공식 문서](https://docs.soliditylang.org/) +- [Foundry Book](https://book.getfoundry.sh/) + +--- + +## 도움이 필요하면 + +1. `solidity-basics.md` 가이드를 먼저 읽어보세요 +2. 테스트 파일의 주석을 확인하세요 (기대하는 동작이 설명되어 있습니다) +3. Slack에서 질문하세요 + +--- + +*[메인 README로 돌아가기](../../README.md)* diff --git a/week-01/dev/src/Counter.sol b/week-01/dev/src/Counter.sol index f9687e3a..9624f5b1 100644 --- a/week-01/dev/src/Counter.sol +++ b/week-01/dev/src/Counter.sol @@ -1,51 +1,55 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -/// @title Counter - Week 1 과제 -/// @notice 간단한 카운터 컨트랙트입니다. TODO 부분을 구현하세요. -/// @dev 이 컨트랙트는 Solidity 기초를 배우기 위한 연습용입니다. -contract Counter { - // ============================================================ - // 상태 변수 (State Variables) - // ============================================================ - - /// @notice 현재 카운트 값을 저장합니다 - /// @dev public으로 선언하면 자동으로 getter 함수가 생성됩니다 - uint256 public count; - - // ============================================================ - // 읽기 함수 (View Functions) - // ============================================================ - - /// @notice 현재 카운트 값을 반환합니다 - /// @return 현재 count 값 - function getCount() public view returns (uint256) { - return count; - } - - // ============================================================ - // 쓰기 함수 (State-Changing Functions) - // ============================================================ - - /// @notice 카운트를 1 증가시킵니다 - /// @dev count 값을 1만큼 증가시키는 로직을 구현하세요 - function increment() public { - // TODO: count를 1 증가시키세요 - // 힌트: count += 1; 또는 count = count + 1; 또는 count++; - } - - /// @notice 카운트를 1 감소시킵니다 - /// @dev count가 0일 때 감소시키면 언더플로우가 발생합니다 - function decrement() public { - // TODO: count를 1 감소시키세요. 단, count가 0이면 revert해야 합니다. - // 힌트: require(조건, "에러 메시지"); 를 사용하세요 - // 힌트: require(count > 0, "Count cannot go below zero"); - } - - /// @notice 카운트를 0으로 초기화합니다 - /// @dev count 값을 0으로 설정하는 로직을 구현하세요 - function reset() public { - // TODO: count를 0으로 초기화하세요 - // 힌트: count = 0; - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/// @title Counter - Week 1 과제 +/// @notice 간단한 카운터 컨트랙트입니다. TODO 부분을 구현하세요. +/// @dev 이 컨트랙트는 Solidity 기초를 배우기 위한 연습용입니다. +contract Counter { + // ============================================================ + // 상태 변수 (State Variables) + // ============================================================ + + /// @notice 현재 카운트 값을 저장합니다 + /// @dev public으로 선언하면 자동으로 getter 함수가 생성됩니다 + uint256 public count; + + // ============================================================ + // 읽기 함수 (View Functions) + // ============================================================ + + /// @notice 현재 카운트 값을 반환합니다 + /// @return 현재 count 값 + function getCount() public view returns (uint256) { + return count; + } + + // ============================================================ + // 쓰기 함수 (State-Changing Functions) + // ============================================================ + + /// @notice 카운트를 1 증가시킵니다 + /// @dev count 값을 1만큼 증가시키는 로직을 구현하세요 + function increment() public { + // TODO: count를 1 증가시키세요 + // 힌트: count += 1; 또는 count = count + 1; 또는 count++; + count++; + } + + /// @notice 카운트를 1 감소시킵니다 + /// @dev count가 0일 때 감소시키면 언더플로우가 발생합니다 + function decrement() public { + // TODO: count를 1 감소시키세요. 단, count가 0이면 revert해야 합니다. + // 힌트: require(조건, "에러 메시지"); 를 사용하세요 + // 힌트: require(count > 0, "Count cannot go below zero"); + require(count > 0, "Count cannot go below zero"); + count--; + } + + /// @notice 카운트를 0으로 초기화합니다 + /// @dev count 값을 0으로 설정하는 로직을 구현하세요 + function reset() public { + // TODO: count를 0으로 초기화하세요 + // 힌트: count = 0; + count = 0; + } +} diff --git a/week-01/dev/test/Counter.t.sol b/week-01/dev/test/Counter.t.sol index 3f9c482c..c66623be 100644 --- a/week-01/dev/test/Counter.t.sol +++ b/week-01/dev/test/Counter.t.sol @@ -1,97 +1,97 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import "forge-std/Test.sol"; -import "../src/Counter.sol"; - -/// @title CounterTest - Week 1 테스트 -/// @notice 이 테스트들이 모두 통과하도록 Counter.sol의 TODO를 구현하세요 -/// @dev Foundry의 기본 테스트 패턴을 학습합니다 -contract CounterTest is Test { - // ============================================================ - // 테스트 상태 변수 - // ============================================================ - - /// @notice 테스트할 Counter 컨트랙트 인스턴스 - Counter public counter; - - // ============================================================ - // setUp 함수 - // ============================================================ - - /// @notice 각 테스트 전에 실행되는 설정 함수 - /// @dev 매 테스트마다 새로운 Counter 인스턴스가 생성됩니다 (테스트 격리) - function setUp() public { - // 새로운 Counter 컨트랙트 배포 - counter = new Counter(); - } - - // ============================================================ - // 테스트 함수들 - // ============================================================ - - /// @notice 초기 count 값이 0인지 확인합니다 - /// @dev 상태 변수의 기본값 테스트 - function test_InitialCount_IsZero() public view { - // Arrange: setUp에서 이미 counter가 생성됨 - - // Act: count 값 조회 - uint256 currentCount = counter.count(); - - // Assert: 초기값이 0인지 확인 - assertEq(currentCount, 0, "Initial count should be 0"); - } - - /// @notice increment 함수가 count를 1 증가시키는지 확인합니다 - /// @dev 기본 상태 변경 테스트 - function test_Increment_IncreasesCount() public { - // Arrange: 초기 상태 (count = 0) - - // Act: increment 호출 - counter.increment(); - - // Assert: count가 1이 되었는지 확인 - assertEq(counter.count(), 1, "Count should be 1 after increment"); - } - - /// @notice decrement 함수가 count를 1 감소시키는지 확인합니다 - /// @dev 두 번 증가 후 한 번 감소하여 count = 1 확인 - function test_Decrement_DecreasesCount() public { - // Arrange: count를 2로 만듦 - counter.increment(); - counter.increment(); - - // Act: decrement 호출 - counter.decrement(); - - // Assert: count가 1이 되었는지 확인 - assertEq(counter.count(), 1, "Count should be 1 after two increments and one decrement"); - } - - /// @notice count가 0일 때 decrement를 호출하면 revert되는지 확인합니다 - /// @dev vm.expectRevert를 사용한 예외 테스트 - function test_RevertWhen_DecrementBelowZero() public { - // Arrange: count = 0 (초기 상태) - - // Assert: 다음 호출이 revert될 것을 예상 - vm.expectRevert("Count cannot go below zero"); - - // Act: decrement 호출 (0에서 감소 시도) - counter.decrement(); - } - - /// @notice reset 함수가 count를 0으로 초기화하는지 확인합니다 - /// @dev 증가 후 reset하여 0으로 돌아가는지 확인 - function test_Reset_SetsCountToZero() public { - // Arrange: count를 3으로 만듦 - counter.increment(); - counter.increment(); - counter.increment(); - - // Act: reset 호출 - counter.reset(); - - // Assert: count가 0이 되었는지 확인 - assertEq(counter.count(), 0, "Count should be 0 after reset"); - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +/// @title CounterTest - Week 1 테스트 +/// @notice 이 테스트들이 모두 통과하도록 Counter.sol의 TODO를 구현하세요 +/// @dev Foundry의 기본 테스트 패턴을 학습합니다 +contract CounterTest is Test { + // ============================================================ + // 테스트 상태 변수 + // ============================================================ + + /// @notice 테스트할 Counter 컨트랙트 인스턴스 + Counter public counter; + + // ============================================================ + // setUp 함수 + // ============================================================ + + /// @notice 각 테스트 전에 실행되는 설정 함수 + /// @dev 매 테스트마다 새로운 Counter 인스턴스가 생성됩니다 (테스트 격리) + function setUp() public { + // 새로운 Counter 컨트랙트 배포 + counter = new Counter(); + } + + // ============================================================ + // 테스트 함수들 + // ============================================================ + + /// @notice 초기 count 값이 0인지 확인합니다 + /// @dev 상태 변수의 기본값 테스트 + function test_InitialCount_IsZero() public view { + // Arrange: setUp에서 이미 counter가 생성됨 + + // Act: count 값 조회 + uint256 currentCount = counter.count(); + + // Assert: 초기값이 0인지 확인 + assertEq(currentCount, 0, "Initial count should be 0"); + } + + /// @notice increment 함수가 count를 1 증가시키는지 확인합니다 + /// @dev 기본 상태 변경 테스트 + function test_Increment_IncreasesCount() public { + // Arrange: 초기 상태 (count = 0) + + // Act: increment 호출 + counter.increment(); + + // Assert: count가 1이 되었는지 확인 + assertEq(counter.count(), 1, "Count should be 1 after increment"); + } + + /// @notice decrement 함수가 count를 1 감소시키는지 확인합니다 + /// @dev 두 번 증가 후 한 번 감소하여 count = 1 확인 + function test_Decrement_DecreasesCount() public { + // Arrange: count를 2로 만듦 + counter.increment(); + counter.increment(); + + // Act: decrement 호출 + counter.decrement(); + + // Assert: count가 1이 되었는지 확인 + assertEq(counter.count(), 1, "Count should be 1 after two increments and one decrement"); + } + + /// @notice count가 0일 때 decrement를 호출하면 revert되는지 확인합니다 + /// @dev vm.expectRevert를 사용한 예외 테스트 + function test_RevertWhen_DecrementBelowZero() public { + // Arrange: count = 0 (초기 상태) + + // Assert: 다음 호출이 revert될 것을 예상 + vm.expectRevert("Count cannot go below zero"); + + // Act: decrement 호출 (0에서 감소 시도) + counter.decrement(); + } + + /// @notice reset 함수가 count를 0으로 초기화하는지 확인합니다 + /// @dev 증가 후 reset하여 0으로 돌아가는지 확인 + function test_Reset_SetsCountToZero() public { + // Arrange: count를 3으로 만듦 + counter.increment(); + counter.increment(); + counter.increment(); + + // Act: reset 호출 + counter.reset(); + + // Assert: count가 0이 되었는지 확인 + assertEq(counter.count(), 0, "Count should be 0 after reset"); + } +} diff --git a/week-01/quiz/quiz-01-solution.md b/week-01/quiz/quiz-01-solution.md new file mode 100644 index 00000000..5925eb62 --- /dev/null +++ b/week-01/quiz/quiz-01-solution.md @@ -0,0 +1,265 @@ +# Week 1 퀴즈: State/Account + Solidity 기초 + +**제출 방법:** +1. 이 파일을 복사하여 `quiz-01-solution.md`로 저장 +2. 각 문제에 답변 작성 (왜 그런지 설명 포함) +3. Pull Request 생성 (`quiz_submission` 템플릿 사용) + +**평가 기준:** +- 정답 여부보다 **개념 이해도**를 중점 평가합니다 +- "왜"에 대한 설명이 충분한지 확인합니다 +- 문법 오류는 크게 감점하지 않습니다 + +--- + +## 문제 1: [이론] 상태 머신 (객관식) + +이더리움에서 "상태 전이가 원자적(atomic)이다"라는 말의 의미를 가장 잘 설명한 것은? + +다음 상황을 고려하세요: + +``` +Alice가 Bob에게 1 ETH를 보내는 트랜잭션을 실행합니다. +중간에 가스가 부족해져서 트랜잭션이 실패했습니다. +``` + +**보기:** +A) Alice의 잔액만 감소하고 Bob의 잔액은 변하지 않는다 +B) Alice의 잔액과 Bob의 잔액 모두 변하지 않고, 가스비만 소모된다 +C) 네트워크가 자동으로 부족한 가스를 보충해서 트랜잭션을 완료한다 +D) 트랜잭션이 절반만 실행되어 0.5 ETH만 전송된다 + +**답변:** +B) Alice의 잔액과 Bob의 잔액 모두 변하지 않고, 가스비만 소모된다 + +**설명:** +"원자적(Atomic)"이라는 단어는 **"All or Nothing"**을 의미합니다. 트랜잭션 내부의 여러 작업 중 하나라도 실패하면(가스 부족, 조건 미달 등), 이전의 모든 변경 사항이 없었던 것처럼 **롤백(Rollback)**되어 초기 상태로 돌아갑니다. 하지만 트랜잭션을 실행하기 위해 네트워크 자원을 사용했으므로, 검증자에게 지불하는 가스비는 반환되지 않고 소모됩니다. + +--- + +## 문제 2: [이론] 결정론적 실행 (객관식) + +이더리움 EVM이 "결정론적(deterministic)"으로 실행된다는 것의 핵심 이유는 무엇인가요? + +**보기:** +A) 모든 노드가 같은 하드웨어를 사용해야 해서 +B) 같은 입력(트랜잭션)이 주어지면 모든 노드가 같은 결과(상태)를 도출해야 하므로 +C) 중앙 서버가 모든 계산을 수행하고 결과를 배포해서 +D) 트랜잭션이 항상 1초 안에 처리되어야 해서 + +**답변:** +B) 같은 입력(트랜잭션)이 주어지면 모든 노드가 같은 결과(상태)를 도출해야 하므로 + +**설명:** +블록체인은 분산 시스템입니다. 만약 EVM이 결정론적이지 않아서(예: 현재 시간이나 랜덤 함수 사용) 노드마다 결과가 다르게 나온다면, 네트워크 전체가 하나의 통일된 상태에 도달하는 **'합의(Consensus)'**가 불가능해집니다. 결과가 다르면 하드포크가 발생하거나 네트워크가 멈추게 됩니다. + +--- + +## 문제 3: [이론] EOA vs CA (객관식) + +다음 중 EOA(Externally Owned Account)와 CA(Contract Account)의 차이를 올바르게 설명한 것은? + +**보기:** +A) EOA는 코드를 실행할 수 있고, CA는 코드를 실행할 수 없다 +B) EOA만 트랜잭션을 시작할 수 있고, CA는 EOA에 의해 호출될 때만 실행된다 +C) CA만 ETH를 보유할 수 있고, EOA는 ETH를 보유할 수 없다 +D) EOA와 CA는 동일한 기능을 가지며 이름만 다르다 + +**답변:** +B) EOA만 트랜잭션을 시작할 수 있고, CA는 EOA에 의해 호출될 때만 실행된다 + +**설명:** +트랜잭션을 시작하려면 해당 트랜잭션이 유효하다는 것을 증명하는 **'디지털 서명'**이 필요합니다. 서명은 **개인키(Private Key)**를 가진 주체만이 생성할 수 있는데, EOA는 개인키가 있는 사람이 통제하지만 CA는 개인키 없이 코드에 의해서만 동작합니다. 따라서 CA는 스스로 트랜잭션을 생성(Trigger)할 수 없으며, 반드시 외부의 호출이 있어야만 작동합니다. + +--- + +## 문제 4: [이론] 계정 상태 필드 (객관식) + +이더리움 계정 상태의 4가지 필드 중 `nonce`의 역할을 올바르게 설명한 것은? + +다음 상황을 고려하세요: + +``` +Alice의 현재 nonce: 5 +Alice가 두 개의 트랜잭션을 동시에 전송합니다: +- TX-A: nonce=5, Bob에게 1 ETH 전송 +- TX-B: nonce=5, Charlie에게 2 ETH 전송 +``` + +**보기:** +A) 두 트랜잭션 모두 성공적으로 처리된다 +B) TX-A만 처리되고 TX-B는 무시된다 (또는 그 반대) +C) 두 트랜잭션 모두 실패하고 Alice의 자산이 동결된다 +D) 네트워크가 자동으로 TX-B의 nonce를 6으로 변경한다 + +**답변:** +답변: B) TX-A만 처리되고 TX-B는 무시된다 (또는 그 반대) + +**설명:** +nonce는 해당 계정에서 보낸 트랜잭션의 순서를 보장합니다. 같은 nonce를 가진 트랜잭션이 두 개 오면, 네트워크는 하나만 선택하고 나머지는 거절합니다. +- `재사용 공격 방지`: 한 번 사용된 nonce는 다시 사용할 수 없으므로, 공격자가 과거의 유효한 송금 트랜잭션을 가로채서 다시 네트워크에 전송(Replay)하더라도 nonce 중복으로 인해 무효 처리됩니다. + +--- + +## 문제 5: [이론] World State (객관식) + +World State에 대한 설명 중 올바른 것은? + +**보기:** +A) World State는 최신 100개 블록의 트랜잭션만 저장한다 +B) World State는 모든 계정의 현재 상태(주소 -> 상태 매핑)를 나타낸다 +C) World State는 EOA의 정보만 저장하고 CA 정보는 별도로 관리한다 +D) World State는 각 노드마다 다른 값을 가질 수 있다 + +**답변:** +B) World State는 모든 계정의 현재 상태(주소 -> 상태 매핑)를 나타낸다 + +**설명:** +World State는 이더리움 전체 네트워크의 **'현재 스냅샷'**입니다. 주소를 입력하면 해당 계정의 잔액, 논스, 코드 등을 바로 찾아볼 수 있는 거대한 매핑 구조이기 때문에, 특정 사람의 정보를 찾기 위해 이름(주소)을 검색하는 **'전화번호부'**에 비유됩니다. + +--- + +## 문제 6: [이론] 상태 변수 vs 지역 변수 (단답형) + +Solidity에서 `상태 변수(state variable)`와 `지역 변수(local variable)`의 차이는 무엇인가요? + +다음 코드를 보고 설명하세요: + +```solidity +contract Example { + uint256 public count; // 이것은 무엇인가요? + + function calculate(uint256 input) public pure returns (uint256) { + uint256 result = input * 2; // 이것은 무엇인가요? + return result; + } +} +``` + +**답변:** +***`count`(상태 변수)*** +* **저장 위치:** 블록체인의 영구적인 저장소인 **Storage**에 저장됩니다. +* **지속성:** 트랜잭션이 끝나도 값이 유지됩니다. +* **비용:** 데이터를 영구히 기록하므로 가스비가 매우 비쌉니다. + +***`result`(지역 변수)*** +* **저장 위치:** 함수의 실행 환경인 **Memory**나 **Stack**에 일시적으로 저장됩니다. +* **지속성:** 함수 실행이 종료되면 즉시 사라집니다. +* **비용:** 스토리지에 접근하지 않으므로 가스비가 매우 저렴합니다. + +--- + +## 문제 7: [이론] 원자성의 이유 (단답형) + +이더리움에서 트랜잭션이 "원자적(atomic)"으로 처리되어야 하는 이유는 무엇인가요? + +**왜** 부분적으로 성공하는 트랜잭션을 허용하면 문제가 될까요? 구체적인 예시와 함께 설명하세요. + +**답변:** +데이터의 **일관성(Consistency)**을 유지하기 위해서입니다. 만약 송금 트랜잭션에서 "Alice의 잔액 감소"는 성공하고 "Bob의 잔액 증가"는 실패하는 '부분 성공'이 발생하면, 1 ETH가 공중으로 사라지는 심각한 오류가 발생합니다. 이는 화폐 시스템의 신뢰를 무너뜨리므로, 모든 트랜잭션은 반드시 완전히 성공하거나 아예 실행되지 않아야 합니다. + +--- + +## 문제 8: [이론] 계정 구조 설명 (단답형) + +EOA에는 `codeHash`와 `storageRoot`가 왜 의미가 없나요? + +**답변:** +EOA는 사용자가 개인키로 직접 제어하는 계정으로, 실행할 **코드(codeHash)**가 존재하지 않으며 데이터를 저장할 공간인 **저장소(storageRoot)**도 필요하지 않기 때문입니다. EOA는 오직 잔액(balance)과 거래 순서(nonce) 정보만 관리합니다. 반면 CA는 로직이 담긴 코드와 그 로직을 실행하며 변경되는 변수들을 저장할 공간이 필수적입니다. + +--- + +## 문제 9: [코드] Counter 읽기 (코드 읽기) + +다음 Counter.sol 코드를 분석하세요: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +contract Counter { + uint256 public count; + + function getCount() public view returns (uint256) { + return count; + } + + function increment() public { + count += 1; + } + + function decrement() public { + require(count > 0, "Count cannot go below zero"); + count -= 1; + } +} +``` + +**1) `public` 키워드의 역할:** +`count` 변수에 `public`이 붙으면 어떤 일이 자동으로 일어나나요? + +**답변:** +Solidity 컴파일러가 해당 변수의 값을 외부에서 조회할 수 있는 Getter 함수를 자동으로 생성해 줍니다. 즉, 외부에서 count()라는 이름의 함수를 호출하여 현재 값을 읽어올 수 있게 됩니다. + +**2) `view` 키워드의 의미:** +`getCount()` 함수에 `view`가 붙은 이유는 무엇인가요? `view`를 제거하면 어떻게 될까요? + +**답변:** +함수가 블록체인의 상태를 '읽기'만 하고 '수정'하지 않음을 보장합니다. view를 제거하면 컴파일러는 이 함수가 상태를 변경할 가능성이 있다고 판단하여 경고를 주거나, 호출 시 불필요하게 가스 소모를 가정할 수 있습니다. + +--- + +## 문제 10: [코드] Counter 동작 예측 (코드 읽기) + +위의 Counter 컨트랙트에서 다음 시나리오를 분석하세요: + +**시나리오:** +``` +초기 상태: count = 0 + +1. increment() 호출 +2. increment() 호출 +3. decrement() 호출 +4. decrement() 호출 +5. decrement() 호출 +``` + +**질문 1:** 5번째 `decrement()` 호출의 결과는 무엇인가요? + +**답변:** +- 5번째 decrement() 호출은 **실패(Revert)**합니다. +- 이유: 초기값 0에서 increment 2번(+2), decrement 2번(-2)을 거치면 count는 다시 0이 됩니다. 5번째 호출 시 require(count > 0, ...) 조건을 확인하는데, 0은 0보다 크지 않으므로 에러 메시지와 함께 트랜잭션이 취소됩니다. +* 시작 `count=0` +1. increment → 1 +2. increment → 2 +3. decrement → 1 (성공) +4. decrement → 0 (성공) +5. decrement → `require(count > 0)`를 만족하지 못해서 **revert로 실패**합니다. + 따라서 5번째 호출은 실패하고, 그 트랜잭션에서의 상태 변화는 없으며(원자성), 가스는 사용됩니다. + + +**질문 2:** 왜 `decrement()` 함수에 `require(count > 0, ...)` 조건이 필요한가요? + +**답변:** +- uint256의 **언더플로우(Underflow)**를 방지하기 위해 필요합니다. +- 이유: uint256은 부호 없는 정수로 0보다 작은 값을 가질 수 없습니다. 만약 0에서 1을 빼려고 하면, Solidity 0.8 버전 이상에서는 에러를 내며 멈추지만, 명시적인 require 문을 통해 사용자에게 정확한 실패 이유(에러 메시지)를 알려주는 것이 좋은 개발 관례입니다. + +--- + +## 자기 평가 + +모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: + +- [x] 상태 머신과 원자성 개념을 이해했다 +- [x] EOA와 CA의 차이를 설명할 수 있다 +- [x] 계정 상태의 4가지 필드(nonce, balance, storageRoot, codeHash)를 이해했다 +- [x] Solidity 기본 문법(public, view, require)을 이해했다 +- [x] 상태 변수와 지역 변수의 차이를 설명할 수 있다 + +--- + +## 참고 자료 + +- 이론: `eth-materials/week-01/theory/slides.md` +- 코드: `eth-homework/week-01/dev/src/Counter.sol` +- 용어: `eth-materials/resources/glossary.md` diff --git a/week-01/quiz/quiz-01.md b/week-01/quiz/quiz-01.md index 537d6835..32205736 100644 --- a/week-01/quiz/quiz-01.md +++ b/week-01/quiz/quiz-01.md @@ -1,278 +1,278 @@ -# Week 1 퀴즈: State/Account + Solidity 기초 - -**제출 방법:** -1. 이 파일을 복사하여 `quiz-01-solution.md`로 저장 -2. 각 문제에 답변 작성 (왜 그런지 설명 포함) -3. Pull Request 생성 (`quiz_submission` 템플릿 사용) - -**평가 기준:** -- 정답 여부보다 **개념 이해도**를 중점 평가합니다 -- "왜"에 대한 설명이 충분한지 확인합니다 -- 문법 오류는 크게 감점하지 않습니다 - ---- - -## 문제 1: [이론] 상태 머신 (객관식) - -이더리움에서 "상태 전이가 원자적(atomic)이다"라는 말의 의미를 가장 잘 설명한 것은? - -다음 상황을 고려하세요: - -``` -Alice가 Bob에게 1 ETH를 보내는 트랜잭션을 실행합니다. -중간에 가스가 부족해져서 트랜잭션이 실패했습니다. -``` - -**보기:** -A) Alice의 잔액만 감소하고 Bob의 잔액은 변하지 않는다 -B) Alice의 잔액과 Bob의 잔액 모두 변하지 않고, 가스비만 소모된다 -C) 네트워크가 자동으로 부족한 가스를 보충해서 트랜잭션을 완료한다 -D) 트랜잭션이 절반만 실행되어 0.5 ETH만 전송된다 - -**답변:** - - - ---- - -## 문제 2: [이론] 결정론적 실행 (객관식) - -이더리움 EVM이 "결정론적(deterministic)"으로 실행된다는 것의 핵심 이유는 무엇인가요? - -**보기:** -A) 모든 노드가 같은 하드웨어를 사용해야 해서 -B) 같은 입력(트랜잭션)이 주어지면 모든 노드가 같은 결과(상태)를 도출해야 하므로 -C) 중앙 서버가 모든 계산을 수행하고 결과를 배포해서 -D) 트랜잭션이 항상 1초 안에 처리되어야 해서 - -**답변:** - - - ---- - -## 문제 3: [이론] EOA vs CA (객관식) - -다음 중 EOA(Externally Owned Account)와 CA(Contract Account)의 차이를 올바르게 설명한 것은? - -**보기:** -A) EOA는 코드를 실행할 수 있고, CA는 코드를 실행할 수 없다 -B) EOA만 트랜잭션을 시작할 수 있고, CA는 EOA에 의해 호출될 때만 실행된다 -C) CA만 ETH를 보유할 수 있고, EOA는 ETH를 보유할 수 없다 -D) EOA와 CA는 동일한 기능을 가지며 이름만 다르다 - -**답변:** - - - ---- - -## 문제 4: [이론] 계정 상태 필드 (객관식) - -이더리움 계정 상태의 4가지 필드 중 `nonce`의 역할을 올바르게 설명한 것은? - -다음 상황을 고려하세요: - -``` -Alice의 현재 nonce: 5 -Alice가 두 개의 트랜잭션을 동시에 전송합니다: -- TX-A: nonce=5, Bob에게 1 ETH 전송 -- TX-B: nonce=5, Charlie에게 2 ETH 전송 -``` - -**보기:** -A) 두 트랜잭션 모두 성공적으로 처리된다 -B) TX-A만 처리되고 TX-B는 무시된다 (또는 그 반대) -C) 두 트랜잭션 모두 실패하고 Alice의 자산이 동결된다 -D) 네트워크가 자동으로 TX-B의 nonce를 6으로 변경한다 - -**답변:** - - - ---- - -## 문제 5: [이론] World State (객관식) - -World State에 대한 설명 중 올바른 것은? - -**보기:** -A) World State는 최신 100개 블록의 트랜잭션만 저장한다 -B) World State는 모든 계정의 현재 상태(주소 -> 상태 매핑)를 나타낸다 -C) World State는 EOA의 정보만 저장하고 CA 정보는 별도로 관리한다 -D) World State는 각 노드마다 다른 값을 가질 수 있다 - -**답변:** - - - ---- - -## 문제 6: [이론] 상태 변수 vs 지역 변수 (단답형) - -Solidity에서 `상태 변수(state variable)`와 `지역 변수(local variable)`의 차이는 무엇인가요? - -다음 코드를 보고 설명하세요: - -```solidity -contract Example { - uint256 public count; // 이것은 무엇인가요? - - function calculate(uint256 input) public pure returns (uint256) { - uint256 result = input * 2; // 이것은 무엇인가요? - return result; - } -} -``` - -**답변:** - - - ---- - -## 문제 7: [이론] 원자성의 이유 (단답형) - -이더리움에서 트랜잭션이 "원자적(atomic)"으로 처리되어야 하는 이유는 무엇인가요? - -**왜** 부분적으로 성공하는 트랜잭션을 허용하면 문제가 될까요? 구체적인 예시와 함께 설명하세요. - -**답변:** - - - ---- - -## 문제 8: [이론] 계정 구조 설명 (단답형) - -EOA에는 `codeHash`와 `storageRoot`가 왜 의미가 없나요? - -**답변:** - - - ---- - -## 문제 9: [코드] Counter 읽기 (코드 읽기) - -다음 Counter.sol 코드를 분석하세요: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -contract Counter { - uint256 public count; - - function getCount() public view returns (uint256) { - return count; - } - - function increment() public { - count += 1; - } - - function decrement() public { - require(count > 0, "Count cannot go below zero"); - count -= 1; - } -} -``` - -**1) `public` 키워드의 역할:** -`count` 변수에 `public`이 붙으면 어떤 일이 자동으로 일어나나요? - -**답변:** - - - -**2) `view` 키워드의 의미:** -`getCount()` 함수에 `view`가 붙은 이유는 무엇인가요? `view`를 제거하면 어떻게 될까요? - -**답변:** - - - ---- - -## 문제 10: [코드] Counter 동작 예측 (코드 읽기) - -위의 Counter 컨트랙트에서 다음 시나리오를 분석하세요: - -**시나리오:** -``` -초기 상태: count = 0 - -1. increment() 호출 -2. increment() 호출 -3. decrement() 호출 -4. decrement() 호출 -5. decrement() 호출 -``` - -**질문 1:** 5번째 `decrement()` 호출의 결과는 무엇인가요? - -**답변:** - - - -**질문 2:** 왜 `decrement()` 함수에 `require(count > 0, ...)` 조건이 필요한가요? - -**답변:** - - - ---- - -## 자기 평가 - -모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: - -- [ ] 상태 머신과 원자성 개념을 이해했다 -- [ ] EOA와 CA의 차이를 설명할 수 있다 -- [ ] 계정 상태의 4가지 필드(nonce, balance, storageRoot, codeHash)를 이해했다 -- [ ] Solidity 기본 문법(public, view, require)을 이해했다 -- [ ] 상태 변수와 지역 변수의 차이를 설명할 수 있다 - ---- - -## 참고 자료 - -- 이론: `eth-materials/week-01/theory/slides.md` -- 코드: `eth-homework/week-01/dev/src/Counter.sol` -- 용어: `eth-materials/resources/glossary.md` +# Week 1 퀴즈: State/Account + Solidity 기초 + +**제출 방법:** +1. 이 파일을 복사하여 `quiz-01-solution.md`로 저장 +2. 각 문제에 답변 작성 (왜 그런지 설명 포함) +3. Pull Request 생성 (`quiz_submission` 템플릿 사용) + +**평가 기준:** +- 정답 여부보다 **개념 이해도**를 중점 평가합니다 +- "왜"에 대한 설명이 충분한지 확인합니다 +- 문법 오류는 크게 감점하지 않습니다 + +--- + +## 문제 1: [이론] 상태 머신 (객관식) + +이더리움에서 "상태 전이가 원자적(atomic)이다"라는 말의 의미를 가장 잘 설명한 것은? + +다음 상황을 고려하세요: + +``` +Alice가 Bob에게 1 ETH를 보내는 트랜잭션을 실행합니다. +중간에 가스가 부족해져서 트랜잭션이 실패했습니다. +``` + +**보기:** +A) Alice의 잔액만 감소하고 Bob의 잔액은 변하지 않는다 +B) Alice의 잔액과 Bob의 잔액 모두 변하지 않고, 가스비만 소모된다 +C) 네트워크가 자동으로 부족한 가스를 보충해서 트랜잭션을 완료한다 +D) 트랜잭션이 절반만 실행되어 0.5 ETH만 전송된다 + +**답변:** + + + +--- + +## 문제 2: [이론] 결정론적 실행 (객관식) + +이더리움 EVM이 "결정론적(deterministic)"으로 실행된다는 것의 핵심 이유는 무엇인가요? + +**보기:** +A) 모든 노드가 같은 하드웨어를 사용해야 해서 +B) 같은 입력(트랜잭션)이 주어지면 모든 노드가 같은 결과(상태)를 도출해야 하므로 +C) 중앙 서버가 모든 계산을 수행하고 결과를 배포해서 +D) 트랜잭션이 항상 1초 안에 처리되어야 해서 + +**답변:** + + + +--- + +## 문제 3: [이론] EOA vs CA (객관식) + +다음 중 EOA(Externally Owned Account)와 CA(Contract Account)의 차이를 올바르게 설명한 것은? + +**보기:** +A) EOA는 코드를 실행할 수 있고, CA는 코드를 실행할 수 없다 +B) EOA만 트랜잭션을 시작할 수 있고, CA는 EOA에 의해 호출될 때만 실행된다 +C) CA만 ETH를 보유할 수 있고, EOA는 ETH를 보유할 수 없다 +D) EOA와 CA는 동일한 기능을 가지며 이름만 다르다 + +**답변:** + + + +--- + +## 문제 4: [이론] 계정 상태 필드 (객관식) + +이더리움 계정 상태의 4가지 필드 중 `nonce`의 역할을 올바르게 설명한 것은? + +다음 상황을 고려하세요: + +``` +Alice의 현재 nonce: 5 +Alice가 두 개의 트랜잭션을 동시에 전송합니다: +- TX-A: nonce=5, Bob에게 1 ETH 전송 +- TX-B: nonce=5, Charlie에게 2 ETH 전송 +``` + +**보기:** +A) 두 트랜잭션 모두 성공적으로 처리된다 +B) TX-A만 처리되고 TX-B는 무시된다 (또는 그 반대) +C) 두 트랜잭션 모두 실패하고 Alice의 자산이 동결된다 +D) 네트워크가 자동으로 TX-B의 nonce를 6으로 변경한다 + +**답변:** + + + +--- + +## 문제 5: [이론] World State (객관식) + +World State에 대한 설명 중 올바른 것은? + +**보기:** +A) World State는 최신 100개 블록의 트랜잭션만 저장한다 +B) World State는 모든 계정의 현재 상태(주소 -> 상태 매핑)를 나타낸다 +C) World State는 EOA의 정보만 저장하고 CA 정보는 별도로 관리한다 +D) World State는 각 노드마다 다른 값을 가질 수 있다 + +**답변:** + + + +--- + +## 문제 6: [이론] 상태 변수 vs 지역 변수 (단답형) + +Solidity에서 `상태 변수(state variable)`와 `지역 변수(local variable)`의 차이는 무엇인가요? + +다음 코드를 보고 설명하세요: + +```solidity +contract Example { + uint256 public count; // 이것은 무엇인가요? + + function calculate(uint256 input) public pure returns (uint256) { + uint256 result = input * 2; // 이것은 무엇인가요? + return result; + } +} +``` + +**답변:** + + + +--- + +## 문제 7: [이론] 원자성의 이유 (단답형) + +이더리움에서 트랜잭션이 "원자적(atomic)"으로 처리되어야 하는 이유는 무엇인가요? + +**왜** 부분적으로 성공하는 트랜잭션을 허용하면 문제가 될까요? 구체적인 예시와 함께 설명하세요. + +**답변:** + + + +--- + +## 문제 8: [이론] 계정 구조 설명 (단답형) + +EOA에는 `codeHash`와 `storageRoot`가 왜 의미가 없나요? + +**답변:** + + + +--- + +## 문제 9: [코드] Counter 읽기 (코드 읽기) + +다음 Counter.sol 코드를 분석하세요: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +contract Counter { + uint256 public count; + + function getCount() public view returns (uint256) { + return count; + } + + function increment() public { + count += 1; + } + + function decrement() public { + require(count > 0, "Count cannot go below zero"); + count -= 1; + } +} +``` + +**1) `public` 키워드의 역할:** +`count` 변수에 `public`이 붙으면 어떤 일이 자동으로 일어나나요? + +**답변:** + + + +**2) `view` 키워드의 의미:** +`getCount()` 함수에 `view`가 붙은 이유는 무엇인가요? `view`를 제거하면 어떻게 될까요? + +**답변:** + + + +--- + +## 문제 10: [코드] Counter 동작 예측 (코드 읽기) + +위의 Counter 컨트랙트에서 다음 시나리오를 분석하세요: + +**시나리오:** +``` +초기 상태: count = 0 + +1. increment() 호출 +2. increment() 호출 +3. decrement() 호출 +4. decrement() 호출 +5. decrement() 호출 +``` + +**질문 1:** 5번째 `decrement()` 호출의 결과는 무엇인가요? + +**답변:** + + + +**질문 2:** 왜 `decrement()` 함수에 `require(count > 0, ...)` 조건이 필요한가요? + +**답변:** + + + +--- + +## 자기 평가 + +모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: + +- [ ] 상태 머신과 원자성 개념을 이해했다 +- [ ] EOA와 CA의 차이를 설명할 수 있다 +- [ ] 계정 상태의 4가지 필드(nonce, balance, storageRoot, codeHash)를 이해했다 +- [ ] Solidity 기본 문법(public, view, require)을 이해했다 +- [ ] 상태 변수와 지역 변수의 차이를 설명할 수 있다 + +--- + +## 참고 자료 + +- 이론: `eth-materials/week-01/theory/slides.md` +- 코드: `eth-homework/week-01/dev/src/Counter.sol` +- 용어: `eth-materials/resources/glossary.md` diff --git a/week-01/theory/quiz-01-solution.md b/week-01/theory/quiz-01-solution.md new file mode 100644 index 00000000..5e2166a8 --- /dev/null +++ b/week-01/theory/quiz-01-solution.md @@ -0,0 +1,70 @@ +# Week 1 이론 퀴즈 + +이 퀴즈를 복사하여 `quiz-01-solution.md`로 저장한 후 답변을 작성하세요. + +--- + +## 문제 1: 블록체인 기초 + +블록체인의 핵심 목적은 무엇인가요? 중앙화된 데이터베이스와 비교하여 설명해주세요. + +**답변:** +블록체인의 핵심 목적은 'Trusted Third Party(TTP)' 없이도 데이터의 무결성과 신뢰성을 보장하는 탈중앙화된 합의 시스템을 구축하는 것입니다. +- `중앙화 데이터베이스`: 특정 주체(관리자)가 데이터를 통제하며, 보안과 무결성이 관리자의 역량과 정직함에 의존합니다. 관리자가 데이터를 임의로 수정하거나 서버가 공격받으면 시스템 전체가 위협받는 단일 실패 지점(Single Point of Failure) 문제가 존재합니다. +- `블록체인`: 네트워크의 모든 참여자가 동일한 장부를 공유하고 검증합니다. 한 번 기록된 데이터는 수정이나 삭제가 거의 불가능한 불변성(Immutability)을 가지며, 분산 거버넌스를 통해 데이터의 투명성과 가용성을 극대화합니다. +--- + +## 문제 2: 이더리움의 특징 + +이더리움이 비트코인과 다른 점은 무엇인가요? 스마트 컨트랙트의 관점에서 설명해주세요. + +**답변:** +가장 큰 차이점은 '튜링 완전성(Turing-completeness)'을 갖춘 스마트 컨트랙트 플랫폼이라는 점입니다. +- `비트코인`: 단순한 가치 전송(결제)을 목적으로 설계되었으며, 사용되는 '스크립트' 언어는 보안을 위해 반복문(Loop) 등이 제한된 비튜링 완전 언어입니다. +- `이더리움`: 이더리움 가상 머신(EVM) 위에서 동작하는 스마트 컨트랙트를 통해 복잡한 비즈니스 로직을 코드로 구현할 수 있습니다. 이를 통해 단순 송금을 넘어 DeFi(탈중앙화 금융), NFT, DAO 등 다양한 분산 애플리케이션(dApp)을 구축할 수 있는 '월드 컴퓨터' 역할을 수행합니다. + +--- + +## 문제 3: Solidity 기초 + +다음 Solidity 코드에서 `public`과 `view` 키워드의 의미를 각각 설명하세요. + +```solidity +function count() public view returns (uint256) { + return _count; +} +``` + +**답변:** +- `public`: 함수의 **가시성(Visibility)**을 정의합니다. 컨트랙트 내부뿐만 아니라 외부(EOA 지갑, 타 컨트랙트 등)에서도 자유롭게 호출할 수 있음을 의미합니다. +- `view`: 함수의 **상태 변경 여부**를 정의합니다. 블록체인 상의 데이터를 읽기만 하고, 상태 변수를 수정하지 않는 함수임을 명시합니다. view 함수는 가스 비용을 지불하지 않고 호출할 수 있습니다(단, 트랜잭션 내에서 호출될 때는 예외). + +--- + +## 문제 4: 상태 변수 + +Solidity에서 상태 변수(state variable)와 지역 변수(local variable)의 차이점을 설명하세요. + +**답변:** +- `상태 변수 (State Variables)`: **함수 밖(컨트랙트 레벨)**에서 선언되며, **블록체인**에 영구적으로 저장됩니다. 변경 시 가스 비용이 높게 발생하며, 컨트랙트의 '상태'를 결정합니다. +- `지역 변수 (Local Variables)`: **함수 내부**에서 선언되며, 함수가 실행되는 동안에만 **메모리나 스택**에 일시적으로 존재합니다. 함수 실행이 종료되면 소멸하며, 스토리지에 접근하지 않으므로 상대적으로 가스비가 저렴합니다. + +--- + +## 문제 5: Gas 개념 + +이더리움에서 Gas란 무엇이며, 왜 필요한가요? + +**답변:** +Gas는 이더리움 네트워크에서 트랜잭션을 실행하거나 스마트 컨트랙트를 구동하기 위해 필요한 연산 비용의 단위입니다. +* 필요성: +- 스팸 및 공격 방지: 무한 루프나 과도한 연산을 유발하는 공격으로부터 네트워크를 보호합니다. 공격자는 모든 연산에 대해 비용을 지불해야 하므로 공격의 경제적 유인이 차단됩니다. +- 자원 할당의 우선순위: 제한된 네트워크 자원을 효율적으로 배분합니다. 사용자가 높은 가스 가격(Gas Price)을 제시하면 채굴자(또는 검증자)는 해당 트랜잭션을 우선적으로 처리합니다. + +--- + +**제출 방법:** +1. 이 파일을 복사하여 `quiz-01-solution.md`로 저장 +2. 각 문제에 대한 답변 작성 +3. Git으로 커밋 및 푸시 +4. Pull Request 생성 diff --git a/week-01/theory/quiz-01-template.md b/week-01/theory/quiz-01-template.md index 507bb5e5..0cd6a7d8 100644 --- a/week-01/theory/quiz-01-template.md +++ b/week-01/theory/quiz-01-template.md @@ -1,63 +1,63 @@ -# Week 1 이론 퀴즈 - -이 퀴즈를 복사하여 `quiz-01-solution.md`로 저장한 후 답변을 작성하세요. - ---- - -## 문제 1: 블록체인 기초 - -블록체인의 핵심 목적은 무엇인가요? 중앙화된 데이터베이스와 비교하여 설명해주세요. - -**답변:** -[여기에 답변을 작성하세요] - ---- - -## 문제 2: 이더리움의 특징 - -이더리움이 비트코인과 다른 점은 무엇인가요? 스마트 컨트랙트의 관점에서 설명해주세요. - -**답변:** -[여기에 답변을 작성하세요] - ---- - -## 문제 3: Solidity 기초 - -다음 Solidity 코드에서 `public`과 `view` 키워드의 의미를 각각 설명하세요. - -```solidity -function count() public view returns (uint256) { - return _count; -} -``` - -**답변:** -- `public`: -- `view`: - ---- - -## 문제 4: 상태 변수 - -Solidity에서 상태 변수(state variable)와 지역 변수(local variable)의 차이점을 설명하세요. - -**답변:** -[여기에 답변을 작성하세요] - ---- - -## 문제 5: Gas 개념 - -이더리움에서 Gas란 무엇이며, 왜 필요한가요? - -**답변:** -[여기에 답변을 작성하세요] - ---- - -**제출 방법:** -1. 이 파일을 복사하여 `quiz-01-solution.md`로 저장 -2. 각 문제에 대한 답변 작성 -3. Git으로 커밋 및 푸시 -4. Pull Request 생성 +# Week 1 이론 퀴즈 + +이 퀴즈를 복사하여 `quiz-01-solution.md`로 저장한 후 답변을 작성하세요. + +--- + +## 문제 1: 블록체인 기초 + +블록체인의 핵심 목적은 무엇인가요? 중앙화된 데이터베이스와 비교하여 설명해주세요. + +**답변:** +[여기에 답변을 작성하세요] + +--- + +## 문제 2: 이더리움의 특징 + +이더리움이 비트코인과 다른 점은 무엇인가요? 스마트 컨트랙트의 관점에서 설명해주세요. + +**답변:** +[여기에 답변을 작성하세요] + +--- + +## 문제 3: Solidity 기초 + +다음 Solidity 코드에서 `public`과 `view` 키워드의 의미를 각각 설명하세요. + +```solidity +function count() public view returns (uint256) { + return _count; +} +``` + +**답변:** +- `public`: +- `view`: + +--- + +## 문제 4: 상태 변수 + +Solidity에서 상태 변수(state variable)와 지역 변수(local variable)의 차이점을 설명하세요. + +**답변:** +[여기에 답변을 작성하세요] + +--- + +## 문제 5: Gas 개념 + +이더리움에서 Gas란 무엇이며, 왜 필요한가요? + +**답변:** +[여기에 답변을 작성하세요] + +--- + +**제출 방법:** +1. 이 파일을 복사하여 `quiz-01-solution.md`로 저장 +2. 각 문제에 대한 답변 작성 +3. Git으로 커밋 및 푸시 +4. Pull Request 생성 diff --git a/week-02/dev/README.md b/week-02/dev/README.md index 51e878b1..1019a90b 100644 --- a/week-02/dev/README.md +++ b/week-02/dev/README.md @@ -1,127 +1,127 @@ -# Week 2: Foundry 테스트 - -> SimpleStorage 컨트랙트를 완성하여 Foundry 테스트 기법을 학습합니다. - ---- - -## 학습 목표 - -이 과제를 완료하면 다음을 이해하게 됩니다: - -- `mapping`을 사용한 데이터 저장 -- `event`와 `emit`으로 로그 기록 -- `payable` 함수와 ETH 전송 -- Foundry의 테스트 패턴 (setUp, assert, vm.prank) - ---- - -## 파일 구조 - -``` -week-02/dev/ -├── src/ -│ └── SimpleStorage.sol # 과제 파일 (TODO를 구현하세요) -├── test/ -│ └── SimpleStorage.t.sol # 테스트 파일 (수정하지 마세요) -├── script/ -│ └── .gitkeep -└── README.md # 이 파일 -``` - ---- - -## 과제 안내 - -### 구현할 함수 (SimpleStorage.sol) - -| 함수 | 설명 | 힌트 | -|------|------|------| -| `deposit()` | msg.value를 잔액에 추가 | `balances[msg.sender] += msg.value;` | -| `withdraw(amount)` | 잔액에서 amount 출금 | `require`, `transfer`, `emit` | - -### TODO 완성 방법 - -1. `src/SimpleStorage.sol` 파일을 엽니다 -2. `// TODO:` 주석이 있는 부분을 찾습니다 -3. 힌트를 참고하여 로직을 구현합니다 -4. 테스트를 실행하여 확인합니다 - ---- - -## 테스트 실행 - -### 프로젝트 루트에서 실행 - -```bash -# eth-homework 디렉토리에서 -cd eth-homework - -# Week 2 테스트만 실행 -forge test --match-path week-02/dev/test/SimpleStorage.t.sol -vv - -# 모든 테스트 실행 -forge test -vv -``` - -### 가스 리포트 - -```bash -# 가스 사용량 확인 -forge test --gas-report -``` - -### 테스트 출력 해석 - -``` -[PASS] test_Deposit_UpdatesBalance() # 입금 시 잔액 업데이트 -[PASS] test_Deposit_EmitsEvent() # 입금 시 이벤트 발생 -[PASS] test_Withdraw_UpdatesBalance() # 출금 시 잔액 업데이트 -[PASS] test_Withdraw_TransfersEther() # 출금 시 ETH 전송 -[PASS] test_RevertWhen_WithdrawExceedsBalance() # 잔액 부족 시 revert -[PASS] test_Withdraw_EmitsEvent() # 출금 시 이벤트 발생 -[PASS] testFuzz_Deposit(amount) # Fuzz 테스트 (다양한 입력) -``` - -모든 테스트가 `[PASS]`로 표시되면 과제 완료입니다! - ---- - -## Foundry Cheatcodes 참고 - -테스트 파일에서 사용된 Foundry cheatcodes: - -| Cheatcode | 설명 | -|-----------|------| -| `vm.deal(addr, amount)` | addr에 ETH 지급 | -| `vm.prank(addr)` | 다음 호출의 msg.sender를 addr로 변경 | -| `vm.expectRevert(msg)` | 다음 호출이 msg로 revert될 것을 예상 | -| `vm.expectEmit(...)` | 다음 호출에서 특정 이벤트가 발생할 것을 예상 | -| `vm.assume(cond)` | Fuzz 테스트에서 cond를 만족하는 입력만 사용 | - ---- - -## 통과 기준 - -- [ ] 7개 테스트 모두 통과 -- [ ] `forge build` 시 컴파일 에러 없음 -- [ ] PR 제출 시 CI 통과 - ---- - -## 참고 자료 - -- [Foundry 테스트 가이드](../../../eth-materials/week-02/dev/foundry-testing.md) -- [Foundry Book - Testing](https://book.getfoundry.sh/forge/tests) -- [Foundry Cheatcodes Reference](https://book.getfoundry.sh/cheatcodes/) - ---- - -## 도움이 필요하면 - -1. `foundry-testing.md` 가이드를 먼저 읽어보세요 -2. 테스트 파일의 주석을 확인하세요 (기대하는 동작이 설명되어 있습니다) -3. Slack에서 질문하세요 - ---- - -*[메인 README로 돌아가기](../../README.md)* +# Week 2: Foundry 테스트 + +> SimpleStorage 컨트랙트를 완성하여 Foundry 테스트 기법을 학습합니다. + +--- + +## 학습 목표 + +이 과제를 완료하면 다음을 이해하게 됩니다: + +- `mapping`을 사용한 데이터 저장 +- `event`와 `emit`으로 로그 기록 +- `payable` 함수와 ETH 전송 +- Foundry의 테스트 패턴 (setUp, assert, vm.prank) + +--- + +## 파일 구조 + +``` +week-02/dev/ +├── src/ +│ └── SimpleStorage.sol # 과제 파일 (TODO를 구현하세요) +├── test/ +│ └── SimpleStorage.t.sol # 테스트 파일 (수정하지 마세요) +├── script/ +│ └── .gitkeep +└── README.md # 이 파일 +``` + +--- + +## 과제 안내 + +### 구현할 함수 (SimpleStorage.sol) + +| 함수 | 설명 | 힌트 | +|------|------|------| +| `deposit()` | msg.value를 잔액에 추가 | `balances[msg.sender] += msg.value;` | +| `withdraw(amount)` | 잔액에서 amount 출금 | `require`, `transfer`, `emit` | + +### TODO 완성 방법 + +1. `src/SimpleStorage.sol` 파일을 엽니다 +2. `// TODO:` 주석이 있는 부분을 찾습니다 +3. 힌트를 참고하여 로직을 구현합니다 +4. 테스트를 실행하여 확인합니다 + +--- + +## 테스트 실행 + +### 프로젝트 루트에서 실행 + +```bash +# eth-homework 디렉토리에서 +cd eth-homework + +# Week 2 테스트만 실행 +forge test --match-path week-02/dev/test/SimpleStorage.t.sol -vv + +# 모든 테스트 실행 +forge test -vv +``` + +### 가스 리포트 + +```bash +# 가스 사용량 확인 +forge test --gas-report +``` + +### 테스트 출력 해석 + +``` +[PASS] test_Deposit_UpdatesBalance() # 입금 시 잔액 업데이트 +[PASS] test_Deposit_EmitsEvent() # 입금 시 이벤트 발생 +[PASS] test_Withdraw_UpdatesBalance() # 출금 시 잔액 업데이트 +[PASS] test_Withdraw_TransfersEther() # 출금 시 ETH 전송 +[PASS] test_RevertWhen_WithdrawExceedsBalance() # 잔액 부족 시 revert +[PASS] test_Withdraw_EmitsEvent() # 출금 시 이벤트 발생 +[PASS] testFuzz_Deposit(amount) # Fuzz 테스트 (다양한 입력) +``` + +모든 테스트가 `[PASS]`로 표시되면 과제 완료입니다! + +--- + +## Foundry Cheatcodes 참고 + +테스트 파일에서 사용된 Foundry cheatcodes: + +| Cheatcode | 설명 | +|-----------|------| +| `vm.deal(addr, amount)` | addr에 ETH 지급 | +| `vm.prank(addr)` | 다음 호출의 msg.sender를 addr로 변경 | +| `vm.expectRevert(msg)` | 다음 호출이 msg로 revert될 것을 예상 | +| `vm.expectEmit(...)` | 다음 호출에서 특정 이벤트가 발생할 것을 예상 | +| `vm.assume(cond)` | Fuzz 테스트에서 cond를 만족하는 입력만 사용 | + +--- + +## 통과 기준 + +- [ ] 7개 테스트 모두 통과 +- [ ] `forge build` 시 컴파일 에러 없음 +- [ ] PR 제출 시 CI 통과 + +--- + +## 참고 자료 + +- [Foundry 테스트 가이드](../../../eth-materials/week-02/dev/foundry-testing.md) +- [Foundry Book - Testing](https://book.getfoundry.sh/forge/tests) +- [Foundry Cheatcodes Reference](https://book.getfoundry.sh/cheatcodes/) + +--- + +## 도움이 필요하면 + +1. `foundry-testing.md` 가이드를 먼저 읽어보세요 +2. 테스트 파일의 주석을 확인하세요 (기대하는 동작이 설명되어 있습니다) +3. Slack에서 질문하세요 + +--- + +*[메인 README로 돌아가기](../../README.md)* diff --git a/week-02/dev/src/SimpleStorage.sol b/week-02/dev/src/SimpleStorage.sol index 322647da..3bee4f7e 100644 --- a/week-02/dev/src/SimpleStorage.sol +++ b/week-02/dev/src/SimpleStorage.sol @@ -1,73 +1,73 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -/// @title SimpleStorage - Week 2 과제 -/// @notice ETH를 입금하고 출금하는 간단한 저장소 컨트랙트입니다. -/// @dev mapping, event, payable 함수를 학습합니다. -contract SimpleStorage { - // ============================================================ - // 상태 변수 (State Variables) - // ============================================================ - - /// @notice 각 주소별 잔액을 저장합니다 - /// @dev mapping은 key(address) => value(uint256) 형태의 저장소입니다 - mapping(address => uint256) public balances; - - // ============================================================ - // 이벤트 (Events) - // ============================================================ - - /// @notice 입금 시 발생하는 이벤트 - /// @param user 입금한 사용자 주소 (indexed로 검색 가능) - /// @param amount 입금한 금액 (wei 단위) - event Deposited(address indexed user, uint256 amount); - - /// @notice 출금 시 발생하는 이벤트 - /// @param user 출금한 사용자 주소 (indexed로 검색 가능) - /// @param amount 출금한 금액 (wei 단위) - event Withdrawn(address indexed user, uint256 amount); - - // ============================================================ - // 읽기 함수 (View Functions) - // ============================================================ - - /// @notice 특정 사용자의 잔액을 조회합니다 - /// @param user 조회할 사용자 주소 - /// @return 해당 사용자의 잔액 (wei 단위) - function getBalance(address user) public view returns (uint256) { - return balances[user]; - } - - // ============================================================ - // 쓰기 함수 (State-Changing Functions) - // ============================================================ - - /// @notice ETH를 입금합니다 - /// @dev msg.value는 함수 호출 시 전송된 ETH 양입니다 - function deposit() public payable { - // TODO: 입금 로직을 구현하세요 - // 1. balances[msg.sender]에 msg.value를 더합니다 - // 2. Deposited 이벤트를 발생시킵니다 - // - // 힌트: - // balances[msg.sender] += msg.value; - // emit Deposited(msg.sender, msg.value); - } - - /// @notice ETH를 출금합니다 - /// @param amount 출금할 금액 (wei 단위) - /// @dev 잔액이 충분한지 확인 후, ETH를 전송합니다 - function withdraw(uint256 amount) public { - // TODO: 출금 로직을 구현하세요 - // 1. 사용자의 잔액이 amount 이상인지 확인합니다 (require 사용) - // 2. balances[msg.sender]에서 amount를 뺍니다 - // 3. msg.sender에게 ETH를 전송합니다 - // 4. Withdrawn 이벤트를 발생시킵니다 - // - // 힌트: - // require(balances[msg.sender] >= amount, "Insufficient balance"); - // balances[msg.sender] -= amount; - // payable(msg.sender).transfer(amount); - // emit Withdrawn(msg.sender, amount); - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/// @title SimpleStorage - Week 2 과제 +/// @notice ETH를 입금하고 출금하는 간단한 저장소 컨트랙트입니다. +/// @dev mapping, event, payable 함수를 학습합니다. +contract SimpleStorage { + // ============================================================ + // 상태 변수 (State Variables) + // ============================================================ + + /// @notice 각 주소별 잔액을 저장합니다 + /// @dev mapping은 key(address) => value(uint256) 형태의 저장소입니다 + mapping(address => uint256) public balances; + + // ============================================================ + // 이벤트 (Events) + // ============================================================ + + /// @notice 입금 시 발생하는 이벤트 + /// @param user 입금한 사용자 주소 (indexed로 검색 가능) + /// @param amount 입금한 금액 (wei 단위) + event Deposited(address indexed user, uint256 amount); + + /// @notice 출금 시 발생하는 이벤트 + /// @param user 출금한 사용자 주소 (indexed로 검색 가능) + /// @param amount 출금한 금액 (wei 단위) + event Withdrawn(address indexed user, uint256 amount); + + // ============================================================ + // 읽기 함수 (View Functions) + // ============================================================ + + /// @notice 특정 사용자의 잔액을 조회합니다 + /// @param user 조회할 사용자 주소 + /// @return 해당 사용자의 잔액 (wei 단위) + function getBalance(address user) public view returns (uint256) { + return balances[user]; + } + + // ============================================================ + // 쓰기 함수 (State-Changing Functions) + // ============================================================ + + /// @notice ETH를 입금합니다 + /// @dev msg.value는 함수 호출 시 전송된 ETH 양입니다 + function deposit() public payable { + // TODO: 입금 로직을 구현하세요 + // 1. balances[msg.sender]에 msg.value를 더합니다 + // 2. Deposited 이벤트를 발생시킵니다 + // + // 힌트: + // balances[msg.sender] += msg.value; + // emit Deposited(msg.sender, msg.value); + } + + /// @notice ETH를 출금합니다 + /// @param amount 출금할 금액 (wei 단위) + /// @dev 잔액이 충분한지 확인 후, ETH를 전송합니다 + function withdraw(uint256 amount) public { + // TODO: 출금 로직을 구현하세요 + // 1. 사용자의 잔액이 amount 이상인지 확인합니다 (require 사용) + // 2. balances[msg.sender]에서 amount를 뺍니다 + // 3. msg.sender에게 ETH를 전송합니다 + // 4. Withdrawn 이벤트를 발생시킵니다 + // + // 힌트: + // require(balances[msg.sender] >= amount, "Insufficient balance"); + // balances[msg.sender] -= amount; + // payable(msg.sender).transfer(amount); + // emit Withdrawn(msg.sender, amount); + } +} diff --git a/week-02/dev/test/SimpleStorage.t.sol b/week-02/dev/test/SimpleStorage.t.sol index 92abd34c..f4a43011 100644 --- a/week-02/dev/test/SimpleStorage.t.sol +++ b/week-02/dev/test/SimpleStorage.t.sol @@ -1,175 +1,175 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import "forge-std/Test.sol"; -import "../src/SimpleStorage.sol"; - -/// @title SimpleStorageTest - Week 2 테스트 -/// @notice 이 테스트들이 모두 통과하도록 SimpleStorage.sol의 TODO를 구현하세요 -/// @dev Foundry의 고급 테스트 기능(cheatcode, fuzz)을 학습합니다 -contract SimpleStorageTest is Test { - // ============================================================ - // 테스트 상태 변수 - // ============================================================ - - /// @notice 테스트할 SimpleStorage 컨트랙트 인스턴스 - SimpleStorage public simpleStorage; - - /// @notice 테스트에 사용할 사용자 주소 - address public user = address(0x1234); - - // ============================================================ - // setUp 함수 - // ============================================================ - - /// @notice 각 테스트 전에 실행되는 설정 함수 - /// @dev 새로운 SimpleStorage 인스턴스를 생성하고 테스트 계정에 ETH를 지급합니다 - function setUp() public { - // 새로운 SimpleStorage 컨트랙트 배포 - simpleStorage = new SimpleStorage(); - - // vm.deal: 테스트 계정에 ETH 지급 (cheatcode) - // user 주소에 10 ETH를 지급합니다 - vm.deal(user, 10 ether); - } - - // ============================================================ - // 입금(Deposit) 테스트 - // ============================================================ - - /// @notice deposit 함수가 잔액을 올바르게 업데이트하는지 확인합니다 - function test_Deposit_UpdatesBalance() public { - // Arrange: user가 호출하도록 설정 - // vm.prank: 다음 호출의 msg.sender를 변경 (cheatcode) - vm.prank(user); - - // Act: 1 ETH 입금 - simpleStorage.deposit{value: 1 ether}(); - - // Assert: 잔액이 1 ETH인지 확인 - assertEq( - simpleStorage.getBalance(user), - 1 ether, - "Balance should be 1 ether after deposit" - ); - } - - /// @notice deposit 함수가 Deposited 이벤트를 발생시키는지 확인합니다 - function test_Deposit_EmitsEvent() public { - // Arrange: user가 호출하도록 설정 - vm.prank(user); - - // vm.expectEmit: 다음 호출에서 특정 이벤트가 발생할 것을 예상 - // (checkTopic1, checkTopic2, checkTopic3, checkData) - // true = 해당 항목을 검증함 - vm.expectEmit(true, false, false, true); - - // 예상되는 이벤트 (비교 대상) - emit SimpleStorage.Deposited(user, 1 ether); - - // Act: 1 ETH 입금 - 이 호출에서 이벤트가 발생해야 함 - simpleStorage.deposit{value: 1 ether}(); - } - - // ============================================================ - // 출금(Withdraw) 테스트 - // ============================================================ - - /// @notice withdraw 함수가 잔액을 올바르게 업데이트하는지 확인합니다 - function test_Withdraw_UpdatesBalance() public { - // Arrange: 먼저 2 ETH 입금 - vm.prank(user); - simpleStorage.deposit{value: 2 ether}(); - - // Act: 1 ETH 출금 - vm.prank(user); - simpleStorage.withdraw(1 ether); - - // Assert: 잔액이 1 ETH인지 확인 - assertEq( - simpleStorage.getBalance(user), - 1 ether, - "Balance should be 1 ether after withdrawing 1 from 2" - ); - } - - /// @notice withdraw 함수가 ETH를 실제로 전송하는지 확인합니다 - function test_Withdraw_TransfersEther() public { - // Arrange: user의 초기 ETH 잔액 기록 (vm.deal로 10 ETH 지급됨) - uint256 initialBalance = user.balance; - - // 2 ETH 입금 - vm.prank(user); - simpleStorage.deposit{value: 2 ether}(); - - // 입금 후 user의 ETH 잔액 (10 - 2 = 8 ETH) - uint256 afterDepositBalance = user.balance; - assertEq(afterDepositBalance, initialBalance - 2 ether); - - // Act: 1 ETH 출금 - vm.prank(user); - simpleStorage.withdraw(1 ether); - - // Assert: user의 ETH 잔액이 1 ETH 증가했는지 확인 - assertEq( - user.balance, - afterDepositBalance + 1 ether, - "User should receive 1 ether back" - ); - } - - /// @notice 잔액보다 많은 금액을 출금하려고 하면 revert되는지 확인합니다 - function test_RevertWhen_WithdrawExceedsBalance() public { - // Arrange: 1 ETH 입금 - vm.prank(user); - simpleStorage.deposit{value: 1 ether}(); - - // vm.expectRevert: 다음 호출이 revert될 것을 예상 - vm.expectRevert("Insufficient balance"); - - // Act: 2 ETH 출금 시도 (잔액 1 ETH보다 많음) - vm.prank(user); - simpleStorage.withdraw(2 ether); - } - - /// @notice withdraw 함수가 Withdrawn 이벤트를 발생시키는지 확인합니다 - function test_Withdraw_EmitsEvent() public { - // Arrange: 먼저 2 ETH 입금 - vm.prank(user); - simpleStorage.deposit{value: 2 ether}(); - - // 다음 호출에서 Withdrawn 이벤트 예상 - vm.expectEmit(true, false, false, true); - emit SimpleStorage.Withdrawn(user, 1 ether); - - // Act: 1 ETH 출금 - vm.prank(user); - simpleStorage.withdraw(1 ether); - } - - // ============================================================ - // Fuzz 테스트 - // ============================================================ - - /// @notice 임의의 금액으로 입금 테스트 (Fuzz Testing) - /// @dev Foundry가 자동으로 다양한 amount 값을 생성하여 테스트합니다 - /// @param amount 입금할 금액 (Foundry가 자동 생성) - function testFuzz_Deposit(uint256 amount) public { - // vm.assume: 특정 조건을 만족하는 입력만 테스트 - // amount가 0보다 크고, user의 잔액(10 ETH) 이하인 경우만 테스트 - vm.assume(amount > 0 && amount <= 10 ether); - - // Arrange: user가 호출하도록 설정 - vm.prank(user); - - // Act: amount만큼 입금 - simpleStorage.deposit{value: amount}(); - - // Assert: 잔액이 amount와 같은지 확인 - assertEq( - simpleStorage.getBalance(user), - amount, - "Balance should equal deposited amount" - ); - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "../src/SimpleStorage.sol"; + +/// @title SimpleStorageTest - Week 2 테스트 +/// @notice 이 테스트들이 모두 통과하도록 SimpleStorage.sol의 TODO를 구현하세요 +/// @dev Foundry의 고급 테스트 기능(cheatcode, fuzz)을 학습합니다 +contract SimpleStorageTest is Test { + // ============================================================ + // 테스트 상태 변수 + // ============================================================ + + /// @notice 테스트할 SimpleStorage 컨트랙트 인스턴스 + SimpleStorage public simpleStorage; + + /// @notice 테스트에 사용할 사용자 주소 + address public user = address(0x1234); + + // ============================================================ + // setUp 함수 + // ============================================================ + + /// @notice 각 테스트 전에 실행되는 설정 함수 + /// @dev 새로운 SimpleStorage 인스턴스를 생성하고 테스트 계정에 ETH를 지급합니다 + function setUp() public { + // 새로운 SimpleStorage 컨트랙트 배포 + simpleStorage = new SimpleStorage(); + + // vm.deal: 테스트 계정에 ETH 지급 (cheatcode) + // user 주소에 10 ETH를 지급합니다 + vm.deal(user, 10 ether); + } + + // ============================================================ + // 입금(Deposit) 테스트 + // ============================================================ + + /// @notice deposit 함수가 잔액을 올바르게 업데이트하는지 확인합니다 + function test_Deposit_UpdatesBalance() public { + // Arrange: user가 호출하도록 설정 + // vm.prank: 다음 호출의 msg.sender를 변경 (cheatcode) + vm.prank(user); + + // Act: 1 ETH 입금 + simpleStorage.deposit{value: 1 ether}(); + + // Assert: 잔액이 1 ETH인지 확인 + assertEq( + simpleStorage.getBalance(user), + 1 ether, + "Balance should be 1 ether after deposit" + ); + } + + /// @notice deposit 함수가 Deposited 이벤트를 발생시키는지 확인합니다 + function test_Deposit_EmitsEvent() public { + // Arrange: user가 호출하도록 설정 + vm.prank(user); + + // vm.expectEmit: 다음 호출에서 특정 이벤트가 발생할 것을 예상 + // (checkTopic1, checkTopic2, checkTopic3, checkData) + // true = 해당 항목을 검증함 + vm.expectEmit(true, false, false, true); + + // 예상되는 이벤트 (비교 대상) + emit SimpleStorage.Deposited(user, 1 ether); + + // Act: 1 ETH 입금 - 이 호출에서 이벤트가 발생해야 함 + simpleStorage.deposit{value: 1 ether}(); + } + + // ============================================================ + // 출금(Withdraw) 테스트 + // ============================================================ + + /// @notice withdraw 함수가 잔액을 올바르게 업데이트하는지 확인합니다 + function test_Withdraw_UpdatesBalance() public { + // Arrange: 먼저 2 ETH 입금 + vm.prank(user); + simpleStorage.deposit{value: 2 ether}(); + + // Act: 1 ETH 출금 + vm.prank(user); + simpleStorage.withdraw(1 ether); + + // Assert: 잔액이 1 ETH인지 확인 + assertEq( + simpleStorage.getBalance(user), + 1 ether, + "Balance should be 1 ether after withdrawing 1 from 2" + ); + } + + /// @notice withdraw 함수가 ETH를 실제로 전송하는지 확인합니다 + function test_Withdraw_TransfersEther() public { + // Arrange: user의 초기 ETH 잔액 기록 (vm.deal로 10 ETH 지급됨) + uint256 initialBalance = user.balance; + + // 2 ETH 입금 + vm.prank(user); + simpleStorage.deposit{value: 2 ether}(); + + // 입금 후 user의 ETH 잔액 (10 - 2 = 8 ETH) + uint256 afterDepositBalance = user.balance; + assertEq(afterDepositBalance, initialBalance - 2 ether); + + // Act: 1 ETH 출금 + vm.prank(user); + simpleStorage.withdraw(1 ether); + + // Assert: user의 ETH 잔액이 1 ETH 증가했는지 확인 + assertEq( + user.balance, + afterDepositBalance + 1 ether, + "User should receive 1 ether back" + ); + } + + /// @notice 잔액보다 많은 금액을 출금하려고 하면 revert되는지 확인합니다 + function test_RevertWhen_WithdrawExceedsBalance() public { + // Arrange: 1 ETH 입금 + vm.prank(user); + simpleStorage.deposit{value: 1 ether}(); + + // vm.expectRevert: 다음 호출이 revert될 것을 예상 + vm.expectRevert("Insufficient balance"); + + // Act: 2 ETH 출금 시도 (잔액 1 ETH보다 많음) + vm.prank(user); + simpleStorage.withdraw(2 ether); + } + + /// @notice withdraw 함수가 Withdrawn 이벤트를 발생시키는지 확인합니다 + function test_Withdraw_EmitsEvent() public { + // Arrange: 먼저 2 ETH 입금 + vm.prank(user); + simpleStorage.deposit{value: 2 ether}(); + + // 다음 호출에서 Withdrawn 이벤트 예상 + vm.expectEmit(true, false, false, true); + emit SimpleStorage.Withdrawn(user, 1 ether); + + // Act: 1 ETH 출금 + vm.prank(user); + simpleStorage.withdraw(1 ether); + } + + // ============================================================ + // Fuzz 테스트 + // ============================================================ + + /// @notice 임의의 금액으로 입금 테스트 (Fuzz Testing) + /// @dev Foundry가 자동으로 다양한 amount 값을 생성하여 테스트합니다 + /// @param amount 입금할 금액 (Foundry가 자동 생성) + function testFuzz_Deposit(uint256 amount) public { + // vm.assume: 특정 조건을 만족하는 입력만 테스트 + // amount가 0보다 크고, user의 잔액(10 ETH) 이하인 경우만 테스트 + vm.assume(amount > 0 && amount <= 10 ether); + + // Arrange: user가 호출하도록 설정 + vm.prank(user); + + // Act: amount만큼 입금 + simpleStorage.deposit{value: amount}(); + + // Assert: 잔액이 amount와 같은지 확인 + assertEq( + simpleStorage.getBalance(user), + amount, + "Balance should equal deposited amount" + ); + } +} diff --git a/week-02/quiz/quiz-02.md b/week-02/quiz/quiz-02.md index b5c0ac39..0d010c32 100644 --- a/week-02/quiz/quiz-02.md +++ b/week-02/quiz/quiz-02.md @@ -1,290 +1,290 @@ -# Week 2 퀴즈: Transaction/서명 + Foundry - -**제출 방법:** -1. 이 파일을 복사하여 `quiz-02-solution.md`로 저장 -2. 각 문제에 답변 작성 (왜 그런지 설명 포함) -3. Pull Request 생성 (`quiz_submission` 템플릿 사용) - -**평가 기준:** -- 정답 여부보다 **개념 이해도**를 중점 평가합니다 -- "왜"에 대한 설명이 충분한지 확인합니다 -- 코드 문제는 문법보다 논리적 정확성을 평가합니다 - ---- - -## 문제 1: [이론] 트랜잭션 필드 (객관식) - -다음 중 이더리움 트랜잭션에서 `gasPrice`와 `gasLimit`의 관계를 올바르게 설명한 것은? - -**보기:** -A) gasPrice는 최대 사용량, gasLimit은 단위당 가격이다 -B) gasPrice는 단위당 가격, gasLimit은 최대 사용량이다 -C) 둘 다 같은 의미이며 호환되어 사용된다 -D) gasLimit이 높을수록 트랜잭션이 빨리 처리된다 - -**답변:** - - - ---- - -## 문제 2: [이론] nonce의 역할 (객관식) - -다음 상황에서 어떤 일이 발생하나요? - -``` -Alice가 다음 두 트랜잭션을 동시에 네트워크에 브로드캐스트합니다: -- TX-A: nonce=5, Bob에게 1 ETH (gasPrice: 50 Gwei) -- TX-B: nonce=6, Charlie에게 2 ETH (gasPrice: 100 Gwei) - -Alice의 현재 nonce: 5 -``` - -**보기:** -A) TX-B가 gasPrice가 높아서 먼저 처리되고, TX-A는 나중에 처리된다 -B) TX-A가 먼저 처리되어야 TX-B가 처리될 수 있다. gasPrice와 무관하게 순서대로 처리된다 -C) 두 트랜잭션이 동시에 처리된다 -D) 둘 다 실패하고 Alice의 계정이 잠긴다 - -**답변:** - - - ---- - -## 문제 3: [이론] 디지털 서명 (객관식) - -디지털 서명(ECDSA)이 보장하는 세 가지 속성 중, "누군가 내 트랜잭션을 위조할 수 없다"를 보장하는 것은? - -**보기:** -A) 인증 (Authentication) -B) 무결성 (Integrity) -C) 부인 방지 (Non-repudiation) -D) 암호화 (Encryption) - -**답변:** - - - ---- - -## 문제 4: [이론] 키 유도 (객관식) - -다음 중 키 유도 과정에서 올바른 방향을 설명한 것은? - -**보기:** -A) Public Key -> Private Key -> Address 순으로 유도된다 -B) Address -> Public Key -> Private Key 순으로 역추적 가능하다 -C) Private Key -> Public Key -> Address 순으로 유도되며, 역방향은 불가능하다 -D) 세 값은 독립적으로 생성되며 서로 연관이 없다 - -**답변:** - - - ---- - -## 문제 5: [이론] nonce의 필요성 (단답형) - -이더리움에서 **왜** nonce가 필요한가요? - -만약 nonce가 없다면 어떤 공격이 가능해질까요? 구체적인 예시와 함께 설명하세요. - -**답변:** - - - ---- - -## 문제 6: [이론] Private Key 보안 (단답형) - -2022년 Ronin Bridge 해킹에서 약 $625M이 탈취되었습니다. - -**왜** Private Key 유출이 이렇게 치명적인가요? 은행 계좌 비밀번호 유출과 비교해서 설명하세요. - -**답변:** - - - ---- - -## 문제 7: [이론] EIP-1559 이해 (단답형) - -EIP-1559 이전과 이후의 가스 수수료 메커니즘의 가장 큰 차이점은 무엇인가요? - -**힌트:** `baseFee`와 `priorityFee`의 역할을 설명하면서 답변하세요. - -**답변:** - - - ---- - -## 문제 8: [코드] SimpleStorage 테스트 (빈칸 채우기) - -다음 테스트 코드의 빈칸을 채워서 deposit 기능을 테스트하세요: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import "forge-std/Test.sol"; -import "../src/SimpleStorage.sol"; - -contract SimpleStorageTest is Test { - SimpleStorage public storage_; - address public user = address(0x1); - - function setUp() public { - storage_ = new SimpleStorage(); - // user에게 10 ETH 부여 - vm.deal(user, 10 ether); - } - - function test_DepositUpdatesBalance() public { - // Arrange: user 관점에서 실행 - _______________________; // TODO: user로 전환하는 코드 - - // Act: 1 ETH 입금 - _______________________; // TODO: 1 ether를 입금하는 코드 - - // Assert: 잔액 확인 - assertEq(storage_.getBalance(user), ______); // TODO: 예상 잔액 - } -} -``` - -**답변:** -```solidity -// 빈칸을 채운 완성 코드를 작성하세요 -``` - -**왜 이렇게 작성했나요:** - - - ---- - -## 문제 9: [코드] require 조건 (취약점 찾기) - -다음 코드에서 잠재적 문제점을 찾으세요: - -```solidity -// BAD CODE - 문제점 찾기 -contract Wallet { - mapping(address => uint256) public balances; - - function deposit() public payable { - balances[msg.sender] += msg.value; - } - - function withdraw(uint256 amount) public { - // 잔액 차감 - balances[msg.sender] -= amount; - - // ETH 전송 - payable(msg.sender).transfer(amount); - } -} -``` - -**1) 발견한 문제점:** - - - -**2) 왜 이것이 문제인가:** - - - -**3) 올바른 수정 방법:** -```solidity -// GOOD CODE - 수정된 withdraw 함수를 작성하세요 -``` - ---- - -## 문제 10: [코드] 테스트 실패 이유 (코드 분석) - -다음 테스트가 실패하는 이유를 분석하세요: - -```solidity -contract SimpleStorageTest is Test { - SimpleStorage public storage_; - - function setUp() public { - storage_ = new SimpleStorage(); - } - - function test_WithdrawFails() public { - // 입금 없이 바로 출금 시도 - storage_.withdraw(1 ether); - } -} -``` - -**질문 1:** 이 테스트가 실패하는 이유는 무엇인가요? - -**답변:** - - - -**질문 2:** 이 테스트를 "출금 실패를 테스트하는 정상 테스트"로 바꾸려면 어떻게 수정해야 하나요? - -**답변:** -```solidity -// 힌트: vm.expectRevert를 사용하세요 -// 수정된 테스트 코드를 작성하세요 -``` - ---- - -## 자기 평가 - -모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: - -- [ ] 트랜잭션 필드(nonce, gasPrice, gasLimit 등)의 역할을 이해했다 -- [ ] 디지털 서명의 세 가지 보장(인증, 무결성, 부인 방지)을 설명할 수 있다 -- [ ] Private Key 보안의 중요성을 이해했다 -- [ ] Foundry 테스트 기본 패턴(vm.prank, vm.deal, assertEq)을 사용할 수 있다 -- [ ] require 조건의 필요성을 이해했다 - ---- - -## 참고 자료 - -- 이론: `eth-materials/week-02/theory/slides.md` -- 코드: `eth-homework/week-02/dev/src/SimpleStorage.sol` -- 테스트: `eth-homework/week-02/dev/test/SimpleStorage.t.sol` -- 용어: `eth-materials/resources/glossary.md` +# Week 2 퀴즈: Transaction/서명 + Foundry + +**제출 방법:** +1. 이 파일을 복사하여 `quiz-02-solution.md`로 저장 +2. 각 문제에 답변 작성 (왜 그런지 설명 포함) +3. Pull Request 생성 (`quiz_submission` 템플릿 사용) + +**평가 기준:** +- 정답 여부보다 **개념 이해도**를 중점 평가합니다 +- "왜"에 대한 설명이 충분한지 확인합니다 +- 코드 문제는 문법보다 논리적 정확성을 평가합니다 + +--- + +## 문제 1: [이론] 트랜잭션 필드 (객관식) + +다음 중 이더리움 트랜잭션에서 `gasPrice`와 `gasLimit`의 관계를 올바르게 설명한 것은? + +**보기:** +A) gasPrice는 최대 사용량, gasLimit은 단위당 가격이다 +B) gasPrice는 단위당 가격, gasLimit은 최대 사용량이다 +C) 둘 다 같은 의미이며 호환되어 사용된다 +D) gasLimit이 높을수록 트랜잭션이 빨리 처리된다 + +**답변:** + + + +--- + +## 문제 2: [이론] nonce의 역할 (객관식) + +다음 상황에서 어떤 일이 발생하나요? + +``` +Alice가 다음 두 트랜잭션을 동시에 네트워크에 브로드캐스트합니다: +- TX-A: nonce=5, Bob에게 1 ETH (gasPrice: 50 Gwei) +- TX-B: nonce=6, Charlie에게 2 ETH (gasPrice: 100 Gwei) + +Alice의 현재 nonce: 5 +``` + +**보기:** +A) TX-B가 gasPrice가 높아서 먼저 처리되고, TX-A는 나중에 처리된다 +B) TX-A가 먼저 처리되어야 TX-B가 처리될 수 있다. gasPrice와 무관하게 순서대로 처리된다 +C) 두 트랜잭션이 동시에 처리된다 +D) 둘 다 실패하고 Alice의 계정이 잠긴다 + +**답변:** + + + +--- + +## 문제 3: [이론] 디지털 서명 (객관식) + +디지털 서명(ECDSA)이 보장하는 세 가지 속성 중, "누군가 내 트랜잭션을 위조할 수 없다"를 보장하는 것은? + +**보기:** +A) 인증 (Authentication) +B) 무결성 (Integrity) +C) 부인 방지 (Non-repudiation) +D) 암호화 (Encryption) + +**답변:** + + + +--- + +## 문제 4: [이론] 키 유도 (객관식) + +다음 중 키 유도 과정에서 올바른 방향을 설명한 것은? + +**보기:** +A) Public Key -> Private Key -> Address 순으로 유도된다 +B) Address -> Public Key -> Private Key 순으로 역추적 가능하다 +C) Private Key -> Public Key -> Address 순으로 유도되며, 역방향은 불가능하다 +D) 세 값은 독립적으로 생성되며 서로 연관이 없다 + +**답변:** + + + +--- + +## 문제 5: [이론] nonce의 필요성 (단답형) + +이더리움에서 **왜** nonce가 필요한가요? + +만약 nonce가 없다면 어떤 공격이 가능해질까요? 구체적인 예시와 함께 설명하세요. + +**답변:** + + + +--- + +## 문제 6: [이론] Private Key 보안 (단답형) + +2022년 Ronin Bridge 해킹에서 약 $625M이 탈취되었습니다. + +**왜** Private Key 유출이 이렇게 치명적인가요? 은행 계좌 비밀번호 유출과 비교해서 설명하세요. + +**답변:** + + + +--- + +## 문제 7: [이론] EIP-1559 이해 (단답형) + +EIP-1559 이전과 이후의 가스 수수료 메커니즘의 가장 큰 차이점은 무엇인가요? + +**힌트:** `baseFee`와 `priorityFee`의 역할을 설명하면서 답변하세요. + +**답변:** + + + +--- + +## 문제 8: [코드] SimpleStorage 테스트 (빈칸 채우기) + +다음 테스트 코드의 빈칸을 채워서 deposit 기능을 테스트하세요: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "../src/SimpleStorage.sol"; + +contract SimpleStorageTest is Test { + SimpleStorage public storage_; + address public user = address(0x1); + + function setUp() public { + storage_ = new SimpleStorage(); + // user에게 10 ETH 부여 + vm.deal(user, 10 ether); + } + + function test_DepositUpdatesBalance() public { + // Arrange: user 관점에서 실행 + _______________________; // TODO: user로 전환하는 코드 + + // Act: 1 ETH 입금 + _______________________; // TODO: 1 ether를 입금하는 코드 + + // Assert: 잔액 확인 + assertEq(storage_.getBalance(user), ______); // TODO: 예상 잔액 + } +} +``` + +**답변:** +```solidity +// 빈칸을 채운 완성 코드를 작성하세요 +``` + +**왜 이렇게 작성했나요:** + + + +--- + +## 문제 9: [코드] require 조건 (취약점 찾기) + +다음 코드에서 잠재적 문제점을 찾으세요: + +```solidity +// BAD CODE - 문제점 찾기 +contract Wallet { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw(uint256 amount) public { + // 잔액 차감 + balances[msg.sender] -= amount; + + // ETH 전송 + payable(msg.sender).transfer(amount); + } +} +``` + +**1) 발견한 문제점:** + + + +**2) 왜 이것이 문제인가:** + + + +**3) 올바른 수정 방법:** +```solidity +// GOOD CODE - 수정된 withdraw 함수를 작성하세요 +``` + +--- + +## 문제 10: [코드] 테스트 실패 이유 (코드 분석) + +다음 테스트가 실패하는 이유를 분석하세요: + +```solidity +contract SimpleStorageTest is Test { + SimpleStorage public storage_; + + function setUp() public { + storage_ = new SimpleStorage(); + } + + function test_WithdrawFails() public { + // 입금 없이 바로 출금 시도 + storage_.withdraw(1 ether); + } +} +``` + +**질문 1:** 이 테스트가 실패하는 이유는 무엇인가요? + +**답변:** + + + +**질문 2:** 이 테스트를 "출금 실패를 테스트하는 정상 테스트"로 바꾸려면 어떻게 수정해야 하나요? + +**답변:** +```solidity +// 힌트: vm.expectRevert를 사용하세요 +// 수정된 테스트 코드를 작성하세요 +``` + +--- + +## 자기 평가 + +모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: + +- [ ] 트랜잭션 필드(nonce, gasPrice, gasLimit 등)의 역할을 이해했다 +- [ ] 디지털 서명의 세 가지 보장(인증, 무결성, 부인 방지)을 설명할 수 있다 +- [ ] Private Key 보안의 중요성을 이해했다 +- [ ] Foundry 테스트 기본 패턴(vm.prank, vm.deal, assertEq)을 사용할 수 있다 +- [ ] require 조건의 필요성을 이해했다 + +--- + +## 참고 자료 + +- 이론: `eth-materials/week-02/theory/slides.md` +- 코드: `eth-homework/week-02/dev/src/SimpleStorage.sol` +- 테스트: `eth-homework/week-02/dev/test/SimpleStorage.t.sol` +- 용어: `eth-materials/resources/glossary.md` diff --git a/week-03/dev/README.md b/week-03/dev/README.md index 435cd9c0..5e7f8a68 100644 --- a/week-03/dev/README.md +++ b/week-03/dev/README.md @@ -1,219 +1,219 @@ -# Week 3: 스마트 컨트랙트 보안 패턴 - -## 학습 목표 - -이번 주차에서는 스마트 컨트랙트 보안의 핵심 개념을 배웁니다: - -1. **재진입 공격 (Reentrancy Attack) 이해** - - 외부 호출 시 발생할 수 있는 취약점 - - The DAO 해킹 사례 분석 - -2. **CEI (Checks-Effects-Interactions) 패턴** - - 안전한 함수 작성 순서 - - 상태 변경과 외부 호출의 올바른 배치 - -3. **OpenZeppelin ReentrancyGuard** - - 검증된 라이브러리 활용 - - `nonReentrant` modifier 사용법 - ---- - -## The DAO 해킹 사례 (2016) - -2016년 6월, 이더리움 역사상 가장 유명한 해킹 사건이 발생했습니다. - -- **피해 금액**: 약 $60M (당시 이더리움 시가총액의 약 14%) -- **원인**: 재진입 취약점 (Reentrancy Vulnerability) -- **결과**: 이더리움 하드포크 → Ethereum(ETH)과 Ethereum Classic(ETC)으로 분리 - -공격자는 DAO 컨트랙트의 `withdraw` 함수가 잔액을 업데이트하기 전에 외부 호출을 수행한다는 점을 악용했습니다. `receive()` 함수에서 다시 `withdraw()`를 호출하여 반복적으로 자금을 인출했습니다. - -**교훈**: 외부 호출 전에 반드시 상태를 업데이트하세요! - ---- - -## 파일 구조 - -``` -week-03/dev/ -├── src/ -│ ├── Vault.sol # ❌ 취약한 버전 (BAD - 교육용) -│ └── VaultSecure.sol # ✅ 구현 대상 (GOOD - 여러분이 완성) -├── test/ -│ └── Vault.t.sol # 테스트 스위트 (TDD) -└── README.md # 이 파일 -``` - -### 파일 설명 - -| 파일 | 설명 | 수정 여부 | -|------|------|----------| -| `Vault.sol` | 재진입에 취약한 예시 코드. **절대 프로덕션에서 사용하지 마세요!** | 읽기 전용 | -| `VaultSecure.sol` | 여러분이 구현할 안전한 버전. TODO 주석을 따라 완성하세요. | **수정 대상** | -| `Vault.t.sol` | 모든 테스트가 포함된 TDD 테스트 파일. 테스트를 읽고 예상 동작을 파악하세요. | 읽기 전용 | - ---- - -## TDD (Test-Driven Development) 접근법 - -이 과제는 TDD 방식으로 진행됩니다: - -### 1단계: 테스트 읽기 - -먼저 `test/Vault.t.sol`의 테스트들을 읽어보세요: - -```bash -# 테스트 파일 확인 -cat week-03/dev/test/Vault.t.sol -``` - -각 테스트 함수의 이름이 예상 동작을 설명합니다: -- `test_Deposit_IncreasesUserBalance()` → 입금 시 잔액 증가 -- `test_Withdraw_DecreasesUserBalance()` → 출금 시 잔액 감소 -- `test_RevertWhen_WithdrawExceedsBalance()` → 잔액 초과 출금 시 revert -- `test_ReentrancyAttack_CannotDrainVault()` → **재진입 공격 방어** (핵심!) - -### 2단계: 구현하기 - -`src/VaultSecure.sol`의 TODO 주석을 따라 구현하세요: -- `deposit()` 함수 -- `withdraw()` 함수 (재진입에 안전하게!) - -### 3단계: 테스트 실행 - -```bash -# Week 3 테스트만 실행 -forge test --match-path week-03/dev/test/Vault.t.sol -vv - -# 특정 테스트만 실행 -forge test --match-test test_ReentrancyAttack -vv - -# 가스 리포트 포함 -forge test --match-path week-03/dev/test/Vault.t.sol --gas-report -``` - -### 4단계: 모든 테스트 통과 확인 - -``` -Running 12 tests for test/Vault.t.sol:VaultSecureTest -[PASS] test_Deposit_AccumulatesBalance() -[PASS] test_Deposit_EmitsDepositedEvent() -[PASS] test_Deposit_IncreasesContractBalance() -[PASS] test_Deposit_IncreasesUserBalance() -[PASS] test_GetBalance_ReturnsContractBalance() -[PASS] test_ReentrancyAttack_AttackerGetsOnlyOwnDeposit() -[PASS] test_ReentrancyAttack_CannotDrainVault() ← 핵심! -[PASS] test_RevertWhen_WithdrawExceedsBalance() -[PASS] test_RevertWhen_WithdrawWithZeroBalance() -[PASS] test_Withdraw_DecreasesUserBalance() -[PASS] test_Withdraw_EmitsWithdrawnEvent() -[PASS] test_Withdraw_TransfersEtherToUser() -``` - ---- - -## 구현 선택지 - -`VaultSecure.sol`을 구현할 때 두 가지 방법 중 선택할 수 있습니다: - -### 선택 1: CEI (Checks-Effects-Interactions) 패턴 - -코드 순서만 바꾸면 됩니다: - -```solidity -function withdraw(uint256 amount) public { - // 1. Checks (검증) - require(balances[msg.sender] >= amount, "Insufficient balance"); - - // 2. Effects (상태 변경) - 외부 호출 전에! - balances[msg.sender] -= amount; - - // 3. Interactions (외부 호출) - 마지막에! - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Transfer failed"); - - emit Withdrawn(msg.sender, amount); -} -``` - -**장점:** -- 가스 효율적 (추가 스토리지 연산 없음) -- 외부 라이브러리 의존성 없음 - -**단점:** -- 개발자가 순서를 직접 관리해야 함 -- 복잡한 컨트랙트에서는 실수 가능성 - -### 선택 2: OpenZeppelin ReentrancyGuard - -검증된 라이브러리의 modifier를 사용합니다: - -```solidity -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; - -contract VaultSecure is ReentrancyGuard { - function withdraw(uint256 amount) public nonReentrant { - require(balances[msg.sender] >= amount, "Insufficient balance"); - - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Transfer failed"); - - balances[msg.sender] -= amount; - emit Withdrawn(msg.sender, amount); - } -} -``` - -**장점:** -- 명시적이고 안전함 -- OpenZeppelin이 검증한 코드 -- 실수 방지 - -**단점:** -- 약간의 가스 오버헤드 (~2,500 gas) -- 외부 의존성 - -### 권장 사항 - -1. **학습 목적**: CEI 패턴을 먼저 이해하고 구현해보세요 -2. **프로덕션**: CEI + ReentrancyGuard 둘 다 사용하는 것이 안전합니다 - ---- - -## 디버깅 팁 - -### 테스트 실패 시 - -```bash -# 상세 출력으로 실행 -forge test --match-path week-03/dev/test/Vault.t.sol -vvvv - -# 특정 실패 테스트만 -forge test --match-test test_ReentrancyAttack_CannotDrainVault -vvvv -``` - -### 흔한 실수 - -1. **Checks를 빠뜨림**: `require(balances[msg.sender] >= amount, ...)` 필수 -2. **Effects 순서 잘못**: 상태 변경이 call() 후에 있으면 취약 -3. **이벤트 누락**: `emit Withdrawn(...)` 잊지 마세요 - ---- - -## 추가 학습 자료 - -- [eth-materials/week-03/dev/security-patterns.md](../../materials/week-03/dev/security-patterns.md) - 보안 패턴 상세 가이드 -- [SWC-107: Reentrancy](https://swcregistry.io/docs/SWC-107) - 공식 취약점 레지스트리 -- [OpenZeppelin ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) - ---- - -## 체크리스트 - -과제 제출 전 확인하세요: - -- [ ] `deposit()` 함수 구현 완료 -- [ ] `withdraw()` 함수 구현 완료 (CEI 또는 ReentrancyGuard) -- [ ] 모든 테스트 통과 (`forge test --match-path week-03/dev/test/Vault.t.sol`) -- [ ] 특히 `test_ReentrancyAttack_CannotDrainVault` 통과 -- [ ] 코드에 적절한 주석 추가 +# Week 3: 스마트 컨트랙트 보안 패턴 + +## 학습 목표 + +이번 주차에서는 스마트 컨트랙트 보안의 핵심 개념을 배웁니다: + +1. **재진입 공격 (Reentrancy Attack) 이해** + - 외부 호출 시 발생할 수 있는 취약점 + - The DAO 해킹 사례 분석 + +2. **CEI (Checks-Effects-Interactions) 패턴** + - 안전한 함수 작성 순서 + - 상태 변경과 외부 호출의 올바른 배치 + +3. **OpenZeppelin ReentrancyGuard** + - 검증된 라이브러리 활용 + - `nonReentrant` modifier 사용법 + +--- + +## The DAO 해킹 사례 (2016) + +2016년 6월, 이더리움 역사상 가장 유명한 해킹 사건이 발생했습니다. + +- **피해 금액**: 약 $60M (당시 이더리움 시가총액의 약 14%) +- **원인**: 재진입 취약점 (Reentrancy Vulnerability) +- **결과**: 이더리움 하드포크 → Ethereum(ETH)과 Ethereum Classic(ETC)으로 분리 + +공격자는 DAO 컨트랙트의 `withdraw` 함수가 잔액을 업데이트하기 전에 외부 호출을 수행한다는 점을 악용했습니다. `receive()` 함수에서 다시 `withdraw()`를 호출하여 반복적으로 자금을 인출했습니다. + +**교훈**: 외부 호출 전에 반드시 상태를 업데이트하세요! + +--- + +## 파일 구조 + +``` +week-03/dev/ +├── src/ +│ ├── Vault.sol # ❌ 취약한 버전 (BAD - 교육용) +│ └── VaultSecure.sol # ✅ 구현 대상 (GOOD - 여러분이 완성) +├── test/ +│ └── Vault.t.sol # 테스트 스위트 (TDD) +└── README.md # 이 파일 +``` + +### 파일 설명 + +| 파일 | 설명 | 수정 여부 | +|------|------|----------| +| `Vault.sol` | 재진입에 취약한 예시 코드. **절대 프로덕션에서 사용하지 마세요!** | 읽기 전용 | +| `VaultSecure.sol` | 여러분이 구현할 안전한 버전. TODO 주석을 따라 완성하세요. | **수정 대상** | +| `Vault.t.sol` | 모든 테스트가 포함된 TDD 테스트 파일. 테스트를 읽고 예상 동작을 파악하세요. | 읽기 전용 | + +--- + +## TDD (Test-Driven Development) 접근법 + +이 과제는 TDD 방식으로 진행됩니다: + +### 1단계: 테스트 읽기 + +먼저 `test/Vault.t.sol`의 테스트들을 읽어보세요: + +```bash +# 테스트 파일 확인 +cat week-03/dev/test/Vault.t.sol +``` + +각 테스트 함수의 이름이 예상 동작을 설명합니다: +- `test_Deposit_IncreasesUserBalance()` → 입금 시 잔액 증가 +- `test_Withdraw_DecreasesUserBalance()` → 출금 시 잔액 감소 +- `test_RevertWhen_WithdrawExceedsBalance()` → 잔액 초과 출금 시 revert +- `test_ReentrancyAttack_CannotDrainVault()` → **재진입 공격 방어** (핵심!) + +### 2단계: 구현하기 + +`src/VaultSecure.sol`의 TODO 주석을 따라 구현하세요: +- `deposit()` 함수 +- `withdraw()` 함수 (재진입에 안전하게!) + +### 3단계: 테스트 실행 + +```bash +# Week 3 테스트만 실행 +forge test --match-path week-03/dev/test/Vault.t.sol -vv + +# 특정 테스트만 실행 +forge test --match-test test_ReentrancyAttack -vv + +# 가스 리포트 포함 +forge test --match-path week-03/dev/test/Vault.t.sol --gas-report +``` + +### 4단계: 모든 테스트 통과 확인 + +``` +Running 12 tests for test/Vault.t.sol:VaultSecureTest +[PASS] test_Deposit_AccumulatesBalance() +[PASS] test_Deposit_EmitsDepositedEvent() +[PASS] test_Deposit_IncreasesContractBalance() +[PASS] test_Deposit_IncreasesUserBalance() +[PASS] test_GetBalance_ReturnsContractBalance() +[PASS] test_ReentrancyAttack_AttackerGetsOnlyOwnDeposit() +[PASS] test_ReentrancyAttack_CannotDrainVault() ← 핵심! +[PASS] test_RevertWhen_WithdrawExceedsBalance() +[PASS] test_RevertWhen_WithdrawWithZeroBalance() +[PASS] test_Withdraw_DecreasesUserBalance() +[PASS] test_Withdraw_EmitsWithdrawnEvent() +[PASS] test_Withdraw_TransfersEtherToUser() +``` + +--- + +## 구현 선택지 + +`VaultSecure.sol`을 구현할 때 두 가지 방법 중 선택할 수 있습니다: + +### 선택 1: CEI (Checks-Effects-Interactions) 패턴 + +코드 순서만 바꾸면 됩니다: + +```solidity +function withdraw(uint256 amount) public { + // 1. Checks (검증) + require(balances[msg.sender] >= amount, "Insufficient balance"); + + // 2. Effects (상태 변경) - 외부 호출 전에! + balances[msg.sender] -= amount; + + // 3. Interactions (외부 호출) - 마지막에! + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + + emit Withdrawn(msg.sender, amount); +} +``` + +**장점:** +- 가스 효율적 (추가 스토리지 연산 없음) +- 외부 라이브러리 의존성 없음 + +**단점:** +- 개발자가 순서를 직접 관리해야 함 +- 복잡한 컨트랙트에서는 실수 가능성 + +### 선택 2: OpenZeppelin ReentrancyGuard + +검증된 라이브러리의 modifier를 사용합니다: + +```solidity +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +contract VaultSecure is ReentrancyGuard { + function withdraw(uint256 amount) public nonReentrant { + require(balances[msg.sender] >= amount, "Insufficient balance"); + + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + + balances[msg.sender] -= amount; + emit Withdrawn(msg.sender, amount); + } +} +``` + +**장점:** +- 명시적이고 안전함 +- OpenZeppelin이 검증한 코드 +- 실수 방지 + +**단점:** +- 약간의 가스 오버헤드 (~2,500 gas) +- 외부 의존성 + +### 권장 사항 + +1. **학습 목적**: CEI 패턴을 먼저 이해하고 구현해보세요 +2. **프로덕션**: CEI + ReentrancyGuard 둘 다 사용하는 것이 안전합니다 + +--- + +## 디버깅 팁 + +### 테스트 실패 시 + +```bash +# 상세 출력으로 실행 +forge test --match-path week-03/dev/test/Vault.t.sol -vvvv + +# 특정 실패 테스트만 +forge test --match-test test_ReentrancyAttack_CannotDrainVault -vvvv +``` + +### 흔한 실수 + +1. **Checks를 빠뜨림**: `require(balances[msg.sender] >= amount, ...)` 필수 +2. **Effects 순서 잘못**: 상태 변경이 call() 후에 있으면 취약 +3. **이벤트 누락**: `emit Withdrawn(...)` 잊지 마세요 + +--- + +## 추가 학습 자료 + +- [eth-materials/week-03/dev/security-patterns.md](../../materials/week-03/dev/security-patterns.md) - 보안 패턴 상세 가이드 +- [SWC-107: Reentrancy](https://swcregistry.io/docs/SWC-107) - 공식 취약점 레지스트리 +- [OpenZeppelin ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) + +--- + +## 체크리스트 + +과제 제출 전 확인하세요: + +- [ ] `deposit()` 함수 구현 완료 +- [ ] `withdraw()` 함수 구현 완료 (CEI 또는 ReentrancyGuard) +- [ ] 모든 테스트 통과 (`forge test --match-path week-03/dev/test/Vault.t.sol`) +- [ ] 특히 `test_ReentrancyAttack_CannotDrainVault` 통과 +- [ ] 코드에 적절한 주석 추가 diff --git a/week-03/dev/src/Vault.sol b/week-03/dev/src/Vault.sol index 7ce2e5fa..0050cef7 100644 --- a/week-03/dev/src/Vault.sol +++ b/week-03/dev/src/Vault.sol @@ -1,125 +1,125 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -/// @title Vault (취약한 버전 - 교육용) -/// @author Bay-17th Ethereum Study -/// @notice WARNING: 이 컨트랙트는 재진입 공격에 취약합니다. 절대 프로덕션에서 사용하지 마세요! -/// @dev The DAO 해킹(2016, $60M 손실)과 동일한 취약점을 포함합니다. -/// -/// ============================================ -/// WARNING: 취약한 코드 - 학습 목적 전용 -/// ============================================ -/// -/// 이 컨트랙트는 재진입(Reentrancy) 공격을 설명하기 위해 의도적으로 취약하게 작성되었습니다. -/// 실제 프로젝트에서는 절대 이런 패턴을 사용하지 마세요. -/// -/// 공격 시나리오: -/// 1. 공격자가 Attacker 컨트랙트를 통해 deposit()으로 1 ETH 입금 -/// 2. 공격자가 withdraw(1 ether) 호출 -/// 3. Vault가 공격자에게 1 ETH 전송 (call) -/// 4. Attacker의 receive()가 트리거되어 다시 withdraw(1 ether) 호출 -/// 5. Vault는 아직 balances를 업데이트하지 않았으므로 또 전송 -/// 6. 반복... Vault가 빈털터리가 될 때까지 -/// -/// 공격자 컨트랙트 예시: -/// ```solidity -/// contract Attacker { -/// Vault public vault; -/// -/// constructor(address _vault) { -/// vault = Vault(_vault); -/// } -/// -/// function attack() external payable { -/// vault.deposit{value: msg.value}(); -/// vault.withdraw(msg.value); -/// } -/// -/// receive() external payable { -/// if (address(vault).balance >= 1 ether) { -/// vault.withdraw(1 ether); -/// } -/// } -/// } -/// ``` -contract Vault { - // ============================================ - // 상태 변수 - // ============================================ - - /// @dev 사용자별 예치금 잔액 - mapping(address => uint256) public balances; - - // ============================================ - // 이벤트 - // ============================================ - - /// @dev 입금 시 발생하는 이벤트 - event Deposited(address indexed user, uint256 amount); - - /// @dev 출금 시 발생하는 이벤트 - event Withdrawn(address indexed user, uint256 amount); - - // ============================================ - // 외부 함수 - // ============================================ - - /// @notice ETH를 Vault에 예치합니다 - /// @dev msg.value만큼 예치하고 Deposited 이벤트를 발생시킵니다 - function deposit() public payable { - // 잔액 증가 - balances[msg.sender] += msg.value; - - // 입금 이벤트 발생 - emit Deposited(msg.sender, msg.value); - } - - /// @notice 예치한 ETH를 출금합니다 - /// @dev WARNING: 이 함수는 재진입 공격에 취약합니다! - /// @param amount 출금할 ETH 양 (wei 단위) - /// - /// ======================================== - /// ❌ 취약한 코드: 외부 호출 후 상태 업데이트 - /// ======================================== - /// - /// 문제점: - /// - call()로 외부 호출을 먼저 수행 - /// - 상태(balances) 업데이트는 나중에 수행 - /// - 외부 호출 중에 재귀적으로 withdraw() 호출 가능 - function withdraw(uint256 amount) public { - // ======================================== - // Checks (검증) - 이 부분은 올바름 - // ======================================== - // 잔액이 충분한지 확인 - require(balances[msg.sender] >= amount, "Insufficient balance"); - - // ======================================== - // ❌ 위험: Interactions (상호작용) 먼저! - // ======================================== - // call()로 ETH 전송 - 이 시점에서 공격자의 receive()가 실행됨 - // 공격자의 receive()가 다시 withdraw()를 호출하면 - // balances[msg.sender]는 아직 그대로이므로 검증을 통과함 - (bool success,) = msg.sender.call{value: amount}(""); - require(success, "Transfer failed"); - - // ======================================== - // ❌ 위험: Effects (상태변경) 나중에! - // ======================================== - // 이 줄에 도달하기 전에 위의 call()에서 재진입이 발생하면 - // 공격자는 balances가 업데이트되기 전에 반복 출금 가능 - balances[msg.sender] -= amount; - - // 출금 이벤트 발생 - emit Withdrawn(msg.sender, amount); - } - - // ============================================ - // View 함수 - // ============================================ - - /// @notice Vault의 총 잔액을 반환합니다 - /// @return Vault 컨트랙트가 보유한 ETH 총량 (wei) - function getBalance() public view returns (uint256) { - return address(this).balance; - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/// @title Vault (취약한 버전 - 교육용) +/// @author Bay-17th Ethereum Study +/// @notice WARNING: 이 컨트랙트는 재진입 공격에 취약합니다. 절대 프로덕션에서 사용하지 마세요! +/// @dev The DAO 해킹(2016, $60M 손실)과 동일한 취약점을 포함합니다. +/// +/// ============================================ +/// WARNING: 취약한 코드 - 학습 목적 전용 +/// ============================================ +/// +/// 이 컨트랙트는 재진입(Reentrancy) 공격을 설명하기 위해 의도적으로 취약하게 작성되었습니다. +/// 실제 프로젝트에서는 절대 이런 패턴을 사용하지 마세요. +/// +/// 공격 시나리오: +/// 1. 공격자가 Attacker 컨트랙트를 통해 deposit()으로 1 ETH 입금 +/// 2. 공격자가 withdraw(1 ether) 호출 +/// 3. Vault가 공격자에게 1 ETH 전송 (call) +/// 4. Attacker의 receive()가 트리거되어 다시 withdraw(1 ether) 호출 +/// 5. Vault는 아직 balances를 업데이트하지 않았으므로 또 전송 +/// 6. 반복... Vault가 빈털터리가 될 때까지 +/// +/// 공격자 컨트랙트 예시: +/// ```solidity +/// contract Attacker { +/// Vault public vault; +/// +/// constructor(address _vault) { +/// vault = Vault(_vault); +/// } +/// +/// function attack() external payable { +/// vault.deposit{value: msg.value}(); +/// vault.withdraw(msg.value); +/// } +/// +/// receive() external payable { +/// if (address(vault).balance >= 1 ether) { +/// vault.withdraw(1 ether); +/// } +/// } +/// } +/// ``` +contract Vault { + // ============================================ + // 상태 변수 + // ============================================ + + /// @dev 사용자별 예치금 잔액 + mapping(address => uint256) public balances; + + // ============================================ + // 이벤트 + // ============================================ + + /// @dev 입금 시 발생하는 이벤트 + event Deposited(address indexed user, uint256 amount); + + /// @dev 출금 시 발생하는 이벤트 + event Withdrawn(address indexed user, uint256 amount); + + // ============================================ + // 외부 함수 + // ============================================ + + /// @notice ETH를 Vault에 예치합니다 + /// @dev msg.value만큼 예치하고 Deposited 이벤트를 발생시킵니다 + function deposit() public payable { + // 잔액 증가 + balances[msg.sender] += msg.value; + + // 입금 이벤트 발생 + emit Deposited(msg.sender, msg.value); + } + + /// @notice 예치한 ETH를 출금합니다 + /// @dev WARNING: 이 함수는 재진입 공격에 취약합니다! + /// @param amount 출금할 ETH 양 (wei 단위) + /// + /// ======================================== + /// ❌ 취약한 코드: 외부 호출 후 상태 업데이트 + /// ======================================== + /// + /// 문제점: + /// - call()로 외부 호출을 먼저 수행 + /// - 상태(balances) 업데이트는 나중에 수행 + /// - 외부 호출 중에 재귀적으로 withdraw() 호출 가능 + function withdraw(uint256 amount) public { + // ======================================== + // Checks (검증) - 이 부분은 올바름 + // ======================================== + // 잔액이 충분한지 확인 + require(balances[msg.sender] >= amount, "Insufficient balance"); + + // ======================================== + // ❌ 위험: Interactions (상호작용) 먼저! + // ======================================== + // call()로 ETH 전송 - 이 시점에서 공격자의 receive()가 실행됨 + // 공격자의 receive()가 다시 withdraw()를 호출하면 + // balances[msg.sender]는 아직 그대로이므로 검증을 통과함 + (bool success,) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + + // ======================================== + // ❌ 위험: Effects (상태변경) 나중에! + // ======================================== + // 이 줄에 도달하기 전에 위의 call()에서 재진입이 발생하면 + // 공격자는 balances가 업데이트되기 전에 반복 출금 가능 + balances[msg.sender] -= amount; + + // 출금 이벤트 발생 + emit Withdrawn(msg.sender, amount); + } + + // ============================================ + // View 함수 + // ============================================ + + /// @notice Vault의 총 잔액을 반환합니다 + /// @return Vault 컨트랙트가 보유한 ETH 총량 (wei) + function getBalance() public view returns (uint256) { + return address(this).balance; + } +} diff --git a/week-03/dev/src/VaultSecure.sol b/week-03/dev/src/VaultSecure.sol index a9a19e38..5ba8925b 100644 --- a/week-03/dev/src/VaultSecure.sol +++ b/week-03/dev/src/VaultSecure.sol @@ -1,140 +1,140 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -/// @title VaultSecure (안전한 버전) -/// @author Bay-17th Ethereum Study -/// @notice 이 컨트랙트를 CEI 패턴 또는 ReentrancyGuard로 구현하세요 -/// @dev 테스트를 통과하도록 아래 함수들을 구현하세요 -/// -/// ============================================ -/// 구현 과제: 재진입 공격에 안전한 Vault 만들기 -/// ============================================ -/// -/// 이 컨트랙트는 여러분이 직접 구현해야 합니다. -/// Vault.sol의 취약점을 이해하고, 안전한 버전을 작성하세요. -/// -/// ============================================ -/// 선택 1: CEI (Checks-Effects-Interactions) 패턴 -/// ============================================ -/// -/// 순서만 바꾸면 됩니다: -/// 1. Checks: 조건 검증 (require, if) -/// 2. Effects: 상태 변경 (balances 업데이트) -/// 3. Interactions: 외부 호출 (call, transfer) -/// -/// 예시: -/// ```solidity -/// function withdraw(uint256 amount) public { -/// // Checks -/// require(balances[msg.sender] >= amount, "Insufficient balance"); -/// -/// // Effects (상태 먼저 변경!) -/// balances[msg.sender] -= amount; -/// -/// // Interactions (외부 호출은 마지막!) -/// (bool success, ) = msg.sender.call{value: amount}(""); -/// require(success, "Transfer failed"); -/// } -/// ``` -/// -/// ============================================ -/// 선택 2: OpenZeppelin ReentrancyGuard -/// ============================================ -/// -/// OpenZeppelin의 검증된 라이브러리를 사용합니다: -/// -/// ```solidity -/// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -/// -/// contract VaultSecure is ReentrancyGuard { -/// function withdraw(uint256 amount) public nonReentrant { -/// // 로직... -/// } -/// } -/// ``` -/// -/// nonReentrant modifier가 재진입을 막아줍니다. -/// -/// ============================================ -/// 어떤 방법을 선택할까? -/// ============================================ -/// -/// CEI 패턴: -/// - 장점: 가스 효율적, 외부 의존성 없음 -/// - 단점: 개발자가 순서를 직접 관리해야 함 -/// -/// ReentrancyGuard: -/// - 장점: 실수 방지, 명시적, 검증된 코드 -/// - 단점: 약간의 가스 오버헤드 (~2,500 gas) -/// -/// 권장: 학습 목적으로 CEI 먼저 이해하고, 프로덕션에서는 둘 다 사용 - -// ============================================ -// OpenZeppelin 사용 시 주석 해제 -// ============================================ -// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; - -/// @dev ReentrancyGuard 사용 시: contract VaultSecure is ReentrancyGuard -contract VaultSecure { - // ============================================ - // 상태 변수 - // ============================================ - - /// @dev 사용자별 예치금 잔액 - mapping(address => uint256) public balances; - - // ============================================ - // 이벤트 - // ============================================ - - /// @dev 입금 시 발생하는 이벤트 - event Deposited(address indexed user, uint256 amount); - - /// @dev 출금 시 발생하는 이벤트 - event Withdrawn(address indexed user, uint256 amount); - - // ============================================ - // 외부 함수 - // ============================================ - - /// @notice ETH를 Vault에 예치합니다 - /// @dev msg.value만큼 예치하고 Deposited 이벤트를 발생시킵니다 - /// - /// TODO: deposit() 함수를 구현하세요 - /// - msg.value를 balances[msg.sender]에 추가 - /// - Deposited 이벤트 발생 - /// - /// 힌트: Vault.sol의 deposit()과 동일하게 구현하면 됩니다 - function deposit() public payable { - // TODO: 구현하세요 - } - - /// @notice 예치한 ETH를 출금합니다 - /// @param amount 출금할 ETH 양 (wei 단위) - /// - /// TODO: withdraw(uint256 amount) 함수를 구현하세요 - /// - 재진입 공격에 안전하게 구현 - /// - CEI 패턴 또는 ReentrancyGuard 사용 - /// - /// 필수 요소: - /// 1. 잔액 확인 (require) - /// 2. 잔액 차감 (balances 업데이트) - /// 3. ETH 전송 (call) - /// 4. Withdrawn 이벤트 발생 - /// - /// CEI 패턴 사용 시 순서: Checks -> Effects -> Interactions - /// ReentrancyGuard 사용 시: nonReentrant modifier 추가 - function withdraw(uint256 amount) public { - // TODO: 구현하세요 - } - - // ============================================ - // View 함수 - // ============================================ - - /// @notice Vault의 총 잔액을 반환합니다 - /// @return Vault 컨트랙트가 보유한 ETH 총량 (wei) - function getBalance() public view returns (uint256) { - return address(this).balance; - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/// @title VaultSecure (안전한 버전) +/// @author Bay-17th Ethereum Study +/// @notice 이 컨트랙트를 CEI 패턴 또는 ReentrancyGuard로 구현하세요 +/// @dev 테스트를 통과하도록 아래 함수들을 구현하세요 +/// +/// ============================================ +/// 구현 과제: 재진입 공격에 안전한 Vault 만들기 +/// ============================================ +/// +/// 이 컨트랙트는 여러분이 직접 구현해야 합니다. +/// Vault.sol의 취약점을 이해하고, 안전한 버전을 작성하세요. +/// +/// ============================================ +/// 선택 1: CEI (Checks-Effects-Interactions) 패턴 +/// ============================================ +/// +/// 순서만 바꾸면 됩니다: +/// 1. Checks: 조건 검증 (require, if) +/// 2. Effects: 상태 변경 (balances 업데이트) +/// 3. Interactions: 외부 호출 (call, transfer) +/// +/// 예시: +/// ```solidity +/// function withdraw(uint256 amount) public { +/// // Checks +/// require(balances[msg.sender] >= amount, "Insufficient balance"); +/// +/// // Effects (상태 먼저 변경!) +/// balances[msg.sender] -= amount; +/// +/// // Interactions (외부 호출은 마지막!) +/// (bool success, ) = msg.sender.call{value: amount}(""); +/// require(success, "Transfer failed"); +/// } +/// ``` +/// +/// ============================================ +/// 선택 2: OpenZeppelin ReentrancyGuard +/// ============================================ +/// +/// OpenZeppelin의 검증된 라이브러리를 사용합니다: +/// +/// ```solidity +/// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +/// +/// contract VaultSecure is ReentrancyGuard { +/// function withdraw(uint256 amount) public nonReentrant { +/// // 로직... +/// } +/// } +/// ``` +/// +/// nonReentrant modifier가 재진입을 막아줍니다. +/// +/// ============================================ +/// 어떤 방법을 선택할까? +/// ============================================ +/// +/// CEI 패턴: +/// - 장점: 가스 효율적, 외부 의존성 없음 +/// - 단점: 개발자가 순서를 직접 관리해야 함 +/// +/// ReentrancyGuard: +/// - 장점: 실수 방지, 명시적, 검증된 코드 +/// - 단점: 약간의 가스 오버헤드 (~2,500 gas) +/// +/// 권장: 학습 목적으로 CEI 먼저 이해하고, 프로덕션에서는 둘 다 사용 + +// ============================================ +// OpenZeppelin 사용 시 주석 해제 +// ============================================ +// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +/// @dev ReentrancyGuard 사용 시: contract VaultSecure is ReentrancyGuard +contract VaultSecure { + // ============================================ + // 상태 변수 + // ============================================ + + /// @dev 사용자별 예치금 잔액 + mapping(address => uint256) public balances; + + // ============================================ + // 이벤트 + // ============================================ + + /// @dev 입금 시 발생하는 이벤트 + event Deposited(address indexed user, uint256 amount); + + /// @dev 출금 시 발생하는 이벤트 + event Withdrawn(address indexed user, uint256 amount); + + // ============================================ + // 외부 함수 + // ============================================ + + /// @notice ETH를 Vault에 예치합니다 + /// @dev msg.value만큼 예치하고 Deposited 이벤트를 발생시킵니다 + /// + /// TODO: deposit() 함수를 구현하세요 + /// - msg.value를 balances[msg.sender]에 추가 + /// - Deposited 이벤트 발생 + /// + /// 힌트: Vault.sol의 deposit()과 동일하게 구현하면 됩니다 + function deposit() public payable { + // TODO: 구현하세요 + } + + /// @notice 예치한 ETH를 출금합니다 + /// @param amount 출금할 ETH 양 (wei 단위) + /// + /// TODO: withdraw(uint256 amount) 함수를 구현하세요 + /// - 재진입 공격에 안전하게 구현 + /// - CEI 패턴 또는 ReentrancyGuard 사용 + /// + /// 필수 요소: + /// 1. 잔액 확인 (require) + /// 2. 잔액 차감 (balances 업데이트) + /// 3. ETH 전송 (call) + /// 4. Withdrawn 이벤트 발생 + /// + /// CEI 패턴 사용 시 순서: Checks -> Effects -> Interactions + /// ReentrancyGuard 사용 시: nonReentrant modifier 추가 + function withdraw(uint256 amount) public { + // TODO: 구현하세요 + } + + // ============================================ + // View 함수 + // ============================================ + + /// @notice Vault의 총 잔액을 반환합니다 + /// @return Vault 컨트랙트가 보유한 ETH 총량 (wei) + function getBalance() public view returns (uint256) { + return address(this).balance; + } +} diff --git a/week-03/dev/test/Vault.t.sol b/week-03/dev/test/Vault.t.sol index 46cb50b2..0d48cf76 100644 --- a/week-03/dev/test/Vault.t.sol +++ b/week-03/dev/test/Vault.t.sol @@ -1,430 +1,430 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import {Test, console} from "forge-std/Test.sol"; -import {Vault} from "../src/Vault.sol"; -import {VaultSecure} from "../src/VaultSecure.sol"; - -/// @title VaultTest -/// @author Bay-17th Ethereum Study -/// @notice VaultSecure 컨트랙트의 TDD 테스트 스위트 -/// @dev 이 테스트들을 모두 통과하도록 VaultSecure를 구현하세요 -/// -/// ============================================ -/// TDD (Test-Driven Development) 접근법 -/// ============================================ -/// -/// 1. 테스트를 먼저 읽고 예상 동작을 파악하세요 -/// 2. VaultSecure.sol을 구현하세요 -/// 3. 모든 테스트가 통과하는지 확인하세요 -/// -/// 테스트 실행: -/// forge test --match-path week-03/dev/test/Vault.t.sol -vv -/// -/// 특정 테스트만 실행: -/// forge test --match-test test_Deposit -vv - -/// @dev 재진입 공격을 시뮬레이션하는 공격자 컨트랙트 -/// 이 컨트랙트는 VaultSecure가 재진입에 안전한지 테스트합니다 -contract Attacker { - VaultSecure public vault; - uint256 public attackCount; - uint256 public attackAmount; - - /// @dev 공격자 컨트랙트 생성자 - /// @param _vault 공격 대상 VaultSecure 주소 - constructor(address _vault) { - vault = VaultSecure(_vault); - } - - /// @notice 재진입 공격을 시작합니다 - /// @dev msg.value만큼 입금 후 출금을 시도하여 재진입 공격 - function attack() external payable { - attackAmount = msg.value; - attackCount = 0; - - // 1. 정상적으로 입금 - vault.deposit{value: msg.value}(); - - // 2. 출금 시도 - 이 때 receive()가 트리거됨 - vault.withdraw(msg.value); - } - - /// @dev ETH를 받을 때 자동으로 호출되는 함수 - /// 재진입 공격의 핵심: 출금받을 때 다시 출금 시도 - receive() external payable { - // 최대 5번까지 재진입 시도 (무한 루프 방지) - // VaultSecure가 안전하다면 두 번째 withdraw()는 실패해야 함 - if (attackCount < 5 && address(vault).balance >= attackAmount) { - attackCount++; - console.log("Reentrancy attempt:", attackCount); - vault.withdraw(attackAmount); - } - } - - /// @notice 공격자가 탈취한 ETH 확인 - function getBalance() external view returns (uint256) { - return address(this).balance; - } -} - -/// @title VaultSecureTest -/// @notice VaultSecure 컨트랙트의 모든 기능을 테스트합니다 -contract VaultSecureTest is Test { - // ============================================ - // 상태 변수 - // ============================================ - - VaultSecure public vault; - Attacker public attacker; - - address public alice = makeAddr("alice"); - address public bob = makeAddr("bob"); - - uint256 public constant INITIAL_BALANCE = 10 ether; - - // ============================================ - // 테스트 이벤트 (이벤트 발생 검증용) - // ============================================ - - event Deposited(address indexed user, uint256 amount); - event Withdrawn(address indexed user, uint256 amount); - - // ============================================ - // Setup - // ============================================ - - /// @dev 각 테스트 전에 실행되는 설정 함수 - function setUp() public { - // VaultSecure 배포 - vault = new VaultSecure(); - - // Attacker 배포 (재진입 테스트용) - attacker = new Attacker(address(vault)); - - // 테스트 계정에 ETH 지급 - vm.deal(alice, INITIAL_BALANCE); - vm.deal(bob, INITIAL_BALANCE); - vm.deal(address(attacker), INITIAL_BALANCE); - } - - // ============================================ - // Deposit 테스트 - // ============================================ - - /// @notice 입금 시 사용자 잔액이 증가하는지 테스트 - /// @dev 예상: 1 ETH 입금 후 balances[alice] == 1 ether - function test_Deposit_IncreasesUserBalance() public { - // Arrange (준비) - // Alice가 1 ETH를 입금할 예정 - - // Act (실행) - vm.prank(alice); - vault.deposit{value: 1 ether}(); - - // Assert (검증) - // Alice의 balances가 1 ether인지 확인 - assertEq(vault.balances(alice), 1 ether, "User balance should increase after deposit"); - } - - /// @notice 입금 시 컨트랙트 잔액이 증가하는지 테스트 - /// @dev 예상: 1 ETH 입금 후 address(vault).balance == 1 ether - function test_Deposit_IncreasesContractBalance() public { - // Arrange - uint256 initialVaultBalance = address(vault).balance; - - // Act - vm.prank(alice); - vault.deposit{value: 1 ether}(); - - // Assert - assertEq( - address(vault).balance, - initialVaultBalance + 1 ether, - "Contract balance should increase after deposit" - ); - } - - /// @notice 입금 시 Deposited 이벤트가 발생하는지 테스트 - /// @dev 예상: Deposited(alice, 1 ether) 이벤트 발생 - function test_Deposit_EmitsDepositedEvent() public { - // Arrange - // 이벤트 발생 예상 설정 - vm.expectEmit(true, false, false, true); - emit Deposited(alice, 1 ether); - - // Act - vm.prank(alice); - vault.deposit{value: 1 ether}(); - - // Assert는 expectEmit이 자동으로 처리 - } - - /// @notice 여러 번 입금 시 잔액이 누적되는지 테스트 - /// @dev 예상: 1 ETH + 2 ETH 입금 후 balances[alice] == 3 ether - function test_Deposit_AccumulatesBalance() public { - // Arrange & Act - vm.startPrank(alice); - vault.deposit{value: 1 ether}(); - vault.deposit{value: 2 ether}(); - vm.stopPrank(); - - // Assert - assertEq(vault.balances(alice), 3 ether, "Multiple deposits should accumulate"); - } - - // ============================================ - // Withdraw 테스트 - // ============================================ - - /// @notice 출금 시 사용자 잔액이 감소하는지 테스트 - /// @dev 예상: 3 ETH 입금 후 1 ETH 출금하면 balances[alice] == 2 ether - function test_Withdraw_DecreasesUserBalance() public { - // Arrange - vm.startPrank(alice); - vault.deposit{value: 3 ether}(); - uint256 balanceBefore = vault.balances(alice); - - // Act - vault.withdraw(1 ether); - vm.stopPrank(); - - // Assert - assertEq(vault.balances(alice), balanceBefore - 1 ether, "User balance should decrease after withdraw"); - } - - /// @notice 출금 시 사용자에게 ETH가 전송되는지 테스트 - /// @dev 예상: 1 ETH 출금 후 Alice의 실제 ETH 잔액이 1 ETH 증가 - function test_Withdraw_TransfersEtherToUser() public { - // Arrange - vm.startPrank(alice); - vault.deposit{value: 3 ether}(); - uint256 aliceBalanceBefore = address(alice).balance; - - // Act - vault.withdraw(1 ether); - vm.stopPrank(); - - // Assert - assertEq( - address(alice).balance, - aliceBalanceBefore + 1 ether, - "ETH should be transferred to user after withdraw" - ); - } - - /// @notice 출금 시 Withdrawn 이벤트가 발생하는지 테스트 - /// @dev 예상: Withdrawn(alice, 1 ether) 이벤트 발생 - function test_Withdraw_EmitsWithdrawnEvent() public { - // Arrange - vm.prank(alice); - vault.deposit{value: 3 ether}(); - - // 이벤트 발생 예상 설정 - vm.expectEmit(true, false, false, true); - emit Withdrawn(alice, 1 ether); - - // Act - vm.prank(alice); - vault.withdraw(1 ether); - } - - /// @notice 잔액보다 많이 출금하려고 하면 revert되는지 테스트 - /// @dev 예상: 1 ETH 입금 후 2 ETH 출금 시도하면 "Insufficient balance" revert - function test_RevertWhen_WithdrawExceedsBalance() public { - // Arrange - vm.prank(alice); - vault.deposit{value: 1 ether}(); - - // Act & Assert - vm.prank(alice); - vm.expectRevert("Insufficient balance"); - vault.withdraw(2 ether); - } - - /// @notice 잔액이 없는 계정이 출금하려고 하면 revert되는지 테스트 - /// @dev 예상: 입금 없이 출금 시도하면 "Insufficient balance" revert - function test_RevertWhen_WithdrawWithZeroBalance() public { - // Act & Assert - vm.prank(alice); - vm.expectRevert("Insufficient balance"); - vault.withdraw(1 ether); - } - - // ============================================ - // 재진입 공격 테스트 (가장 중요!) - // ============================================ - - /// @notice 재진입 공격이 Vault를 drain하지 못하는지 테스트 - /// @dev 이 테스트가 통과하면 VaultSecure는 재진입에 안전합니다! - /// - /// 시나리오: - /// 1. Bob이 5 ETH 입금 (선량한 사용자) - /// 2. Attacker가 1 ETH 입금 후 재진입 공격 시도 - /// 3. VaultSecure가 안전하다면: - /// - Attacker는 자신이 입금한 1 ETH만 출금 가능 - /// - Bob의 5 ETH는 안전하게 보존됨 - /// - Vault에는 여전히 5 ETH가 남아있음 - function test_ReentrancyAttack_CannotDrainVault() public { - // ============================================ - // Arrange (준비) - // ============================================ - - // Bob이 먼저 5 ETH 입금 (선량한 사용자) - vm.prank(bob); - vault.deposit{value: 5 ether}(); - - console.log("=== Before Attack ==="); - console.log("Vault balance:", address(vault).balance); - console.log("Attacker balance:", address(attacker).balance); - - uint256 vaultBalanceBefore = address(vault).balance; - uint256 attackerBalanceBefore = address(attacker).balance; - - // ============================================ - // Act (실행) - // ============================================ - - // Attacker가 1 ETH로 재진입 공격 시도 - // attack() 내부에서: - // 1. 1 ETH 입금 - // 2. 1 ETH 출금 시도 - // 3. receive()에서 추가 출금 시도 (재진입) - attacker.attack{value: 1 ether}(); - - console.log("=== After Attack ==="); - console.log("Vault balance:", address(vault).balance); - console.log("Attacker balance:", address(attacker).balance); - console.log("Reentrancy attempts:", attacker.attackCount()); - - // ============================================ - // Assert (검증) - // ============================================ - - // Vault에는 Bob의 5 ETH가 그대로 있어야 함 - // (Attacker가 입금한 1 ETH는 정상 출금되어 빠짐) - assertEq( - address(vault).balance, - 5 ether, - "Vault should still have Bob's 5 ETH - reentrancy should not drain funds!" - ); - - // Attacker는 자신이 입금한 1 ETH만 돌려받음 - // 10 ETH (초기) - 1 ETH (입금) + 1 ETH (정상 출금) = 10 ETH - assertEq( - address(attacker).balance, - attackerBalanceBefore, - "Attacker should only get back their deposited amount" - ); - - // 재진입 시도는 실패해야 함 (attackCount가 0이거나 revert됨) - // CEI 패턴: 두 번째 withdraw에서 잔액이 이미 0이므로 revert - // ReentrancyGuard: nonReentrant에서 revert - console.log("Reentrancy was blocked! Attack count:", attacker.attackCount()); - } - - /// @notice 재진입 공격 시 Attacker가 자신의 입금액만 받는지 테스트 - /// @dev 정상 동작 확인: 입금한 만큼만 출금 가능 - function test_ReentrancyAttack_AttackerGetsOnlyOwnDeposit() public { - // Arrange - vm.prank(bob); - vault.deposit{value: 5 ether}(); - - // Act - attacker.attack{value: 2 ether}(); - - // Assert - // Attacker의 vault 잔액은 0이어야 함 (정상 출금 완료) - assertEq(vault.balances(address(attacker)), 0, "Attacker balance in vault should be 0 after withdraw"); - - // Bob의 vault 잔액은 그대로 5 ETH - assertEq(vault.balances(bob), 5 ether, "Bob's balance should be intact"); - } - - // ============================================ - // getBalance 테스트 - // ============================================ - - /// @notice getBalance()가 정확한 컨트랙트 잔액을 반환하는지 테스트 - function test_GetBalance_ReturnsContractBalance() public { - // Arrange - vm.prank(alice); - vault.deposit{value: 3 ether}(); - - vm.prank(bob); - vault.deposit{value: 2 ether}(); - - // Act - uint256 balance = vault.getBalance(); - - // Assert - assertEq(balance, 5 ether, "getBalance should return total contract balance"); - } -} - -/// @title VaultVulnerableTest -/// @notice 취약한 Vault 컨트랙트가 실제로 재진입에 취약한지 확인하는 테스트 -/// @dev 이 테스트는 Vault(취약)와 VaultSecure(안전)의 차이를 보여줍니다 -contract VaultVulnerableTest is Test { - Vault public vulnerableVault; - AttackerForVault public attackerVulnerable; - - address public bob = makeAddr("bob"); - - function setUp() public { - vulnerableVault = new Vault(); - attackerVulnerable = new AttackerForVault(address(vulnerableVault)); - - vm.deal(bob, 10 ether); - vm.deal(address(attackerVulnerable), 10 ether); - } - - /// @notice 취약한 Vault가 실제로 drain되는지 테스트 - /// @dev 이 테스트는 재진입 공격의 위험성을 보여줍니다 - function test_VulnerableVault_CanBeDrained() public { - // Arrange - vm.prank(bob); - vulnerableVault.deposit{value: 5 ether}(); - - console.log("=== Vulnerable Vault Before Attack ==="); - console.log("Vault balance:", address(vulnerableVault).balance); - - // Act - 공격! - attackerVulnerable.attack{value: 1 ether}(); - - console.log("=== Vulnerable Vault After Attack ==="); - console.log("Vault balance:", address(vulnerableVault).balance); - console.log("Attacker stolen:", address(attackerVulnerable).balance - 9 ether); - - // Assert - // 취약한 Vault는 완전히 drain됨! - assertEq( - address(vulnerableVault).balance, 0, "Vulnerable vault should be completely drained by reentrancy attack" - ); - - // 공격자가 Bob의 ETH까지 탈취 - assertGt(address(attackerVulnerable).balance, 10 ether, "Attacker should have stolen ETH"); - } -} - -/// @dev 취약한 Vault를 공격하는 컨트랙트 -contract AttackerForVault { - Vault public vault; - uint256 public attackCount; - - constructor(address _vault) { - vault = Vault(_vault); - } - - function attack() external payable { - vault.deposit{value: msg.value}(); - vault.withdraw(msg.value); - } - - receive() external payable { - if (attackCount < 10 && address(vault).balance >= 1 ether) { - attackCount++; - vault.withdraw(1 ether); - } - } -} +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Test, console} from "forge-std/Test.sol"; +import {Vault} from "../src/Vault.sol"; +import {VaultSecure} from "../src/VaultSecure.sol"; + +/// @title VaultTest +/// @author Bay-17th Ethereum Study +/// @notice VaultSecure 컨트랙트의 TDD 테스트 스위트 +/// @dev 이 테스트들을 모두 통과하도록 VaultSecure를 구현하세요 +/// +/// ============================================ +/// TDD (Test-Driven Development) 접근법 +/// ============================================ +/// +/// 1. 테스트를 먼저 읽고 예상 동작을 파악하세요 +/// 2. VaultSecure.sol을 구현하세요 +/// 3. 모든 테스트가 통과하는지 확인하세요 +/// +/// 테스트 실행: +/// forge test --match-path week-03/dev/test/Vault.t.sol -vv +/// +/// 특정 테스트만 실행: +/// forge test --match-test test_Deposit -vv + +/// @dev 재진입 공격을 시뮬레이션하는 공격자 컨트랙트 +/// 이 컨트랙트는 VaultSecure가 재진입에 안전한지 테스트합니다 +contract Attacker { + VaultSecure public vault; + uint256 public attackCount; + uint256 public attackAmount; + + /// @dev 공격자 컨트랙트 생성자 + /// @param _vault 공격 대상 VaultSecure 주소 + constructor(address _vault) { + vault = VaultSecure(_vault); + } + + /// @notice 재진입 공격을 시작합니다 + /// @dev msg.value만큼 입금 후 출금을 시도하여 재진입 공격 + function attack() external payable { + attackAmount = msg.value; + attackCount = 0; + + // 1. 정상적으로 입금 + vault.deposit{value: msg.value}(); + + // 2. 출금 시도 - 이 때 receive()가 트리거됨 + vault.withdraw(msg.value); + } + + /// @dev ETH를 받을 때 자동으로 호출되는 함수 + /// 재진입 공격의 핵심: 출금받을 때 다시 출금 시도 + receive() external payable { + // 최대 5번까지 재진입 시도 (무한 루프 방지) + // VaultSecure가 안전하다면 두 번째 withdraw()는 실패해야 함 + if (attackCount < 5 && address(vault).balance >= attackAmount) { + attackCount++; + console.log("Reentrancy attempt:", attackCount); + vault.withdraw(attackAmount); + } + } + + /// @notice 공격자가 탈취한 ETH 확인 + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +/// @title VaultSecureTest +/// @notice VaultSecure 컨트랙트의 모든 기능을 테스트합니다 +contract VaultSecureTest is Test { + // ============================================ + // 상태 변수 + // ============================================ + + VaultSecure public vault; + Attacker public attacker; + + address public alice = makeAddr("alice"); + address public bob = makeAddr("bob"); + + uint256 public constant INITIAL_BALANCE = 10 ether; + + // ============================================ + // 테스트 이벤트 (이벤트 발생 검증용) + // ============================================ + + event Deposited(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + + // ============================================ + // Setup + // ============================================ + + /// @dev 각 테스트 전에 실행되는 설정 함수 + function setUp() public { + // VaultSecure 배포 + vault = new VaultSecure(); + + // Attacker 배포 (재진입 테스트용) + attacker = new Attacker(address(vault)); + + // 테스트 계정에 ETH 지급 + vm.deal(alice, INITIAL_BALANCE); + vm.deal(bob, INITIAL_BALANCE); + vm.deal(address(attacker), INITIAL_BALANCE); + } + + // ============================================ + // Deposit 테스트 + // ============================================ + + /// @notice 입금 시 사용자 잔액이 증가하는지 테스트 + /// @dev 예상: 1 ETH 입금 후 balances[alice] == 1 ether + function test_Deposit_IncreasesUserBalance() public { + // Arrange (준비) + // Alice가 1 ETH를 입금할 예정 + + // Act (실행) + vm.prank(alice); + vault.deposit{value: 1 ether}(); + + // Assert (검증) + // Alice의 balances가 1 ether인지 확인 + assertEq(vault.balances(alice), 1 ether, "User balance should increase after deposit"); + } + + /// @notice 입금 시 컨트랙트 잔액이 증가하는지 테스트 + /// @dev 예상: 1 ETH 입금 후 address(vault).balance == 1 ether + function test_Deposit_IncreasesContractBalance() public { + // Arrange + uint256 initialVaultBalance = address(vault).balance; + + // Act + vm.prank(alice); + vault.deposit{value: 1 ether}(); + + // Assert + assertEq( + address(vault).balance, + initialVaultBalance + 1 ether, + "Contract balance should increase after deposit" + ); + } + + /// @notice 입금 시 Deposited 이벤트가 발생하는지 테스트 + /// @dev 예상: Deposited(alice, 1 ether) 이벤트 발생 + function test_Deposit_EmitsDepositedEvent() public { + // Arrange + // 이벤트 발생 예상 설정 + vm.expectEmit(true, false, false, true); + emit Deposited(alice, 1 ether); + + // Act + vm.prank(alice); + vault.deposit{value: 1 ether}(); + + // Assert는 expectEmit이 자동으로 처리 + } + + /// @notice 여러 번 입금 시 잔액이 누적되는지 테스트 + /// @dev 예상: 1 ETH + 2 ETH 입금 후 balances[alice] == 3 ether + function test_Deposit_AccumulatesBalance() public { + // Arrange & Act + vm.startPrank(alice); + vault.deposit{value: 1 ether}(); + vault.deposit{value: 2 ether}(); + vm.stopPrank(); + + // Assert + assertEq(vault.balances(alice), 3 ether, "Multiple deposits should accumulate"); + } + + // ============================================ + // Withdraw 테스트 + // ============================================ + + /// @notice 출금 시 사용자 잔액이 감소하는지 테스트 + /// @dev 예상: 3 ETH 입금 후 1 ETH 출금하면 balances[alice] == 2 ether + function test_Withdraw_DecreasesUserBalance() public { + // Arrange + vm.startPrank(alice); + vault.deposit{value: 3 ether}(); + uint256 balanceBefore = vault.balances(alice); + + // Act + vault.withdraw(1 ether); + vm.stopPrank(); + + // Assert + assertEq(vault.balances(alice), balanceBefore - 1 ether, "User balance should decrease after withdraw"); + } + + /// @notice 출금 시 사용자에게 ETH가 전송되는지 테스트 + /// @dev 예상: 1 ETH 출금 후 Alice의 실제 ETH 잔액이 1 ETH 증가 + function test_Withdraw_TransfersEtherToUser() public { + // Arrange + vm.startPrank(alice); + vault.deposit{value: 3 ether}(); + uint256 aliceBalanceBefore = address(alice).balance; + + // Act + vault.withdraw(1 ether); + vm.stopPrank(); + + // Assert + assertEq( + address(alice).balance, + aliceBalanceBefore + 1 ether, + "ETH should be transferred to user after withdraw" + ); + } + + /// @notice 출금 시 Withdrawn 이벤트가 발생하는지 테스트 + /// @dev 예상: Withdrawn(alice, 1 ether) 이벤트 발생 + function test_Withdraw_EmitsWithdrawnEvent() public { + // Arrange + vm.prank(alice); + vault.deposit{value: 3 ether}(); + + // 이벤트 발생 예상 설정 + vm.expectEmit(true, false, false, true); + emit Withdrawn(alice, 1 ether); + + // Act + vm.prank(alice); + vault.withdraw(1 ether); + } + + /// @notice 잔액보다 많이 출금하려고 하면 revert되는지 테스트 + /// @dev 예상: 1 ETH 입금 후 2 ETH 출금 시도하면 "Insufficient balance" revert + function test_RevertWhen_WithdrawExceedsBalance() public { + // Arrange + vm.prank(alice); + vault.deposit{value: 1 ether}(); + + // Act & Assert + vm.prank(alice); + vm.expectRevert("Insufficient balance"); + vault.withdraw(2 ether); + } + + /// @notice 잔액이 없는 계정이 출금하려고 하면 revert되는지 테스트 + /// @dev 예상: 입금 없이 출금 시도하면 "Insufficient balance" revert + function test_RevertWhen_WithdrawWithZeroBalance() public { + // Act & Assert + vm.prank(alice); + vm.expectRevert("Insufficient balance"); + vault.withdraw(1 ether); + } + + // ============================================ + // 재진입 공격 테스트 (가장 중요!) + // ============================================ + + /// @notice 재진입 공격이 Vault를 drain하지 못하는지 테스트 + /// @dev 이 테스트가 통과하면 VaultSecure는 재진입에 안전합니다! + /// + /// 시나리오: + /// 1. Bob이 5 ETH 입금 (선량한 사용자) + /// 2. Attacker가 1 ETH 입금 후 재진입 공격 시도 + /// 3. VaultSecure가 안전하다면: + /// - Attacker는 자신이 입금한 1 ETH만 출금 가능 + /// - Bob의 5 ETH는 안전하게 보존됨 + /// - Vault에는 여전히 5 ETH가 남아있음 + function test_ReentrancyAttack_CannotDrainVault() public { + // ============================================ + // Arrange (준비) + // ============================================ + + // Bob이 먼저 5 ETH 입금 (선량한 사용자) + vm.prank(bob); + vault.deposit{value: 5 ether}(); + + console.log("=== Before Attack ==="); + console.log("Vault balance:", address(vault).balance); + console.log("Attacker balance:", address(attacker).balance); + + uint256 vaultBalanceBefore = address(vault).balance; + uint256 attackerBalanceBefore = address(attacker).balance; + + // ============================================ + // Act (실행) + // ============================================ + + // Attacker가 1 ETH로 재진입 공격 시도 + // attack() 내부에서: + // 1. 1 ETH 입금 + // 2. 1 ETH 출금 시도 + // 3. receive()에서 추가 출금 시도 (재진입) + attacker.attack{value: 1 ether}(); + + console.log("=== After Attack ==="); + console.log("Vault balance:", address(vault).balance); + console.log("Attacker balance:", address(attacker).balance); + console.log("Reentrancy attempts:", attacker.attackCount()); + + // ============================================ + // Assert (검증) + // ============================================ + + // Vault에는 Bob의 5 ETH가 그대로 있어야 함 + // (Attacker가 입금한 1 ETH는 정상 출금되어 빠짐) + assertEq( + address(vault).balance, + 5 ether, + "Vault should still have Bob's 5 ETH - reentrancy should not drain funds!" + ); + + // Attacker는 자신이 입금한 1 ETH만 돌려받음 + // 10 ETH (초기) - 1 ETH (입금) + 1 ETH (정상 출금) = 10 ETH + assertEq( + address(attacker).balance, + attackerBalanceBefore, + "Attacker should only get back their deposited amount" + ); + + // 재진입 시도는 실패해야 함 (attackCount가 0이거나 revert됨) + // CEI 패턴: 두 번째 withdraw에서 잔액이 이미 0이므로 revert + // ReentrancyGuard: nonReentrant에서 revert + console.log("Reentrancy was blocked! Attack count:", attacker.attackCount()); + } + + /// @notice 재진입 공격 시 Attacker가 자신의 입금액만 받는지 테스트 + /// @dev 정상 동작 확인: 입금한 만큼만 출금 가능 + function test_ReentrancyAttack_AttackerGetsOnlyOwnDeposit() public { + // Arrange + vm.prank(bob); + vault.deposit{value: 5 ether}(); + + // Act + attacker.attack{value: 2 ether}(); + + // Assert + // Attacker의 vault 잔액은 0이어야 함 (정상 출금 완료) + assertEq(vault.balances(address(attacker)), 0, "Attacker balance in vault should be 0 after withdraw"); + + // Bob의 vault 잔액은 그대로 5 ETH + assertEq(vault.balances(bob), 5 ether, "Bob's balance should be intact"); + } + + // ============================================ + // getBalance 테스트 + // ============================================ + + /// @notice getBalance()가 정확한 컨트랙트 잔액을 반환하는지 테스트 + function test_GetBalance_ReturnsContractBalance() public { + // Arrange + vm.prank(alice); + vault.deposit{value: 3 ether}(); + + vm.prank(bob); + vault.deposit{value: 2 ether}(); + + // Act + uint256 balance = vault.getBalance(); + + // Assert + assertEq(balance, 5 ether, "getBalance should return total contract balance"); + } +} + +/// @title VaultVulnerableTest +/// @notice 취약한 Vault 컨트랙트가 실제로 재진입에 취약한지 확인하는 테스트 +/// @dev 이 테스트는 Vault(취약)와 VaultSecure(안전)의 차이를 보여줍니다 +contract VaultVulnerableTest is Test { + Vault public vulnerableVault; + AttackerForVault public attackerVulnerable; + + address public bob = makeAddr("bob"); + + function setUp() public { + vulnerableVault = new Vault(); + attackerVulnerable = new AttackerForVault(address(vulnerableVault)); + + vm.deal(bob, 10 ether); + vm.deal(address(attackerVulnerable), 10 ether); + } + + /// @notice 취약한 Vault가 실제로 drain되는지 테스트 + /// @dev 이 테스트는 재진입 공격의 위험성을 보여줍니다 + function test_VulnerableVault_CanBeDrained() public { + // Arrange + vm.prank(bob); + vulnerableVault.deposit{value: 5 ether}(); + + console.log("=== Vulnerable Vault Before Attack ==="); + console.log("Vault balance:", address(vulnerableVault).balance); + + // Act - 공격! + attackerVulnerable.attack{value: 1 ether}(); + + console.log("=== Vulnerable Vault After Attack ==="); + console.log("Vault balance:", address(vulnerableVault).balance); + console.log("Attacker stolen:", address(attackerVulnerable).balance - 9 ether); + + // Assert + // 취약한 Vault는 완전히 drain됨! + assertEq( + address(vulnerableVault).balance, 0, "Vulnerable vault should be completely drained by reentrancy attack" + ); + + // 공격자가 Bob의 ETH까지 탈취 + assertGt(address(attackerVulnerable).balance, 10 ether, "Attacker should have stolen ETH"); + } +} + +/// @dev 취약한 Vault를 공격하는 컨트랙트 +contract AttackerForVault { + Vault public vault; + uint256 public attackCount; + + constructor(address _vault) { + vault = Vault(_vault); + } + + function attack() external payable { + vault.deposit{value: msg.value}(); + vault.withdraw(msg.value); + } + + receive() external payable { + if (attackCount < 10 && address(vault).balance >= 1 ether) { + attackCount++; + vault.withdraw(1 ether); + } + } +} diff --git a/week-03/quiz/quiz-03.md b/week-03/quiz/quiz-03.md index d60b5e79..625da7f3 100644 --- a/week-03/quiz/quiz-03.md +++ b/week-03/quiz/quiz-03.md @@ -1,343 +1,343 @@ -# Week 3 퀴즈: EVM/Security patterns - -**제출 방법:** -1. 이 파일을 복사하여 `quiz-03-solution.md`로 저장 -2. 각 문제에 답변 작성 (왜 그런지 설명 포함) -3. Pull Request 생성 (`quiz_submission` 템플릿 사용) - -**평가 기준:** -- 정답 여부보다 **개념 이해도**를 중점 평가합니다 -- 특히 **보안 취약점 식별과 방어 패턴**을 중점 평가합니다 -- 코드 문제는 문법보다 보안 논리를 평가합니다 - ---- - -## 문제 1: [이론] EVM 개념 (객관식) - -EVM(Ethereum Virtual Machine)이 "결정론적(deterministic)"으로 실행되어야 하는 이유는? - -**보기:** -A) 모든 노드가 같은 CPU를 사용해야 하므로 -B) 모든 노드가 같은 입력에 대해 같은 결과를 얻어야 합의가 가능하므로 -C) 트랜잭션 처리 속도를 높이기 위해 -D) 개발자가 코드를 디버깅하기 쉽게 하기 위해 - -**답변:** - - - ---- - -## 문제 2: [이론] Storage vs Memory (객관식) - -다음 코드에서 `data` 변수의 저장 위치와 특성을 올바르게 설명한 것은? - -```solidity -function process(uint[] memory data) public pure returns (uint) { - uint sum = 0; - for (uint i = 0; i < data.length; i++) { - sum += data[i]; - } - return sum; -} -``` - -**보기:** -A) Storage에 저장되며 함수 종료 후에도 유지된다 -B) Memory에 저장되며 함수 종료 시 삭제된다 -C) Stack에 저장되며 가장 비싼 저장 공간이다 -D) Calldata에 저장되며 수정이 가능하다 - -**답변:** - - - ---- - -## 문제 3: [이론] Gas 비용 (객관식) - -다음 중 Gas 비용이 가장 높은 연산은? - -**보기:** -A) ADD (덧셈) -B) MUL (곱셈) -C) SLOAD (Storage 읽기) -D) SSTORE (Storage 쓰기) - -**답변:** - - - ---- - -## 문제 4: [이론] CEI 패턴 (단답형) - -**왜** CEI(Checks-Effects-Interactions) 패턴에서 Effects(상태 변경)가 Interactions(외부 호출)보다 먼저 와야 하나요? - -재진입 공격 시나리오와 연결해서 구체적으로 설명하세요. - -**답변:** - - - ---- - -## 문제 5: [이론] The DAO 사건 교훈 (단답형) - -2016년 The DAO 해킹($60M 피해)에서 우리가 배워야 할 **가장 중요한 교훈**은 무엇인가요? - -이 사건 이후 이더리움 생태계에 어떤 변화가 있었나요? - -**답변:** - - - ---- - -## 문제 6: [코드] 재진입 공격 식별 (취약점 찾기) - -다음 코드에서 재진입 공격 취약점을 찾으세요: - -```solidity -// BAD CODE - 취약점 찾기 -contract VulnerableVault { - mapping(address => uint256) public balances; - - function deposit() public payable { - balances[msg.sender] += msg.value; - } - - function withdraw(uint256 amount) public { - require(balances[msg.sender] >= amount, "Insufficient balance"); - - // ETH 전송 - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Transfer failed"); - - // 잔액 차감 - balances[msg.sender] -= amount; - } -} -``` - -**1) 발견한 취약점:** - - - -**2) 왜 이것이 문제인가:** - - - -**3) 올바른 수정 방법 (CEI 패턴):** -```solidity -// GOOD CODE - CEI 패턴으로 수정하세요 -function withdraw(uint256 amount) public { - // 여기에 안전한 코드를 작성하세요 -} -``` - ---- - -## 문제 7: [코드] CEI 패턴 구현 (빈칸 채우기) - -다음 코드의 빈칸을 채워 CEI 패턴을 완성하세요: - -```solidity -function secureWithdraw(uint256 amount) public { - // 1. Checks - 조건 확인 - require(______________________, "Insufficient balance"); - - // 2. Effects - 상태 변경 (외부 호출 전에!) - ______________________; - - // 3. Interactions - 외부 호출 (마지막에!) - (bool success, ) = msg.sender.call{value: ______}(""); - require(success, "Transfer failed"); -} -``` - -**답변:** -```solidity -// 빈칸을 채운 완성 코드를 작성하세요 -``` - -**왜 이 순서가 중요한가요:** - - - ---- - -## 문제 8: [코드] tx.origin 취약점 (취약점 찾기) - -다음 코드에서 보안 취약점을 찾으세요: - -```solidity -// BAD CODE - 취약점 찾기 -contract PhishingVulnerable { - address public owner; - - constructor() { - owner = msg.sender; - } - - function transferOwnership(address newOwner) public { - require(tx.origin == owner, "Not owner"); - owner = newOwner; - } -} -``` - -**1) 발견한 취약점:** - - - -**2) 공격 시나리오:** - - - -**3) 올바른 수정 방법:** -```solidity -// GOOD CODE - 수정된 코드를 작성하세요 -``` - ---- - -## 문제 9: [코드] ReentrancyGuard 적용 (빈칸 채우기) - -다음 코드의 빈칸을 채워 ReentrancyGuard를 적용하세요: - -```solidity -// TODO: OpenZeppelin import -______________________________________ - -// TODO: 상속 추가 -contract SecureVault _________________ { - mapping(address => uint256) public balances; - - function deposit() public payable { - balances[msg.sender] += msg.value; - } - - // TODO: modifier 추가 - function withdraw(uint256 amount) public _________________ { - require(balances[msg.sender] >= amount, "Insufficient"); - balances[msg.sender] -= amount; - (bool success, ) = msg.sender.call{value: amount}(""); - require(success, "Failed"); - } -} -``` - -**답변:** -```solidity -// 빈칸을 채운 완성 코드를 작성하세요 -``` - -**CEI 패턴 vs ReentrancyGuard - 언제 무엇을 사용하나요:** - - - ---- - -## 문제 10: [다이어그램] 재진입 공격 흐름 해석 (다이어그램 분석) - -다음 재진입 공격 시퀀스 다이어그램을 분석하세요: - -```mermaid -sequenceDiagram - participant A as 공격자 - participant V as VulnerableVault - - Note over A,V: 초기 상태: Vault 잔액 10 ETH, 공격자 예치금 1 ETH - - A->>V: 1. withdraw(1 ether) 호출 - V->>V: 2. require 통과 (잔액 1 ETH >= 1 ETH) - V->>A: 3. call{value: 1 ether}() - ETH 전송 - Note over A: 4. receive() 트리거됨 - A->>V: 5. receive()에서 다시 withdraw(1 ether) 호출 - V->>V: 6. require 통과 (잔액 아직 1 ETH!) - V->>A: 7. 또 1 ETH 전송 - Note over A: 8. 반복... - Note over V: 9. Vault 잔액 0이 될 때까지 반복 - V->>V: 10. 최종: balances[attacker] -= 1 ether (여러 번 실행됨) -``` - -**질문 1:** 6번에서 require 체크가 통과하는 이유는 무엇인가요? - -**답변:** - - - -**질문 2:** CEI 패턴을 적용하면 6번에서 어떻게 되나요? - -**답변:** - - - -**질문 3:** 공격자가 총 몇 ETH를 탈취할 수 있나요? (예치금 1 ETH, Vault 총 잔액 10 ETH 가정) - -**답변:** - - - ---- - -## 자기 평가 - -모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: - -- [ ] EVM의 결정론적 실행 필요성을 이해했다 -- [ ] Storage/Memory/Stack의 차이와 비용을 알고 있다 -- [ ] 재진입 공격의 원리를 설명할 수 있다 -- [ ] CEI 패턴으로 재진입 공격을 방어할 수 있다 -- [ ] tx.origin vs msg.sender의 보안 차이를 알고 있다 -- [ ] ReentrancyGuard를 적용할 수 있다 - ---- - -## 참고 자료 - -- 이론: `eth-materials/week-03/theory/slides.md` -- 취약한 코드: `eth-homework/week-03/dev/src/Vault.sol` -- 안전한 코드: `eth-homework/week-03/dev/src/VaultSecure.sol` -- 테스트: `eth-homework/week-03/dev/test/Vault.t.sol` -- 용어: `eth-materials/resources/glossary.md` +# Week 3 퀴즈: EVM/Security patterns + +**제출 방법:** +1. 이 파일을 복사하여 `quiz-03-solution.md`로 저장 +2. 각 문제에 답변 작성 (왜 그런지 설명 포함) +3. Pull Request 생성 (`quiz_submission` 템플릿 사용) + +**평가 기준:** +- 정답 여부보다 **개념 이해도**를 중점 평가합니다 +- 특히 **보안 취약점 식별과 방어 패턴**을 중점 평가합니다 +- 코드 문제는 문법보다 보안 논리를 평가합니다 + +--- + +## 문제 1: [이론] EVM 개념 (객관식) + +EVM(Ethereum Virtual Machine)이 "결정론적(deterministic)"으로 실행되어야 하는 이유는? + +**보기:** +A) 모든 노드가 같은 CPU를 사용해야 하므로 +B) 모든 노드가 같은 입력에 대해 같은 결과를 얻어야 합의가 가능하므로 +C) 트랜잭션 처리 속도를 높이기 위해 +D) 개발자가 코드를 디버깅하기 쉽게 하기 위해 + +**답변:** + + + +--- + +## 문제 2: [이론] Storage vs Memory (객관식) + +다음 코드에서 `data` 변수의 저장 위치와 특성을 올바르게 설명한 것은? + +```solidity +function process(uint[] memory data) public pure returns (uint) { + uint sum = 0; + for (uint i = 0; i < data.length; i++) { + sum += data[i]; + } + return sum; +} +``` + +**보기:** +A) Storage에 저장되며 함수 종료 후에도 유지된다 +B) Memory에 저장되며 함수 종료 시 삭제된다 +C) Stack에 저장되며 가장 비싼 저장 공간이다 +D) Calldata에 저장되며 수정이 가능하다 + +**답변:** + + + +--- + +## 문제 3: [이론] Gas 비용 (객관식) + +다음 중 Gas 비용이 가장 높은 연산은? + +**보기:** +A) ADD (덧셈) +B) MUL (곱셈) +C) SLOAD (Storage 읽기) +D) SSTORE (Storage 쓰기) + +**답변:** + + + +--- + +## 문제 4: [이론] CEI 패턴 (단답형) + +**왜** CEI(Checks-Effects-Interactions) 패턴에서 Effects(상태 변경)가 Interactions(외부 호출)보다 먼저 와야 하나요? + +재진입 공격 시나리오와 연결해서 구체적으로 설명하세요. + +**답변:** + + + +--- + +## 문제 5: [이론] The DAO 사건 교훈 (단답형) + +2016년 The DAO 해킹($60M 피해)에서 우리가 배워야 할 **가장 중요한 교훈**은 무엇인가요? + +이 사건 이후 이더리움 생태계에 어떤 변화가 있었나요? + +**답변:** + + + +--- + +## 문제 6: [코드] 재진입 공격 식별 (취약점 찾기) + +다음 코드에서 재진입 공격 취약점을 찾으세요: + +```solidity +// BAD CODE - 취약점 찾기 +contract VulnerableVault { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function withdraw(uint256 amount) public { + require(balances[msg.sender] >= amount, "Insufficient balance"); + + // ETH 전송 + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Transfer failed"); + + // 잔액 차감 + balances[msg.sender] -= amount; + } +} +``` + +**1) 발견한 취약점:** + + + +**2) 왜 이것이 문제인가:** + + + +**3) 올바른 수정 방법 (CEI 패턴):** +```solidity +// GOOD CODE - CEI 패턴으로 수정하세요 +function withdraw(uint256 amount) public { + // 여기에 안전한 코드를 작성하세요 +} +``` + +--- + +## 문제 7: [코드] CEI 패턴 구현 (빈칸 채우기) + +다음 코드의 빈칸을 채워 CEI 패턴을 완성하세요: + +```solidity +function secureWithdraw(uint256 amount) public { + // 1. Checks - 조건 확인 + require(______________________, "Insufficient balance"); + + // 2. Effects - 상태 변경 (외부 호출 전에!) + ______________________; + + // 3. Interactions - 외부 호출 (마지막에!) + (bool success, ) = msg.sender.call{value: ______}(""); + require(success, "Transfer failed"); +} +``` + +**답변:** +```solidity +// 빈칸을 채운 완성 코드를 작성하세요 +``` + +**왜 이 순서가 중요한가요:** + + + +--- + +## 문제 8: [코드] tx.origin 취약점 (취약점 찾기) + +다음 코드에서 보안 취약점을 찾으세요: + +```solidity +// BAD CODE - 취약점 찾기 +contract PhishingVulnerable { + address public owner; + + constructor() { + owner = msg.sender; + } + + function transferOwnership(address newOwner) public { + require(tx.origin == owner, "Not owner"); + owner = newOwner; + } +} +``` + +**1) 발견한 취약점:** + + + +**2) 공격 시나리오:** + + + +**3) 올바른 수정 방법:** +```solidity +// GOOD CODE - 수정된 코드를 작성하세요 +``` + +--- + +## 문제 9: [코드] ReentrancyGuard 적용 (빈칸 채우기) + +다음 코드의 빈칸을 채워 ReentrancyGuard를 적용하세요: + +```solidity +// TODO: OpenZeppelin import +______________________________________ + +// TODO: 상속 추가 +contract SecureVault _________________ { + mapping(address => uint256) public balances; + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + // TODO: modifier 추가 + function withdraw(uint256 amount) public _________________ { + require(balances[msg.sender] >= amount, "Insufficient"); + balances[msg.sender] -= amount; + (bool success, ) = msg.sender.call{value: amount}(""); + require(success, "Failed"); + } +} +``` + +**답변:** +```solidity +// 빈칸을 채운 완성 코드를 작성하세요 +``` + +**CEI 패턴 vs ReentrancyGuard - 언제 무엇을 사용하나요:** + + + +--- + +## 문제 10: [다이어그램] 재진입 공격 흐름 해석 (다이어그램 분석) + +다음 재진입 공격 시퀀스 다이어그램을 분석하세요: + +```mermaid +sequenceDiagram + participant A as 공격자 + participant V as VulnerableVault + + Note over A,V: 초기 상태: Vault 잔액 10 ETH, 공격자 예치금 1 ETH + + A->>V: 1. withdraw(1 ether) 호출 + V->>V: 2. require 통과 (잔액 1 ETH >= 1 ETH) + V->>A: 3. call{value: 1 ether}() - ETH 전송 + Note over A: 4. receive() 트리거됨 + A->>V: 5. receive()에서 다시 withdraw(1 ether) 호출 + V->>V: 6. require 통과 (잔액 아직 1 ETH!) + V->>A: 7. 또 1 ETH 전송 + Note over A: 8. 반복... + Note over V: 9. Vault 잔액 0이 될 때까지 반복 + V->>V: 10. 최종: balances[attacker] -= 1 ether (여러 번 실행됨) +``` + +**질문 1:** 6번에서 require 체크가 통과하는 이유는 무엇인가요? + +**답변:** + + + +**질문 2:** CEI 패턴을 적용하면 6번에서 어떻게 되나요? + +**답변:** + + + +**질문 3:** 공격자가 총 몇 ETH를 탈취할 수 있나요? (예치금 1 ETH, Vault 총 잔액 10 ETH 가정) + +**답변:** + + + +--- + +## 자기 평가 + +모든 문제를 풀었다면, 아래 체크리스트로 자기 평가를 해보세요: + +- [ ] EVM의 결정론적 실행 필요성을 이해했다 +- [ ] Storage/Memory/Stack의 차이와 비용을 알고 있다 +- [ ] 재진입 공격의 원리를 설명할 수 있다 +- [ ] CEI 패턴으로 재진입 공격을 방어할 수 있다 +- [ ] tx.origin vs msg.sender의 보안 차이를 알고 있다 +- [ ] ReentrancyGuard를 적용할 수 있다 + +--- + +## 참고 자료 + +- 이론: `eth-materials/week-03/theory/slides.md` +- 취약한 코드: `eth-homework/week-03/dev/src/Vault.sol` +- 안전한 코드: `eth-homework/week-03/dev/src/VaultSecure.sol` +- 테스트: `eth-homework/week-03/dev/test/Vault.t.sol` +- 용어: `eth-materials/resources/glossary.md` diff --git a/week-04/dev/README.md b/week-04/dev/README.md index 705ae6c2..cc9a4762 100644 --- a/week-04/dev/README.md +++ b/week-04/dev/README.md @@ -1,69 +1,69 @@ -# Week 4: wagmi 연동 및 Web3 프론트엔드 - -이번 주에는 wagmi를 사용하여 Web3 프론트엔드를 개발하는 방법을 배웁니다. - -## 학습 목표 - -이번 주를 마치면 다음을 할 수 있습니다: - -- [x] wagmi의 핵심 개념(hooks, config, provider)을 이해한다 -- [x] useAccount, useBalance 등 계정 관련 훅을 사용한다 -- [x] useReadContract로 컨트랙트 상태를 읽는다 -- [x] useWriteContract로 컨트랙트 함수를 호출한다 -- [x] BigInt와 viem의 유틸리티 함수를 다룬다 - -## 사전 준비 - -1. Node.js 18+ 설치 -2. MetaMask 또는 다른 Web3 지갑 설치 -3. Sepolia 테스트넷 ETH (Faucet에서 받기) - -## 실습 내용 - -### 1. 프론트엔드 템플릿 설정 - -`eth-materials/resources/frontend-template/` 폴더를 복사하여 시작합니다. - -```bash -# 템플릿 복사 -cp -r eth-materials/resources/frontend-template/ week-04/dev/my-dapp/ - -# 의존성 설치 -cd week-04/dev/my-dapp -npm install - -# 개발 서버 실행 -npm run dev -``` - -### 2. Counter 컨트랙트 연동 - -지난 주에 배포한 Counter 컨트랙트(또는 예제 컨트랙트)와 연동합니다. - -**해야 할 것:** -1. 컨트랙트 ABI를 프로젝트에 추가 -2. useReadContract로 count 값 표시 -3. useWriteContract로 increment/decrement 호출 -4. 트랜잭션 상태(pending, confirming, success) 표시 - -### 3. 추가 과제 (선택) - -- [ ] 이벤트 리스닝으로 실시간 업데이트 구현 -- [ ] 에러 메시지를 사용자 친화적으로 표시 -- [ ] 트랜잭션 히스토리 표시 - -## 참고 자료 - -- [wagmi 상세 가이드](/eth-materials/week-04/dev/wagmi-basics.md) -- [wagmi 공식 문서](https://wagmi.sh) -- [viem 공식 문서](https://viem.sh) - -## 제출 - -1. Counter 컨트랙트와 연동된 프론트엔드 코드 -2. 동작 스크린샷 또는 짧은 데모 영상 -3. (선택) 추가 과제 구현 내용 - ---- - -> **팁:** 처음에는 복잡해 보이지만, useReadContract와 useWriteContract 두 개의 훅만 이해하면 대부분의 dApp을 만들 수 있습니다! +# Week 4: wagmi 연동 및 Web3 프론트엔드 + +이번 주에는 wagmi를 사용하여 Web3 프론트엔드를 개발하는 방법을 배웁니다. + +## 학습 목표 + +이번 주를 마치면 다음을 할 수 있습니다: + +- [x] wagmi의 핵심 개념(hooks, config, provider)을 이해한다 +- [x] useAccount, useBalance 등 계정 관련 훅을 사용한다 +- [x] useReadContract로 컨트랙트 상태를 읽는다 +- [x] useWriteContract로 컨트랙트 함수를 호출한다 +- [x] BigInt와 viem의 유틸리티 함수를 다룬다 + +## 사전 준비 + +1. Node.js 18+ 설치 +2. MetaMask 또는 다른 Web3 지갑 설치 +3. Sepolia 테스트넷 ETH (Faucet에서 받기) + +## 실습 내용 + +### 1. 프론트엔드 템플릿 설정 + +`eth-materials/resources/frontend-template/` 폴더를 복사하여 시작합니다. + +```bash +# 템플릿 복사 +cp -r eth-materials/resources/frontend-template/ week-04/dev/my-dapp/ + +# 의존성 설치 +cd week-04/dev/my-dapp +npm install + +# 개발 서버 실행 +npm run dev +``` + +### 2. Counter 컨트랙트 연동 + +지난 주에 배포한 Counter 컨트랙트(또는 예제 컨트랙트)와 연동합니다. + +**해야 할 것:** +1. 컨트랙트 ABI를 프로젝트에 추가 +2. useReadContract로 count 값 표시 +3. useWriteContract로 increment/decrement 호출 +4. 트랜잭션 상태(pending, confirming, success) 표시 + +### 3. 추가 과제 (선택) + +- [ ] 이벤트 리스닝으로 실시간 업데이트 구현 +- [ ] 에러 메시지를 사용자 친화적으로 표시 +- [ ] 트랜잭션 히스토리 표시 + +## 참고 자료 + +- [wagmi 상세 가이드](/eth-materials/week-04/dev/wagmi-basics.md) +- [wagmi 공식 문서](https://wagmi.sh) +- [viem 공식 문서](https://viem.sh) + +## 제출 + +1. Counter 컨트랙트와 연동된 프론트엔드 코드 +2. 동작 스크린샷 또는 짧은 데모 영상 +3. (선택) 추가 과제 구현 내용 + +--- + +> **팁:** 처음에는 복잡해 보이지만, useReadContract와 useWriteContract 두 개의 훅만 이해하면 대부분의 dApp을 만들 수 있습니다! diff --git a/week-04/quiz/quiz-04.md b/week-04/quiz/quiz-04.md index 91dfa07a..393ef532 100644 --- a/week-04/quiz/quiz-04.md +++ b/week-04/quiz/quiz-04.md @@ -1,340 +1,340 @@ -# Week 4 Quiz: Network/Block + wagmi - -> **제출 방법:** 이 파일을 복사하여 답변을 작성한 후, PR로 제출하세요. -> **평가 기준:** 개념 이해도 중심 - 문법 오류보다 논리적 설명을 중시합니다. - ---- - -## 문제 1: 블록 헤더 필드 (객관식) - -다음 상황을 고려하세요: - -``` -블록 100의 해시: 0xabc123... -블록 101의 해시: 0xdef456... -``` - -블록 101의 `parentHash` 필드에는 어떤 값이 저장되어 있나요? 그리고 **왜** 이런 방식으로 연결하나요? - -**보기:** -A) 0xdef456... - 자기 자신의 해시를 저장하여 무결성을 보장한다 -B) 0xabc123... - 이전 블록의 해시를 저장하여 체인 연결과 불변성을 보장한다 -C) 블록 번호 100 - 숫자로 순서를 추적한다 -D) 빈 값 - 헤더에는 해시가 저장되지 않는다 - -**답변:** - - - ---- - -## 문제 2: MPT 목적 (객관식) - -이더리움에서 Merkle Patricia Trie(MPT)를 사용하는 **가장 중요한 이유**는 무엇인가요? - -**보기:** -A) 데이터를 암호화하여 외부에서 읽을 수 없게 한다 -B) 트랜잭션 처리 속도를 10배 이상 높인다 -C) 전체 데이터 없이도 특정 데이터의 존재와 정확성을 효율적으로 증명한다 -D) 블록 크기를 줄여서 저장 공간을 절약한다 - -**답변:** - - - ---- - -## 문제 3: 체인 연결과 보안 (객관식) - -공격자가 블록 50의 트랜잭션을 수정하려고 합니다. 현재 체인의 최신 블록은 100입니다. 이 공격이 **왜** 어려운가요? - -**보기:** -A) 블록 50은 너무 오래되어서 시스템에서 접근할 수 없다 -B) 블록 50을 수정하면 해시가 바뀌고, 블록 51부터 100까지 모든 블록의 parentHash가 불일치하게 된다 -C) 블록 50은 이미 암호화되어 있어서 복호화 키가 필요하다 -D) 네트워크 관리자만 과거 블록을 수정할 수 있다 - -**답변:** - - - ---- - -## 문제 4: MPT 진화 과정 (단답형) - -MPT(Merkle Patricia Trie)는 세 가지 자료구조의 장점을 결합한 것입니다: -1. **Trie** -> 2. **Patricia Trie** -> 3. **Merkle Patricia Trie** - -**왜** 각 단계의 발전이 필요했나요? 각 단계가 해결하는 문제를 간단히 설명하세요. - -**답변:** - - - ---- - -## 문제 5: Eclipse Attack 방어 (단답형) - -Eclipse Attack은 공격자가 피해자 노드의 **모든 피어 연결**을 자신이 통제하는 노드로 바꾸는 공격입니다. - -1) 이 공격이 성공하면 피해자에게 **어떤 피해**가 발생할 수 있나요? -2) 개인 노드 운영자가 이 공격을 **방어**하기 위해 할 수 있는 행동은 무엇인가요? - -**답변:** - - - ---- - -## 문제 6: 노드 종류 선택 (단답형) - -친구가 이더리움 개발을 시작하려고 합니다. 다음 세 가지 상황에서 각각 어떤 노드 타입(Full, Light, Archive)을 추천하시겠습니까? **왜** 그 노드를 추천하는지도 설명하세요. - -1) 모바일 지갑 앱 개발 -2) 블록체인 데이터 분석 서비스 개발 -3) 일반적인 dApp 백엔드 개발 - -**답변:** - - - ---- - -## 문제 7: useAccount Hook (빈칸 채우기) - -다음 코드의 빈칸을 채워서 지갑 연결 상태를 표시하는 컴포넌트를 완성하세요: - -```typescript -import { _________________ } from 'wagmi'; - -function WalletStatus() { - // TODO: useAccount hook에서 필요한 값들을 가져오세요 - const { _________________, _________________ } = useAccount(); - - if (!isConnected) { - return
연결된 주소: {address}
-연결된 주소: {address}
+트랜잭션 성공!
} -트랜잭션 성공!
} +찬성: {_________________}
-반대: {noCount?.toString()}
- - - - {isSuccess &&투표 완료!
} -찬성: {voteCount?.toString()}
- - {isSuccess &&투표 완료!
} -찬성: {_________________}
+반대: {noCount?.toString()}
+ + + + {isSuccess &&투표 완료!
} +찬성: {voteCount?.toString()}
+ + {isSuccess &&투표 완료!
} +