ERC-8042 다이아몬드 스토리지 (Diamond Storage)
Final · 기타 · 2025-10-11 작성
사람이 읽을 수 있는 문자열의 keccak256 해시값을 구조체의 저장 위치로 쓰는 다이아몬드 스토리지 패턴을 표준으로 정합니다. 하나의 컨트랙트 안에서 저장 공간을 구역별로 나눠 관리할 수 있게 합니다.
왜 필요한가
스마트 컨트랙트는 데이터를 저장소(storage)라는 공간에 보관하는데, 여러 기능 모듈이 한 저장소를 함께 쓰는 프록시(proxy) 구조에서는 서로 다른 모듈이 같은 자리에 데이터를 써서 덮어쓰는 사고가 날 수 있습니다. 2020년 3월 솔리디티 컴파일러가 구조체를 저장소의 임의 위치에 둘 수 있게 되면서 읽기 쉬운 문자열을 해시해 위치를 정하는 다이아몬드 스토리지 패턴이 생겼고 ERC-2535 다이아몬드를 통해 널리 쓰였습니다. 그러나 이 방식은 오랫동안 정식 표준이 아니었고 2023년에 나온 ERC-7201은 같은 목적의 패턴을 표준화했지만 이미 쓰이던 다이아몬드 스토리지와 다른 계산 공식을 제안했습니다. 그 결과 운영 중인 기존 구현들은 표준의 보호를 받지 못했습니다. 이 표준은 단순하고 검증된 원래 방식을 명문화해 새 프로젝트가 쓸 수 있게 하고 과거 구현도 표준으로 인정합니다.
심층 설명
저장 위치 공식과 식별자 규칙
다이아몬드 스토리지 식별자는 출력 가능한 ASCII 문자(0x20부터 0x7E까지)만 담은 문자열입니다. 구조체의 저장 위치는 이 식별자의 keccak256 해시로 정해집니다. 즉 공식은 erc8042(id) = keccak256(id) 하나이며 솔리디티 표현으로는 keccak256(id)입니다. ERC-7201의 다단계 공식과 달리 해시 한 번으로 위치가 나오므로 계산이 단순하고 검증하기 쉽습니다.
권고 사항 네 가지
첫째, 솔리디티 문자열 리터럴을 쓰는 것을 권장합니다. 컴파일러가 출력 가능한 ASCII만 허용하도록 강제하기 때문이며 bytes32 constant STORAGE_POSITION = keccak256("myproject.erc721.registry")처럼 컴파일 타임 상수로 두는 것을 권장합니다. 16진수(\xNN)와 유니코드(\uNNNN) 이스케이프는 쓰지 않아야 합니다. 둘째, 식별자는 고유하고 사람이 읽을 수 있으며 의미 있는 문자열이어야 합니다. "car.fish.piano.run"처럼 무작위한 문자열은 의미 있는 문자열이 아닙니다. 셋째, 유니코드 리터럴(unicode"...")은 보이지 않는 문자나 비ASCII 문자를 담을 수 있어 쓰지 않아야 합니다. 넷째, 공백 문자(0x20)는 NatSpec 태그 같은 도구와 충돌할 수 있어 권장하지 않습니다.
NatSpec 태그 erc8042
ERC-7201이 정의한 @custom:storage-location <FORMULA_ID>:<NAMESPACE_ID> 태그 체계를 그대로 씁니다. 공식 식별자 erc8042는 erc8042(id: string literal) = keccak256(id)로 정의되며 주석은 @custom:storage-location erc8042:<NAMESPACE_ID> 형태가 됩니다. 예를 들어 @custom:storage-location erc8042:myproject.erc721.registry는 식별자 "myproject.erc721.registry"의 해시 위치에 뿌리를 둔 다이아몬드 스토리지를 표기합니다. 도구는 이 태그를 읽어 저장소 안 데이터의 위치를 파악할 수 있습니다.
ERC-7201과의 비교
ERC-7201의 네임스페이스 id는 공백만 없으면 되고 다른 제한이 없어 무작위 바이트도 쓸 수 있습니다. 위치 공식은 keccak256(abi.encode(uint256(keccak256(bytes(namespace id))) - 1)) & ~bytes32(uint256(0xff))로, 앞부분은 솔리디티 자료형이 keccak256으로 만드는 입력과 겹치지 않게 해 임의 바이트 id를 허용하고 뒷부분은 최종 위치를 256의 배수로 맞춰 미래의 가스 최적화 여지를 둡니다. 반면 ERC-8042는 식별자를 출력 가능한 ASCII로 제한하고 keccak256 한 번으로 위치를 구합니다. 기계가 생성한 무작위 id를 쓰려면 ERC-7201이, ASCII로 강제된 읽기 쉬운 식별자와 단순한 계산을 원하면 ERC-8042가 맞습니다.
참조 구현의 접근 패턴
참조 구현 ERC721Registry는 keccak256("myproject.erc721.registry")를 상수 STORAGE_POSITION으로 두고 ERC721RegistryStorage 구조체에 erc721Contracts 배열과 registryLimit 값을 담습니다. getStorage() 함수가 인라인 어셈블리로 s.slot := position을 실행해 구조체를 그 슬롯에 연결하면, 이후 함수들은 getStorage().registryLimit처럼 일반 구조체 접근 문법으로 읽고 씁니다. 이 패턴은 프록시용으로 시작했지만 어떤 스마트 컨트랙트에서든 저장소와 데이터 접근을 조직하는 데 쓸 수 있습니다.
하위 호환성
이 표준이 기술하는 다이아몬드 스토리지는 5년간 실제로 쓰여 왔습니다. 이 표준은 명세에 부합하는 과거의 모든 다이아몬드 스토리지 사용을 인정하고 표준화합니다. 따라서 기존 프로젝트는 코드를 바꾸지 않고도 표준 준수 상태가 됩니다.
쓸 때 주의할 점
- 식별자 중복: 서로 다른 컨트랙트나 라이브러리가 같은 문자열을 쓰면 같은 저장 슬롯을 가리켜 데이터가 겹치거나 손상될 수 있습니다. 한 컨트랙트 시스템 안에서 식별자가 고유하도록 해야 하며 "myproject.erc721.registry"처럼 프로젝트나 조직 이름을 접두사로 붙이는 것이 일반적인 방법입니다.
- ASCII 제한으로 충돌 방지: 식별자가 널 바이트 같은 비출력 바이트를 담을 수 있으면 "config\x00\x00..." 같은 악의적 식별자가 매핑, 동적 배열, string, bytes가 keccak256으로 계산하는 저장 위치 입력과 충돌해 상태 덮어쓰기나 보안 취약점이 생길 수 있습니다. 출력 가능한 ASCII(0x20~0x7E)로 제한하면 이 충돌 경로가 제거됩니다. 수학적으로 충돌이 불가능하다는 보장은 아니지만 실제 발생 확률은 사실상 0에 가깝습니다.
- 문자열 리터럴 사용 시 이스케이프 금지: 문자열 리터럴은 컴파일러가 출력 가능한 ASCII만 허용하므로 권장되지만 유니코드 이스케이프(\uNNNN)나 16진수 이스케이프(\xNN)는 쓰지 않아야 합니다.
- 유니코드 리터럴 금지: unicode"..." 리터럴은 비출력 바이트를 담을 수 있고 키릴 문자 а(\u0430)가 ASCII a(\u0061)와 똑같이 보이는 것처럼 비슷하게 생긴 문자로 식별자를 위장하는 데 악용될 수 있습니다. 텍스트 표시 방향을 바꾸는 제어 문자도 있어 쓰지 않아야 합니다.
- keccak256 충돌 저항성: 다이아몬드 스토리지 구조체가 기존 데이터 위치에 우연히 겹칠 가능성을 걱정할 수 있지만 256비트 저장소 주소 공간은 매우 넓어 통계적으로 일어나기 어렵습니다. 솔리디티의 매핑, 동적 배열, string, bytes도 같은 keccak256으로 위치를 정합니다.