이더리움 CREATE2 완전 정리: 결정론적 컨트랙트 주소 원리
이더리움 CREATE2가 팩토리·솔트·초기화 코드로 컨트랙트 주소를 미리 계산하는 원리와 Solidity 예제, 활용법, 보안 점검을 설명해요.

컨트랙트가 아직 없는데도 그 주소부터 알아야 하는 상황이 있어요. 지갑 서비스는 배포 전 스마트 계정 주소를 보여주고 싶을 수 있고, 멀티체인 앱은 여러 네트워크에 예측 가능한 인프라를 놓아야 할 수 있습니다. 이더리움 CREATE2는 이를 가능하게 해요. 다만 주소가 예측된다는 사실만으로 미래 코드의 안전성까지 보장하지는 않습니다. 그래서 블록체인 기초에서 계산 원리와 검증법을 함께 알아둘 필요가 있어요.
이 글은 프로토콜 사양과 Solidity 공식 문서를 기준으로 주소 계산식, 구현법, 활용 사례와 실패 지점을 설명합니다. CREATE2는 유용한 배포 도구이지 안전 인증서가 아니에요.
CREATE2는 무엇을 바꾸나요?
일반 CREATE는 생성자 주소와 논스(Nonce)로 새 컨트랙트 주소를 구합니다. 번호표를 순서대로 뽑는 창구와 비슷해요. 생성자가 앞에서 컨트랙트를 몇 개 만들었는지에 따라 다음 번호가 달라집니다. 따라서 일반 배포에서는 이더리움 계정 논스가 주소에 영향을 줘요.
EIP-1014가 도입한 CREATE2는 이 순서 의존성을 명시적인 재료로 바꿉니다. 팩토리(Factory), 솔트(Salt), 설치할 패키지를 넣으면 사물함 번호가 정해지는 예약 보관함을 떠올려 보세요. 실제 설치 전에도 번호를 계산할 수 있습니다.
주소 계산식은 다음과 같아요.
address = last20bytes(
keccak256(0xff ++ deployer ++ salt ++ keccak256(init_code))
)0xff는 1바이트 구분자입니다.deployer는CREATE2를 실행하는 20바이트 주소이며 보통 팩토리 컨트랙트예요.salt는 배포 방식이 선택한 32바이트 값입니다.init_code는 실행되어 런타임 바이트코드를 만드는 초기화 코드예요.
최종 해시의 뒤 20바이트가 주소가 됩니다. 체인 ID는 공식 계산식의 입력값이 아니에요. 하지만 여러 EVM 체인에서 같은 주소를 얻으려면 팩토리 주소, 솔트, 초기화 코드 해시가 모두 같아야 합니다.
초기화 코드와 런타임 코드는 달라요
이 구분을 놓치면 주소 예측이 자주 틀립니다. 초기화 코드(Init Code)는 컨트랙트 생성 바이트코드와 인코딩된 생성자 인자(Constructor Arguments)를 포함해요. EVM이 이를 한 번 실행하고, 그 결과로 나온 런타임 코드를 새 계정에 저장합니다.
생성자 인자 하나가 바뀌어도 초기화 코드 해시와 주소가 달라져요. 컴파일러 버전, 최적화 설정, 연결된 라이브러리 주소, 소스 메타데이터의 변화도 생성 바이트코드를 바꿀 수 있습니다. 동작이 비슷한 컨트랙트라도 초기화 코드가 한 바이트 다르면 주소가 달라질 수 있어요.
반대 방향의 주의점도 있습니다. Solidity 공식 문서는 생성자가 외부 상태를 읽을 수 있다고 설명해요. 별도의 배포에서 같은 생성 바이트코드를 사용해도 생성자가 관찰한 상태에 따라 최종 런타임 코드가 달라질 수 있습니다. 주소는 초기화 코드에 묶이지만 생성자가 읽는 모든 환경까지 자동 고정하지는 않아요.
Solidity에서 CREATE2를 어떻게 쓰나요?
Solidity는 new Contract{salt: salt}(arguments) 문법으로 솔트 기반 생성을 지원합니다. 아래 팩토리는 같은 주소를 예측하고 배포해요.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Vault {
address public immutable owner;
constructor(address owner_) {
owner = owner_;
}
}
contract VaultFactory {
function predict(bytes32 salt, address owner)
public
view
returns (address)
{
bytes memory initCode = abi.encodePacked(
type(Vault).creationCode,
abi.encode(owner)
);
bytes32 digest = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(initCode)
)
);
return address(uint160(uint256(digest)));
}
function deploy(bytes32 salt, address owner)
external
returns (Vault vault)
{
vault = new Vault{salt: salt}(owner);
require(address(vault) == predict(salt, owner));
}
}예측과 실제 배포는 생성자 인자를 완전히 같은 방식으로 인코딩해야 합니다. 여기서는 abi.encode(owner)가 type(Vault).creationCode 뒤에 붙어요. 소유자가 달라지면 목적지도 달라집니다.
실서비스 팩토리라면 누가 배포할 수 있는지, 솔트를 사용자별로 묶을지, 네이티브 코인을 어떻게 전달할지, 목적지에 이미 코드가 있을 때 어떻게 처리할지도 정해야 해요. 검증된 라이브러리는 인코딩 실수를 줄여 주지만, 주소 예측에 어떤 팩토리 주소를 쓰는지는 직접 이해해야 합니다.
결정론적 주소는 어디에 쓰이나요?
카운터팩추얼 상호작용
EIP-1014는 아직 코드가 없는 주소와의 상호작용을 주요 동기로 제시했어요. 참여자들은 잠재 주소에 먼저 합의하고, 분쟁처럼 필요한 상황에서만 지정된 컨트랙트를 배포할 수 있습니다. 카운터팩추얼(Counterfactual)은 알려진 조건에서 배포될 수 있는 컨트랙트를 미리 다룬다는 뜻이지, 코드가 이미 존재한다고 가정하라는 뜻은 아니에요.
스마트 계정과 온보딩
지갑 시스템은 사용자가 배포 가스를 내기 전에 미래 스마트 계정 주소를 계산할 수 있습니다. 자산이나 권한을 그 목적지와 연결한 뒤 실제 온체인 실행이 필요할 때 팩토리가 계정을 배포해요. 이때 의도하지 않은 사용자가 초기화 데이터나 소유권을 정하지 못하도록 설계해야 합니다.
체인마다 반복 가능한 인프라
동일한 신뢰 가능한 팩토리가 같은 주소에 있고 같은 솔트와 초기화 코드를 받으면 여러 EVM 체인에 같은 주소를 만들 수 있어요. ERC-2470 싱글턴 팩토리는 허가 없이 쓸 수 있는 결정론적 팩토리의 초기 표준 접근입니다. 더 최근 제안인 EIP-7997은 EVM 체인이 공유할 최소 팩토리를 규정해요. 제안 상태와 대상 체인의 실제 배포 코드를 확인해야 하며, 모든 체인에 당연히 있다고 가정하면 안 됩니다.
미니멀 프록시 클론
팩토리는 결정론적 배포와 미니멀 프록시(Minimal Proxy)를 결합해 예측 가능한 인스턴스를 만들 수 있습니다. 계정이나 볼트를 반복 생성할 때 유용하지만 구현 컨트랙트와 초기화라는 가정이 추가돼요. 프록시 실행 구조의 위험은 별도로 검토하세요.
CREATE2의 리스크와 한계
예측 가능하다고 배포된 것은 아니에요
코드가 없는 주소에도 네이티브 코인이나 토큰을 보낼 수 있습니다. 입력 기록이 틀렸거나 팩토리를 쓸 수 없거나 배포가 실패하면 미리 보낸 자산이 묶일 수 있어요. 사전 입금 전에 전체 계산 재료를 확인하고 테스트 배포로 경로를 증명하세요.
공개 솔트는 접근 제어가 아니에요
공개 멤풀에 보낸 솔트는 누구나 볼 수 있습니다. 허가 없는 팩토리가 같은 초기화 코드와 솔트를 누구에게나 허용하면 다른 계정이 먼저 배포할 수 있어요. 주소의 코드는 예상과 같더라도 타이밍 때문에 작업 흐름이 깨질 수 있습니다. 호출자나 소유권이 중요하다면 사용자 정보를 솔트 또는 생성자 데이터에 묶고 팩토리에서 권한을 검사해야 해요.
주소 충돌은 실패해요
EIP-1014에는 기존 충돌 규칙이 적용됩니다. 목적지의 논스가 0이 아니거나 코드가 비어 있지 않으면 생성이 실패해요. 같은 팩토리, 솔트, 초기화 코드를 다시 쓰는 것은 덮어쓰기 기능이 아닙니다. EIP는 SELFDESTRUCT를 사용해도 같은 트랜잭션 안에서 파괴 후 재배포할 수 없다고 명시해요.
생성자 동작이 신뢰 범위를 넓힐 수 있어요
계산식은 초기화 코드를 해시하지만 생성자는 외부 컨트랙트, 잔액, 블록 맥락이나 다른 상태를 볼 수 있습니다. 생성자가 무엇을 읽는지 감사하고 배포 후 런타임 바이트코드를 확인하세요. 예측 주소는 배포 레시피에 대한 약속이지 모든 실행 결과의 신원 증명이 아닙니다.
멀티체인 가정이 깨질 수 있어요
솔트 하나만 같아서는 부족합니다. 팩토리 주소, 연결 라이브러리, 컴파일 결과, 생성자 인자나 체인별 의존성이 달라지면 주소 또는 동작이 달라져요. 체인마다 팩토리 런타임 코드와 계산 결과를 다시 검증하세요.
배포 전 체크리스트
- 컴파일러 버전, 최적화 설정, 소스, 라이브러리와 생성자 인자를 고정
- 정확한 팩토리 주소, 솔트, 초기화 코드와 그 해시를 기록
- 주소를 독립적으로 계산해 팩토리 도우미 결과와 비교
- 배포 전 목적지의 논스와 코드를 확인
- 선점 위험이 있다면 사용자 신원 또는 소유권을 입력값에 결합
- 의도한 배포 경로를 테스트하기 전에는 사전 입금을 피하기
- 생성 뒤 런타임 바이트코드와 초기화 상태를 검증
- 대상 체인마다 팩토리 코드와 주소를 다시 확인
자주 묻는 질문
CREATE2는 가스 없이 배포하나요?
아니요. 주소를 만드는 방식만 바꿉니다. 초기화 코드를 실행하고 배포 가스를 내야 하며, EIP-1014는 초기화 코드 해싱 비용도 규정해요.
솔트는 비밀이어야 하나요?
보통 그렇지 않습니다. 솔트는 주소 입력값이지 비밀번호가 아니에요. 공개 때문에 경쟁이 생긴다면 멤풀의 비밀성에 기대지 말고 팩토리 권한이나 호출자와 결합된 입력값으로 설계해야 합니다.
생성자 인자를 바꾸고 같은 주소를 쓸 수 있나요?
같은 팩토리와 솔트를 쓴다면 불가능합니다. 생성자 인자는 초기화 코드의 일부이므로 이를 바꾸면 해시와 결과 주소가 바뀌어요.
CREATE2와 업그레이드 프록시는 같은가요?
아니요. CREATE2는 새 컨트랙트를 어느 주소에 만들지 정합니다. 프록시는 한 주소를 유지하면서 실행을 다른 구현에 위임해요. 둘을 함께 쓸 수 있지만 위험은 서로 다릅니다.
레시피를 예측하고 결과를 검증하세요
CREATE2는 배포 순서 대신 팩토리, 솔트, 초기화 코드라는 결정론적 레시피를 사용합니다. 그래서 사전 계산 계정, 카운터팩추얼 작업 흐름과 반복 가능한 인프라가 가능해져요. 안전한 절차도 결정론적으로 만들어야 합니다. 모든 입력을 고정하고, 독립적으로 다시 계산하고, 배포 권한을 통제한 뒤 목적지와 런타임 코드, 초기 상태를 검증하세요.
이 글은 교육 목적이며 투자 조언이 아닙니다. 스마트 컨트랙트 거래는 되돌릴 수 없고 암호화폐는 변동성이 커요. 올바른 네트워크에서 테스트하고, 잃어도 되는 범위의 자금만 사용하며, 최신 공식 문서를 확인하고 스스로 조사하세요(DYOR).
주요 출처
함께 읽으면 좋은 글

스마트 컨트랙트란? 작동 원리와 활용 사례 완전 정리 (2026)
스마트 컨트랙트의 개념부터 자판기 비유로 풀어낸 작동 원리, 디파이·NFT·RWA 활용 사례, 버그·해킹·불변성 리스크, FAQ까지 한 번에 정리했습니다.

이더리움 계정 논스 완벽 정리: 순서·재전송 방지·RPC 조회법
이더리움 계정 논스가 거래 순서를 정하고 재전송을 막는 원리, 논스 공백과 대체 거래, RPC 조회 시 주의점을 설명합니다.
이더리움 프록시 컨트랙트 완전 정리: delegatecall·UUPS·업그레이드 리스크
이더리움 프록시 컨트랙트의 delegatecall 구조, 투명·UUPS·비콘 방식의 차이와 실제 업그레이드 권한을 안전하게 확인하는 법을 설명해요.
관련 주제 살펴보기

이더리움 글램스테르담 업그레이드: ePBS·BAL과 확정된 사실
글램스테르담은 2026년 4분기 예정이에요. 확정된 범위, 세폴리아 일정, ePBS, 블록 레벨 접근 리스트와 남은 변수를 설명합니다.
크립토 주소 포이즈닝: 송금 전 지갑 주소를 검증하는 방법
주소 포이즈닝은 거래 내역에 닮은 지갑 주소를 심는 사기예요. 작동 원리와 안전한 전체 주소 검증 체크리스트를 알아보세요.