ERC-2535: 다이아몬드 패턴 이해하기
14 Mar 2025 | BlockchainERC-2535: 다이아몬드 패턴
Overview
이더리움 블록체인의 스마트 컨트랙트를 개발할 때 가장 큰 제약 중 하나는 컨트랙트 크기 제한(24KB)과 업그레이드 가능성입니다. 이러한 문제를 해결하기 위해 등장한 것이 ERC-2535 다이아몬드 패턴입니다. 이 표준은 모듈식 스마트 컨트랙트 시스템을 구현하기 위한 구조와 방법을 정의합니다.
이 포스팅에서는 ERC-2535 다이아몬드 패턴의 개념, 구성 요소, 이점 및 구현 방법에 대해 알아보겠습니다.
다이아몬드 패턴이란?
다이아몬드 패턴은 2020년 Nick Mudge에 의해 제안된 EIP(Ethereum Improvement Proposal)로, 스마트 컨트랙트를 더 모듈화하고 확장 가능하게 만드는 아키텍처입니다. 전통적인 스마트 컨트랙트와 달리, 다이아몬드 패턴은 컨트랙트를 여러 개의 작은 조각(facet)으로 나누어 관리합니다.
주요 구성 요소
다이아몬드 패턴의 주요 구성 요소는 다음과 같습니다:
- 다이아몬드 컨트랙트: 중앙 허브 역할을 하는 코어 컨트랙트로, 함수 호출을 적절한 facet으로 위임(delegate)합니다.
- Facet 컨트랙트: 특정 기능을 포함하는 모듈식 컨트랙트입니다. 하나의 다이아몬드는 여러 facet을 가질 수 있으며, 각 facet은 전체 로직의 다른 부분을 담당합니다.
- Selector: 함수 선택자는 호출을 올바른 facet으로 라우팅하는 데 사용됩니다. 다이아몬드 컨트랙트는 함수 선택자에서 해당 facet 주소로의 조회 테이블을 유지합니다.
- Loupe 함수: 다이아몬드에 대한 정보를 쿼리할 수 있는 내부 검사 함수들로, facet과 그들이 제공하는 함수에 대한 정보를 제공합니다.
ERC-2535의 이점
다이아몬드 패턴을 사용하면 다음과 같은 이점이 있습니다:
- 모듈성: 스마트 컨트랙트를 작은 facet으로 나누면 전체 시스템에 영향을 주지 않고 특정 부분을 관리하고 업데이트할 수 있습니다.
- 확장성: 다이아몬드는 유기적으로 성장할 수 있는 대규모 복잡한 시스템의 개발을 가능하게 합니다. 새로운 기능은 새로운 facet을 배포하여 추가할 수 있습니다.
- 업그레이드 가능성: 개별 facet은 독립적으로 업그레이드될 수 있어 전체 계약을 다시 배포하지 않고도 유연하고 원활한 업데이트가 가능합니다.
- 가스 비용 절감: 업데이트되는 facet만 재배포하면 되므로 계약 업그레이드에 대한 전체 가스 비용이 크게 낮아질 수 있습니다.
- 조직화: 코드를 facet으로 구성함으로써 개발자는 코드베이스를 깔끔하게 유지하고 탐색하기 쉽게 만들어 유지보수성을 향상시킬 수 있습니다.
- 무제한 크기: 다이아몬드는 최대 계약 크기 제한이 없어, 시간이 지남에 따라 추가할 수 있는 기능의 양에 제한이 없습니다.
다이아몬드 패턴 구현하기
1. 개발 환경 설정
먼저 Hardhat을 이용하여 환경을 설정합니다:
npm install --save-dev hardhat
2. 다이아몬드 컨트랙트 생성
다이아몬드 컨트랙트는 중앙 허브 역할을 하며, 함수 호출을 적절한 facet으로 위임하는 로직을 포함합니다:
// Diamond.sol
pragma solidity ^0.8.0;
import "./IDiamondCut.sol";
contract Diamond {
// 다이아몬드 저장소
struct Facet {
address facetAddress;
bytes4[] selectors;
}
mapping(bytes4 => address) public selectorToFacet;
constructor(IDiamondCut.FacetCut[] memory _diamondCut) {
// 다이아몬드 컷 구현
}
fallback() external payable {
address facet = selectorToFacet[msg.sig];
require(facet != address(0), "Function does not exist");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {revert(0, returndatasize())}
default {return(0, returndatasize())}
}
}
}
3. Facet 정의
특정 기능을 포함하는 facet 컨트랙트를 생성합니다. 예를 들어, Counter facet을 만들 수 있습니다:
// CounterFacet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../libraries/LibDiamond.sol";
import "../libraries/LibCounter.sol";
contract CounterFacet {
// Access to counter storage
function getCounterStorage() internal pure returns (LibCounter.CounterStorage storage) {
return LibCounter.counterStorage();
}
// Get the current counter value
function getCount() external view returns (uint256) {
return getCounterStorage().count;
}
// Increment the counter
function increment() external {
getCounterStorage().count += 1;
}
// Decrement the counter
function decrement() external {
LibCounter.CounterStorage storage cs = getCounterStorage();
require(cs.count > 0, "Count cannot be negative");
cs.count -= 1;
}
// Set the counter to a specific value
function setCount(uint256 _count) external {
LibDiamond.enforceIsContractOwner(); // Only owner can set count
getCounterStorage().count = _count;
}
}
4. 다이아몬드와 Facet 배포
다이아몬드 컨트랙트와 facet 컨트랙트를 배포한 후, Diamond Cut 메커니즘을 사용하여 facet을 다이아몬드에 연결합니다:
async function main() {
const diamondCutFacet = await deploy("DiamondCutFacet", {
from: deployer,
log: true,
});
// Deploy Diamond
const diamond = await deploy("Diamond", {
from: deployer,
args: [deployer, diamondCutFacet.address],
log: true,
});
// Deploy DiamondInit
const diamondInit = await deploy("DiamondInit", {
from: deployer,
log: true,
});
// Deploy facets
console.log("");
console.log("Deploying facets");
const FacetNames = ["DiamondLoupeFacet", "OwnershipFacet", "CounterFacet", "ERC20Facet"];
const cut: FacetCut[] = [];
for (const FacetName of FacetNames) {
const facetDeployment = await deploy(FacetName, {
from: deployer,
log: true,
});
console.log(`${FacetName} deployed: ${facetDeployment.address}`);
const facetContract = await ethers.getContractAt(FacetName, facetDeployment.address);
cut.push({
facetAddress: facetDeployment.address,
action: FacetCutAction.Add,
functionSelectors: getSelectors(facetContract) || [],
});
}
const diamondCutContract = await ethers.getContractAt("IDiamondCut", diamond.address);
const diamondInitContract = await ethers.getContractAt("DiamondInit", diamondInit.address);
// Call to init function
let functionCall = diamondInitContract.interface.encodeFunctionData("init");
const tx = await diamondCutContract.diamondCut(cut, diamondInit.address, functionCall);
}
5. 다이아몬드와 상호작용
배포 후, 다이아몬드 컨트랙트와 상호작용할 수 있습니다. 다이아몬드에 대한 호출은 적절한 facet으로 라우팅됩니다:
const diamondAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512";
const counterFacet = await ethers.getContractAt(
"CounterFacet",
diamondAddress,
);
const count = await counterFacet.getCount();
console.log("Initial count:", count.toString());
console.log("Incrementing counter...");
const incrementTx = await counterFacet.increment();
await incrementTx.wait();
const newCount = await counterFacet.getCount();
console.log("New count:", newCount.toString());
결론
ERC-2535 다이아몬드 패턴은 이더리움에서 모듈식, 확장 가능하고 업그레이드 가능한 스마트 컨트랙트를 구축하기 위한 강력한 프레임워크를 제공합니다. 스마트 컨트랙트를 facet으로 나눔으로써 개발자는 복잡한 시스템을 더 효과적으로 관리하고, 가스 비용을 줄이며, 원활한 업그레이드를 보장할 수 있습니다.