블록체인 학습
0 XP

transfer / send / call 비교

컨트랙트에서 다른 주소로 Ether 를 보내는 방법은 세 가지가 있습니다. transfer, send, call 입니다. 셋 다 결국 "돈을 보낸다"는 같은 일을 하지만, 실패했을 때의 동작과 전달되는 가스의 양이 서로 다릅니다. 이 차이가 곧 컨트랙트의 안전성과 호환성을 좌우하므로 정확히 구분해야 합니다.

transfer, send, call 의 차이

방법실패 시 동작가스 한도권장 여부
transfer자동 revert2300 gas 고정제한적
sendfalse 반환2300 gas 고정비권장
callfalse 반환가스 전달 조절 가능권장
  • transfer: 송금이 실패하면 자동으로 전체 트랜잭션을 되돌립니다(revert). 별도 검사 없이도 안전해 보이지만, 단 2300 gas 만 전달합니다.
  • send: 실패해도 revert 하지 않고 false 를 반환만 합니다. 따라서 반환값을 직접 확인하지 않으면 송금이 실패했는데도 코드가 그냥 진행되는 위험한 버그가 생깁니다.
  • call: 가스를 자유롭게 전달할 수 있고 가장 유연합니다. 실패 시 false 를 반환하므로 반드시 결과를 확인해야 하며, 오늘날 표준으로 권장되는 방식입니다.

아래 예제를 Run 하면 transfer·send 줄에서 'transfer' is deprecated and scheduled for removal 경고가 나옵니다. 오류가 아니라 경고이지만, 컴파일러가 직접 이 둘을 쓰지 말라고 알려 주는 것입니다. 셋의 차이를 이해하기 위해 예제는 그대로 두었고, 실제 코드에서는 call 만 쓰면 됩니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
contract Sender {
    // 1) transfer: 실패하면 자동으로 revert
    function sendByTransfer(address payable to) public payable {
        to.transfer(msg.value);
    }
 
    // 2) send: 실패하면 false 반환 -> 직접 확인 필요
    function sendBySend(address payable to) public payable {
        bool ok = to.send(msg.value);
        require(ok, "send failed"); // send 실패
    }
 
    // 3) call: 가장 유연하며 현재 권장되는 방식
    function sendByCall(address payable to) public payable {
        (bool ok, ) = to.call{value: msg.value}("");
        require(ok, "call failed"); // call 실패
    }
}

왜 call 이 권장되는가

transfer 와 send 가 전달하는 2300 gas 는 단순한 로그 한 줄 정도만 처리할 수 있는 아주 적은 양입니다. 받는 쪽이 컨트랙트인데 그 receive/fallback 안에 조금이라도 복잡한 로직이 있으면 가스가 부족해 송금이 실패합니다. 과거에는 이 고정 가스가 재진입 공격을 막는 안전장치로 여겨졌지만, 이더리움 가스 비용이 바뀌면서 오히려 정상적인 컨트랙트로의 송금까지 깨뜨리는 문제가 되었습니다. 그래서 가스를 충분히 전달할 수 있는 call 이 현재의 표준이 되었습니다.

call 은 강력한 만큼 재진입(reentrancy) 공격에 주의해야 합니다. 송금하기 전에 먼저 상태 변수를 갱신하는 Checks-Effects-Interactions 패턴(검사 -> 상태 변경 -> 외부 호출 순서)을 지키면 재진입 공격을 효과적으로 막을 수 있습니다.

Code Editor
에디터를 불러오는 중...
실행 결과

Run 버튼을 눌러 코드를 실행하세요.