위임 호출(delegatecall) 프록시에서는 구현 코드가 프록시의 저장소를 읽고 씁니다. 그래서 새 구현의 변수 배치(layout)는 이전 구현과 바이트 단위까지 맞아야 합니다. 기존 변수 앞에 새 변수를 끼워 넣거나 순서를 바꾸고, 타입이나 구조체 멤버, 상속 순서를 고치면 같은 슬롯과 오프셋을 다른 의미로 읽어 잔액, 소유자, 매핑 기준 위치가 망가질 수 있습니다. 안전한 기본값은 기존 선언을 손대지 않고 맨 끝에 새 변수를 붙이는 것입니다. 그리고 컴파일러의 storageLayout JSON에서 이름표만이 아니라 슬롯, 오프셋, 타입 인코딩까지 이전 빌드와 비교합니다.
위임 호출은 구현의 배치로 프록시 값을 읽습니다
프록시의 슬롯 0에 소유자 주소가, 슬롯 1에 잔액 매핑의 기준 위치가 있다고 가정하겠습니다. 새 구현 계약이 슬롯 0에 일시 정지 플래그를 추가하면, 기존 소유자 주소의 하위 바이트를 참거짓 값으로 읽고 새 소유자 변수는 다음 슬롯을 읽게 됩니다. 함수 선택자와 ABI가 그대로여도 권한 확인과 자산 장부가 곧바로 어긋납니다.
솔리디티는 값 타입을 32바이트 슬롯 안에서 낮은 자리부터 채워 넣습니다. 작은 타입 하나를 중간에 넣으면 뒤에 오는 변수의 오프셋이 밀릴 수 있습니다. 구조체와 배열은 새 슬롯에서 시작하고, 매핑과 동적 배열은 선언된 슬롯을 기준으로 Keccak 위치를 계산합니다. 따라서 기준 슬롯이 바뀌면 모든 원소의 주소가 달라집니다.
업그레이드 호환성은 변수 이름이 남아 있는지의 문제가 아닙니다. 기존의 모든 바이트를 새 코드가 같은 의미로 읽는지의 문제입니다.
JOBCOIN 해설

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.
- 위임 호출은 구현의 배치로 프록시 값을 읽습니다
프록시의 슬롯 0에 소유자 주소가, 슬롯 1에 잔액 매핑의 기준 위치가 있다고 가정하겠습니다.
- 변경 유형마다 위험을 나눠 봅니다
맨 끝에 uint256 변수를 새로 붙이는 변경은 대개 기존 슬롯을 지킵니다.
- 컴파일러 JSON을 바이트 위치 기준으로 맞춰 봅니다
솔리디티 표준 JSON의 저장소 배치 출력은 항목마다 이름표, 슬롯, 오프셋, 타입 ID를 알려 주고, 타입 정보에는 인코딩과 numberOfBytes, 구조체 멤버가 들어 있습니다.
설명을 위한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.
변경 유형마다 위험을 나눠 봅니다
맨 끝에 uint256 변수를 새로 붙이는 변경은 대개 기존 슬롯을 지킵니다. 하지만 저장소 여백(storage gap)을 쓰거나 상속과 사용자 정의 배치가 얽혀 있다면 실제 컴파일러 출력을 확인해야 합니다. 기존 uint128을 uint256으로 넓히면 같은 슬롯의 이웃 값을 덮을 수 있고, 열거형이나 구조체 정의를 바꾸는 것도 타입 인코딩을 바꿉니다.
부모 계약에 변수를 더하거나 상속 순서를 바꾸면 C3 선형화 규칙에 따라 전체 배치가 움직일 수 있습니다. 상수(constant)와 불변(immutable) 변수는 일반 저장소 배치에 같은 방식으로 들어가지 않지만, 새 구현 바이트코드에 담긴 값과 초기화 의미는 따로 검토합니다.
| 변경 | 기존 슬롯 영향 | 판정 |
|---|---|---|
| 맨 끝에 변수 추가 | 대개 유지 | 배치 비교 필요 |
| 중간 삽입·순서 변경 | 뒤쪽 슬롯·오프셋 이동 | 금지 |
| 기존 타입 변경 | 폭·인코딩 변경 | 금지 |
| 구조체 멤버 변경 | 내부 오프셋 이동 | 기존 구조체 위험 |
| 부모 계약 변경 | 상속 전체 배치 변화 | 전체 비교 |
컴파일러 JSON을 바이트 위치 기준으로 맞춰 봅니다
솔리디티 표준 JSON의 저장소 배치 출력은 항목마다 이름표, 슬롯, 오프셋, 타입 ID를 알려 주고, 타입 정보에는 인코딩과 numberOfBytes, 구조체 멤버가 들어 있습니다. 숫자처럼 보이는 슬롯 값도 아주 클 수 있으니 문자열을 함부로 자바스크립트 Number로 바꾸지 않습니다.
비교 도구는 변수명이 바뀐 경우도 같은 슬롯의 의미가 바뀐 것으로 보고 경고해야 하며, 매핑의 키·값, 배열 기준 위치, 구조체 멤버까지 재귀적으로 비교해야 합니다. 이 JSON 형식은 아직 실험 단계로 표시돼 있으므로 컴파일러 버전을 고정하고, 도구 파서의 회귀 테스트를 계속 돌립니다.
- 이전과 새 컴파일러, 최적화 설정을 고정합니다.
- 저장소 배치 JSON을 빌드 산출물로 보관합니다.
- 슬롯·오프셋·인코딩·바이트 폭을 재귀적으로 비교합니다.
- 프록시의 실제 상태를 포크해 업그레이드 거래를 돌려 봅니다.
- 핵심 잔액·소유자·허용량 불변 조건을 전후로 검산합니다.
여백 배열과 네임스페이스도 저절로 지켜 주지는 않습니다
상속해서 쓰는 업그레이드용 부모 계약은 예약용 여백 배열을 두고, 나중에 추가할 변수를 그 공간에 배치할 수 있습니다. 새 변수 크기만큼 여백을 정확히 줄여야 뒤따르는 슬롯이 모두 제자리를 지킵니다. 여백을 없애거나 잘못 계산하면 충돌이 납니다. 사용하는 도구가 여백 규칙을 알아보는지 확인합니다.
네임스페이스 저장소는 고유한 이름 공간에서 계산한 기준 슬롯 아래에 구조체를 두어 모듈끼리의 충돌을 줄입니다. 그러나 네임스페이스를 다시 쓰거나 구조체 멤버 순서를 바꾸거나 어셈블리에서 슬롯을 잘못 지정하면 여전히 상태가 깨집니다. 패턴 이름보다 계산된 슬롯과 배치 비교 결과가 증거입니다.
되돌리기보다 사전 시뮬레이션이 중요합니다
잘못된 구현이 한 번이라도 위임 호출로 실행돼 저장소를 덮어쓰면, 이전 구현으로 되돌려도 원래 바이트가 자동 복구되지 않습니다. 업그레이드 권한 관리와 타임록은 악의적인 변경 기회를 줄여 주지만, 배치 실수까지 고쳐 주지는 않습니다.
메인넷 상태를 포크한 환경에서 업그레이드 전후의 조회 함수 값만 비교하지 말고, 원시 저장소와 프로토콜 불변 조건, 과거 사용자 포지션을 표본으로 검사합니다. 업그레이드 호출과 초기화 함수가 한꺼번에 실행된다면 중간 상태와 권한, 재진입 가능성도 검사 범위에 넣습니다.
자주 묻는 질문
변수 이름만 바꿔도 배치가 깨지나요?
슬롯·오프셋·타입이 같다면 바이트 배치는 그대로일 수 있습니다. 다만 도구와 ABI, 운영상의 의미가 달라지므로 이름 변경을 명시적으로 검토해야 합니다.
새 변수는 맨 끝에만 넣으면 언제나 안전한가요?
상속 구조, 여백 배열, 네임스페이스, 컴파일러 설정에 따라 결과가 달라지니 실제 배치 비교가 필요합니다.
업그레이드를 되돌리면 망가진 상태도 돌아오나요?
아닙니다. 이미 잘못 써 버린 저장소는 별도의 복구 거래 없이는 원래대로 돌아오지 않습니다.
더 깊이 읽기
본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.



