블록체인 학습
0 XP

payable 키워드

기본적으로 Solidity 의 함수와 주소는 Ether 를 받을 수 없습니다. 버그가 아니라 의도된 안전장치입니다. 모든 함수가 아무 때나 자금을 받을 수 있다면, 실수로 잘못된 함수에 Ether 를 보냈을 때 그 돈이 컨트랙트에 갇혀 영영 꺼내지 못할 수 있습니다.

그래서 Solidity 는 "이 함수/주소는 명시적으로 자금을 받을 준비가 되어 있다"고 선언할 때만 payable 키워드를 붙이도록 강제합니다.

payable 함수

함수 선언에 payable 을 붙이면, 그 함수는 호출과 함께 전송된 Ether 를 받아들일 수 있습니다. 이렇게 받은 Ether 는 별도 코드 없이도 자동으로 컨트랙트의 잔액(address(this).balance)에 더해집니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
contract Wallet {
    // payable 이 있어야 이 함수로 Ether 를 보낼 수 있습니다.
    function deposit() public payable {
        // 보내진 Ether 는 자동으로 컨트랙트 잔액에 더해집니다.
    }
 
    // payable 이 없는 함수로 Ether 를 보내면 트랜잭션이 실패(revert)합니다.
    function justRead() public pure returns (string memory) {
        return "no ether here";
    }
}

payable 이 없는 함수에 Ether 를 함께 보내면 트랜잭션 전체가 되돌려집니다(revert). 자금을 받는 송금 경로에는 반드시 payable 이 있어야 합니다. 빠뜨리면 컴파일은 되지만 입금할 때 실패합니다.

다만 payable 이 자금 유입을 완전히 막아 주지는 못합니다. 다른 컨트랙트가 selfdestruct 로 잔액을 떠넘기거나, 이 컨트랙트 주소가 블록 보상 수령 주소로 지정되면 payable·receive·fallback 이 하나도 없어도 잔액이 늘어납니다. 그래서 "내가 센 입금 금액 == address(this).balance" 라고 가정하는 코드는 위험합니다. 잔액은 별도의 상태 변수 로 직접 관리하세요.

payable 주소

payable 은 함수뿐 아니라 주소 타입에도 적용됩니다. 일반 address 타입에는 transfer 와 send 멤버가 없습니다. 이 둘로 Ether 를 보내려면 먼저 payable(address) 로 변환해 address payable 타입으로 만들어야 합니다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
contract Payout {
    function toPayable(address who) public pure returns (address payable) {
        // 일반 address 를 송금 가능한 address payable 로 변환
        return payable(who);
    }
}

다만 "Ether 를 보내려면 무조건 address payable 이어야 한다" 는 것은 사실이 아닙니다. transfer·send 만 address payable 을 요구하고, 오늘날 권장되는 addr.call{value: amount}("") 는 일반 address 에서도 그대로 호출할 수 있습니다. 세 방식의 차이는 이 차시 뒤쪽에서 자세히 비교합니다.

생성자(constructor)에도 payable 을 붙일 수 있습니다. 그러면 컨트랙트를 배포하는 트랜잭션과 동시에 Ether 를 함께 받아 초기 자금을 넣을 수 있습니다.

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

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