PullPayment 는 스마트 컨트랙트에서 여러 사용자에게 지불해야 할 금액을 관리하면서, 재진입 공격을 방지하고 가스비 문제를 해결하기 위해 사용됩니다.

PullPayment는 사용자에게 직접 돈을 보내는 방식(Push-Payment)이 아닌, 사용자가 스스로 요청하여 지불을 받을 수 있도록 설계되었습니다. 


 

주로 사용되는 상황

  1. 경매: 경매에서 낙찰되지 않은 사용자가 입찰금을 환불받아야 하는 경우.
  2. 마켓플레이스: 판매자와 구매자 간 결제를 중재하는 경우.
  3. 펀드 배분: 다수의 수익자에게 분배할 금액이 있을 때.
  4. 보안: 외부 호출로 인해 발생할 수 있는 재진입 공격을 방지해야 할 때.

 

코드 예시

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/PullPayment.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Auction is PullPayment, Ownable {
    uint256 public highestBid;
    address public highestBidder;

    // Function to place a bid
    function bid() external payable {
        require(msg.value > highestBid, "Bid must be higher than current highest bid");

        // Refund the previous highest bidder using PullPayment
        if (highestBidder != address(0)) {
            _asyncTransfer(highestBidder, highestBid);
        }

        highestBid = msg.value;
        highestBidder = msg.sender;
    }

    // Function to withdraw funds (for testing purposes)
    function withdrawPayments() external {
        withdrawPayments(payable(msg.sender));
    }
}

 


함수 설명

  1. _asyncTransfer(address recipient, uint256 amount)
    • 설명 : 특정 수신인에게 비동기적으로 지불 금액을 기록합니다. 실질적으로 금액을 즉시 전송하지 않고, PullPayment 컨트랙트의 내부 payments 매핑에 금액을 기록합니다. 
    • 작동방식:
      • payments[recipient] += amount;
      • 수신인의 총 금액이 누적됩니다. 
  2. withdrawPayments(address payable payee)
    • 설명: 특정 사용자가 자신의 누적된 지불금을 인출할 수 있습니다. 
    • 작동방식:
      • payments[payee] 값을 확인.
      • 값이 0보다 크면, 해당 금액을 payee 에게 전송.
      • 성공적으로 전송 후 payments[payee] = 0; 으로 초기화
  3. payments(address payee)
    • 설명: 특정 사용자(payee)가 받을 수 있는 잔액을 반환합니다. 
    • 작동방식:
      • 단순히 payments 매핑에서 값을 조회.

 


작동 흐름

  1. 비동기 전송 기록: _asyncTransfer를 사용하여 지불금을 payments에 기록
  2. 사용자 요청: 사용자는 withdrawPayments를 호출하여 자신의 금액ㅇ르 요청.
  3. 안전한 전송: 재진입 공격 방지를 위해 상태를 먼저 업데이트(payments[payee] = 0). 이후 payee.transfer(amount)로 금액 전송

장점

  1. 재진입 공격 방지: 직접적인 transfer가 아니라 withdraw 방식으로 외부 호출의 영향을 줄임.
  2. 효율적인 지불 관리: 여러 사용자의 결제 요청을 개별적으로 처리
  3. 가스비 절약: 모든 지불을 한 번에 처리하지 않고 필요한 사용자만 요청하게 함.

레퍼런스

OpenZeppelin Documentation - PullPayment

Ethereum Best Practices

+ Recent posts