-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauction.sol
More file actions
120 lines (93 loc) · 3.88 KB
/
Copy pathauction.sol
File metadata and controls
120 lines (93 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Auction contract with owner-controlled finalization and partial refunds
/// @author Francesco Centarti Maestu
/// @notice Implements a timed auction with incremental bids, commission, partial refunds, and emergency withdrawal
contract Auction {
address public owner;
uint public auctionEndTime;
address public highestBidder;
uint public highestBid;
bool public auctionEnded;
uint constant COMMISSION_RATE = 2; // 2% commission
uint constant MIN_INCREMENT_PERCENT = 5; // Minimum 5% increase to outbid
uint constant EXTENSION_TIME = 10 minutes;
mapping(address => uint) public bids;
mapping(address => uint[]) public bidHistory;
mapping(address => uint) public refundableBalances;
address[] private biddersList;
event NewBid(address indexed bidder, uint amount);
event AuctionEnded(address winner, uint amount);
event PartialRefund(address indexed bidder, uint amount);
modifier onlyOwner() {
require(msg.sender == owner, "notOwner");
_;
}
constructor(uint _durationInMinutes) {
owner = msg.sender;
auctionEndTime = block.timestamp + (_durationInMinutes * 1 minutes);
}
function timeLeft() external view returns (uint) {
if (block.timestamp >= auctionEndTime) {
return 0;
}
return auctionEndTime - block.timestamp;
}
function placeBid() external payable {
require(block.timestamp < auctionEndTime, "ended");
require(msg.value > 0, "zero");
uint minRequired = highestBid == 0 ? 0 : highestBid + (highestBid * MIN_INCREMENT_PERCENT) / 100;
require(msg.value > minRequired, "lowBid");
if (bids[msg.sender] > 0) {
refundableBalances[msg.sender] += bids[msg.sender];
} else {
biddersList.push(msg.sender);
}
bids[msg.sender] = msg.value;
bidHistory[msg.sender].push(msg.value);
highestBidder = msg.sender;
highestBid = msg.value;
if (auctionEndTime - block.timestamp <= EXTENSION_TIME) {
auctionEndTime += EXTENSION_TIME;
}
emit NewBid(msg.sender, msg.value);
}
function showWinner() external view returns (address winner, uint bid) {
return (highestBidder, highestBid);
}
function showBidHistory(address bidder) external view returns (uint[] memory) {
return bidHistory[bidder];
}
function withdrawPartialRefund() external {
uint amount = refundableBalances[msg.sender];
require(amount > 0, "noRefund");
// Reset refundable balance before transfer to prevent re-entrancy
refundableBalances[msg.sender] = 0;
payable(msg.sender).transfer(amount);
emit PartialRefund(msg.sender, amount);
}
function endAuction() external onlyOwner {
require(block.timestamp >= auctionEndTime, "ongoing");
require(!auctionEnded, "ended");
auctionEnded = true;
uint commission = (highestBid * COMMISSION_RATE) / 100;
// Transfer commission to owner first
payable(owner).transfer(commission);
uint biddersCount = biddersList.length;
for (uint i = 0; i < biddersCount; i++) {
address bidder = biddersList[i];
if (bidder != highestBidder) {
uint refundAmount = bids[bidder];
if (refundAmount > 0) {
bids[bidder] = 0;
payable(bidder).transfer(refundAmount);
}
}
}
emit AuctionEnded(highestBidder, highestBid);
}
function emergencyWithdraw() external onlyOwner {
require(auctionEnded, "notFinalized");
payable(owner).transfer(address(this).balance);
}
}