EIP-7 DELEGATECALL 명령어 추가 (DELEGATECALL)
최종 확정 · EVM·실행 · 원문 상태 Final · 2015-11-15 작성
EVM에 새 명령어 DELEGATECALL(0xf4)을 추가합니다. 다른 컨트랙트의 코드를 실행하되 원래 호출의 보낸 사람과 보낸 금액 정보를 그대로 유지합니다.
왜 필요한가
컨트랙트가 다른 컨트랙트의 코드를 빌려 쓰는 방법은 이전에도 있었습니다. CALLCODE라는 명령어를 쓰면 다른 주소에 있는 코드를 자기 저장소 위에서 실행할 수 있습니다. 이렇게 하면 누가 보냈는지(보낸 사람)와 얼마를 보냈는지(금액) 정보가 원래 사용자가 아니라 중간 컨트랙트 기준으로 바뀌는 문제가 있습니다. 그래서 빌려 온 코드 안에서는 원래 사용자가 누구였는지 알 수 없습니다. 이를 피하려면 호출 데이터 앞 20바이트에 보낸 사람 주소를 끼워 넣는 우회가 필요한데, 그러면 코드를 위임용으로 따로 컴파일해야 하고 같은 코드를 위임 호출과 일반 호출에 동시에 쓸 수 없습니다. DELEGATECALL은 보낸 사람과 금액을 부모 호출에서 자식 호출로 그대로 넘겨, 컨트랙트가 다른 주소에 있는 코드를 거의 자기 환경 그대로 실행할 수 있게 합니다.
무엇이 바뀌나
지금
- 다른 주소의 코드를 빌려 실행하면 보낸 사람과 금액 정보가 중간 컨트랙트 기준으로 바뀝니다
- 빌려 온 코드가 원래 사용자를 알려면 호출 데이터에 주소를 끼워 넣는 우회와 전용 컴파일이 필요합니다
- 이렇게 만든 코드는 위임 호출 전용이 되어 일반 호출에는 같이 쓸 수 없습니다
EIP-7 이후
- 다른 주소의 코드를 실행해도 보낸 사람(CALLER)과 금액(VALUE)이 원래 호출 그대로 유지됩니다
- 컨트랙트가 다른 주소를 코드 저장소처럼 두고 호출을 그대로 넘기는 구조를 만들 수 있습니다
- 코드를 담은 주소를 바꿀 수 있게 설계하면 컨트랙트의 동작 코드를 교체할 수 있습니다
누구에게 영향이 있나
- 스마트컨트랙트 개발자: 코드를 여러 주소로 쪼개거나, 코드 주소를 바꿀 수 있는 구조를 만들 때 보낸 사람과 금액 정보를 잃지 않고 호출을 넘길 수 있습니다.
- 클라이언트 팀: EVM에 0xf4 명령어와 그 가스 규칙(기본 지급분 없음, 계정 생성 없음)을 구현해야 합니다.
- 사용자: 직접 쓰는 기능은 아니지만 이 명령어를 쓰는 컨트랙트와 상호작용할 때 자신의 주소와 보낸 금액이 위임된 코드까지 그대로 전달됩니다.
심층 설명
명령어와 피연산자
DELEGATECALL은 0xf4에 배정된 새 opcode로, 스택에서 6개의 피연산자를 받습니다. gas(자식 코드가 쓸 수 있는 가스), to(실행할 코드가 있는 목적지 주소), in_offset(입력이 시작되는 메모리 오프셋), in_size(입력 크기, 바이트), out_offset(출력을 쓸 메모리 오프셋), out_size(출력용 스크래치 패드 크기)입니다. 코드는 to 주소에서 가져오지만 실행 컨텍스트(저장소 등)는 호출자의 것입니다.
가스 규칙
CALL과 달리 기본 지급분(basic stipend)을 주지 않습니다. 피연산자로 넘긴 gas가 피호출자가 받는 전부입니다. CALLCODE처럼 계정 생성이 일어나지 않으므로 선지불 가스 비용은 항상 schedule.callGas + gas로 고정됩니다. 쓰지 않은 가스는 평소처럼 환불됩니다.
보낸 사람·금액 전파와 깊이 제한
피호출자 환경에서 CALLER와 VALUE는 호출자 환경에서와 정확히 같게 동작합니다. 즉 부모 호출의 보낸 사람과 금액이 자식 호출로 그대로 전파됩니다. 호출 깊이 제한 1024는 기존대로 유지되므로, DELEGATECALL도 호출 스택 깊이를 1 늘립니다.
CALLCODE와의 차이와 설계 의도
발상 자체는 CALLCODE와 비슷하지만 sender와 value를 부모 스코프에서 자식 스코프로 전파한다는 차이가 있습니다. 덕분에 컨트랙트가 다른 주소를 바꿀 수 있는 코드 저장소로 두고 호출을 그대로 통과(pass through)시키기가 훨씬 쉬워집니다. 자식 코드는 가스가 줄고 콜스택 깊이가 늘어난 것을 빼면 부모와 본질적으로 같은 환경에서 실행되므로, msg.sender와 msg.value를 자유롭게 참조할 수 있습니다.
원문이 든 두 가지 사용 예
첫째, 300만 가스 장벽을 피하기 위한 코드 분할입니다. 호출 데이터의 값 범위에 따라 ADDR1, ADDR2 같은 여러 주소로 delegate_call을 분기해, 큰 코드를 여러 컨트랙트로 쪼개면서도 하나처럼 동작하게 합니다. 둘째, 코드 주소를 바꿀 수 있는 컨트랙트입니다. 소유자가 특정 함수 선택자(0x12345678)로 호출하면 self.delegate를 갱신하고 그 외의 호출은 모두 self.delegate 주소의 코드로 넘깁니다.
반대 논거와 그에 대한 답
호출 데이터의 앞 20바이트에 보낸 사람 주소를 넣으면 같은 기능을 흉내 낼 수 있다는 반론이 있습니다. 원문은 이에 대해, 그 방식은 위임용 컨트랙트 코드를 따로 컴파일해야 하고 같은 코드를 위임 컨텍스트와 일반 컨텍스트에서 동시에 쓸 수 없다는 점을 지적합니다.
활성화와 상태
홈스테드(Homestead) 하드포크로 활성화됐습니다. 메인넷은 블록 1,150,000 이상, 모던(Morden) 테스트넷은 블록 494,000 이상, 이후 테스트넷은 블록 0부터 적용됩니다. 상태는 Final로 확정된 명세입니다.