Auctions in the Digital Age: Crafting Your Debut Smart Contract as a Novice Developer

Daniel Pham
6 min readAug 11, 2023

--

Walkthrough of the Auction Smart Contract

In the landscape of decentralized commerce, auctions play a pivotal role in enabling transparent and competitive transactions. Imagine a scenario where participants compete to place their bids, aiming to win an item up for grabs. The power of smart contracts allows us to create a robust and automated system for conducting auctions, revolutionizing the way assets change hands within the digital realm.

Coding Phase: Initiating the Implementation Process

Variables and Constructors

The owner, whose address will deploy the contract, is owner. We will declare two variables of type uint as startBlock and endBlock. These will be used to calculate the start and end times of the auction.

The item listed for auction will also have a description, and the product will include some descriptive images. Storing this information on the blockchain is costly, so we will use an alternative off-chain solution.

For instance, we can store all information about the item on IPFS or a file system. We will use an IPFS Hash to uniquely identify the information, setting the variable as ipfsHash.

address payable public owner;
uint public startBlock;
uint public endBlock;
string public ipfsHash;

We declare an enum type called State to store the status of the auction session: enum State {Started, Running, Ended, Canceled}.

We declare a variable named auctionState of the type State to represent the status of the auction session, and a public uint variable named highestBindingBid to hold the highest bidding price.

There’s also a variable named highestBidder to store the address of the highest bidder.

enum State {Started, Running, Ended, Canceled}
State public auctionState;
uint public highestBindingBid;
address payable public highestBidder;

We require mapping variables referred to as bids to store the bids for each bidder’s address and the corresponding value sent in the auction. The variable indicating the bidding price increment is bidIncrement and the variable ownerFinalized marks the completion of the highest bid.

mapping(address => uint) public bids;
uint bidIncrement;
// the owner can finalize the auction and get the highestBindingBid only once
bool public ownerFinalized = false;

We initialize the owner to the deploying contract’s address: owner = payable(msg.sender) and set the auction status to “Running”: auctionState = State.Running .

Next, we establish the start and end dates of the auction. Considering that an Ethereum block time is around 15 seconds, a new block is added to the blockchain approximately every 15 seconds. Using block intervals, we can calculate the start and end times of the auction. So, startBlock = block.number, where block.number is a global variable in Solidity.

To determine the number of blocks, we divide the time in seconds by 15. In a week, around 40,320 blocks are generated, indicating the auction’s duration for a week. For our smart contract testing purposes, let’s use 3 blocks: endBlock = startBlock + 3 .

We initialize other state variables: ipfsHash = “” (an empty string), and the bidding price increment bidIncrement will be 10¹⁸ wei.

constructor() {
owner = payable(msg.sender);
auctionState = State.Running;

startBlock = block.number;
endBlock = startBlock + 3;

ipfsHash = "";
bidIncrement = 1000000000000000000; // bidding in multiple of ETH
}

The function min(uint a, uint b) serves as a simple utility. It compares the minimum value between “a” and “b,” returning the smaller value as the result.

function min(uint a, uint b) pure internal returns(uint) {
if (a <= b) {
return a;
} else {
return b;
}
}

Function Modifiers

Modifier functions are employed to modify the behavior of a function. The body of the function is executed only if the required condition of the modifier returns true. We require four modifier functions as follows:

  • notOwner() executes when (msg.sender != owner) .
  • onlyOwner() executes when (msg.sender == owner) .
  • afterStart() executes when (block.number >= startBlock) .
  • beforeEnd() executes when (block.number <= endBlock) .
modifier notOwner() {
require(msg.sender != owner);
_;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

modifier afterStart() {
require(block.number >= startBlock);
_;
}

modifier beforeEnd() {
require(block.number <= endBlock);
_;
}

cancelAuction

In case of emergencies, such as discovering a vulnerability in the code or any adverse events, the owner has the capability to cancel the auction. This allows all bidders to request refunds for their contributions.

function cancelAuction() public beforeEnd onlyOwner {
auctionState = State.Canceled;
}

placeBid

We will declare a function named placeBid, which will be called when someone intends to place a buying bid.

There are certain limitations when calling this function. We do not permit the owner to place a buying bid as they might artificially inflate the price. Therefore, a modifier function named notOwner will be applied to this function.

Another constraint is that the auction can only occur within the start and end ranges. So, additional modifier functions are applied: afterStart and a second modifier function applied on the end: beforeEnd.

We add another restriction that the auction session must be in the “Running” state: require(auctionState == State.Running) .

Adding a local uint variable named currentBid. This value includes the amount sent by the current address plus the value sent with this transaction: uint currentBid = bids[msg.sender] + msg.value .

Next, we update the bidding price for the current user, hence: bids[msg.sender] = currentBid .

Now, there are two possibilities:

  1. The current bidding price is higher than highestBindingBid but less than bids[highestBidder]. In this case, the highest bidder remains unchanged. highestBindingBid = min(currentBid + bidIncrement, bids[highestBidder]) .
  2. Another possibility is that currentBid > bids[highestBidder]. In this case, highestBindingBid = min(currentBid, bids[highestBidder] + bidIncrement) .
function placeBid() public payable notOwner afterStart beforeEnd returns(bool) {
require(auctionState == State.Running);

uint currentBid = bids[msg.sender] + msg.value;

require(currentBid > highestBindingBid);
bids[msg.sender] = currentBid;

if (currentBid <= bids[highestBidder]) {
highestBindingBid = min(currentBid + bidIncrement, bids[highestBidder]);
} else {
highestBindingBid = min(currentBid, bids[highestBidder] + bidIncrement);
highestBidder = payable(msg.sender);
}
return true;
}

finalizeAuction

The function finalizeAuction checks whether the auction is canceled or has ended. If the auction was canceled or ended by the owner, they can finalize it. We define two variables: recipient to store the address to receive the money sent in the auction, and value to store the amount.

In the case of a canceled auction, if auctionState == State.Canceled, the recipient is set to the sender, and the value is the bid amount placed by the sender.

If the auction has ended (not canceled), meaning it was concluded by the owner, they will receive the highest binding bid.

The funds are then transferred to the recipient, and the auction state is set to State.Ended.

There is another branch; the auction finalization could be requested by an address that is not the owner, but a bidder who wants to claim their own funds. In this case, there are also two possibilities:

  1. If the caller is the highest bidder, they will receive the difference between the value they bid and the highest binding bid: recipient = highestBidder and value = bids[highestBidder] — highestBindingBid .
  2. If the caller is neither the owner nor the highest bidder, they are one of the bidders in the auction: recipient = payable(msg.sender) and value = bids[msg.sender] .
function finalizeAuction() public {
require(auctionState == State.Canceled || block.number > endBlock);
require(msg.sender == owner || bids[msg.sender] > 0);

address payable recipient;
uint value;
if (auctionState == State.Canceled) {
recipient = payable(msg.sender);
value = bids[msg.sender];
} else {
if (msg.sender == owner && ownerFinalized == false) {
recipient = owner;
value = highestBindingBid;
ownerFinalized = true;
} else {
if (msg.sender == highestBidder) {
recipient = highestBidder;
value = bids[highestBidder] - highestBindingBid;
} else {
recipient = payable(msg.sender);
value = bids[msg.sender];
}
}
}
bids[recipient] = 0;
recipient.transfer(value);
}

Conclusion

Combining all the codes together, we create a simple yet practical and fully functional Auction smart contract.

Congratulations on successfully designing and implementing the Auction Smart Contract! Feel free to explore and experiment with more advanced concepts as you continue your venture in blockchain development.

--

--

Daniel Pham
Daniel Pham

Written by Daniel Pham

Blockchain Developer | Mobile Developer | Full-stack Developer

No responses yet