일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 웹툰
- 옵저버패턴
- solidity
- 인센티브 기반 커뮤니티
- 부트캠프
- 재진입
- 멀티시그
- RBAC
- 트러블슈팅
- 코드리뷰
- 팀 프로젝트
- 토큰노믹스
- 업그레이더블 컨트랙트
- 투표
- 솔리디티
- 블록체인
- 싱글톤패턴
- github 에러
- 팩토리패턴
- 디자인 패턴
- 배팅
- git add
- git commit
- 일시정지
- 프록시패턴
- 회로차단
- 스테이트머신패턴
- 코드스테이츠
- github
- Today
- Total
보다 더 생생한 기록
[Solidity] 업그레이더블 컨트랙트(Upgradable Contract) 본문
업그레이더블 컨트랙트를 이용해서 간단한 프로젝트를 만들기전에, 제대로 짚고 넘어가보자.
업그레이더블 컨트랙트(Upgradable Contract)
: 한번 배포하면 수정할 수 없는 불변성의 특징을 가진 스마트컨트랙트를 업그레이드 시킬 수 있는 방식으로 설계한 스마트컨트랙트
프록시 패턴(Proxy Pattern)
: 실제 로직 컨트랙트(Logic Contract)을 대리하는 역할을 하는 프록시 컨트랙트
사용자는 프록시 컨트랙트와 상호 작용하지만, 실제 실행되는 로직은 별도의 논리 컨트랙트에서 수행
위 그림처럼 사용자가 프록시 컨트랙트의 함수를 호출하면, 프록시 컨트랙트에 저장된 주소를 이용하여 로직 컨트랙트의 함수를 호출할 수 있도록 한다.
위 그림1만 보면 다른 컨트랙트의 함수를 불러와 사용하는 것과 뭐가 다른가 싶을 수 있다.
Call vs DelegateCall
차이점은 State가 달라지는 위치에 있다. 아래와 같이 컨트랙트 A와 B가 있고, A에서는 B의 함수를 호출하는 기능이 있다고 하자.
그림1을 적용하면, 컨트랙트 A가 Proxy Contract, 컨트랙트 B가 Logic Contract이다.
일반적인 Call로 B 컨트랙트의 함수를 호출하면, B 컨트랙트의 state가 1로 변경되고 A 컨트랙트의 state는 0으로 그대로이다.
이번엔 업그레이더블 컨트랙트에 사용되는 DelegateCall로 타 컨트랙트의 함수를 호출하면, A 컨트랙트의 state가 1로 변경되고 B컨트랙트의 state는 0으로 그대로이다.
이를 이용하면, 추후 state를 변화시키는 알고리즘이 바뀌어야할때, 메인인 컨트랙트 A를 재 배포할 필요 없이 로직을 담당하는 B 만 갈아치워도 되는 장점이 있다. 이렇게 불변성의 한계를 극복하고 기능을 바꿀 수 있는 점 때문에 Upgradable Contract 라고 한다.
스토리지 레이아웃(Storage Layout)
조금 더 딥하게 들어가기 전에 스토리지 레이아웃을 알고가야한다.
우리가 솔리디티로 컨트랙트를 작성할 때, 여러 가지 상태 변수를 선언하고 사용하게 된다. 이때 상태 변수들이 저장되는 방식을 스토리지 레이아웃이라고 한다. 업그레이더블 컨트랙트를 사용할 때 스토리지 레이아웃은 매우 중요한 개념이다. 왜냐하면 잘못된 스토리지 레이아웃 관리로 인해 데이터 손실이나 예기치 않은 오류가 발생할 수 있기 때문이다.
스토리지 슬롯 정해지는 원리
솔리디티에서 상태 변수들은 특정한 방식으로 저장된다. 상태 변수는 스토리지 슬롯이라는 고정된 위치에 저장되며, 각 슬롯은 256비트의 공간을 가진다.
- 상태 변수의 선언 순서: 상태 변수는 선언된 순서대로 슬롯에 할당된다. 예를 들어, 첫 번째 상태 변수는 슬롯 0에, 두 번째 상태 변수는 슬롯 1에 저장된다.
- 슬롯 내 배치: 256비트 이하의 변수들은 한 슬롯에 여러 개가 배치될 수 있다. 예를 들어, uint128 변수 두 개는 한 슬롯에 들어갈 수 있다.
- 구조체와 배열: 구조체와 배열은 특별한 슬롯 배치 규칙을 따른다. 구조체의 각 필드는 순서대로 슬롯에 저장되며, 배열은 시작 슬롯만 저장되고 실제 데이터는 그 뒤에 연속적으로 저장된다.
스토리지 변수 위치 선정 (별표 다섯개)
업그레이더블 컨트랙트를 설계할 때는 스토리지 변수의 위치를 신중하게 선정해야 한다. 프록시 컨트랙트는 상태 변수를 저장하고, 로직 컨트랙트는 함수 로직만을 담기 때문이다. 따라서 로직 컨트랙트를 업그레이드하더라도 프록시 컨트랙트의 스토리지 레이아웃은 유지되어야 한다.
- 업그레이드 시 주의사항:
- 새로운 상태 변수를 추가할 때는 기존 상태 변수의 순서를 변경하지 않아야 한다.
- 가능한 한 새로운 상태 변수는 기존 상태 변수 뒤에 추가해야 한다.
- 상태 변수의 타입을 변경하면 안 된다. 타입을 변경하면 슬롯 배치가 달라져 예기치 않은 동작이 발생할 수 있다.
컨트랙트 A와 컨트랙트 B가 공유하는 변수의 순서(슬롯 번호)는 동일해야한다.
스토리지 충돌
프록시 컨트랙트와 로직 컨트랙트 간의 스토리지 충돌은 심각한 문제를 야기할 수 있다. 예를 들어, 프록시 컨트랙트가 가지고 있는 상태 변수를 로직 컨트랙트가 덮어쓰게 되면 데이터가 손상될 수 있다. 이를 방지하기 위해 프록시 컨트랙트는 상태 변수의 이름이나 타입을 변경하지 않고 유지해야 한다.
프록시 패턴과 constructor 사용 불가
프록시 패턴을 사용할 때 주의할 점은 로직 컨트랙트에 constructor를 사용할 수 없다는 것이다. 대신 초기화 로직은 별도의 initialize 함수로 구현해야 한다. 이는 프록시 컨트랙트가 로직 컨트랙트의 constructor를 호출할 수 없기 때문이다.
fallback 누수를 막기 위한 UUPS
UUPS(Upgradeable Proxy) 패턴은 업그레이더블 컨트랙트의 보안성을 강화하기 위해 도입된 방식이다. UUPS는 프록시 컨트랙트가 잘못된 로직 컨트랙트를 참조하지 않도록 하기 위해 upgradeTo 함수를 통해 업그레이드를 관리한다. 이 함수는 권한이 있는 관리자만 호출할 수 있으며, 이를 통해 프록시 컨트랙트의 fallback 함수가 예상치 못한 주소로 delegatecall을 하지 않도록 방지한다.
자 이제 이 내용들을 기반으로 업그레이더블 컨트랙트를 구축하고 하드햇으로 테스트까지 진행해보자.
'블록체인' 카테고리의 다른 글
[Solidity][공부 마라톤] 솔리디티로 디자인 패턴 알아보고 적용하기 (上) (0) | 2024.06.27 |
---|---|
[Solidity][공부 마라톤] 업그레이더블 컨트랙트 프로젝트 + 하드햇 테스트 (1) | 2024.06.16 |
[Solidity] 하드햇 튜토리얼 요약 (0) | 2024.06.11 |
[Solidity] 면접 복기 [transfer/send/call, receive/fallback, Call/staticCall/delegateCall] (0) | 2024.04.27 |
[블록체인][리서치] 실제 사례를 보며 ERC-404를 이해해보자 (0) | 2024.04.10 |