CertiK: 다이아몬드 에이전트 계약 최상의 보안 관행

CertiK
2023-06-21 12:38:33
수집
본 문서에서는 개발자 커뮤니티에서 매우 선호되는 또 다른 프록시 패턴인 다이아몬드 프록시 패턴에 대해 소개합니다.

저자:CertiK

프록시 계약은 스마트 계약 개발자에게 중요한 도구입니다. 현재 계약 시스템에는 여러 가지 프록시 모델과 해당 사용 규칙이 존재합니다. 우리는 이전에 업그레이드 가능한 프록시 계약의 보안 모범 사례를 개요했습니다.

이 글에서는 개발자 커뮤니티에서 인기가 높은 또 다른 프록시 모델인 다이아몬드 프록시 모델에 대해 소개합니다.

image 다이아몬드 프록시 계약, 즉 "다이아몬드"는 이더리움 스마트 계약의 디자인 패턴으로, 이더리움 개선 제안(EIP) 2535에 의해 도입되었습니다.

다이아몬드 패턴은 계약의 기능을 더 작은 계약(비유적으로 "면"이라고도 함)으로 분할하여 계약이 무한한 기능을 가질 수 있도록 합니다. 다이아몬드는 프록시 역할을 하여 함수 호출을 적절한 면으로 라우팅합니다.

다이아몬드 패턴의 설계는 이더리움 네트워크의 최대 계약 크기 제한 문제를 해결할 수 있습니다. 대형 계약을 더 작은 면으로 분해함으로써, 다이아몬드 패턴은 개발자가 크기 제한의 영향을 받지 않고 더 복잡하고 기능이 풍부한 스마트 계약을 구축할 수 있도록 합니다.

전통적인 업그레이드 가능한 계약과 비교할 때, 다이아몬드 프록시는 엄청난 유연성을 제공합니다. 이들은 계약의 일부를 업그레이드하고, 선택된 함수의 일부를 추가, 교체 또는 삭제할 수 있도록 하며, 다른 부분에는 영향을 미치지 않습니다.

이 글에서는 EIP-2535에 대한 개요를 제공하며, 널리 사용되는 투명 프록시 모델 및 UUPS 프록시 모델과의 비교와 개발자 커뮤니티의 보안 고려 사항을 다룹니다.

image EIP-2535의 맥락에서 "다이아몬드"는 다양한 논리 계약에 의해 제공되는 기능을 구현하는 프록시 계약으로, 이를 "면"이라고 합니다.

진짜 다이아몬드가 다양한 면을 가지고 있다고 상상해 보세요. 이더리움 다이아몬드 계약도 다양한 면을 가지고 있습니다. 각 다이아몬드가 기능을 빌리는 계약은 서로 다른 면 또는 면(facet)입니다.

다이아몬드 표준은 "다이아몬드 절단" 기능을 확장하여 면과 기능을 추가, 교체 또는 삭제하는 데 사용됩니다.

또한, 다이아몬드 표준은 "다이아몬드 확대경(Diamond Loupe)"이라는 기능을 제공하여 면에 대한 정보와 다이아몬드에 존재하는 기능을 반환합니다. image 전통적인 프록시 모델과 비교할 때, "다이아몬드"는 프록시 계약에 해당하며, 서로 다른 "면"은 구현 계약에 해당합니다. 하나의 다이아몬드 프록시의 서로 다른 면은 내부 함수, 라이브러리 및 상태 변수를 공유할 수 있습니다. 다이아몬드의 주요 구성 요소는 다음과 같습니다:

image 프록시의 중앙 계약으로, 함수 호출을 적절한 면으로 라우팅합니다. "면" 주소에 대한 함수 선택기 매핑을 포함합니다.

image 특정 기능을 구현하는 단일 계약입니다. 각 면은 다이아몬드에 의해 호출될 수 있는 함수 집합을 포함합니다.

image EIP-2535에서 정의된 표준 함수 집합으로, 다이아몬드에서 사용되는 면과 함수 선택기에 대한 정보를 제공합니다. 다이아몬드 확대경은 개발자와 사용자가 다이아몬드의 구조를 검사하고 이해할 수 있도록 합니다.

image 다이아몬드 내의 면과 해당 기능 선택기를 추가, 교체 또는 삭제하는 함수입니다. 권한이 있는 주소(예: 다이아몬드 소유자 또는 다중 서명 계약)만이 다이아몬드 절단을 수행할 수 있습니다.

전통적인 프록시와 유사하게, 다이아몬드 프록시에서 함수 호출이 발생하면 프록시의 fallback 함수(회귀 함수)가 트리거됩니다. 다이아몬드 프록시와의 주요 차이점은 회귀 함수 내에 selectorToFacet 매핑이 있어 호출된 함수의 구현을 가진 논리 계약 주소를 저장하고 결정한다는 점입니다. 그런 다음, 이는 delegatecall을 사용하여 해당 함수를 실행하며, 전통적인 프록시와 마찬가지입니다.

image 모든 프록시는 fallback() 함수를 사용하여 함수 호출을 외부 주소에 위임합니다. 아래는 다이아몬드 프록시의 구현과 전통적인 프록시의 구현입니다.

그들의 어셈블리 코드 블록이 매우 유사하다는 점에 유의해야 합니다. 따라서 유일한 차이점은 다이아몬드 프록시의 위임 호출에서의 면 주소와 전통적인 프록시의 위임 호출에서의 impl 주소입니다.

주요 차이점은 다이아몬드 프록시에서 면의 주소가 호출자의 msg.sig(함수 선택기)에 따라 면의 주소의 hashmap에 의해 결정되는 반면, 전통적인 프록시에서는 impl 주소가 호출자의 입력에 의존하지 않는다는 점입니다.

image 다이아몬드 프록시 fallback 함수

image 전통적인 프록시 fallback 함수 image SelectorToFacet 매핑은 어떤 계약이 각 함수 선택기의 구현을 포함하는지를 결정합니다. 프로젝트 팀은 종종 이러한 함수 선택기를 구현 계약에 추가, 교체 또는 삭제해야 합니다. EIP-2535는 이를 위해 diamondCut() 함수가 필요하다고 규정합니다. 아래는 예시 인터페이스입니다.

image 각 FacetCut 구조는 면 주소와 다이아몬드 프록시 계약에서 업데이트를 위한 4바이트 기능 선택기 배열을 포함합니다. FaceCutAction은 사람들이 기능 선택기를 추가, 교체 및 삭제할 수 있도록 합니다. diamondCut() 함수의 구현은 저장 슬롯 충돌을 방지하고 실패 시 복구를 수행하는 충분한 접근 제어를 포함해야 합니다. image 다이아몬드 프록시가 어떤 기능을 가지고 있는지, 어떤 면을 사용하는지 조회하기 위해 "다이아몬드 확대경"을 사용합니다. "다이아몬드 확대경"은 EIP-2535에서 정의된 다음 인터페이스를 구현하는 특별한 면입니다:

image facets() 함수는 모든 면의 주소와 그들의 4바이트 함수 선택기를 반환해야 합니다. facetFunctionSelectors() 함수는 특정 면이 지원하는 모든 함수 선택기를 반환해야 합니다. facetAddresses() 함수는 다이아몬드가 사용하는 모든 면 주소를 반환해야 합니다.

facetAddress() 함수는 주어진 선택기를 지원하는 면을 반환해야 하며, 찾을 수 없는 경우 address(0)를 반환해야 합니다. 동일한 기능 선택기를 가진 면 주소는 하나 이상 존재해서는 안 됩니다.

image 다이아몬드 프록시가 서로 다른 함수 호출을 서로 다른 구현 계약에 위임하기 때문에, 충돌을 방지하기 위해 저장 슬롯을 올바르게 관리하는 것이 중요합니다. EIP-2535는 몇 가지 저장 슬롯 관리 방법을 언급했습니다.

image 이 면은 구조 내에서 상태 변수를 선언할 수 있습니다. 이 면은 서로 다른 저장 위치를 가진 임의의 수의 구조를 사용할 수 있습니다. 각 구조는 계약 저장소 내에서 특정 위치를 가집니다. 면은 자신의 상태 변수를 선언할 수 있지만, 다른 면이 선언한 상태 변수의 저장 위치와 충돌해서는 안 됩니다. EIP-2535에서는 샘플 라이브러리와 다이아몬드 저장 계약을 제공하며, 아래와 같습니다: image image App 저장소는 다이아몬드 저장소의 더 전문화된 버전입니다. 이 패턴은 면의 상태 변수를 더 편리하고 쉽게 공유할 수 있도록 사용됩니다. App 저장소 구조는 애플리케이션에 필요한 임의의 수와 유형의 상태 변수를 포함하도록 정의됩니다. 하나의 면은 항상 AppStorage 구조를 첫 번째이자 유일한 상태 변수로 선언하며, 이는 저장 슬롯의 0위치에 위치합니다. 그런 다음 서로 다른 면은 해당 구조에서 변수를 접근할 수 있습니다. image 또한 다이아몬드 저장소와 AppStorage의 혼합을 포함한 다른 저장 슬롯 관리 전략도 있습니다. 예를 들어, 일부 구조는 서로 다른 면 간에 공유되고, 일부는 특정 면에만 고유합니다. 모든 경우에, 우발적인 저장 슬롯 충돌을 방지하는 것이 매우 중요합니다.

투명 프록시 및 UUPS 프록시와의 비교

현재 Web3 개발자 커뮤니티에서 사용되는 두 가지 주요 프록시 모델은 투명 프록시 모델과 UUPS 프록시 모델입니다. 이 섹션에서는 다이아몬드 프록시 모델과 투명 프록시 및 UUPS 프록시 모델을 간략하게 비교합니다. image 1.EPI-2535:https://eips.ethereum.org/EIPS/eip-2535#Facets,%20State%20Variables%20and%20Diamond%20Storage

2.EPI-1967:https://eips.ethereum.org/EIPS/eip-1967

3.다이아몬드 프록시 참조 구현:https://github.com/mudgen/Diamond

4.OpenZeppelin 구현:https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.0/contracts/proxy

image 프록시 및 업그레이드 가능한 솔루션은 복잡한 시스템이며, OpenZeppelin은 UUPS, 투명 및 Beacon 업그레이드 가능한 프록시에 대한 코드베이스와 포괄적인 문서를 제공합니다. 그러나 다이아몬드 프록시 모델에 대해서는 OpenZeppelin이 그 이점을 인정했지만, EIP-2535 다이아몬드의 구현을 그들의 라이브러리에 포함시키지 않기로 결정했습니다.

따라서 기존의 제3자 라이브러리를 사용하거나 이 솔루션을 직접 구현하는 개발자는 구현 시 각별한 주의가 필요합니다. 여기에서 우리는 개발자 커뮤니티를 위한 보안 모범 사례 목록을 작성했습니다. image 계약 논리를 더 작고 관리하기 쉬운 모듈로 분해함으로써, 개발자는 자신의 코드를 더 쉽게 테스트하고 감사할 수 있습니다.

또한, 이 접근 방식은 개발자가 복잡한 단일 코드베이스를 관리하는 대신 계약의 특정 측면을 구축하고 유지하는 데 집중할 수 있도록 합니다. 최종 결과는 계약의 다른 부분에 영향을 주지 않고도 쉽게 업데이트하고 수정할 수 있는 더 유연하고 모듈화된 코드베이스입니다. image 자료 출처: Aavegotchi Github

image 다이아몬드 프록시 계약이 배포될 때, 다이아몬드 프록시 계약에 DiamondCutFacet 계약의 주소를 추가하고 diamondCut() 함수를 구현해야 합니다. diamondCut() 함수는 면과 기능을 추가, 삭제 또는 교체하는 데 사용되며, DiamondCutFacet와 diamondCut() 없이 다이아몬드 프록시는 정상적으로 작동할 수 없습니다. image 자료 출처: Mugen's Diamond-3-Hardhat image 스마트 계약에서 저장 구조에 새로운 상태 변수를 추가할 때, 구조의 끝에 추가해야 합니다. 구조의 시작이나 중간에 새로운 상태 변수를 추가하면 새로운 상태 변수가 기존 상태 변수 데이터를 덮어쓰게 되며, 새로운 상태 변수 이후의 모든 상태 변수가 잘못된 저장 위치를 참조할 수 있습니다.

image AppStorage 패턴은 다이아몬드 프록시를 위해 하나의 구조만 선언해야 하며, 이 구조는 모든 면에서 공유되어야 합니다. 여러 구조가 필요한 경우, DiamondStorage 패턴을 사용해야 합니다. image 구조를 다른 구조 안에 직접 넣지 마십시오. 내부 구조에 더 많은 상태 변수를 추가할 계획이 없다는 것이 확실하지 않다면, 구조 이후에 선언된 변수의 저장 슬롯을 덮어쓰지 않도록 해야 합니다.

image 해결 방법은 새로운 상태 변수를 저장 매핑 구조에 추가하는 것이며, "구조"를 "구조" 안에 직접 두지 않는 것입니다. 매핑 내의 변수 저장 슬롯 계산 방식이 다르며, 저장소에서 연속적이지 않습니다.

image 배열의 크기는 구조의 크기에 영향을 받습니다. 새로운 상태 변수가 구조에 추가되면, 해당 구조의 크기와 레이아웃이 변경됩니다.

이 구조가 배열의 요소로 사용될 경우 문제가 발생할 수 있습니다. 구조의 크기와 레이아웃이 변경되면 배열의 크기와 레이아웃도 변경되며, 이는 인덱스 또는 다른 구조 크기와 레이아웃에 의존하는 작업에 문제를 일으킬 수 있습니다.

image 다른 프록시 모델과 마찬가지로, 각 변수는 고유한 저장 슬롯을 가져야 합니다. 그렇지 않으면 동일한 위치의 두 개의 서로 다른 구조가 서로 덮어쓰게 됩니다.

image initialize() 함수는 일반적으로 특권 역할의 주소와 같은 중요한 변수를 설정하는 데 사용됩니다. 계약 배포 시 초기화되지 않으면 악의적인 행위자가 계약을 호출하고 제어할 수 있습니다.

초기화/설정 함수에 적절한 접근 제어를 추가하거나, 해당 함수가 계약 배포 시 호출되도록 보장하고 다시 호출할 수 없도록 하는 것이 좋습니다.

image 계약 내의 어떤 면이 selfdestruct() 함수를 호출할 수 있다면, 전체 계약을 파괴할 수 있으며, 이는 자금이나 데이터 손실로 이어질 수 있습니다. 이는 다이아몬드 프록시 패턴에서 매우 위험합니다. 여러 면이 프록시 계약의 저장소와 데이터에 접근할 수 있기 때문입니다. image 현재 우리는 점점 더 많은 프로젝트가 그들의 스마트 계약에서 다이아몬드 프록시 패턴을 채택하는 것을 보고 있습니다. 전통적인 프록시와 비교할 때, 유연성과 다른 이점을 가지고 있습니다.

그러나 추가적인 유연성은 공격자에게 더 넓은 공격 표면을 제공할 수도 있습니다. 우리는 이 글이 개발자 커뮤니티가 다이아몬드 프록시 패턴의 메커니즘과 보안 고려 사항을 이해하는 데 도움이 되기를 바랍니다.

동시에 프로젝트 팀은 다이아몬드 프록시 계약 구현과 관련된 취약성 위험을 줄이기 위해 철저한 테스트와 제3자 감사를 수행해야 합니다.

체인캐처(ChainCatcher)는 독자들에게 블록체인을 이성적으로 바라보고, 리스크 인식을 실제로 향상시키며, 다양한 가상 토큰 발행 및 조작에 경계해야 함을 상기시킵니다. 사이트 내 모든 콘텐츠는 시장 정보나 관련 당사자의 의견일 뿐이며 어떠한 형태의 투자 조언도 제공하지 않습니다. 만약 사이트 내에서 민감한 정보를 발견하면 “신고하기”를 클릭하여 신속하게 처리할 것입니다.
체인캐처 혁신가들과 함께하는 Web3 세상 구축