> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/basics/2./undefined-1/mapping.md).

# mapping

mapping은 솔리디티의 기본적인 key-value 저장 방식으로 해시 테이블과 유사하게 작동한다. 특정 키(key)에 대응되는 값을 빠르게 조회할 수 있다.

## Mapping Types

```solidity
mapping(KeyType => ValueType) visibility 이름;
```

* `KeyType` : `uint, address, bool` 등 값 타입만 허용
* `ValueType` : 어떤 타입이든 가능 (`struct, array`, 또 다른 `mapping`도 가능)
* `visibility` : `public, private` 등 접근 제어자

{% hint style="danger" %}
`KeyType`으로 `mapping, struct, array`는 사용할 수 없다.&#x20;
{% endhint %}

`mapping`은 일종의 해시 테이블처럼 작동한다고 생각할 수 있다. 모든 가능한 키가 가상으로 존재하며, 각각은 해당 타입의 기본값(바이트로 0)으로 초기화되어 있다고 간주된다. 그러나 일반 해시 테이블과는 달리 키 자체는 저장되지 않고, 대신 키의 `keccak256` 해시값을 이용해 값을 조회한다. 이러한 특성 때문에 mapping은 길이가 없고, 특정 키나 값이 "설정되었는지" 확인할 방법도 없다. 따라서 매핑을 지우려면 어떤 키가 쓰였는지 별도로 추적해야 한다. (`Clearing Mappings` 참고)

`mapping`은 반드시 `storage` 위치를 가져야 하므로, 상태 변수로 선언하거나, 함수에서 `storage` 참조 타입으로 사용하거나, 라이브러리 함수의 인자로만 사용할 수 있다. 그러나 `public` 또는 `external` 함수의 입력값이나 반환 값으로는 사용할 수 없다. 이 제한은 `mapping`을 포함한 배열이나 구조체에도 똑같이 적용된다.&#x20;

mapping 타입의 상태 변수를 public으로 선언하면 솔리디티가 자동으로 getter 함수를 생성해준다. 이때 KeyType은 getter 함수의 파라미터가 되고, ValueType이 값 타입 또는 구조체인 경우 그대로 반환된다. 만약 값이 배열이나 또 다른 매핑이라면, getter는 각 KeyType에 대해 재귀적으로 파라미터를 받는다.&#x20;

아래는 간단한 mapping 사용 예시이다. MappingExample 컨트랙트는 주소를 키로, uint 값으로 하는 balances매핑을 선언하고 있다. 이는 MappingUser 컨트랙트에서 해당 주소에 해당하는 값을 조회하는 방식으로 사용된다.&#x20;

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract MappingExample {
    mapping(address => uint) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

contract MappingUser {
    function f() public returns (uint) {
        MappingExample m = new MappingExample();
        m.update(100);
        return m.balances(address(this));
    }
}
```

다음 예시는 선택적인 KeyName, ValueName을 함께 지정한 mapping 사용 예시이다. 이들은 ABI에만 영향을 주며, 바이트코드나 실행에는 영향을 주지 않는다.&#x20;

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;

contract MappingExampleWithNames {
    // 사용자 주소를 key로, 잔액을 value로 설정한 public 매핑
    mapping(address user => uint balance) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

```

## 반복 가능한 mapping

mapping은 키를 열거(enumerate)하거나 순회(iterate)할 수 없다. 하지만, mapping 위에 별도의 데이터 구조를 덧붙여 키들을 추적하면 간접적으로 순회 기능을 구현할 수 있다.&#x20;

아래 예제는 IterableMapping 이라는 라이브러리를 정의하고, User 컨트랙트가 해당 라이브러리를 사용하여 데이터를 추가하고, sum()함수에서 전체 값을 순서대로 반복하며 합산하는 구조를 보여준다.&#x20;

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }

struct itmap {
    mapping(uint => IndexValue) data;
    KeyFlag[] keys;
    uint size;
}

type Iterator is uint;

library IterableMapping {
    // 키와 값을 삽입하는 함수
    // 이미 존재하는 키면 값만 갱신하고 true 반환
    function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
        uint keyIndex = self.data[key].keyIndex;
        self.data[key].value = value;
        if (keyIndex > 0)
            return true;
        else {
            keyIndex = self.keys.length;
            self.keys.push();
            self.data[key].keyIndex = keyIndex + 1;
            self.keys[keyIndex].key = key;
            self.size++;
            return false;
        }
    }

    // 키 제거
    function remove(itmap storage self, uint key) internal returns (bool success) {
        uint keyIndex = self.data[key].keyIndex;
        if (keyIndex == 0)
            return false;
        delete self.data[key];
        self.keys[keyIndex - 1].deleted = true;
        self.size --;
    }

    // 키 존재 여부 확인
    function contains(itmap storage self, uint key) internal view returns (bool) {
        return self.data[key].keyIndex > 0;
    }

    // 순회 시작 지점 반환
    function iterateStart(itmap storage self) internal view returns (Iterator) {
        return iteratorSkipDeleted(self, 0);
    }

    // 주어진 iterator가 유효한지 확인
    function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {
        return Iterator.unwrap(iterator) < self.keys.length;
    }

    // 다음 iterator로 이동
    function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {
        return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
    }

    // 현재 iterator 위치의 키와 값 조회
    function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {
        uint keyIndex = Iterator.unwrap(iterator);
        key = self.keys[keyIndex].key;
        value = self.data[key].value;
    }

    // 삭제된 키를 건너뛰며 iterator 이동
    function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {
        while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
            keyIndex++;
        return Iterator.wrap(keyIndex);
    }
}

// 사용 예시
contract User {
    // 데이터를 담는 struct 선언
    itmap data;
    // 라이브러리 함수들을 itmap에 연결
    using IterableMapping for itmap;

    // 데이터 삽입
    function insert(uint k, uint v) public returns (uint size) {
        // 내부적으로 IterableMapping.insert(data, k, v)를 호출
        data.insert(k, v);
        // struct의 멤버에도 접근할 수 있으나,
        // 직접 수정하는 것은 피해야 한다
        return data.size;
    }

    // 저장된 모든 값을 더함
    function sum() public view returns (uint s) {
        for (
            Iterator i = data.iterateStart(); // 순회 시작
            data.iterateValid(i);             // 유효한 동안 반복
            i = data.iterateNext(i)           // 다음 위치로 이동
        ) {
            (, uint value) = data.iterateGet(i); // 값 가져오기
            s += value;
        }
    }
}

```

출처 : <https://docs.soliditylang.org/en/latest/types.html#mapping-types>
