Noah Bragg's First Stoke Fire Livestream

Michael Lynch

노아 브래그의 첫 스토크 파이어 라이브스트림

지난 1년간 이더리움, 특히 Base 생태계에 관심을 가져왔습니다. 문제는 Base에 대해 몇 시간씩 읽어봐도 Base가 도대체 무엇인지 아직 이해하지 못했다는 점입니다.

몇 달에 한 번씩 Base 웹사이트의 개발자 섹션을 다시 확인하며 초보자가 Base 위에서 개발할 수 있는 길이 있는지 살펴보는데, 경로는 늘 “아주 구체적인 주제별로 흩어진 튜토리얼 몇 개가 있고, 질문이 있으면 디스코드에서 물어보세요” 정도인 것 같습니다.

그래서 트위터에서 팔로우하던 인디 창업가 Noah Bragg가 Base 생태계 위에서 간단한 게임을 만드는 과정을 라이브스트리밍하기 시작했다는 소식을 보고 반가웠습니다.

인디 창업가 Noah Bragg가 이더리움 블록체인 위에서 게임을 만드는 과정을 라이브스트리밍하고 있습니다.

저는 Noah의 첫 번째 스트림을 시청했고, 아래에 느낀 점을 정리해 공유합니다.

게임: Stoke Fire

  • Stoke Fire는 마을에서 나무를 베어 계속 타오르는 불을 지펴 마을을 따뜻하게 유지하는 자원 관리 게임입니다.
  • 영감의 원천
    • 어릴 적 Noah가 즐겨 했던 Age of Empires II
    • 텍스트 기반 웹 게임인 A Dark Room
    • 1인 개발자가 6년에 걸쳐 만든 토탈 워 시뮬레이션 Manor Lords
  • 참여를 장려하기 위해 무료로 플레이할 수 있습니다.
  • 게임은 지갑 간 이동을 쉽게 하기 위해 상태를 NFT에 저장할 예정이지만, Noah는 사람들이 아트 NFT처럼 게임 상태 NFT를 거래하게 될 것이라고는 예상하지 않습니다.
  • Coinbase의 투자 덕분에 소비자들이 그곳에 모일 것이라고 기대하기 때문에 Base 블록체인 위에서 개발하고 있습니다.

얼리 액세스

  • Stoke Fire의 얼리 액세스는 Hypersub에서 Noah의 I Must Build 구독을 통해 이용할 수 있습니다.
    • Hypersub은 온체인용 Patreon 같은 서비스입니다.

스트림에서 있었던 일

  • Noah는 게임에 나무를 사용해 오두막을 짓는 기능을 추가했습니다.
  • 마을에 있는 자원에 따라 주민을 끌어들이는 기능을 구현하기 시작했지만, 정신적으로 지쳐 결국 작업을 중간에 중단했습니다.

Solidity

  • Noah는 스마트 컨트랙트 개발에 Solidity를 사용하고 있습니다.
    • Vyper에 대해 들어본 적은 있습니다. Solidity 생태계가 다른 어떤 것보다 훨씬 성숙하기 때문에 사용해 본 적은 없습니다.
    • Huff는 들어본 적도 없습니다.

Michael의 메모: Solidity는 여전히 제게 거부감을 줍니다. 정확성과 가독성이 무엇보다 중요한 분야에서 Solidity는 불필요한 함정과 예상치 못한 동작을 수없이 만들어냅니다. 마치 C++와 JavaScript를 철저히 연구해서 그 언어들의 최악의 특징만을 가져온 것 같습니다.

Diamond

  • Diamond는 배포 후에도 변경 가능한 스마트 컨트랙트를 배포하기 위한 프레임워크입니다.
    • 일반적으로 이더리움 스마트 컨트랙트는 한 번 배포되면 변경할 수 없습니다.
    • Diamond는 “업그레이드 가능한” 스마트 컨트랙트를 제공하므로 배포 후에도 변경할 수 있습니다.
  • Diamond는 스마트 컨트랙트의 보장을 약화시키지만, Stoke Fire 같은 프로젝트에서는 반복 개발을 용이하게 합니다.
  • “Facets”는 (제 생각에는) 스마트 컨트랙트에서 수정 가능한 부분을 말합니다.

Michael의 메모: Facets를 관리하는 일은 번거로워 보이며 Noah가 보여준 코드 중 가장 취약한 부분입니다. 그는 여러 개의 배열을 수동으로 선언한 뒤 새로운 facet을 추가할 때마다 수동으로 인덱스를 지정해야 하며, 한때는 어딘가에 숨겨진 선언에서 facet 개수와 배열 크기를 맞추느라 한동안 애를 먹기도 했습니다. 채팅의 시청자들은 헬퍼를 사용하면 facet을 자동으로 가져올 수 있다고 했으니, 어쩌면 더 쉬운 방법이 있을지도 모릅니다.

Forge

  • Noah는 스마트 컨트랙트 로직을 테스트하기 위해 Forge를 사용하고 있습니다.
  • Noah는 Forge에 대해 긍정적으로 평가했습니다.
    • Forge를 사용하면 Solidity로 테스트를 작성할 수 있어 프로덕션 스마트 컨트랙트와 코드를 많이 공유할 수 있다는 점을 좋아합니다.
    • Forge는 타임스탬프나 지갑 잔액 같은 다양한 블록체인 조건을 테스트하기 쉽게 만들어 줍니다.
  • Noah는 때때로 Forge의 출력을 해석하는 데 어려움을 겪었고, 저 개인적으로도 출력이 매우 지저분하고 읽기 어렵다고 느꼈습니다.

    Forge의 테스트 출력이 매우 지저분하다고 느꼈습니다

    • Forge에서 assert가 실패해도 실패를 일으킨 줄 번호를 출력하지 않습니다. 스트림의 한 장면에서 Forge가 출력한 실패 메시지는 단순히 5 != 4 뿐이었고, 소스 코드에서 어디에 있는 assertion인지 개발자가 직접 찾아야 합니다. assertion이 실패한 줄 번호를 출력하지 않는 테스트 프레임워크는 본 적이 없습니다.
  • Noah의 프로덕션 코드가 특정 오류를 던졌는지 확인하려면 테스트 코드에서 그 오류를 다시 정의해야 하는데, 저는 이 점이 이상하게 느껴졌습니다.

Warpcast에는 웹 앱이 있습니다

  • Farcaster는 트위터나 Mastodon의 이더리움 버전과 같습니다.
  • Farcaster는 Warpcast가 모바일 전용인 것처럼 보이게 하지만, Noah의 스트림을 보고 Warpcast에 웹 앱이 있다는 것을 알게 되었습니다.
    • 계정을 만들 때는 여전히 모바일 앱이 필요한 것으로 생각됩니다.

스트리밍

  • Noah는 최고 250명의 시청자를 기록했습니다.
    • 나중에 밝혀진 바로는 이것이 동시 시청자 수인지, 스트림을 어느 시점에든 시청한 총 누적 시청자 수인지 그 자신도 잘 모른다고 합니다.
  • 대부분의 시청자는 트위터에서 유입되었습니다.
  • 그는 라이브스트리밍이 오랜만이라 감이 떨어졌음을 인정했으며, 스트림 중에 말이 끊기는 구간이 많았습니다.

개선할 점

편의용 개발 스크립트

Noah는 매번 전체 테스트 스위트를 실행하는 대신 단일 테스트만 실행하는 방법을 기억해 내려고 한때 몇 분 동안 막혀 있었습니다. 결국 채팅에 있던 시청자들의 도움을 받아 올바른 문법을 찾아냈습니다:

forge test --match-contract BuildFacetTest -vvvv

기억하기 어려운 명령줄 문법의 경우, 편의 스크립트를 작성해 레포지토리에 저장해 두는 것을 제안합니다. 작업하는 다양한 기술 스택마다 단일 테스트를 실행하는 올바른 문법을 외우는 대신, 다음과 같은 명령어를 실행하면 됩니다:

./dev-scripts/run-single-test BuildFacetTest

저는 여러 레포지토리에서 이렇게 하고 있습니다.

Noah는 이미 make를 사용하고 있으므로, 이런 스크립트들을 make 명령어로 만들 수도 있습니다.

테스트를 더 유지보수하기 쉽게 만들기

오두막 짓기 기능에 대한 Noah의 최종 통합 테스트는 다음과 같습니다:

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에 고정된 시드 값을 넣어 테스트마다 동일한 난수 시퀀스가 반복되도록 했지만, 그럼에도 테스트 로직을 따라가기 어렵습니다.

스트림에서 Noah는 테스트가 통과할 때까지 무작정 나무 베기 횟수를 계속 추가해야 했습니다. 결국 난수 시드를 다른 값으로 바꿔 두 번의 베기만으로도 테스트가 통과하도록 만들었습니다. 그래서 이 테스트는 남은 나무의 정확한 양을 검증하지 못하고 단지 일정 범위 안에 있다고 추정할 뿐입니다. 이 테스트를 실제로 실행해 보지 않고는 정확성을 검증하는 것이 불가능합니다.

제가 이 코드를 작업한다면 아마 나무 베기 기능을 목(mock)으로 대체해 베기마다 생성되는 나무의 정확한 양을 지정할 수 있는 테스트 구현을 만들 것입니다. 혹은 실제 나무 생성 코드를 그대로 테스트하고 싶다면, 시드를 하드코딩하는 대신 특정 숫자 시퀀스를 내보내는 가짜 난수 생성기를 사용할 것입니다.

또 다른 문제는 함수 파라미터 상당수가 호출부에서 가독성이 떨어진다는 점입니다. 예를 들어 buildHut(1)이나 chopWood(1, ...) 같은 호출에서 1이 무엇을 의미하는지, 그리고 테스트에 등장하는 여러 개의 매직 넘버 1이 모두 같은 값을 가리키는지 아니면 우연히 모두 1인 것인지 알기 어렵습니다.

남은 질문들

왜 블록체인인가?

스트림이 끝난 뒤 가장 크게 남은 질문은 ‘왜 블록체인인가?’였습니다.

Noah는 블록체인 위에서 좀 더 독창적인 것을 시도해 보고 싶었다고 말했고, 그 점이 제 흥미를 끌었지만, 블록체인이 어떻게 도움이 되는지 여전히 이해할 수 없습니다.

지금까지 보기에는 블록체인이 모든 것을 10배 더 복잡하게 만들 뿐, 모든 것을 단일 SQLite 데이터베이스에 넣는 것에 비해 아무런 이점도 제공하지 않는 것처럼 보입니다.

마찬가지로, 제가 답을 얻고 싶었던 “Base가 도대체 뭔가?”라는 질문에 대한 답도 아직 찾지 못했습니다. Noah가 왜 이더리움이나 다른 체인에 직접 구축하지 않고 Base 위에서 구축하는지 여전히 잘 모르겠습니다. 그는 Coinbase의 투자를 언급했지만, 그것이 Noah 같은 개발자에게 무엇을 의미하는지 이해하지 못하겠습니다.

정수 크기가 정말 크네요!

Noah가 타임스탬프나 나무 개수처럼 과도해 보이는 곳에서도 uint256을 자주 사용하는 것을 발견했습니다. 현재 게임에서 ‘베기’ 한 번당 2~4개의 나무를 지급하는데, 그 결과를 uint256에 저장해야 할 필요성을 상상하기 어렵습니다.

이렇게 큰 정수 타입이 가스비를 증가시키지 않나요?

제가 직접 만든 EVM 구현을 작업해 본 경험으로, 이더리움 네트워크는 처리해야 하는 데이터에 대해 비용을 부과한다는 것을 알고 있습니다. 그래서 256비트 값을 스택에 푸시하는 비용은 32비트 워드를 푸시하는 비용보다 8배 비쌉니다. (수정: 256비트 워드와 32비트 워드를 푸시하는 비용은 실제로 동일합니다. 정정해 준 a14u에게 감사드립니다.)

이것이 단순한 간과인지, 아니면 가스비가 제가 상상하는 것보다 실제로 적은 것인지 잘 모르겠습니다.

왜 언더스코어를 붙일까?

Noah는 모든 함수 파라미터 이름 앞에 언더스코어를 붙이는 네이밍 컨벤션을 사용하고 있습니다.

함수 파라미터 이름 앞에 언더스코어를 붙이는 컨벤션은 본 적이 없으며, Noah가 왜 그렇게 하는지 잘 모르겠습니다.

이 컨벤션은 Python이나 JavaScript에서 변수가 private/protected임을 독자에게 암시하기 위해 쓰는 것으로 알고 있지만, 함수 인자는 이미 private하지 않나요? 제가 지금까지 제한적으로 읽어본 Solidity 코드에서는 이런 컨벤션을 본 적이 없습니다.

원문은 Michael Lynch님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.