[Solidity] Function selector, Delegate call

임형석·2023년 9월 26일
0

Solidity


Function selector

Delegate call 을 공부하기 전, fucntion selector 에 대해 공부해보았다.

솔리디티의 컨트랙트에서 어떠한 함수를 호출할 때는 함수의 이름과 인자의 타입으로

4바이트 크기의 형태로 변환되어 함수를 식별하는데, 이를 function selector 또는

함수 식별자라고 부른다.


아래와 같이 event, emit 코드를 작성하고 transfer 함수를 호출한다.

logs - event - data 부분을 보면

4바이트 크기의 함수 식별자, 그리고 address, amount 값이 순서대로 data 에 포함되어

있는 것을 확인할 수 있다.

앞의 함수식별자인 4바이트는 0xa9059cbb 이다.


함수 식별자가 가르키는 함수가 맞는지 확인하기 위해 컨트랙트 작성.

인자를 "transfer(address,uint256)" 으로 넣고 함수를 호출해서 확인한다.

따라서, 함수 식별자는 함수명 + 인자의 타입으로 함수를 구분한다.


함수명, 인자의 타입, visibility 등을 변경해서 확인해보았다.

visibility 는 식별에 영향을 주지 않으며,

인자의 타입이 완전히 같더라도, 함수명의 대소문자만 달라도 다르게 식별하며,

uint256 => uint8 로 타입이 바뀌어도 이를 식별한다.


delegate call

지금까지 function selector 에 대해 확인하고 공부했던 것은 delegate call 때문이다.

delegate call 은 곧 공부할 proxy contract 에서 사용되는 개념이다.

proxy 란, 무언가를 대신해서 이어주는 역할. 중개를 해주는 역할이라고 보면 된다.

기존의 스마트 컨트랙트는 한번 배포하면 변경을 할 수 없기에, 장점이자 치명적인

단점을 가지고 있었는데, 이 때문에 EIP-1967 에서 proxy contract 를 이용해

기존에 배포한 컨트랙트를 업그레이드 할 수 있도록 제안했다.


A 라는 proxy 컨트랙트가 있고, B 라는 일반적인 컨트랙트가 있다고 했을 때.

A 라는 proxy 컨트랙트가 B 컨트랙트의 함수를 이용해서 자신의 State 를 바꾸는 것을

delegate call 이라고 한다.


오픈제플린의 proxy 컨트랙트 라이브러리이다.

abstract contract Proxy {
 	...
	...
	...
    function _delegate(address implementation) internal virtual {
        assembly {
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())
            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }
//
}

여기서 _delegate() 함수는 delegatecall 을 사용하여 implemetation 주소의 컨트랙트를 사용할 수 있도록 하고, calldata 를 통해 함수 호출에 필요한 데이터를 불러와 함수를 호출하고 자신의 state 를 변경시킨다. 실패한다면 revert 하게 된다.


그리고 internal 인 _delegate() 를 호출하는 _fallback() 함수가 있다.

    function _implementation() internal view virtual returns (address);
    
    function _fallback() internal virtual {
        _beforeFallback();
        _delegate(_implementation());
    }
		fallback() external payable virtual {
        _fallback();
    }

fallback 함수는 어떠한 calldata 도 제공되지 않거나, receive 함수가 없는 경우에 호출된다.

만약 A 컨트랙트가 transfer() 함수를 호출하면, transfer() 함수의 식별자가 calldata 에 포함된다. 이 식별자와 일치하는 함수가 A 컨트랙트에 없기에, _fallback 함수가 호출되고, 뒤이어 _delegate 함수도 호출되는 것이다.


0개의 댓글