Delegatecall은 Solidity 공식 문서에서 보면 대상 주소의 코드가 호출한 주소의 context에서 실행되고 msg.sender 및 msg.value가 값을 변경하지 않는 사실을 제외하고는 call과 비슷하다고 나와 있어
이게 무슨말일까?? 호출한 주소의 context??
조금 쉽게 call과 비교해서 그림으로 알려주께
Call
deploy 되어 있는 Contract A에 내가 B() 함수를 호출했어. 그러면 내가 Contract A의 B함수를 호출했으니까 msg.sender는 내 지갑의 주소가 되겠지??
그리고 B()는 Contract B의 changeNum() 함수를 call 했어. 그러면 다시 Contract B의 changeNum()으로 가겠지?? 그리고 그 함수 안에 있는 코드를 실행시키겠지?? Contract B입장에서는 누가 자기를 불렀어?? A가 자기를 불렀지?? 그러면
Contract B의 msg.sender는 A의 주소가 되는 거야.
B에서 changeNum() 함수를 실행시키면 num = Y니까 return이 없으면 Contract A의 num은 X 그대로겠지??
즉 call은 컨트랙트 A를 통해 컨트랙트 B의 함수를 호출 시 B의 Storage를 변경시켜.
일반 call처럼 컨트랙트 A가 B의 함수를 호출하면 컨트랙트 B로 넘어간다. 그래서 컨트랙트 B에서 모든 과정이 일어나
DelegateCall
delegatecall는 컨트랙트 A를 통해 컨트랙트 B의 함수를 호출 시 호출한 함수를 컨트랙트 B에서 가져와서 컨트랙트 A에서 실행을 해. 이 부분이 delegatecall의 핵심이야. 호출을 하면 Contract B에서 실행이 되는 게 아니라 호출한 함수를 Contract A로 들고 와서 마치 A는 자기 함수인 마냥 실행을 시켜. 그러면 어떻게 되겠어?? 원래 Contract A의 num = X였는데 Y로 바뀌겠지?? 그리고 Contract B의 num은 안바껴 왜냐?? B에서 함수를 실행시키지 않았거든. 단순히 함수를 들고 온 것뿐이야
그래서 delegatecall을 실행하면 contract A에 대한 인자가 변경돼!!!
이렇게 이론으로는 쉽지?? 그럼 같이 코드를 보면서 이해를 해볼까??
// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.7.0 < 0.9.0;
contract ChangeNum{
uint256 public num;
event whoCall(address _address, uint256 _num);
function changeNumber() public {
num = 50;
emit whoCall(msg.sender, num);
}
}
contract DelePractice{
uint256 public num;
function callFunc(address _address) public payable{
(bool success, ) = _address.call(abi.encodeWithSignature("changeNumber()"));
require(success, "Failed call");
}
function delegatecallFunc(address _address) public payable {
(bool success, ) = _address.delegatecall(abi.encodeWithSignature("changeNumber()"));
require(success, "Failed delegatecall");
}
}
DelePractice를 보면 2개의 function이 있어 callFunc랑 delegatecallFunc가 있는데 먼저 callFunc를 보자!!
_address는 call 할 Contract의 address를 적으면 된다.
현재 두 Contract의 초기 상태는 다음과 같다. 둘 다 num이 0이다.
callFunc를 한번 실행 켜보자
ChangeNum의 컨트랙트의 num이 변경된 것을 확인할 수 있다.
Log를 보면 msg.sender의 주소가 DelePractice 컨트랙트의 주소로 되어 있다.
그럼 이제 deleFunc() 함수를 실행시켜 보자.
DelePractice의 num이 변경된 것을 확인할 수 있다.
Log를 보면 msg.sender의 주소가 나의 주소로 되어 있다.
어때?? 이제 흐름과 코드를 보면서 delegatecall을 이해했어??
그럼 delegatecall왜 사용할까??
그 이유는 컨트랙트의 업데이트나 업그레이드하기 편리하다는 거지
블록체인 특성상 스마트 컨트랙트를 deploy 하면 그 상태로 바꿀 수가 없단 말이야.
코드를 변경하면 다시 재배포를 해야 해.. 그런데 갑자기 어느 Dapp 서비스가 중요한 컨트랙트의 변수의 값이나 함수를 변경해야 해. 변경하고 재배포하면 되는 거 아니야?? 이러는데 그럼 재배포하면 기존에 있던 데이터들이 싹 다 날아가겠지?? 심지어 재배포를 하면 Address도 바뀔 거 아니야 그야말로 대참사....
그래서 delegatecall을 쓰는 거야 예를 들어 중요한 데이터들은 다 컨트랙트 A에 저장되어 있고 필요할 때마다 특정 컨트랙트의 함수를 가져와서 사용하면 편리할 거 같지 않아?? 데이터들을 변경하고 싶다!! 그러면 특정 컨트랙트를 변경하고 재배포한다면 다시 그 함수를 가져오면 되잖아. 컨트랙트 A의 주소도 바뀔 일도 없고 정보들도 초기화될 일도 없고.. 그래서 delegatecall을 사용하는 거야!!