Solidity를 이용해 간단한 경매 시스템을 구현하는 SimpleAuction 스마트 컨트랙트를 소개합니다. 이 컨트랙트는 경매 주최자가 설정한 기간 동안 입찰을 받고, 가장 높은 금액을 제시한 사람이 낙찰받도록 설계되었습니다.
// SPDX-License-Identifier: MIT
pragma solidity >= 0.7.0 < 0.8.0;
contract SimpleAuction {
// 경매에서 최종적으로 수익을 받을 주소 (경매 주최자)
address payable public beneficiary;
// 경매 종료 시간 (Unix timestamp 형식)
uint public auctionEnd;
// 현재 경매에서 최고 입찰자 주소
address public highestBidder;
// 현재 경매에서 제시된 최고 입찰 금액
uint public highestBid;
// 입찰에서 반환될 금액을 저장하는 매핑 (입찰자가 이전 입찰을 취소할 경우 반환됨)
mapping(address => uint) pendingReturns;
// 경매 종료 여부를 나타내는 변수
bool ended;
// 입찰이 증가할 때 발생하는 이벤트
event HighestBidIncreased(address bidder, uint amount);
// 경매 종료 시 발생하는 이벤트
event AuctionEnded(address winner, uint amount);
// 생성자: 경매 기간과 수혜자 주소를 설정
constructor(uint _biddingTime, address payable _beneficiary) {
beneficiary = _beneficiary; // 수혜자 주소 설정
auctionEnd = _biddingTime + block.timestamp; // 현재 시간에 경매 시간을 더해 종료 시간 설정
}
// 입찰 함수: payable을 사용하여 Ether를 받을 수 있도록 함
function bid() public payable {
// 경매가 종료되지 않았는지 확인
require(block.timestamp <= auctionEnd, "경매가 이미 종료되었습니다.");
// 현재 입찰가보다 높은지 확인
require(msg.value > highestBid, "이미 더 높은 입찰이 있습니다.");
// 이전 최고 입찰자의 금액을 반환하도록 설정 (직접 송금하지 않고 매핑을 이용하여 보안 강화)
if (highestBid != 0) {
pendingReturns[highestBidder] += highestBid;
}
// 새로운 최고 입찰자와 입찰 금액을 설정
highestBidder = msg.sender;
highestBid = msg.value;
// 이벤트 발생 (새로운 최고 입찰자 및 금액 기록)
emit HighestBidIncreased(msg.sender, msg.value);
}
// 이전 입찰자가 자신의 금액을 반환받을 수 있도록 하는 함수
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender]; // 반환할 금액을 가져옴
if (amount > 0) {
pendingReturns[msg.sender] = 0; // 재진입 공격 방지를 위해 먼저 0으로 설정
// 송금을 시도하고, 실패하면 금액을 다시 복원
if (!msg.sender.send(amount)) {
pendingReturns[msg.sender] = amount;
return false; // 송금 실패 시 false 반환
}
}
return true; // 정상적으로 반환되었을 경우 true 반환
}
// 경매 종료 함수
function aunctionEnd() public {
// 경매 시간이 지났는지 확인
require(block.timestamp >= auctionEnd, "경매가 아직 종료되지 않았습니다.");
// 이미 종료되었는지 확인
require(!ended, "경매 종료 함수가 이미 호출되었습니다.");
ended = true; // 경매 종료 상태로 변경
// 경매 종료 이벤트 발생 (승자 및 최고 입찰 금액 전달)
emit AuctionEnded(highestBidder, highestBid);
// 최고 입찰 금액을 수혜자(경매 주최자)에게 송금
beneficiary.transfer(highestBid);
}
}
코드 분석
아래는 스마트 컨트랙트의 주요 기능과 설명입니다.
1. 주요 변수
// 경매 주최자가 받을 주소
address payable public beneficiary;
// 경매 종료 시간
uint public auctionEnd;
// 최고 입찰자 정보
address public highestBidder;
uint public highestBid;
// 입찰자가 반환받을 금액을 저장하는 매핑
mapping(address => uint) pendingReturns;
// 경매 종료 여부
bool ended;
위 변수들은 경매의 기본적인 정보를 저장합니다. beneficiary는 경매 주최자의 주소이며, auctionEnd는 경매가 종료되는 시간을 나타냅니다. 최고 입찰자의 정보와 현재 최고 입찰 금액을 추적하기 위해 highestBidder와 highestBid 변수를 사용합니다. 또한, 이전 입찰자들이 입찰을 철회할 경우 반환받을 금액을 저장하는 pendingReturns 매핑도 포함됩니다.
2. 이벤트
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
HighestBidIncreased 이벤트는 새로운 최고 입찰자가 등장할 때 실행되며, AuctionEnded 이벤트는 경매가 종료될 때 발생합니다.
3. 생성자 (경매 초기화)
constructor(uint _biddingTime, address payable _beneficiary) {
beneficiary = _beneficiary;
auctionEnd = _biddingTime + block.timestamp;
}
생성자는 경매 주최자(beneficiary)를 설정하고, 경매 종료 시간을 현재 시간 + _biddingTime으로 설정합니다.
4. 입찰 함수
function bid() public payable {
require(block.timestamp <= auctionEnd, "경매가 이미 종료되었습니다.");
require(msg.value > highestBid, "이미 더 높은 입찰이 있습니다.");
if (highestBid != 0) {
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
이 함수는 사용자가 입찰할 때 호출됩니다.
- 현재 시간이 경매 종료 시간보다 작아야 하며 (require(block.timestamp <= auctionEnd)),
- 현재 최고 입찰 금액보다 커야 합니다 (require(msg.value > highestBid)).
- 이전 최고 입찰자는 pendingReturns 매핑을 통해 환불될 수 있도록 설정됩니다.
5. 입찰 금액 반환 (withdraw)
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
이 함수는 이전 입찰자가 자신의 입찰 금액을 철회할 때 사용됩니다. 재진입 공격 방지를 위해 송금 전에 pendingReturns[msg.sender] = 0으로 설정합니다.
6. 경매 종료
function aunctionEnd() public {
require(block.timestamp >= auctionEnd, "경매가 아직 종료되지 않았습니다.");
require(!ended, "경매 종료 함수가 이미 호출되었습니다.");
ended = true;
emit AuctionEnded(highestBidder, highestBid);
beneficiary.transfer(highestBid);
}
이 함수는 경매가 종료되었는지 확인한 후, 최고 입찰자 정보를 기록하고 수혜자(beneficiary)에게 최고 입찰 금액을 송금합니다.
개선해야 할 점
- aunctionEnd 함수의 오타:
aunctionEnd()
→auctionEnd()
로 수정해야 합니다. - send() 대신 call() 사용 고려:
send()
는 실패 시 예외를 발생시키지 않으므로,call()
을 이용하여 더 안전한 송금을 구현할 수 있습니다. - Reentrancy 공격 방지:
withdraw()
함수에서msg.sender.send(amount)
대신call
을 이용하고,Checks-Effects-Interactions
패턴을 적용하면 더 안전한 코드가 됩니다.
결론
이 컨트랙트는 기본적인 경매 시스템을 구현하는 좋은 예제입니다. 추가적인 보안 강화를 통해 더욱 안전한 스마트 컨트랙트를 만들 수 있습니다. 앞으로 업그레이드된 버전을 만들며 보안을 강화하는 과정도 다뤄보겠습니다!
'Decentralization' 카테고리의 다른 글
Win & Ubuntu CLI에서 Truffle 설정 및 스마트 계약 배포 가이드 (1) | 2025.03.17 |
---|---|
Solidity로 기본적인 토큰 판매 스마트 컨트랙트 작성 (1) | 2025.03.15 |
Solidity로 복권 스마트 컨트랙트 구현하기 (2) | 2025.03.15 |
Solidity로 간단한 인사말 스마트 계약 만들기 (2) | 2025.03.15 |
Solidity 기초 스마트 계약을 이용한 토큰 시스템 만들기 (1) | 2025.03.15 |