노아 브래그의 첫 번째 스토크 파이어 라이브스트림
원문은 Michael Lynch님이 에 게재했습니다. 이 블로그 구독하기
지난 1년간 이더리움, 특히 Base 생태계에 관심을 가져왔다. 문제는 Base에 대해 몇 시간씩 읽어봐도 도대체 Base가 무엇인지 여전히 이해가 되지 않는다는 것이다.
몇 달에 한 번씩 Base 웹사이트의 개발자 섹션을 다시 확인하며 초보자가 Base 위에서 뭔가를 만들 수 있는 길이 있는지 찾아보는데, 돌아오는 답은 늘 “아주 특정한 주제에 대한 따로 떨어진 튜토리얼 몇 개가 여기 있고, 질문이 있으면 디스코드로 와서 물어보세요” 정도다.
그래서 트위터에서 팔로우하던 인디 창업가 Noah Bragg가 Base 생태계 위에서 간단한 게임을 만드는 과정을 라이브스트리밍하기 시작했다는 소식을 보고 반가웠다.

인디 창업가 Noah Bragg가 이더리움 블록체인 위에서 게임을 만드는 과정을 라이브스트리밍하고 있다.
노아의 첫 스트리밍을 시청했고, 아래에 느낀 점을 정리해 봤다.
게임: 스토크 파이어
- 스토크 파이어는 마을에서 나무를 베어 계속 타오르는 불을 지피며 마을을 따뜻하게 유지하는 자원 관리 게임이다.

- 영감
- 노아가 어릴 때 즐겼던 Age of Empires II
- 텍스트 기반 웹 게임인 A Dark Room
- 지난 6년간 1인 개발자가 만든 토탈 워 시뮬레이션 Manor Lords
- 참여를 독려하기 위해 무료로 플레이할 수 있다.
- 게임 상태는 지갑 간 이동을 쉽게 하기 위해 NFT에 저장되지만, 노아는 사람들이 아트 NFT처럼 게임 상태 NFT를 거래하게 될 거라고는 예상하지 않는다.
- 코인베이스의 투자 덕분에 소비자들이 Base에 모일 것으로 기대하기 때문에 Base 블록체인 위에서 만들고 있다.
얼리 액세스
- 스토크 파이어의 얼리 액세스는 Hypersub에서 노아의 I Must Build 구독을 통해 제공된다.
- Hypersub은 온체인용 Patreon 같은 서비스다.
스트리밍에서는 무슨 일이 있었나
- 노아는 게임에 나무를 사용해 오두막을 짓는 기능을 추가했다.
- 마을에 있는 자원에 따라 주민을 끌어들이는 기능을 구현하기 시작했지만, 정신적으로 지쳐 결국 작업을 중간에 중단했다.
Solidity
- 노아는 스마트 컨트랙트 개발에 Solidity를 사용하고 있다.
Michael의 노트: Solidity는 여전히 나를 질리게 한다. 정확성과 가독성이 무엇보다 중요한 분야에서 Solidity는 불필요한 함정과 꼼수를 수없이 만들어낸다. 마치 C++와 JavaScript를 그토록 열심히 연구해서 두 언어의 최악의 기능만 쏙쏙 골라 담은 것 같다.
Diamond
- Diamond는 배포 후에도 수정 가능한 스마트 컨트랙트를 배포하기 위한 프레임워크다.
- 보통 이더리움 스마트 컨트랙트는 한 번 배포되면 변경할 수 없다.
- Diamond는 “업그레이드 가능한” 스마트 컨트랙트를 제공해 배포 후에도 변경할 수 있게 해준다.
- Diamond는 스마트 컨트랙트의 보장성을 약화시키지만, 스토크 파이어 같은 프로젝트가 빠르게 반복 개발하는 데는 도움이 된다.
- “Facets”는 (내 생각엔) 스마트 컨트랙트 중 수정 가능한 부분을 말한다.
Michael의 노트: Facet 관리는 지루해 보이고 노아가 보여준 코드 중 가장 취약한 부분이다. 여러 개의 배열을 수동으로 선언한 뒤 새로운 facet을 추가할 때마다 수동으로 인덱스를 지정해야 하는데, 한때는 어딘가에 묻혀 있는 선언부에서 facet 개수와 배열 크기를 맞추느라 한참을 헤매기도 했다. 채팅의 시청자들은 helper를 쓰면 facet을 자동으로 가져올 수 있다고 했으니, 어쩌면 더 쉬운 방법이 있을지도 모른다.
Forge
- 노아는 Forge를 이용해 스마트 컨트랙트 로직을 테스트하고 있다.
- 노아는 Forge에 대해 긍정적으로 평가했다.
- Forge를 쓰면 프로덕션 스마트 컨트랙트와 많은 코드를 공유할 수 있도록 Solidity로 테스트를 작성할 수 있다는 점을 마음에 들어 했다.
- 타임스탬프나 지갑 잔액 같은 다양한 블록체인 조건을 쉽게 테스트할 수 있게 해준다는 점도 좋다고 했다.
- 노아는 때때로 Forge의 출력을 해석하는 데 어려움을 겪었고, 개인적으로 나는 그 출력이 매우 시끄럽고 읽기 어렵다고 느꼈다.

Forge의 테스트 출력이 매우 시끄럽다고 느꼈다
- Forge에서는 assert가 실패해도 실패를 일으킨 줄 번호를 출력하지 않는다. 스트리밍 중 한 장면에서는 Forge가 출력한 실패 메시지가 단순히
5 != 4뿐이었고, 해당 assert가 소스 코드의 어디에 있는지 개발자가 직접 찾아야 했다. assert 실패 위치의 줄 번호를 출력하지 않는 테스트 프레임워크는 처음 본다.
- Forge에서는 assert가 실패해도 실패를 일으킨 줄 번호를 출력하지 않는다. 스트리밍 중 한 장면에서는 Forge가 출력한 실패 메시지가 단순히
- 노아의 프로덕션 코드가 특정 에러를 던졌는지 확인하려면 테스트 코드에 그 에러를 다시 정의해야 했는데, 나는 그 점이 이상하게 느껴졌다.
Warpcast에 웹 앱이 있다
- Farcaster는 트위터나 Mastodon의 이더리움 버전 같은 것이다.
- Farcaster는 Warpcast가 모바일 전용인 것처럼 보이게 하지만, 노아의 스트리밍을 보고 Warpcast에 웹 앱이 있다는 것을 알게 됐다.
- 계정을 만들려면 여전히 모바일 앱이 필요한 것 같다.
스트리밍
- 노아의 스트리밍은 최고 250명의 시청자를 기록했다.
- 나중에 밝혀진 바로는 이 수치가 동시 시청자 수인지, 스트리밍 중 어느 시점에든 접속한 누적 시청자 수인지 본인도 확실하지 않다고 한다.
- 대부분의 시청자는 트위터에서 유입됐다.
- 본인도 인정했듯 라이브스트리밍이 오랜만이라 스트리밍 중에 정적이 흐르는 구간이 많았다.
개선할 점
편의용 개발 스크립트
노아는 한때 전체 테스트 스위트를 매번 실행하는 대신 단일 테스트만 실행하는 방법을 떠올리지 못해 몇 분 동안 헤맸다. 결국 채팅에 있던 시청자들의 도움으로 올바른 문법을 찾아냈다:
forge test --match-contract BuildFacetTest -vvvv기억하기 어려운 커맨드라인 문법은 편의 스크립트로 만들어 레포에 저장해 두는 것이 좋다. 여러 기술 스택마다 단일 테스트를 실행하는 문법을 일일이 기억하는 대신, 이렇게 명령어 하나만 실행하면 된다:
./dev-scripts/run-single-test BuildFacetTest나는 내 레포 여러 개에서 이렇게 하고 있다.
노아는 이미 make를 사용하고 있으니, 이런 스크립트를 make 명령어로 만들어도 된다.
테스트를 더 유지보수하기 쉽게 만들기
오두막 짓기 기능에 대한 노아의 최종 통합 테스트는 이렇게 생겼다:
function testBuildHut() public {
vm.prank(USER);
ResourceFacet(address(diamond)).chopWood(1, someWhatRandNum);
vm.warp (1719981068 + 1 days); //set the block time to the future so I can chop wood again
vm.prank(USER);
ResourceFacet(address(diamond)).chopWood(1, someWhatRandNum);
vm.prank(USER);
BuildFacet(address(diamond)).buildHut(1);
Village memory village = VillageFacet(address(diamond)).getVillage(1);
assertEq(village.timeLastChoppedWood, block.timestamp);
assertGe(village.wood, 4); //at minimum 2 wood per chop.
assertLe(village.wood, 12); //at maximun 6 wood per chop.
assertEq(village.huts, 1);
}이 테스트에서는 몇 가지 개선할 점을 발견했다.
첫째, 이 테스트는 로직을 매우 혼란스럽게 만드는 의사 난수 생성기(PRNG)에 의존한다. 테스트를 위해 노아는 PRNG에 고정된 시드 값을 넣어 테스트마다 같은 난수 시퀀스가 반복되도록 했지만, 여전히 테스트 로직을 따라가기 어렵다.
스트리밍에서 노아는 테스트가 통과할 때까지 나무 베기 횟수를 감으로 계속 추가해야 했다. 결국 난수 시드를 다른 값으로 바꿔서 두 번만 베어도 테스트가 통과하도록 만들었다. 그래서 이 테스트는 남은 나무의 정확한 양을 단언하지 못하고 그저 일정 범위 안에 있을 거라고 추정할 뿐이다. 이 테스트를 실제로 실행해 보지 않고는 정확성을 검증할 방법이 없다.
내가 이 코드를 작업한다면, 아마 나무 베기 기능을 목(mock)으로 대체해 한 번 벨 때마다 얼마의 나무가 나오는지 정확히 지정할 수 있는 테스트용 구현을 만들 것 같다. 아니면 실제 나무 생성 코드를 그대로 테스트하고 싶다면 시드를 하드코딩하는 대신 특정 숫자 시퀀스를 내보내는 가짜 난수 생성기를 사용할 것이다.
또 다른 문제는 호출 지점에서 함수 인자 대부분이 무엇을 뜻하는지 알기 어렵다는 것이다. buildHut(1)이나 chopWood(1, ...) 같은 경우, 1이 무엇을 의미하는지, 테스트에 등장하는 여러 개의 매직 넘버 1이 같은 값을 가리키는 것인지 아니면 우연히 모두 1인 것인지 불분명하다.
아직 풀리지 않은 질문들
왜 블록체인인가?
스트리밍이 끝난 뒤 가장 크게 남은 질문은 “왜 블록체인인가?”였다.
노아는 블록체인 위에서 좀 더 독창적인 것을 시도해 보고 싶었다고 했고, 그 점이 내 흥미를 끌었지만 블록체인이 어떻게 도움이 되는지 여전히 알 수 없었다.
지금까지 보기에는 블록체인이 모든 것을 10배 더 복잡하게 만들 뿐, 그냥 모든 것을 단일 SQLite 데이터베이스에 넣는 것에 비해 어떤 이점도 제공하지 않는 것 같다.
마찬가지로, 내가 답을 찾고 싶었던 “도대체 Base가 뭔가?”라는 질문에도 아직 답을 찾지 못했다. 왜 노아가 이더리움이나 다른 체인에 직접 구축하지 않고 Base 위에서 만드는지 여전히 모르겠다. 코인베이스의 투자를 언급하긴 했지만, 그것이 노아 같은 개발자에게 무엇을 의미하는지는 이해되지 않는다.
정수 크기가 참 크네요!
노아가 타임스탬프나 나무 개수 같은 곳에서 uint256을 빈번하게 사용하는 것을 봤는데, 과도해 보였다. 현재 게임에서 ‘베기’ 한 번에 2~4개의 나무를 주는 상황에서 그 결과를 굳이 uint256에 저장해야 할 필요가 있을지 상상이 되지 않는다.

이렇게 큰 정수 타입은 가스비를 늘리지 않나요?
내 자체 EVM 구현을 작업해 본 경험으로, 이더리움 네트워크는 처리해야 하는 데이터에 대해 비용을 부과한다는 것을 안다. 그래서 256비트 값을 스택에 푸시하는 것이 32비트 워드를 푸시하는 것보다 8배 더 비싸다. (수정: 실제로는 256비트 워드와 32비트 워드를 푸시하는 비용이 동일하다. 정정해 준 a14u에게 감사한다.)
이것이 단순한 실수인지, 아니면 실제 가스비가 내가 생각하는 것보다 적은 것인지는 잘 모르겠다.
왜 언더스코어를 붙일까?
노아는 모든 함수 매개변수 이름 앞에 언더스코어를 붙이는 네이밍 컨벤션을 사용하고 있다.

함수 매개변수 이름 앞에 언더스코어를 붙이는 컨벤션은 본 적이 없고, 왜 노아가 그렇게 하는지 모르겠다.
이 컨벤션은 Python이나 JavaScript에서 변수가 private/protected임을 독자에게 힌트 주기 위해 쓰는 것으로 알고 있는데, 함수 인자는 원래부터 private 아닌가? 물론 내가 Solidity 코드를 읽어본 경험이 많지 않긴 하지만, 이런 컨벤션은 이전에 본 적이 없다.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기