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 를 함께 받아 초기 자금을 넣을 수 있습니다.