EPguy
[EVM - Function Selector] EVM에서 함수를 실행하기 까지의 과정 본문
아래 내용은 OpenZepplin 에서 설명하는 내용중 일부를 가져왔습니다.
https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-iii-the-function-selector-6a9b6886ea49/
Funstion Selector 란?
Function Selector는 함수가 호출 됐을 때 EVM에서 호출된 함수가 어떤 함수인지 판단하고 그 함수를 실행시키는 역할을 합니다.
아래 BasicToken 컨트랙트 코드가 존재합니다.
remix에서 totalSupply 메소드를 호출해보면 calldata로 0x18160ddd 가 들어간 것을 볼 수 있습니다.
여기서 0x18160ddd는 function signature 라고 불리는데요. EVM에선 이 function signature를 가지고 어떤 함수인지 판단을 하고 그 함수를 실행 시킵니다.
pragma solidity ^0.4.24;
contract BasicToken {
uint256 totalSupply_;
mapping(address => uint256) balances;
constructor(uint256 _initialSupply) public {
totalSupply_ = _initialSupply;
balances[msg.sender] = _initialSupply;
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
return true;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
}
Function Signature는 어떻게 만들어질까?
먼저 호출할 함수의 이름과 파라미터 타입을 keccak-256 알고리즘으로 해싱을 합니다.
transfer 함수를 예로 들자면 transfer(address,uint256) 를 해싱한 a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b 가 되겠죠.
여기서 중요한건 파라미터 이름 과 공백은 제거한 채로 해싱을 해야된다는 점입니다.
그다음 앞에서 4byte 만큼 자른 a9059cbb가 transfer 함수의 function signature 가 됩니다.
왜 4byte로 잘라서 사용할까?
해싱된 값을 전부 사용하지 않고 4byte 잘라서 사용하는 이유는 가스비 때문입니다.
CALLDATA가 길어질수록 가스비는 많아집니다. 즉 해싱된 값을 전부 사용하면 가스비도 증가하게 되죠. 또 함수를 실행시키는 Function Selector가 차지하는 메모리의 양도 커지게 되어 가스비도 증가하게 됩니다. 이러한 이유들로 4byte로 잘라서 사용하는 것 입니다.
하지만 4byte만 사용하여 function signature 가 중복될 수 있는 확률이 높아지게 됩니다.
예시로 withdraw(uint256) 와 OwnerTransferV7b711143(uint256)를 해싱 한 후 앞에 4byte를 잘라 보면 중복되는 것을 볼 수 있습니다. 물론 컴파일러에서 function signature 가 중복되면 컴파일 자체가 되지 않으며 확률 자체도 거의 0%에 가깝기 때문에 참고만 해두시면 되겠습니다.
Runtime Code에서 함수를 실행하기 까지의 과정![](https://velog.velcdn.com/images/appduome/post/553be610-cba2-42e8-9d3a-b1c72abe73ee/image.png)
Solidity 로 짠 코드를 컴파일을 하면 Creation Code와 Runtime Code로 나눠지게 됩니다.
컨트랙트를 호출하면 실행되는 코드는 Runtime Code이기 때문에 Function Selector도 Runtime Code에 존재합니다. 위치는 Free memory pointer, Short calldata check 다음으로 존재합니다.
Short calldata check 는 calldata가 4byte보다 작은 경우 Revert 해버립니다. 혹은 receive 함수가 있는 경우 receive 코드를 실행시킵니다. 예시로 컨트랙트에 이더리움만 보내는 경우가 있겠죠.
transfer(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 1)를 호출한다면 CALLDATA는 다음과 같이 들어갑니다.
0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
함수를 호출할 땐 파라미터당 32바이트의 크기가 고정입니다. 즉 , address _to 인자의 경우 5b38da6a701c568545dcfcb03fcb875f56beddc4로 20바이트지만 나머지 12바이트의 크기는 0으로 채워지게 됩니다. uint256 _value 인자도 1이 들어가지만 나머지 크기는 0으로 채워지게 됩니다.
function selector를 분석해보겠습니다.
CALLDATA가0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
인경우
13~52
뒤에 파라미터 인자 는 지우고 앞에 4byte만 스택에 저장함.
즉, a9059cbb
만 스택에 저장됨
참고로 soliditiy 0.5.5 부터는 SHR 명령어를 사용하여 짧게 바뀌었습니다.
PUSH1 0x00
CALLDATALOAD
PUSH1 0xE0
SHR
53 ~ 85
function signature가a9059cbb
인 것을 찾아 해당 코드로 점프한다.
a9059cbb
는 00b0번 코드로 점프함.
86~90
function signature 가 존재하지 않는 경우 실행되는 부분이며
Revert 혹은 fallback 함수가 있는 경우 fallback 함수를 실행함
참고하면 좋은 내용
만약 함수가 수만, 수십만개가 있을경우 함수를 실행할 때 function selector에서 조건을 도는 양이 굉장히 많아져 함수실행 시간이 길어 질 수 있습니다
그래서 EVM에서는 함수의 개수가 많아질 경우 function selector에 binary search 코드를 추가시켜 버립니다.
궁금하시면 Remix 등 에서 확인해보면 function selector에 GT 연산자가 들어간것을 확인할 수 있을겁니다.
'개발 > Solidity' 카테고리의 다른 글
Solidity - 0.8.0 미만 버전에서의 Overflow와 underflow (0) | 2023.09.26 |
---|---|
Solidity - fallback, receive 함수가 없는 컨트랙트에 이더리움 강제로 보내는 방법 (0) | 2023.09.26 |