컨트랙트를 외부에서 호출하는 것은 실수로 또는 고의로 실패할 수 있고 이로 인해 컨트랙트에 DoS의 조건이 생길 수 있다. 이러한 실패로 인한 피해를 최소화하려면 각 외부 호출을 호출 수신자가 시작할 수 있는 자체 트랜잭션으로 분리하는 것이 좋다. 이는 특히 결제와 관련이 있으며, 사용자가 자동으로 자금을 푸시하는 것보다 자금을 인출하는 것이 더 좋다(이는 또한 가스 한도 문제가 발생할 가능성을 줄인다)
그것을 방지하기 위해서는
1. 하나의 트랜잭션에 여러 가지 call을 피한다. 특히 반복되는 call을 실행하는 것을.
2. 항상 외부 호출이 실패할 수 있음을 가정해야 한다.
3. call이 실패를 다루는 컨트랙트 로직을 실행해야 한다.
// bad
contract auction {
address highestBidder;
uint highestBid;
function bid() payable {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
(bool success, ) = highestBidder.call.value(highestBid)("");
require(success); // if this call consistently fails, no one else can bid
}
highestBidder = msg.sender;
highestBid = msg.value;
}
}
여기서 bid함수에서 call을 하는데 실패하면 아무도 bid를 하지 못하니까. bid랑 돈을 받는 함수랑 따로 분리를 하는 게 맞다.
// good
contract auction {
address highestBidder;
uint highestBid;
mapping(address => uint) refunds;
function bid() payable external {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
refunds[highestBidder] += highestBid; // record the refund that this user can claim
}
highestBidder = msg.sender;
highestBid = msg.value;
}
function withdrawRefund() external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
(bool success, ) = msg.sender.call.value(refund)("");
require(success);
}
}
[ 참고 및 추가자료 ]
1. https://swcregistry.io/docs/SWC-113/
2. https://consensys.github.io/smart-contract-best-practices/development-recommendations/general/external-calls/#favor-pull-over-push-for-external-calls
'자주나오는패턴' 카테고리의 다른 글
[Common Vulnerabilities] State Variable Default Visibility (0) | 2024.01.17 |
---|---|
[Common Vulnerabilities] Unprotected SELFDESTRUCT Instruction (0) | 2024.01.16 |
[Common Vulnerabilities] Unchecked Call Return Value (0) | 2024.01.16 |
[Common Vulnerabilities] Floating Pragma (0) | 2024.01.10 |
[Common Vulnerabilities] Integer Overflow and Underflow (0) | 2024.01.10 |