Blind Auction
- 하나의 작품을 경매로 판매
- 경매 단계(init, bidding, reveal, done)
- beneficiary가 경매를 시작하고 bidding단계 동안 입찰 진행
- 입찰은 보안이 보장되고, private하게 진행
- beneficiary제외하고 입찰의 내용 확인 불가
- beneficiary가 경매를 reveal 단계로 변경하면 입찰자의 입찰 내역 공개
가장 높은 가격을 제시한 입찰자와 입찰가격을 찾음
- beneficiary는 done으로 단계 변경 경매 종료
- beneficiary는 어카운트로 최고 입찰가 전송
- 낙찰된 입찰자를 제외한 나머지는 입찰자는 자시느이 예치금 출금 가능
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract BlindAuction {
struct Bid { //입찰정보
bytes32 blindedBid;
uint deposit;
}
// beneficiary에 의해 설정됨
enum Phase {Init, Bidding, Reveal, Done}
Phase public currentPhase = Phase.Init;
// Owner
address payable public beneficiary; //contract 배포자
mapping(address => Bid) public bids; //주소당 오직 1회 입찰
// 최고가 입찰자의 정보
address public highestBidder;
uint public highestBid = 0;
//낙찰 탈락자의 예치금 반환
mapping(address => uint) pendingReturns;
// 수정자
modifier validPhase(Phase phase) { //경매단계를 위한 수정자
require(currentPhase == phase, "phaseError");
_;
}
modifier onlyBeneficiary() { //수혜자를 확인하는 수정자
require(msg.sender == beneficiary, "onlyBeneficiary");
_;
}
// Events
event AuctionEnded(address winner, uint highestBid);
event BiddingStarted();
event RevealStarted();
event AuctionInit();
constructor() public { //constructor가 beneficiary를 설정
beneficiary = payable(msg.sender);
// advancePhase();
}
function advancePhase() public onlyBeneficiary {
// If already in done phase, reset to init phase
if (currentPhase == Phase.Done) {
currentPhase = Phase.Init;
} else {
// else, increment the phase
// Conversion to uint needed as enums are internally uints
uint nextPhase = uint(currentPhase) + 1;
currentPhase = Phase(nextPhase);
}
// Emit appropriate events for the new phase
if (currentPhase == Phase.Reveal) emit RevealStarted();
if (currentPhase == Phase.Bidding) emit BiddingStarted();
if (currentPhase == Phase.Init) emit AuctionInit();
}
function bid(bytes32 blindBid) public payable validPhase(Phase.Bidding) { //블라인드 경매 함수
require(msg.sender != beneficiary,'beneficiaryBid'); // Beneficiary should not be allowed to place bids
bids[msg.sender] = Bid({blindedBid: blindBid, deposit: msg.value});
}
function reveal(uint value, bytes32 secret) public validPhase(Phase.Reveal) { //입찰가격이 최고가인지 확인
require(msg.sender != beneficiary,'beneficiaryReveal'); //입찰자의 입찰가와 시크릿 패스워드 공개
uint refund = 0;
Bid storage bidToCheck = bids[msg.sender];
if (bidToCheck.blindedBid == keccak256(abi.encodePacked(value, secret))) {
refund += bidToCheck.deposit;
if (bidToCheck.deposit >= value*1000000000000000000) {
if (placeBid(msg.sender, value*1000000000000000000))
refund -= value * 1000000000000000000;
}
}
payable(msg.sender).transfer(refund);
}
// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
function placeBid(address bidder, uint value) internal returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// 이전 최고가 입찰자에게 환불
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
// 낙찰 탈락 입찰 출금
// withdraw()는 낙찰 탈락자에 의해 호출
function withdraw() public {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
// 경매를 종료하고 수혜자에게 최고가 입찰액을 전송
//done단계에서 호출된다
function auctionEnd() public validPhase(Phase.Done) {
if(address(this).balance >= highestBid){
beneficiary.transfer(highestBid);
}
emit AuctionEnded(highestBidder, highestBid);
}
function closeAuction() public onlyBeneficiary {
selfdestruct(beneficiary);
}
}