[Solidity] Call vs Delegate Call

드림보이즈·2023년 9월 11일
0

Smart Contract

목록 보기
1/10

학습 배경

생애 2번째 블록체인 개발자 면접에서 받은 질문이었다.

개발 팀장님 : 솔리디티에서 deleagete이 뭐죠?
나 : 태어나서 처음 들어봅니다.

난 팀장님의 표정을 보았다. 씁쓸하게 웃으시면서 넘어가시는 표정, 분명 실망이었으랴.

내가 공부해갔던 솔리디티와 관련 블록체인 지식은 굉장히 올드 스쿨이었다면,
면접에서는 최신 트렌드에 관한 질문들이 많았다.
따로 학회나 뉴스를 찾아보지 않는 한 나는 절대 준비할 수 없었을 거다.

이 내용 말고도 Layer 2 솔루션, EVM 동작원리 등의 질문을 받았으며,
대답을 제대로 못하거나 모르는 내용을 정리할 예정이다.
물어봤다는건 업무에서 쓴다는 것이니까.

1. Call

스마트 컨트랙트나 외부 주소 함수를 호출하는데 사용되는 저수준 함수

코드 양식

(bool success, bytes memory data) = address.call(bytes memory data);

사용하는 이유

그냥 바로 Target 컨트랙트를 호출하면 되는데, 뭣하러 중간에 Caller를 껴서
가스비도 더 들고 불편을 감수해야 하는가?

  • 코드 재사용과 라이브러리 : 여러 컨트랙트가 동일한 로직 공유할 시,
  • 업그레이드 가능성 !!! : 새로운 컨트랙트로 전환하면서 기존 상태를 유지하고 싶을 경우
  • 상태 공유와 복잡한 호출 !!! : delegate call을 사용하면 상태 공유가 가능

2,3번은 이해가 안될 수 있다. 뒤에서 설명할테니 그런갑다 하고 넘어가자.

분류

결국

  • 한 스마트컨트랙트에서 다른 컨트랙트의 함수를 호출하는 경우
  • 한 스마트컨트랙트에서 EOA 호출
  • EOA에서 스마트컨트랙트 함수 호출

세 경우로 나눌 수 있다.

3번째 케이스는 백엔드에서 web3.js를 이용하여 컨트랙트와 상호작용한 경험이 있다.

(프로젝트에서 지겹게 썼었다.)

2번째 케이스는 컨트랙트에서 EOA 계정을 호출하고, 이더를 보내던지 로그를 기록하는지 등의 커스텀이 가능하다.

contract EOAContract {
	//이벤트 정의해 호출 결과 모니터링
   event EOAFunctionCalled(address indexed caller, uint256 result);
   
   //이더 받을 수 있는 함수
   receive() external payable{}
   
   //EOA 호출
   function callEOA(address payable eoaAddress, uint256 value) external {
   	//호출하고 결과 받기
    (bool success, bytes memory result) = eoaAddress.call(value: valueToSend}("");
    
    // 호출 결과 로그에 기록
    emit EOAFunctionCalled(msg.sender, success ? abi.decode(result, (uint256)) : 0);
	}
   }

중요한건 1번째 케이스, 한 컨트랙트에서 다른 컨트랙트를 호출할 경우이다.

pragma solidity ^0.8.0;

contract MyContract {
    function callAnotherContract(address anotherContract, uint256 value) external returns (bool success, bytes memory data) {
        // 다른 컨트랙트의 함수를 호출하고 결과를 받음
        (success, data) = anotherContract.call(abi.encodeWithSignature("myFunction(uint256)", value));
        
        // 호출이 성공한 경우 success는 true이고, data에는 반환된 데이터가 들어감
        if (success) {
            // data를 원하는 형태로 디코딩하여 처리
            // 예: uint256 result = abi.decode(data, (uint256));
        }
    }
}

위 코드에서

(success, data) = anotherContract.call(abi.encodeWithSignature("myFunction(uint256)", value));

abi.encode~ 부분이 어려울 수 있다.
어떤 컨트랙트를 부를지는 주소로 알 수 있지만, 그 컨트랙트의 어떤 함수를 호출하는지를 설정해줘야 할 거 아닌가? 너는 그걸 어떻게 특정지을래?

백엔드에서 지겹도록한 ABI를 이용하는 것이다.
인간 입장에서 특정 함수의 이름("myFunction")과 인자만 넣고
abi.encodeWithSignature 를 사용하면 인코딩을 해주는 것이다.

call 함수 완벽 정리 ㅇㅈ?

2. Deleagate Call

필요한 이유 : 업그레이드 가능한 스마트 컨트랙트를 위해

컨트랙트는 한번 배포되면 바꿀 수 없다. 업그레이드를 하고 싶다면 재배포를 해야되는데, 그럼 그 때까지의 상태들은 전부 날아가는 것이다.

그래서 컨트랙트를 여러 개 만들어서, 상태들을 저장하는 proxy contract를 구현해놓고,
자세한 함수들은 다른 곳에 배포해서 연결을 시켜버리는 것이다.
이렇게 되면 정책이 바뀌어서 포인트를 10주던것을 5로 바꿔야 한다 해도,
Proxy는 냅두고 Logical만 재배포 해서 연결하면 되는 것이다.

특징

delegate call의 경우, msg.sender와 msg.value가 컨트랙트가 아닌 EOA가 처음 보낸 주소로 찍힌다.

예제 코드

시나리오 :
회원 1명만을 관리하는 proxy 컨트랙트,
회원 포인트 올려주는 add 컨트랙트가 있다.
포인트를 10씩 올려주다가, 회사 상황이 나빠져 5로 줄여야 되는 상황, 어찌할 것인가?

proxy

contract proxy{
	// 포인트 상태변수 
    uint256 public point = 0;
    
    // 1. call 함수
    function callNow(address _contractAddr) public payable {
        (bool success,) = _contractAddr.call(abi.encodeWithSignature("plusOne()"));
        require(success, "failed to transfer ether");
    }
    
    // 2. delegate call 함수
    function delegateCallNow(address _contractAddr) public payable {
        (bool success,) = _contractAddr.delegatecall(abi.encodeWithSignature("plusOne()"));
        require(success, "failed to transfer ehter");
    }
}

call 함수와 delegate함수 둘 다 만들어 차이를 실험해보자.

add

contract add {
	// 포인트 상태변수
    uint256 public point = 0;
    event Info(address _addr, uint256 _point);

	// 포인트 증가시키기 
    function plusPoint() public {
        point = point + 10;
        emit Info(msg.sender, point);
    }
}

proxy와 add에서 주의할 점은, 해당하는 상태변수의 순서는 동일해야만 한다. (이름은 상관없음)
EVM에선 storage에 컨트랙트마다 상태변수들을

이렇게 저장하기 때문에, slot의 순서는 맞춰줘야 한다.

배포를 하고 테스트를 해보자.

먼저 proxy의 call 함수를 실행할 경우

add 컨트랙트에서 로그가 나온다. 여기서 from은 proxy 함수의 주소며,
add 컨트랙트의 포인트 변수가 10이 된다.
proxy는 ? 포인트 변수 그대로 0이다.
아니 proxy에 상태를 저장해야 된다니까 왜 변경 가능성 있는 add에 저장을 하냐 ?
그러니까 delegate가 필요한거여 ㅋㅋ

proxy에서 delegate call을 호출할 경우

from이 proxy 주소가 아니라 호출한 개인 EOA 주소로 바뀌었다. 여기까진 그렇다 쳐.

포인트 변수는 어떻게 됐는데?

add는 그대론데
proxy만 10이 증가했다 ㄷㄷ

실행 과정이

나 >> proxy >> add >> proxy

이렇게 된 것이다.

이제 add 함수를 포인트 5만 주는 걸로 고쳐보자.

proxy 컨트랙트는 냅두고 add만 재배포했는데 정상적으로 작동이 된다. ㄷㄷ

결론

  • delegate call은 컨트랙트를 업그레이드하기 위해 사용한다!
  • 상태 저장하는 컨트랙트와 특정 동작 실행하는 함수를 분리해서 관리한다!
  • delegate call시 컨트랙트 간 상태변수 선언 코드의 순서(레이아웃)가 맞아야 한다!
profile
시리즈 클릭하셔서 카테고리 별로 편하게 보세용

0개의 댓글

관련 채용 정보