교육 참고용이며 투자 조언이 아닙니다. 투자로 인해 손실이 발생할 수 있습니다.
직접 답변
delegatecall은 호출자 계약의 컨텍스트에서 대상 계약의 코드를 실행합니다. 호출자는 자체 스토리지, 잔액, address(this)를 유지하며, msg.sender와 msg.value는 원래 호출의 값을 그대로 유지합니다.
이 동작은 프록시, 라이브러리, 스마트 지갑 모듈을 가능하게 하지만 위임된 코드에 호출자의 실질적인 권한도 부여합니다. 스토리지 쓰기, 자산 전송, 승인, 외부 호출은 코드를 제공한 대상이 아니라 호출자로서 실행됩니다.
접근 가능한 모든 delegatecall 대상을 특권 코드로 취급해야 합니다. 안전성은 대상 선택 규칙, 호환되는 스토리지 레이아웃, 초기화 상태, 업그레이드 제어, 그리고 해당 거래에 실제로 사용되는 정확한 구현에 달려 있습니다.
작동 방식
일반 외부 호출에서는 피호출자가 자체 스토리지를 읽고 씁니다. delegatecall에서는 대상의 바이트코드가 호출자의 스토리지를 대상으로 실행됩니다. 즉, SSTORE 명령은 호출자에게 속한 슬롯을 변경합니다. 대상 코드의 변수 이름은 런타임에 중요하지 않으며, 계산된 슬롯 위치만 중요합니다.
따라서 다음 네 가지 경계를 감사해야 합니다.
- 대상 제어: 목적지가 고정되어 있는지, 사용자가 선택하는지, 레지스트리를 통해 결정되는지, 관리자가 변경할 수 있는지 확인합니다.
- 스토리지 호환성: 모든 구현 버전에서 변수 순서, 타입, 상속, 스토리지 갭, 네임스페이스 슬롯 또는 표준화 슬롯을 비교합니다.
- 초기화 및 인가: 초기화 함수를 다시 실행할 수 없는지 확인하고, 업그레이드 또는 모듈 관리 함수가 의도된 호출자와 거버넌스 지연을 강제하는지 확인합니다.
- 반환값 처리: 실패가 상위 호출로 전파되고 반환 데이터가 예상한 타입으로 디코딩되는지 검증합니다. 저수준 호출은 Solidity가 일반적으로 제공하는 계약 타입 검사를 수행하지 않습니다.
ERC-1967은 구현, 비콘, 관리자 주소를 컴파일러의 일반적인 할당 범위 밖에 있는 표준화 슬롯에 배치해 프록시의 스토리지 충돌을 줄입니다. 그러나 특정 구현이 안전하거나 정당한 권한으로 수행된 업그레이드가 무해하다는 사실까지 보장하지는 않습니다.
예시
어떤 지갑이 owner를 slot 0에 저장한다고 가정해 보겠습니다. 플러그인은 컴파일 시 counter를 slot 0에 배치했으며, 지갑이 delegatecall을 통해 이 플러그인을 호출하면 카운터를 증가시킵니다.
스토리지는 지갑에 속하므로 이 쓰기는 지갑의 owner 값을 변경합니다. 변경된 워드가 공격자가 통제하는 주소를 인코딩한다면, 플러그인이 지갑 자산을 보유한 적이 없더라도 이후 인가 검사에서 공격자를 소유자로 인식할 수 있습니다.
성공한 거래 영수증만으로는 의도된 상태 변경과 유해한 상태 변경을 구분할 수 없습니다. 따라서 거래 시뮬레이션에서는 실제 프록시와 구현 주소를 기준으로 스토리지 차이, 자산 및 승인 변경, 발생한 이벤트, 후속 호출을 확인해야 합니다.
위험
- 임의 대상 실행: 사용자가 제어하거나 검증이 미흡한 대상은 호출자의 권한으로 악성 코드를 실행할 수 있습니다.
- 스토리지 충돌: 구현은 소유권, 잔액, 일시 중지 상태, 심지어 다음 구현을 선택하는 슬롯까지 덮어쓸 수 있습니다.
- 안전하지 않은 업그레이드: 관리자나 거버넌스 절차가 침해되면 사용자가 자산을 예치하거나 승인을 부여한 뒤에 이전에 검토된 코드를 교체할 수 있습니다.
- 초기화 실패: 초기화되지 않은 프록시나 구현에서는 다른 계정이 특권 역할을 차지하거나 위험한 종속성을 설정할 수 있습니다.
- 오해를 부르는 검사: 프록시 소스, 현재 구현 또는 인터페이스만 검증하면 비콘, 대기 중인 업그레이드, 모듈 레지스트리 또는 다른 실행 경로를 놓칠 수 있습니다.
서명하기 전에 최근 블록에서 구현을 확인하고, 누가 어느 정도의 지연을 거쳐 이를 변경할 수 있는지 검증해야 합니다. 또한 대상의 검증된 바이트코드와 스토리지 레이아웃을 살펴보고, 전체 calldata를 시뮬레이션하며, 실행 전후의 민감한 스토리지와 토큰 승인을 비교해야 합니다. 스마트 지갑의 경우 모듈이 활성화 및 비활성화되는 방식과 대상을 선택하도록 허용되는 방식도 검토해야 합니다.
흔한 오해
- “대상은 호출자의 자산에 손댈 수 없다.” 위임된 코드는 호출자로서 실행되므로, 호출자에게 해당 능력이 있다면 외부 계약 호출, 자산 전송 또는 승인 생성을 수행할 수 있습니다.
- “변수 이름이 같으면 충돌을 막을 수 있다.” EVM은 소스 코드 이름이 아니라 스토리지 슬롯을 사용합니다. 레이아웃 순서, 상속, 타입이 호환되어야 합니다.
- “프록시 코드가 검증되었으면 시스템도 검증된 것이다.” 현재 구현, 비콘, 업그레이드 관리자, 초기화 상태, 모듈 권한은 각각 신뢰 경계의 독립적인 구성 요소입니다.
관련 주제
출처
- Introduction to Smart Contracts - Solidity Documentation (확인일: 2026-08-20)
- Units and Globally Available Variables - Solidity Documentation (확인일: 2026-08-20)
- ERC-1967: Proxy Storage Slots - Ethereum Improvement Proposals (확인일: 2026-08-20)