[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-18-evm-baiteukoedeu-dibeogig-teureiseu-seutaeg":3},{"article":4,"author":55},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":35,"related_articles":36},"d5000000-0000-0000-0000-000000000118","a0000000-0000-0000-0000-000000000052","Deep EVM #18: EVM 바이트코드 디버깅 — 트레이스, 스택 덤프, cast run","deep-evm-18-evm-baiteukoedeu-dibeogig-teureiseu-seutaeg","cast run을 사용한 트랜잭션 리플레이, forge debug를 통한 단계별 분석, 원시 옵코드 트레이스 읽기 기법으로 EVM 바이트코드 디버깅을 마스터합니다.","## 저수준 EVM 코드의 디버깅 과제\n\nSolidity 트랜잭션이 되돌려지면, 일반적으로 `ERC20: transfer amount exceeds balance`와 같은 설명적 에러 메시지를 받습니다. Huff나 Yul 트랜잭션이 되돌려지면, `0x` — 컨텍스트가 전혀 없는 빈 되돌림 페이로드만 받습니다. 컨트랙트가 단순히 `REVERT` 옵코드에 도달한 것이며, 이유를 파악하는 것은 여러분의 몫입니다.\n\n바이트코드 수준의 디버깅은 다른 도구와 사고 모델을 필요로 합니다. 스택 머신 관점에서 생각하고, 옵코드별로 메모리와 스토리지 변경을 추적하며, EVM이 `JUMP`과 `JUMPI` 명령을 통해 제어 흐름을 어떻게 실행하는지 이해해야 합니다.\n\n이 글에서는 필수 디버깅 툴킷을 다룹니다: 과거 트랜잭션 리플레이를 위한 `cast run`, 대화형 단계별 디버깅을 위한 `forge debug`, 그리고 EVM 내부에서 정확히 무슨 일이 일어났는지 이해하기 위한 수동 트레이스 분석.\n\n## cast run: 트랜잭션 리플레이\n\n`cast run`은 실패한 트랜잭션을 디버깅하는 가장 빠른 방법입니다. 과거 상태에 대해 트랜잭션을 리플레이하고 정확히 무슨 일이 일어났는지 보여줍니다:\n\n```bash\ncast run 0xYOUR_TX_HASH --rpc-url https:\u002F\u002Feth-mainnet.g.alchemy.com\u002Fv2\u002FKEY\n```\n\n출력은 호출 깊이, 가스 사용량, 반환 데이터가 포함된 구조화된 트레이스를 보여줍니다:\n\n```\nTraces:\n  [328439] 0xContractAddr::transfer(0xRecipient, 1000000000000000000)\n    +- [2604] 0xContractAddr::balanceOf(0xSender) [staticcall]\n    |   +- \u003C- 500000000000000000\n    +- \u003C- revert: EvmError: Revert\n```\n\n이것은 발신자가 0.5 ETH를 가지고 있지만 1.0 ETH를 전송하려 했기 때문에 전송이 실패했다는 것을 즉시 알려줍니다. Huff 컨트랙트의 경우, 함수 이름이 디코딩되지 않지만(원시 셀렉터로 표시) 호출 구조와 되돌림 지점은 여전히 볼 수 있습니다.\n\n### 원시 셀렉터 디코딩\n\nHuff 컨트랙트 작업 시 `cast run`은 원시 함수 셀렉터를 보여줍니다. 수동으로 디코딩합니다:\n\n```bash\n# balanceOf(address)의 셀렉터 계산\ncast sig \"balanceOf(address)\"\n# 출력: 0x70a08231\n\n# 또는 콜데이터 디코딩\ncast 4byte-decode 0x70a0823100000000000000000000000042069abcdef\n# 출력: balanceOf(address)(0x42069abcdef)\n```\n\n디버깅 시 컨트랙트의 셀렉터 참조 테이블을 유지합니다:\n\n```\n0x70a08231 -> balanceOf(address)\n0xa9059cbb -> transfer(address,uint256)\n0x23b872dd -> transferFrom(address,address,uint256)\n0x095ea7b3 -> approve(address,uint256)\n```\n\n## forge debug: 대화형 단계별 실행\n\n`forge debug`는 옵코드별로 EVM 실행을 단계별로 진행하는 TUI(터미널 사용자 인터페이스)를 제공합니다:\n\n```bash\nforge debug --debug test\u002FSimpleToken.t.sol \\\n  --sig \"test_transfer()\" -vvvv\n```\n\n인터페이스는 네 개의 패널을 보여줍니다:\n\n1. **옵코드** — 커서가 있는 현재 명령, 실행 중인 바이트코드 표시\n2. **스택** — 모든 32바이트 워드가 포함된 현재 스택 상태\n3. **메모리** — 16진수 원시 메모리 내용\n4. **스토리지** — 실행 중 스토리지 슬롯 변경\n\n탐색 키:\n- `j\u002Fk` — 앞으로\u002F뒤로 단계\n- `g\u002FG` — 시작\u002F끝으로 점프\n- `c` — 다음 호출 경계까지 계속\n- `C` — 다음 테스트까지 계속\n- `q` — 종료\n\n### 디버깅 중 스택 읽기\n\nEVM 스택은 최대 깊이 1024의 후입선출(LIFO)입니다. Huff를 디버깅할 때는 각 옵코드가 무엇을 소비하고 생성하는지 이해하기 위해 스택을 정신적으로 추적해야 합니다.\n\n이 Huff 스니펫을 봅시다:\n\n```huff\n0x04 calldataload   \u002F\u002F 스택: [address]\nBALANCES_SLOT       \u002F\u002F 스택: [slot, address]\n```\n\n`calldataload` 후 스택에는 주소 매개변수가 있습니다. 스토리지 포인터를 푸시한 후에는 `[slot, address]`가 있습니다. 스택 위치 0에서 잘못된 값을 보면, 스토리지 슬롯이 어떻게 계산되는지에 버그가 있다는 것을 알 수 있습니다.\n\n## 옵코드 트레이스 이해하기\n\n프로덕션 디버깅(로컬에서 문제를 재현할 수 없을 때)에서는 아카이브 노드의 원시 옵코드 트레이스가 주요 도구입니다. Tenderly, Etherscan, Alchemy와 같은 서비스가 트레이스 API를 제공합니다:\n\n```bash\n# cast를 통해 트레이스 가져오기\ncast run TX_HASH --rpc-url $RPC -vvvvv 2>&1 | head -200\n```\n\n상세 트레이스 형식은 가스 비용과 스택 상태가 포함된 각 옵코드를 보여줍니다:\n\n```\n[0] PUSH1 0x00          gas: 29234  stack: []\n[2] CALLDATALOAD        gas: 29231  stack: [0x00]\n[3] PUSH1 0xe0          gas: 29228  stack: [0xa9059cbb...]\n[5] SHR                 gas: 29225  stack: [0xa9059cbb..., 0xe0]\n[6] DUP1                gas: 29222  stack: [0xa9059cbb]\n[7] PUSH4 0x70a08231    gas: 29219  stack: [0xa9059cbb, 0xa9059cbb]\n[12] EQ                 gas: 29216  stack: [0xa9059cbb, 0xa9059cbb, 0x70a08231]\n[13] PUSH2 0x0040       gas: 29213  stack: [0xa9059cbb, 0x00]\n[16] JUMPI              gas: 29210  stack: [0xa9059cbb, 0x00, 0x0040]\n```\n\n이 트레이스는 함수 디스패처가 셀렉터가 `balanceOf(address)`와 일치하는지 확인하는 것을 보여줍니다. 실제 셀렉터가 `0xa9059cbb`(transfer)이므로 `EQ`는 `0x00`(거짓)을 생성하고 `JUMPI`는 점프하지 않습니다.\n\n## Huff와 Yul을 위한 일반적인 디버깅 패턴\n\n### 패턴 1: 스택 언더플로\n\n저렴해 보이는 옵코드에서 가스 부족 에러로 실행이 되돌려지면, 스택 언더플로일 가능성이 높습니다. EVM에는 전용 \"스택 언더플로\" 에러가 없으며 — 모든 가스를 소비할 뿐입니다.\n\n```huff\n\u002F\u002F 버그: 스택이 비어 있을 때 pop\n#define macro BROKEN() = takes (0) returns (0) {\n    pop  \u002F\u002F 스택 언더플로! 팝할 항목이 없음\n}\n```\n\n감지: `forge debug`에서 스택 패널을 관찰합니다. 소비 옵코드 전에 0개 항목이 표시되면 그것이 버그입니다.\n\n### 패턴 2: 잘못된 JUMP 목적지\n\nHuff는 점프 목적지에 레이블을 사용합니다. 레이블이 `JUMPDEST`가 아닌 옵코드로 해결되면 트랜잭션이 되돌려집니다:\n\n```huff\n#define macro MAIN() = takes (0) returns (0) {\n    0x01 success jumpi\n    0x00 0x00 revert\n    success:                    \u002F\u002F JUMPDEST여야 함\n        0x00 0x00 return\n}\n```\n\n감지: 트레이스에서 `JUMP` 또는 `JUMPI` 다음에 즉시 가스가 소진되는 것을 찾습니다. 대상 PC는 점프 전 스택 상단에 있습니다.\n\n### 패턴 3: 잘못된 ABI 인코딩\n\nHuff는 반환 값을 자동 인코딩하지 않습니다. 적절한 ABI 인코딩 없이 원시 바이트를 반환하면, 호출하는 컨트랙트의 디코더가 되돌려집니다:\n\n```huff\n\u002F\u002F 잘못됨: 오프셋 없이 원시 uint256 반환\n0x00 mstore\n0x20 0x00 return\n\n\u002F\u002F 동적 타입에 올바른 방법: 오프셋 포함\n0x20 0x00 mstore     \u002F\u002F 오프셋\n0x05 0x20 mstore     \u002F\u002F 길이\n\u002F\u002F ... 0x40의 데이터\n```\n\n감지: 호출하는 컨트랙트의 `abi.decode`가 되돌려집니다. 트레이스에서 컨트랙트의 성공적인 반환 후 부모 컨텍스트에서 되돌림이 나타납니다.\n\n### 패턴 4: 스토리지 충돌\n\nHuff는 `FREE_STORAGE_POINTER()`를 사용하여 스토리지 슬롯을 할당합니다. 두 매크로가 실수로 같은 슬롯을 사용하면 서로를 덮어씁니다:\n\n```huff\n#define constant BALANCES_SLOT = FREE_STORAGE_POINTER()  \u002F\u002F 슬롯 0\n#define constant ALLOWANCES_SLOT = FREE_STORAGE_POINTER() \u002F\u002F 슬롯 1\n#define constant TOTAL_SUPPLY_SLOT = FREE_STORAGE_POINTER() \u002F\u002F 슬롯 2\n```\n\n감지: `forge debug`에서 스토리지 패널을 관찰합니다. 하나의 매핑에 쓰기가 다른 변수를 변경하면 충돌이 있는 것입니다.\n\n## 디버깅 워크플로 구축\n\nHuff 컨트랙트를 디버깅하는 체계적 접근법:\n\n1. **재현** — 버그를 트리거하는 실패 테스트를 Foundry에서 작성\n2. **트레이스** — `-vvvvv`로 실행하여 전체 옵코드 트레이스 획득\n3. **범위 축소** — 동작이 기대에서 벗어나는 정확한 옵코드 식별\n4. **비교** — Solidity 참조 구현에서 같은 시나리오 실행\n5. **수정** — Huff 매크로를 수정하고 차등 테스트가 통과하는지 확인\n6. **회귀** — 실패 케이스를 영구 테스트 스위트에 추가\n\n```bash\n# 1단계: 실패 테스트 실행\nforge test --match-test test_brokenTransfer -vvvvv\n\n# 2단계: 대화형 디버깅\nforge debug --debug test\u002FToken.t.sol --sig \"test_brokenTransfer()\"\n\n# 3단계: 수정 후 검증\nforge test --match-contract DifferentialTest\nforge snapshot --check\n```\n\n## Tenderly를 사용한 프로덕션 디버깅\n\n이미 배포된 컨트랙트의 경우, Tenderly는 디코딩된 함수 호출, 상태 변경, 가스 사용량이 포함된 실행 트레이스를 보여주는 시각적 디버거를 제공합니다:\n\n```bash\n# 분석을 위한 트랜잭션 내보내기\ncast run TX_HASH --rpc-url $RPC --json > trace.json\n```\n\nTenderly의 시각적 디버거는 각 옵코드에 스택에 대한 효과를 주석으로 달아주므로, 수동으로 스택 상태를 추적하지 않고도 에러를 발견할 수 있어 Huff에 특히 유용합니다.\n\n## 결론\n\nEVM 바이트코드 디버깅은 취미 Huff 개발자와 프로덕션급 개발자를 구분하는 기술입니다. 빠른 트랜잭션 리플레이를 위한 `cast run`, 대화형 분석을 위한 `forge debug`, 프로덕션 인시던트를 위한 수동 트레이스 읽기를 마스터하세요. 체계적 워크플로를 구축하세요: 재현, 트레이스, 범위 축소, 비교, 수정, 회귀. EVM 스택에서 더 낮은 수준으로 갈수록 디버깅 프로세스는 더욱 체계적이어야 합니다.","\u003Ch2 id=\"evm\">저수준 EVM 코드의 디버깅 과제\u003C\u002Fh2>\n\u003Cp>Solidity 트랜잭션이 되돌려지면, 일반적으로 \u003Ccode>ERC20: transfer amount exceeds balance\u003C\u002Fcode>와 같은 설명적 에러 메시지를 받습니다. Huff나 Yul 트랜잭션이 되돌려지면, \u003Ccode>0x\u003C\u002Fcode> — 컨텍스트가 전혀 없는 빈 되돌림 페이로드만 받습니다. 컨트랙트가 단순히 \u003Ccode>REVERT\u003C\u002Fcode> 옵코드에 도달한 것이며, 이유를 파악하는 것은 여러분의 몫입니다.\u003C\u002Fp>\n\u003Cp>바이트코드 수준의 디버깅은 다른 도구와 사고 모델을 필요로 합니다. 스택 머신 관점에서 생각하고, 옵코드별로 메모리와 스토리지 변경을 추적하며, EVM이 \u003Ccode>JUMP\u003C\u002Fcode>과 \u003Ccode>JUMPI\u003C\u002Fcode> 명령을 통해 제어 흐름을 어떻게 실행하는지 이해해야 합니다.\u003C\u002Fp>\n\u003Cp>이 글에서는 필수 디버깅 툴킷을 다룹니다: 과거 트랜잭션 리플레이를 위한 \u003Ccode>cast run\u003C\u002Fcode>, 대화형 단계별 디버깅을 위한 \u003Ccode>forge debug\u003C\u002Fcode>, 그리고 EVM 내부에서 정확히 무슨 일이 일어났는지 이해하기 위한 수동 트레이스 분석.\u003C\u002Fp>\n\u003Ch2 id=\"cast-run\">cast run: 트랜잭션 리플레이\u003C\u002Fh2>\n\u003Cp>\u003Ccode>cast run\u003C\u002Fcode>은 실패한 트랜잭션을 디버깅하는 가장 빠른 방법입니다. 과거 상태에 대해 트랜잭션을 리플레이하고 정확히 무슨 일이 일어났는지 보여줍니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">cast run 0xYOUR_TX_HASH --rpc-url https:\u002F\u002Feth-mainnet.g.alchemy.com\u002Fv2\u002FKEY\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>출력은 호출 깊이, 가스 사용량, 반환 데이터가 포함된 구조화된 트레이스를 보여줍니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>Traces:\n  [328439] 0xContractAddr::transfer(0xRecipient, 1000000000000000000)\n    +- [2604] 0xContractAddr::balanceOf(0xSender) [staticcall]\n    |   +- &lt;- 500000000000000000\n    +- &lt;- revert: EvmError: Revert\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이것은 발신자가 0.5 ETH를 가지고 있지만 1.0 ETH를 전송하려 했기 때문에 전송이 실패했다는 것을 즉시 알려줍니다. Huff 컨트랙트의 경우, 함수 이름이 디코딩되지 않지만(원시 셀렉터로 표시) 호출 구조와 되돌림 지점은 여전히 볼 수 있습니다.\u003C\u002Fp>\n\u003Ch3>원시 셀렉터 디코딩\u003C\u002Fh3>\n\u003Cp>Huff 컨트랙트 작업 시 \u003Ccode>cast run\u003C\u002Fcode>은 원시 함수 셀렉터를 보여줍니다. 수동으로 디코딩합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># balanceOf(address)의 셀렉터 계산\ncast sig \"balanceOf(address)\"\n# 출력: 0x70a08231\n\n# 또는 콜데이터 디코딩\ncast 4byte-decode 0x70a0823100000000000000000000000042069abcdef\n# 출력: balanceOf(address)(0x42069abcdef)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>디버깅 시 컨트랙트의 셀렉터 참조 테이블을 유지합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>0x70a08231 -&gt; balanceOf(address)\n0xa9059cbb -&gt; transfer(address,uint256)\n0x23b872dd -&gt; transferFrom(address,address,uint256)\n0x095ea7b3 -&gt; approve(address,uint256)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"forge-debug\">forge debug: 대화형 단계별 실행\u003C\u002Fh2>\n\u003Cp>\u003Ccode>forge debug\u003C\u002Fcode>는 옵코드별로 EVM 실행을 단계별로 진행하는 TUI(터미널 사용자 인터페이스)를 제공합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">forge debug --debug test\u002FSimpleToken.t.sol \\\n  --sig \"test_transfer()\" -vvvv\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>인터페이스는 네 개의 패널을 보여줍니다:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>옵코드\u003C\u002Fstrong> — 커서가 있는 현재 명령, 실행 중인 바이트코드 표시\u003C\u002Fli>\n\u003Cli>\u003Cstrong>스택\u003C\u002Fstrong> — 모든 32바이트 워드가 포함된 현재 스택 상태\u003C\u002Fli>\n\u003Cli>\u003Cstrong>메모리\u003C\u002Fstrong> — 16진수 원시 메모리 내용\u003C\u002Fli>\n\u003Cli>\u003Cstrong>스토리지\u003C\u002Fstrong> — 실행 중 스토리지 슬롯 변경\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>탐색 키:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>j\u002Fk\u003C\u002Fcode> — 앞으로\u002F뒤로 단계\u003C\u002Fli>\n\u003Cli>\u003Ccode>g\u002FG\u003C\u002Fcode> — 시작\u002F끝으로 점프\u003C\u002Fli>\n\u003Cli>\u003Ccode>c\u003C\u002Fcode> — 다음 호출 경계까지 계속\u003C\u002Fli>\n\u003Cli>\u003Ccode>C\u003C\u002Fcode> — 다음 테스트까지 계속\u003C\u002Fli>\n\u003Cli>\u003Ccode>q\u003C\u002Fcode> — 종료\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>디버깅 중 스택 읽기\u003C\u002Fh3>\n\u003Cp>EVM 스택은 최대 깊이 1024의 후입선출(LIFO)입니다. Huff를 디버깅할 때는 각 옵코드가 무엇을 소비하고 생성하는지 이해하기 위해 스택을 정신적으로 추적해야 합니다.\u003C\u002Fp>\n\u003Cp>이 Huff 스니펫을 봅시다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">0x04 calldataload   \u002F\u002F 스택: [address]\nBALANCES_SLOT       \u002F\u002F 스택: [slot, address]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>calldataload\u003C\u002Fcode> 후 스택에는 주소 매개변수가 있습니다. 스토리지 포인터를 푸시한 후에는 \u003Ccode>[slot, address]\u003C\u002Fcode>가 있습니다. 스택 위치 0에서 잘못된 값을 보면, 스토리지 슬롯이 어떻게 계산되는지에 버그가 있다는 것을 알 수 있습니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">옵코드 트레이스 이해하기\u003C\u002Fh2>\n\u003Cp>프로덕션 디버깅(로컬에서 문제를 재현할 수 없을 때)에서는 아카이브 노드의 원시 옵코드 트레이스가 주요 도구입니다. Tenderly, Etherscan, Alchemy와 같은 서비스가 트레이스 API를 제공합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># cast를 통해 트레이스 가져오기\ncast run TX_HASH --rpc-url $RPC -vvvvv 2&gt;&amp;1 | head -200\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>상세 트레이스 형식은 가스 비용과 스택 상태가 포함된 각 옵코드를 보여줍니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>[0] PUSH1 0x00          gas: 29234  stack: []\n[2] CALLDATALOAD        gas: 29231  stack: [0x00]\n[3] PUSH1 0xe0          gas: 29228  stack: [0xa9059cbb...]\n[5] SHR                 gas: 29225  stack: [0xa9059cbb..., 0xe0]\n[6] DUP1                gas: 29222  stack: [0xa9059cbb]\n[7] PUSH4 0x70a08231    gas: 29219  stack: [0xa9059cbb, 0xa9059cbb]\n[12] EQ                 gas: 29216  stack: [0xa9059cbb, 0xa9059cbb, 0x70a08231]\n[13] PUSH2 0x0040       gas: 29213  stack: [0xa9059cbb, 0x00]\n[16] JUMPI              gas: 29210  stack: [0xa9059cbb, 0x00, 0x0040]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이 트레이스는 함수 디스패처가 셀렉터가 \u003Ccode>balanceOf(address)\u003C\u002Fcode>와 일치하는지 확인하는 것을 보여줍니다. 실제 셀렉터가 \u003Ccode>0xa9059cbb\u003C\u002Fcode>(transfer)이므로 \u003Ccode>EQ\u003C\u002Fcode>는 \u003Ccode>0x00\u003C\u002Fcode>(거짓)을 생성하고 \u003Ccode>JUMPI\u003C\u002Fcode>는 점프하지 않습니다.\u003C\u002Fp>\n\u003Ch2 id=\"huff-yul\">Huff와 Yul을 위한 일반적인 디버깅 패턴\u003C\u002Fh2>\n\u003Ch3>패턴 1: 스택 언더플로\u003C\u002Fh3>\n\u003Cp>저렴해 보이는 옵코드에서 가스 부족 에러로 실행이 되돌려지면, 스택 언더플로일 가능성이 높습니다. EVM에는 전용 “스택 언더플로” 에러가 없으며 — 모든 가스를 소비할 뿐입니다.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">\u002F\u002F 버그: 스택이 비어 있을 때 pop\n#define macro BROKEN() = takes (0) returns (0) {\n    pop  \u002F\u002F 스택 언더플로! 팝할 항목이 없음\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>감지: \u003Ccode>forge debug\u003C\u002Fcode>에서 스택 패널을 관찰합니다. 소비 옵코드 전에 0개 항목이 표시되면 그것이 버그입니다.\u003C\u002Fp>\n\u003Ch3>패턴 2: 잘못된 JUMP 목적지\u003C\u002Fh3>\n\u003Cp>Huff는 점프 목적지에 레이블을 사용합니다. 레이블이 \u003Ccode>JUMPDEST\u003C\u002Fcode>가 아닌 옵코드로 해결되면 트랜잭션이 되돌려집니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">#define macro MAIN() = takes (0) returns (0) {\n    0x01 success jumpi\n    0x00 0x00 revert\n    success:                    \u002F\u002F JUMPDEST여야 함\n        0x00 0x00 return\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>감지: 트레이스에서 \u003Ccode>JUMP\u003C\u002Fcode> 또는 \u003Ccode>JUMPI\u003C\u002Fcode> 다음에 즉시 가스가 소진되는 것을 찾습니다. 대상 PC는 점프 전 스택 상단에 있습니다.\u003C\u002Fp>\n\u003Ch3>패턴 3: 잘못된 ABI 인코딩\u003C\u002Fh3>\n\u003Cp>Huff는 반환 값을 자동 인코딩하지 않습니다. 적절한 ABI 인코딩 없이 원시 바이트를 반환하면, 호출하는 컨트랙트의 디코더가 되돌려집니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">\u002F\u002F 잘못됨: 오프셋 없이 원시 uint256 반환\n0x00 mstore\n0x20 0x00 return\n\n\u002F\u002F 동적 타입에 올바른 방법: 오프셋 포함\n0x20 0x00 mstore     \u002F\u002F 오프셋\n0x05 0x20 mstore     \u002F\u002F 길이\n\u002F\u002F ... 0x40의 데이터\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>감지: 호출하는 컨트랙트의 \u003Ccode>abi.decode\u003C\u002Fcode>가 되돌려집니다. 트레이스에서 컨트랙트의 성공적인 반환 후 부모 컨텍스트에서 되돌림이 나타납니다.\u003C\u002Fp>\n\u003Ch3>패턴 4: 스토리지 충돌\u003C\u002Fh3>\n\u003Cp>Huff는 \u003Ccode>FREE_STORAGE_POINTER()\u003C\u002Fcode>를 사용하여 스토리지 슬롯을 할당합니다. 두 매크로가 실수로 같은 슬롯을 사용하면 서로를 덮어씁니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">#define constant BALANCES_SLOT = FREE_STORAGE_POINTER()  \u002F\u002F 슬롯 0\n#define constant ALLOWANCES_SLOT = FREE_STORAGE_POINTER() \u002F\u002F 슬롯 1\n#define constant TOTAL_SUPPLY_SLOT = FREE_STORAGE_POINTER() \u002F\u002F 슬롯 2\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>감지: \u003Ccode>forge debug\u003C\u002Fcode>에서 스토리지 패널을 관찰합니다. 하나의 매핑에 쓰기가 다른 변수를 변경하면 충돌이 있는 것입니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">디버깅 워크플로 구축\u003C\u002Fh2>\n\u003Cp>Huff 컨트랙트를 디버깅하는 체계적 접근법:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>재현\u003C\u002Fstrong> — 버그를 트리거하는 실패 테스트를 Foundry에서 작성\u003C\u002Fli>\n\u003Cli>\u003Cstrong>트레이스\u003C\u002Fstrong> — \u003Ccode>-vvvvv\u003C\u002Fcode>로 실행하여 전체 옵코드 트레이스 획득\u003C\u002Fli>\n\u003Cli>\u003Cstrong>범위 축소\u003C\u002Fstrong> — 동작이 기대에서 벗어나는 정확한 옵코드 식별\u003C\u002Fli>\n\u003Cli>\u003Cstrong>비교\u003C\u002Fstrong> — Solidity 참조 구현에서 같은 시나리오 실행\u003C\u002Fli>\n\u003Cli>\u003Cstrong>수정\u003C\u002Fstrong> — Huff 매크로를 수정하고 차등 테스트가 통과하는지 확인\u003C\u002Fli>\n\u003Cli>\u003Cstrong>회귀\u003C\u002Fstrong> — 실패 케이스를 영구 테스트 스위트에 추가\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 1단계: 실패 테스트 실행\nforge test --match-test test_brokenTransfer -vvvvv\n\n# 2단계: 대화형 디버깅\nforge debug --debug test\u002FToken.t.sol --sig \"test_brokenTransfer()\"\n\n# 3단계: 수정 후 검증\nforge test --match-contract DifferentialTest\nforge snapshot --check\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"tenderly\">Tenderly를 사용한 프로덕션 디버깅\u003C\u002Fh2>\n\u003Cp>이미 배포된 컨트랙트의 경우, Tenderly는 디코딩된 함수 호출, 상태 변경, 가스 사용량이 포함된 실행 트레이스를 보여주는 시각적 디버거를 제공합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 분석을 위한 트랜잭션 내보내기\ncast run TX_HASH --rpc-url $RPC --json &gt; trace.json\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Tenderly의 시각적 디버거는 각 옵코드에 스택에 대한 효과를 주석으로 달아주므로, 수동으로 스택 상태를 추적하지 않고도 에러를 발견할 수 있어 Huff에 특히 유용합니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">결론\u003C\u002Fh2>\n\u003Cp>EVM 바이트코드 디버깅은 취미 Huff 개발자와 프로덕션급 개발자를 구분하는 기술입니다. 빠른 트랜잭션 리플레이를 위한 \u003Ccode>cast run\u003C\u002Fcode>, 대화형 분석을 위한 \u003Ccode>forge debug\u003C\u002Fcode>, 프로덕션 인시던트를 위한 수동 트레이스 읽기를 마스터하세요. 체계적 워크플로를 구축하세요: 재현, 트레이스, 범위 축소, 비교, 수정, 회귀. EVM 스택에서 더 낮은 수준으로 갈수록 디버깅 프로세스는 더욱 체계적이어야 합니다.\u003C\u002Fp>\n","ko","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:28.253235Z","EVM 바이트코드 디버깅 — 트레이스, 스택 덤프, cast run","cast run, forge debug, 원시 옵코드 트레이스 분석을 활용한 Huff 및 Yul 스마트 컨트랙트의 EVM 바이트코드 디버깅 마스터.","EVM 바이트코드 디버깅",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000021","Foundry","foundry",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000017","Huff","huff","블록체인",[37,43,49],{"id":38,"title":39,"slug":40,"excerpt":41,"locale":12,"category_name":35,"published_at":42},"d0000000-0000-0000-0000-000000000605","Ethereum 상호운용성 레이어: 55개 이상의 L2가 하나의 체인이 되는 방법","ethereum-sangho-unyongseong-layer-55-l2-hana-chain","Ethereum에는 55개 이상의 Layer 2 롤업이 있어 유동성과 사용자 경험이 파편화되어 있습니다. Ethereum 상호운용성 레이어 — 크로스 롤업 메시징, 공유 시퀀서, 베이스드 롤업을 결합하여 — 하나의 조합 가능한 네트워크로 통합하는 것을 목표로 합니다.","2026-03-28T10:44:44.895917Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":35,"published_at":48},"d0000000-0000-0000-0000-000000000604","롤업을 넘어선 ZK 증명: Ethereum에서의 검증 가능한 AI 추론","rolleob-eul-neomeo-zk-jeungmyeong-ethereum-geomjeung-ai-churon","영지식 증명은 더 이상 단순한 스케일링 도구가 아닙니다. 2026년, zkML은 온체인에서 검증 가능한 AI 추론을 가능하게 하고, ZK 코프로세서는 무거운 연산을 오프체인으로 이동시키며 온체인에서 검증하고, SP1과 Jolt 같은 새로운 증명 시스템이 이를 실용적으로 만들고 있습니다.","2026-03-28T10:44:44.890168Z",{"id":50,"title":51,"slug":52,"excerpt":53,"locale":12,"category_name":35,"published_at":54},"d0000000-0000-0000-0000-000000000581","EIP-7702 실전 가이드: Pectra 이후 스마트 계정 플로우 구축","eip-7702-siljeon-gaideu-pectra-ihu-seumateu-gyejeong-peulro-guchuk","EIP-7702는 모든 Ethereum EOA가 단일 트랜잭션 내에서 스마트 컨트랙트로 임시 동작할 수 있게 합니다. 새로운 계정 추상화 프리미티브를 사용한 배치 트랜잭션, 가스 후원, 소셜 리커버리 구현 방법을 소개합니다.","2026-03-28T10:44:43.377765Z",{"id":13,"name":56,"slug":57,"bio":58,"photo_url":19,"linkedin":19,"role":59,"created_at":60,"updated_at":60},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]