궁금한 주제를 찾아보세요

비트코인, 스테이블코인, 온체인 데이터처럼 주제로 검색하세요.

다시 읽을 이야기

저장한 글은 이 브라우저에만 보관됩니다.

해설기술·생태계

업그레이드 계약에서 storage layout 충돌이 치명적인 이유

프록시 storage를 새 구현이 slot·offset·type 기준으로 다르게 해석할 때 관리자·잔액이 손상되는 원리와 안전한 append 절차를 설명합니다.

난이도 중급주제 카테고리와 개념 밀도 기준검토 정보AI 보조 초안 · 출처 목록 제공 · 주장별 대조 진행 중
계약 변경 전후 저장 칸의 물건이 어긋난 화살표로 연결되는 storage layout 충돌 그림
주제의 이해를 돕기 위해 imagegen으로 제작한 AI 생성 개념 일러스트
먼저 읽는 핵심

프록시 상태는 주소가 아니라 slot·offset·type 배치에 의존합니다.
재정렬·삭제·type 변경과 base contract 추가는 기존 값을 오독할 수 있습니다.
컴파일러 layout diff와 fork 상태 upgrade simulation을 함께 수행해야 합니다.

Delegatecall 프록시는 구현 코드가 프록시 storage를 읽고 쓰므로 새 구현의 변수 layout이 이전과 byte 단위로 호환돼야 합니다. 기존 변수 앞에 새 변수를 넣거나 순서를 바꾸고, type·struct member·상속 순서를 변경하면 같은 slot과 offset을 다른 의미로 읽어 잔액·owner·mapping base가 손상될 수 있습니다. 안전한 기본은 기존 선언을 그대로 두고 끝에 새 변수를 추가하며 compiler의 storageLayout JSON에서 label뿐 아니라 slot, offset, type encoding을 이전 build와 비교하는 것입니다.

Delegatecall은 구현의 layout으로 프록시 값을 읽습니다

Proxy slot 0에 owner, slot 1에 balances mapping base가 있다고 하겠습니다. 새 implementation이 slot 0에 paused를 추가하면 기존 owner address의 하위 byte를 bool로 읽고 새 owner는 다음 slot을 읽습니다. 함수 selector와 ABI가 같아도 authorization과 자산 장부가 즉시 달라집니다.

Solidity는 value types를 32바이트 slot 안 낮은 order부터 pack합니다. 작은 type 하나를 중간에 넣으면 뒤 변수의 offset이 이동할 수 있습니다. Struct와 array는 새 slot에서 시작하고 mapping·dynamic array는 선언 slot을 base로 Keccak 위치를 계산하므로 base slot 변화가 모든 원소 주소를 바꿉니다.

업그레이드 호환성은 변수 이름이 남았는지가 아니라 기존 모든 byte를 새 코드가 같은 의미로 읽는지의 문제입니다.

JOBCOIN 해설

VISUAL GUIDE프로토콜 실행의 기본 구조
입력이 실행 규칙을 통과할 때 상태가 바뀌는 프로토콜 개념도
그림과 함께 짚어볼 본문 내용

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.

  1. Delegatecall은 구현의 layout으로 프록시 값을 읽습니다

    Proxy slot 0에 owner, slot 1에 balances mapping base가 있다고 하겠습니다.

  2. 변경 유형별 위험을 구분합니다

    끝에 새 uint256을 추가하는 변경은 보통 기존 slot을 유지하지만 storage gap 사용, inheritance와 custom layout이 있으면 실제 compiler output을 확인해야 합니다.

  3. Compiler JSON을 byte 위치 기준으로 비교합니다

    Solidity standard JSON의 storage layout output은 각 항목의 label, slot, offset과 type id를 제공하고 types에는 encoding과 numberOfBytes, struct members가 있습니다.

AI로 제작한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.

변경 유형별 위험을 구분합니다

끝에 새 uint256을 추가하는 변경은 보통 기존 slot을 유지하지만 storage gap 사용, inheritance와 custom layout이 있으면 실제 compiler output을 확인해야 합니다. 기존 uint128을 uint256으로 넓히면 같은 slot의 이웃 값을 덮을 수 있고 enum·struct 정의 변경도 type encoding을 바꿉니다.

Base contract의 변수 추가나 상속 순서 변경은 C3 linearization에 따른 전체 배치를 움직일 수 있습니다. Constant와 immutable은 일반 storage layout에 같은 방식으로 들어가지 않지만 새 구현 bytecode의 값과 초기화 의미를 별도로 검토합니다.

Storage 변경과 일반 위험
변경 기존 slot 영향 판정
끝에 변수 추가 대개 유지 layout diff 필요
중간 삽입·재정렬 뒤 slot·offset 이동 금지
기존 type 변경 폭·encoding 변경 금지
struct member 변경 내부 offset 이동 기존 struct 위험
base contract 변경 상속 전체 배치 변화 전체 diff

Compiler JSON을 byte 위치 기준으로 비교합니다

Solidity standard JSON의 storage layout output은 각 항목의 label, slot, offset과 type id를 제공하고 types에는 encoding과 numberOfBytes, struct members가 있습니다. 숫자처럼 보이는 slot도 큰 값이 가능하므로 문자열을 임의 JavaScript Number로 바꾸지 않습니다.

비교기는 변수명이 바뀐 경우도 같은 slot의 의미 변경으로 경고하고 mapping key·value, array base와 struct member를 재귀 비교해야 합니다. 이 JSON 형식 자체는 experimental 표시가 있으므로 compiler version을 고정하고 tool parser 회귀 테스트를 유지합니다.

  • 이전·새 compiler와 optimizer 설정을 고정합니다.
  • Storage layout JSON을 artifact로 저장합니다.
  • Slot·offset·encoding·byte width를 재귀 비교합니다.
  • Proxy 실제 state를 fork해 upgrade transaction을 실행합니다.
  • 핵심 잔액·owner·allowance invariant를 전후 검산합니다.

Gap과 namespace도 자동 안전장치는 아닙니다

상속 가능한 upgradeable base는 예약 배열 gap을 두고 이후 변수를 그 공간에 배치할 수 있습니다. 새 변수 크기만큼 gap을 정확히 줄여 전체 후속 slot을 유지해야 하며 gap을 없애거나 잘못 계산하면 충돌합니다. Tool이 gap 규칙을 인식하는지 확인합니다.

Namespaced storage는 고유 namespace에서 계산한 base slot 아래 struct를 두어 모듈 충돌을 줄일 수 있습니다. 그러나 namespace 재사용, struct member 재정렬과 잘못된 assembly slot은 여전히 상태를 깨뜨립니다. Pattern 이름보다 계산된 slot과 layout diff가 증거입니다.

Rollback보다 사전 simulation이 중요합니다

잘못된 implementation이 한번 delegatecall되어 storage를 덮으면 이전 implementation으로 되돌려도 원래 bytes가 자동 복구되지 않습니다. Upgrade 권한과 timelock은 악성 변경 기회를 줄이지만 layout 실수를 치료하지 않습니다.

Mainnet state fork에서 upgrade 전후 getter만 비교하지 말고 raw storage와 protocol invariant, 과거 user positions를 표본 검사합니다. Upgrade call과 initializer가 한번에 실행되는 경우 중간 상태·권한과 reentrancy도 포함합니다.

자주 묻는 질문

변수 이름만 바꾸면 layout이 깨지나요?

Slot·offset·type이 같으면 bytes 배치는 유지될 수 있지만 tool·ABI와 운영 의미가 바뀌므로 명시적 rename 검토가 필요합니다.

새 변수는 항상 맨 끝에 넣으면 안전한가요?

상속·gap·namespace와 compiler 설정에 따라 달라 실제 layout diff가 필요합니다.

Upgrade를 되돌리면 손상된 상태도 돌아오나요?

아닙니다. 이미 잘못 쓴 storage는 별도 복구 transaction 없이는 원복되지 않습니다.

더 깊이 읽기

본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.

참고한 원문 자료

자료 확인 기준일 2026.09.27
  1. Solidity Documentation — Layout of State Variables in Storagedocs.soliditylang.org
  2. OpenZeppelin Upgrades — Writing Upgradeable Contractsdocs.openzeppelin.com
자료 대조 기록과 확인 범위
출처 수집
원문 링크 2개 제공
핵심 주장 대조
완료 근거가 아직 기록되지 않았습니다.
분야 전문가 검수
별도 완료 기록이 없습니다.

원문 링크와 자료 확인일은 글 전체의 주장 대조나 전문가 검수 완료를 뜻하지 않습니다. 별도 확인이 필요한 절차는 원문의 적용 대상과 최신 안내를 함께 확인해 주세요.

AI 활용 안내

이 글은 초안 구성과 자료 정리에 AI를 활용했습니다. 글에 표시된 출처와 기준일을 함께 확인해 주세요. 별도 검토 정보가 없다면 전문가 검수를 뜻하지 않습니다.

이해를 위한 정보 콘텐츠

이 글은 특정 자산의 매수·매도 또는 수익을 권유하지 않습니다. 자료의 발표 시점과 이후 변경 사항을 함께 확인해 주세요.

편집 원칙 보기 →이 기사 정정 제보 →