Solidity에서 자주 쓰는 mapping 타입은 내부적으로 해시 테이블처럼 동작하지만, 우리가 일반적으로 아는 HashMap 자료구조와는 구조도, 처리 방식도 매우 다릅니다.
이번에는 Solidity의 mapping이:
• 어떻게 데이터를 저장하고
• 어떤 해시 함수를 사용하며
• 충돌 시 어떻게 대응하는지
를 EVM 관점에서 최대한 자세하게 설명드릴게요.
⸻
🧠 Solidity의 mapping은 어떻게 작동할까?
mapping(address => uint256) public balances;
이 코드는 balances[addr] 값을 EVM의 스토리지 슬롯에 해시 계산을 통해 저장하는 구조입니다.
⸻
✅ 기본 작동 원리
Solidity에서 mapping(KeyType => ValueType)은 실제로는 다음과 같이 동작합니다:
storage slot = keccak256(encode(key) . encode(mappingSlot))
• key: 검색할 주소 등
• mappingSlot: 해당 mapping 자체가 선언된 스토리지 위치(index)
• . : 붙여쓴다는 의미 (ABI encodePacked 방식)⸻
🔎 예시
contract Example {
mapping(address => uint256) public balances; // 슬롯 0
}balances[0xAbc...123] 은
👉 keccak256(abi.encodePacked(address, uint256(0))) 에 저장됨
⸻
🔐 해시 함수: keccak256
Solidity는 모든 mapping 키 계산에 keccak256을 사용합니다.
keccak256(abi.encodePacked(key, mappingSlot))• 강력한 충돌 방지 특성을 가진 SHA3 기반 해시 함수
• Solidity / EVM에서 기본 제공⸻
❓ 충돌(collision)은 어떻게 처리하나요?
✅ 답: 애초에 충돌이 “거의 발생하지 않도록” 설계되어 있음
• Solidity의 keccak256는 256비트의 해시 공간을 사용
• mapping은 해시 테이블처럼 보이지만 실제 테이블을 만들지 않음
• 각 키-슬롯 조합은 고유한 위치에 바로 접근하는 방식이라,
충돌 해결 로직이 필요 없음 (open addressing, chaining 없음)
📌 다시 말해:
충돌이 발생해도 별도로 관리하거나 저장된 값이 엉키는 일은 없습니다.
단지 다른 슬롯 주소에 저장될 뿐이죠.
⸻
📦 EVM 저장 구조 (스토리지 관점)
예시:
contract Example {
uint public normalValue; // slot 0
mapping(address => uint256) public balances; // slot 1
}
이 경우 balances[msg.sender]는 다음과 같이 계산됩니다:
slot = keccak256(abi.encodePacked(key, slotOfBalances)) = keccak256(msg.sender ++ 1)
해당 슬롯에 바로 접근하여 값을 읽거나 씁니다.
⸻
🔧 복합 Key 구조는?
Solidity는 아래처럼 중첩 mapping도 허용합니다:
mapping(address => mapping(uint256 => uint256)) public userVotes;
이 경우:
userVotes[A][B] → slot = keccak256(B ++ keccak256(A ++ mappingSlot))
즉, 깊이에 따라 keccak256가 중첩됩니다.
⸻
📊 장점 vs 단점
| 항목 | 장점 | 단점 |
|---|---|---|
| ✅ 가변성 | 동적으로 키 추가 가능 | 키 목록을 알 수 없음 |
| ✅ 읽기 속도 | O(1) 해시 기반 접근 | 반복 불가 (no length, no iterator) |
| ✅ 충돌 | keccak 기반으로 거의 없음 | 충돌 해결 전략 없음 (필요도 없음) |
| ❌ 데이터 순회 | 불가 (off-chain에서 indexing 필요) | --- |
⸻
📚 보너스: 왜 mapping은 iterable 하지 않나요?
Solidity에서 mapping은 키 목록을 저장하지 않기 때문에:
// ❌ 불가능
for (uint i = 0; i < balances.length; i++) { ... }
✔️ 이걸 해결하려면, 보통은 별도로 address[] public keys; 등을 두어 수동으로 트래킹해야 해요.
⸻
✅ 요약
| 항목 | 설명 |
|---|---|
| 해시 함수 | keccak256 (SHA3) |
| 저장 위치 | keccak256(key ++ mappingSlot) |
| 충돌 처리 | 없음 (슬롯 자체가 다르므로 발생하지 않음) |
| 반복 가능 여부 | ❌ 직접 순회 불가 |
| 읽기 속도 | O(1) 접근 |
| 쓰기 방식 | storage 슬롯 직접 접근 |
'Solidity' 카테고리의 다른 글
| 인터페이스 + 상속 조합 (0) | 2025.03.25 |
|---|---|
| Solidity 에서의 상속 (0) | 2025.03.25 |
| 스마트 컨트랙트 오류 핸들링 (0) | 2025.03.20 |
| 함수 인자, 변수의 저장위치 설정 : memory vs. calldata (0) | 2025.03.19 |
| Ether 전송 : transfer() vs call() (0) | 2025.03.19 |