프록시 컨트랙트를 다루다보면 스토리지 레이아웃의 동작 방식을 알 필요가 있다.
Solidity에는 데이터 저장하는 방식이 메모리, 스토리지 두가지 유형이 존재한다.
메모리는 각 함수 호출 후에 지워지는 임시 저장 위치이며 스토리지의 경우 함수 호출 및 트랜잭션 종료된 후에도 유지되는 영구적인 저장 위치이다.
스토리지 레이아웃은 스토리지에 데이터가 저장되는 방식을 결정하게 된다.
컨트랙트의 경우 32바이트 슬롯으로 나누어진 한정된 저장 공간( 2²⁵⁶ — 1)이 존재한다.
스토리지에 저장할 때 크게 두가지 방법이 있으며, 변수를 사용하거나 슬롯 인덱스를 직접 지정하여 저장 하는 방법이 있다. 정적 변수의 경우 해당하는 크기에 맞게 슬롯에 저장이되며, mapping, 동적배열의 경우 슬롯에 배열의 길이가 저장되고 실제 값은 별도의 위치에 저장되게 된다.
프록시 컨트랙트의 경우 Storage 상에 데이터를 저장하고 관리하는 것이 중요하며 스토리지의 Slot 영역을 유지하는 방법이 크게 두가지로 표현된다.
임의의 공간을 미리 확보하여, 컨트랙트를 업그레이드 할 때 필요한 영역만큼 공간을 사용하는 형태이다.
사용법은 간단히 uint256(32byte)로 슬롯을 미리 예약을하며 이후 업그레이드 시 필요한 영역만큼 슬롯 크기를 조절해가며 사용하게 된다.
contract Base {
uint256 base1;
uint256[49] __gap;
}
contract Child is Base {
uint256 child;
}
contract Base {
uint256 base1;
uint256 base2; // 32 bytes
uint256[48] __gap;
}
contract Base {
uint256 base1;
address base2; // 20 bytes
uint256[48] __gap; // array always starts at a new slot
}
contract Base {
uint256 base1;
uint128 base2a; // 16 bytes
uint128 base2b; // 16 bytes - continues from the same slot as above
uint256[48] __gap;
}
네임스페이드를 통해서 임의의 공간을 지정해 사용하는 형태로 해당 슬롯 위치에서부터 변수 선언 시 자동 할당받는 형태로 진행한다. 다이아몬드 프록시 패턴에서 사용되며, Openzepplin 5.0 부터 업그레이드에 해당 방식을 차용했다.
네임스페이드는 구조체 형태로 되어 있어야 하며 NatSpec tag 는 다음과 같은 규칙을 갖게 된다.
@custom:storage-location <FORMULA_ID>:<NAMESPACE_ID>
(solidity 컴파일러는 v0.8.20부터)
pragma solidity ^0.8.20;
contract Example {
/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 x;
uint256 y;
}
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_LOCATION
}
}
function _getXTimesY() internal view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.x * $.y;
}
}
https://eips.ethereum.org/EIPS/eip-7201
https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#modifying-your-contracts
https://docs.soliditylang.org/en/v0.6.3/miscellaneous.html
https://github.com/4e5ung/solidity-study/tree/main/storage_layout