feat(week-01): complete counter and quiz assignment#16
feat(week-01): complete counter and quiz assignment#16minij02 wants to merge 2 commits intoBay-17th:mainfrom
Conversation
리뷰정말 고생하셨습니다 ㅎㅎ 개발 과제increment, decrement, reset 모두 정확합니다. 퀴즈10문제 + 이론 모두 정확합니다. 인상적인 부분WSL PATH 충돌을 hash -r로 해결한 것도 좋습니다. 블로그나 그런곳에 포스팅해도 좋을거 같아요 |
| /// @dev mapping은 key(address) => value(uint256) 형태의 저장소입니다 | ||
| mapping(address => uint256) public balances; |
Check failure
Code scanning / Slither
Uninitialized state variables High
| /// - 상태(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; |
Check failure
Code scanning / Slither
Reentrancy vulnerabilities High
| /// @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); |
Check warning
Code scanning / Slither
Contracts that lock Ether Medium
| /// @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) |
Check warning
Code scanning / Slither
Contracts that lock Ether Medium
| /// @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 입금 - 이 호출에서 이벤트가 발생해야 함 |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low test
| 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); |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low test
| /// - 상태(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; |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
| } | ||
| /// @notice 입금 시 Deposited 이벤트가 발생하는지 테스트 | ||
| /// @dev 예상: Deposited(alice, 1 ether) 이벤트 발생 | ||
| function test_Deposit_EmitsDepositedEvent() public { | ||
| // Arrange | ||
| // 이벤트 발생 예상 설정 | ||
| vm.expectEmit(true, false, false, true); | ||
| emit Deposited(alice, 1 ether); |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low test
| "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}(); |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low test
과제 제출 정보
주차: Week 01
과제 유형:
구현 내용
배운 점 (What I Learned)
이번 주에 배운 것 (2-3가지)
상태 머신(State Machine)으로 정의하는 관점을 배웠습니다. 이는 분산 네트워크 환경에서 각 노드들이 - P2P 통신을 통해 동일한 상태 전이 결과에 도달해야 하는 분산 합의(Consensus) 문제와 직결됨을 이해했습니다.특히 EVM이
결정론적(Deterministic)으로 설계된 이유를 네트워크 상의 모든 노드가 동일한 입력에 대해 동일한 출력(상태)을 보장하여 데이터 정합성을 유지하기 위함이라는 네트워크 설계 원칙과 연결지어 학습했습니다.경제적 비용(Gas)으로 환산된다는 점이 인상적이었습니다.어려웠던 점과 해결 방법
어려웠던 점:
NULL (이번 과제에서는 없었습니다)
해결 방법:
WSL 환경에서의 PATH 우선순위 충돌로 인한 실행 파일 경로 문제를 hash -r 및 ~/.bashrc 수정을 통해 해결했습니다.
질문 사항
체크리스트
테스트
forge build성공forge test모든 테스트 통과제출 규칙
{username}/week-{XX}형식.env파일이 커밋에 포함되지 않음