external call이 발생하면 (CALL
, STATICCALL
, DELEGATECALL
) 해당 externall call에서 사용되기 위한gas가 전달되게 된다.
value
를 설정하듯이 전달할 gas또한 caller가 설정할 수도 있다.
참고로
CALL
opcode를 사용하게 되면 무조건21000
gas는 소모하여 날아가게 된다.
머나 먼 옛날에는 CALL
의 gas cost가 매우 적었고, 모든 gas를 전달할 수 있었다.
따라서 거의 무한히 call stack을 쌓을 수 있었다.
이 "stack too deep" 문제를 해결하기 위해서, stack의 깊이를 1024
로 제한하게 되었다.
(현재도 이것은 유지되고 있다.)
stack의 깊이가 1024까지 도달되면 revert가 나게 되었다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.5;
// DO NOT USE!!!
contract Auction {
address highestBidder;
uint256 highestBid;
function bid() external payable {
if (msg.value < highestBid) revert();
if (highestBidder != address(0)) {
payable(highestBidder).send(highestBid); // refund previous bidder
}
highestBidder = msg.sender;
highestBid = msg.value;
}
}
공격자가 bid()
함수를 호출하기 전에, 스스로에게 recursive call을 돌려서 1023
개의 call stack을 쌓아놓고,
1024
번 째에 bid()
를 호출한다.
그럼 send
의 경우 조용히 revert
가 나게 되고 코드는 마지막까지 흘러간다.
(전체 트랜잭션 롤백이 일어나지 않아 이전 highestBidder
는 환불을 받지 못하게 된다.)
요약해서 말하자면, call
opcode를 사용할 때 (call stack이 한 칸 늘어날 때) 현재 사용할 수 있는 gas의 63/64 만큼만 전달할 수 있다.
Gas available at Stack depth 0 = Initial gas available * (63/64)^0
Gas available at Stack depth 1 = Initial gas available * (63/64)^1
Gas available at Stack depth 2 = Initial gas available * (63/64)^2
Gas available at Stack depth 3 = Initial gas available * (63/64)^3
.
.
.
Gas available at Stack depth N = Initial gas available * (63/64)^N
이렇게 external call
로 콜 스택이 하나씩 늘어날 때마다 gasleft의 (63/64)*N
만큼씩만 가스를 소모할 수 있는 것이다.
(명시적으로 gasleft
를 전달해 주더라도, 1/64는 caller에게 남게 된다.)
이렇게 call stack이 늘어날 떄마다 급격하게 callee가 사용할 수 있는 gas의 양은 줄어들게 된다.
EIP-150 이후로 사실상 불가능하지만, 이론적으로 EVM에서는 여전히
1024
개의 stack이 허용된다.
EIP-150은 Call Depth Attack을 막기 위해 제안되었으며, 이는 63/64 Rule을 통해서 이루어진다.
참고로 위에서 언급하였듯, call
사용 시에 명시적으로 gas left를 모두 전달해 주어도 여전히 gas left의 1/64
는 Caller contract에 남아있게 된다.