Implementation의 constructor는 implementation 배포 때 implementation 자신의 storage에서만 실행되며 proxy storage를 초기화하지 않습니다. Proxy가 implementation code를 delegatecall할 때 owner·version 같은 proxy 상태를 한번 설정하려면 일반 함수 형태의 initializer가 필요합니다. 하지만 initializer는 constructor와 달리 호출 가능한 함수이므로 initialized flag와 권한을 원자적으로 설정하고 proxy 배포·upgrade transaction에서 즉시 실행해야 합니다. Implementation 자체도 초기화를 잠가 제3자가 ownership을 가져가는 경로를 줄여야 합니다.
두 주소의 storage를 구분합니다
Implementation I를 배포하며 constructor가 owner=deployer를 쓰면 I slot에 값이 저장됩니다. Proxy P가 I에 delegatecall해 함수를 실행할 때 SLOAD는 P slot을 읽으므로 P owner는 여전히 zero입니다. Constructor bytecode는 runtime code에 포함되지 않아 proxy가 나중에 재실행할 수도 없습니다.
Initializer는 I runtime code의 public 또는 external 함수지만 P를 통해 delegatecall하면 P storage를 씁니다. msg.sender도 proxy 호출자 기준으로 보존됩니다. P의 배포 transaction에서 initializer calldata까지 실행하면 주소가 공개된 뒤 누군가 먼저 initialize하는 창을 줄일 수 있습니다.
Initializer는 constructor의 이름을 바꾼 함수가 아니라, proxy storage에서 constructor 효과를 한 번 재현하는 권한 있는 상태 전환입니다.
JOBCOIN 해설

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.
- 두 주소의 storage를 구분합니다
Implementation I를 배포하며 constructor가 owner=deployer를 쓰면 I slot에 값이 저장됩니다.
- 미초기화와 재초기화 위험을 나눕니다
P가 배포됐지만 initialize가 별도 transaction으로 남으면 공격자가 먼저 owner를 자기 주소로 설정할 수 있습니다.
- 상속 초기화는 compiler가 자동 호출하지 않습니다
일반 constructor에서는 compiler가 base constructors 호출을 관리하지만 initializer 함수는 개발자가 parent initializers를 호출해야 합니다.
AI로 제작한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.
미초기화와 재초기화 위험을 나눕니다
P가 배포됐지만 initialize가 별도 transaction으로 남으면 공격자가 먼저 owner를 자기 주소로 설정할 수 있습니다. 반대로 initialized guard가 없으면 정상 초기화 뒤 다시 호출해 owner·token·oracle을 바꿀 수 있습니다. Guard flag 자체의 storage layout도 upgrade에서 유지돼야 합니다.
새 version에서 추가 모듈을 초기화하려면 versioned reinitializer가 필요할 수 있습니다. 이전 version을 다시 실행할 수 없어야 하고 새 initializer가 기존 parent 초기화를 중복 호출하지 않아야 합니다. ‘한 번만’과 ‘version당 한 번’을 구분합니다.
| 실행 | Storage 문맥 | 주요 위험 |
|---|---|---|
| I constructor | Implementation | Proxy 미초기화 |
| P→I initializer | Proxy | 선점·재호출 |
| P constructor delegatecall | Proxy | calldata·실패 처리 |
| Version reinitializer | Proxy | 순서·중복 parent call |
| I 직접 initializer | Implementation | 구현 소유권 탈취 |
상속 초기화는 compiler가 자동 호출하지 않습니다
일반 constructor에서는 compiler가 base constructors 호출을 관리하지만 initializer 함수는 개발자가 parent initializers를 호출해야 합니다. 다중 상속에서 같은 parent를 두 번 호출하거나 빠뜨리면 상태가 중복 설정되거나 zero로 남습니다. Linearization 순서와 각 parent의 onlyInitializing 보호를 확인합니다.
Library의 upgradeable variant는 constructor 대신 __Module_init 같은 함수를 제공할 수 있습니다. 일반 contract package와 upgradeable package를 섞으면 immutable·constructor 전제가 남을 수 있어 실제 source와 storage layout을 확인합니다.
- Proxy 배포와 initializer를 한 transaction으로 묶습니다.
- Initialized version flag의 slot을 보존합니다.
- 모든 parent initializer와 호출 순서를 표로 만듭니다.
- Implementation 배포 직후 초기화를 disable합니다.
- Fork에서 재호출·선점·upgrade 초기화를 공격 계정으로 시험합니다.
Implementation 잠금도 proxy 권한을 대신하지 않습니다
Implementation contract의 initializer를 disable하면 누군가 I 자체의 owner가 되는 일을 막을 수 있습니다. 하지만 Proxy의 admin·UUPS upgrade authorization이 안전해지는 것은 아닙니다. 두 주소의 ownership과 callable functions를 따로 검사합니다.
Constructor에서 immutable을 설정하면 값은 implementation runtime code에 들어가 모든 proxies가 공유할 수 있습니다. Proxy별 설정이 필요한 값을 immutable로 두면 tenant 분리가 깨집니다. Constant·immutable과 proxy storage state를 설계 단계에서 분류합니다.
초기화 완료 증거를 네 단계로 남깁니다
Deployment receipt success만으로 owner·domain separator·roles가 올바르다고 결론 내리지 않습니다. Proxy address에서 getters와 raw slots를 읽고 initializer event, implementation slot과 admin 권한을 함께 확인합니다.
Upgrade 시에는 old state가 유지되는지, 새 fields만 설정되는지, initializer가 revert하면 implementation 전환도 원자적으로 revert되는지 테스트합니다. Separate upgrade와 init을 허용하면 중간 상태에서 호출 가능한 함수와 권한을 threat model에 넣습니다.
자주 묻는 질문
Implementation constructor에서 owner를 설정하면 proxy owner도 설정되나요?
아닙니다. Constructor는 implementation storage에 쓰며 proxy는 별도 initializer가 필요합니다.
Initializer를 internal로 만들면 되나요?
외부 entry initializer가 보호된 internal parent initializers를 호출하는 구조가 일반적입니다. 배포 흐름에 맞는 진입점은 필요합니다.
Implementation 초기화 잠금만 하면 충분한가요?
아닙니다. Proxy 초기화와 upgrade authorization, storage layout을 별도로 보호해야 합니다.
더 깊이 읽기
본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.



