Blockchain based smart contract for auction. Part-II

Mohamed Nufail
7 min readDec 26, 2022

--

Solidity and Remix IDE

Solidity is a programming language for writing smart contracts, which are programs that run on the Ethereum blockchain. Solidity is used to implement, deploy, and execute smart contracts on the Ethereum platform.

Remix is a Integrated Development Environment (IDE) for writing and debugging Solidity contracts. It is a tool that provides a user-friendly interface for writing, testing, and debugging Solidity code. Remix is part of the Ethereum development platform.

Smart contract for the auction

This is a smart contract for an auction. The product owner can specify the end time, product hash, coupon code, and minimum bid for the auction using constructor parameters. The product owner can also end the auction early using the ‘end auction’ function. When the auction ends, the bided amounts of all non-winning bidders will be refunded, and the highest bidder will receive the coupon code.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract Auction {
// save the address of the product owner
address payable public productOwner;
// store the auction end time
uint256 public auctionEndTime;
// store the hash of the product
string public productHash;
// store the coupon code from the product owner
string public couponCode;
// save the minimum bid amount
uint256 public minimumBid;

//current state of auction
address public highestBidder;
uint256 highestBid;
address[] public allBidders;
uint256[] allBids;

//Return the bid amount for lost bidders
mapping(address => uint256) pendingReturns;

//set to true at the end of the auction to disallows any change
//by default initialized to false
bool ended = false;

// Auction in active
modifier isOngoing() {
require(
block.timestamp < auctionEndTime,
"The auction is already ended"
);
_;
}

// Auction ended
modifier notOngoing() {
require(
block.timestamp >= auctionEndTime,
"This auction is still open"
);
_;
}

// check the product owner address
modifier isProductOwner() {
require(msg.sender == productOwner, "Only the owner can end the auction");
_;
}
modifier notProductOwner() {
require(msg.sender != productOwner, "Owner is not allowed to bid");
_;
}

// Check the auction status
modifier notEnded() {
require(ended == false, "The auction is ended by the Seller");
_;
}
modifier isEnded() {
require(ended == true, "This auction is still open");
_;
}

// Perform only by the highest bidder
modifier isHighestBidder() {
require(highestBidder == msg.sender, "You are not the highest bidder");
_;
}

// Bid should be greater than the minimum bid which defined by owner
modifier greterThanMinBid() {
require(
msg.value >= minimumBid,
"Bid must be greater than or equal to the minimum bid"
);
_;
}

// Bid should be greater than the recent highest bid
modifier greaterThanHighestBid() {
require(
msg.value > highestBid,
"Bid must be greater than the current highest bid"
);
_;
}

//event that will be emitted on changes
event highestBidIncreased(address bidder, uint256 amount);
event auctionEnded(address winner, uint256 amount);

///The auction is already ended
error AuctionAlreadyEnded();

///There is already a higher or equal bid
error BidNotHighEnough(uint256 highestBid);

///The auction has not ended yet
error AuctionNotYetEnded();

///The function auctionEnd has already been called
error AuctionEndAlreadyCalled();

error BidNotHigerThanMinimum(uint256 minimumBid);

// Get the initial parameters from the product owner
constructor(
uint256 biddingTime,
string memory _productHash,
string memory _couponCode,
uint256 _minimumBid
) {

productOwner = payable(msg.sender);
auctionEndTime = block.timestamp + biddingTime;
productHash = _productHash;
couponCode = _couponCode;
minimumBid = _minimumBid;
}

// Return the product hash to identify the product
function fetchProductHash() public view returns (string memory) {
return productHash;
}

// Return the coupon code for the highet bidder at the end of the auction
function fetchCouponCode()
public
view
isHighestBidder
isEnded
returns (string memory)
{
return couponCode;
}

// Return the highest bidder address
function fetchHighestBidder() public view isEnded returns (address) {
return highestBidder;
}

// Return the address of the product owner
function getOwner() public view returns (address) {
return productOwner;
}

// Get the minimum bid which is defined by the product owner
function getMinimumBid() public view returns (uint256) {
return minimumBid;
}

// Get the current highest bid of the auction
function getHighestBid() public view returns (uint256) {
return highestBid;
}


// Bidding function with all the modifiers
function bid()
external
payable
notProductOwner
isOngoing
notEnded
greterThanMinBid
greaterThanHighestBid
{
if (highestBid != 0) {
// Save all the bidders addresses and amount
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
allBidders.push(highestBidder);
allBids.push(highestBid);

emit highestBidIncreased(msg.sender, msg.value);
}

///withdraw a bid that was overbid
function withdraw() external notProductOwner returns (bool) {
uint256 amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;

if (!payable(msg.sender).send(amount)) {
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}

function auctionEnd() external isProductOwner {
//effects
ended = true;
emit auctionEnded(highestBidder, highestBid);

//interaction
productOwner.transfer(highestBid);
// delete pendingReturns[highestBidder];

// refund the amounts to the lost bidders
for (uint256 j = 0; j < allBidders.length - 1; j++) {
uint256 amount = pendingReturns[allBidders[j]];
if (amount > 0) {
pendingReturns[allBidders[j]] = 0;

if (!payable(allBidders[j]).send(amount)) {
pendingReturns[allBidders[j]] = amount;
}
}
}
}
}

Test a smart contract using truffle and ganache.

You can also manually test your smart contract by interacting with it using a tool such as Remix or MyEtherWallet. This can help you ensure that your smart contract is working as expected and catch any issues before you deploy it to the main network. Another way is to use a testing framework, such as Mocha, Chai, Waffle, Truffle and Ganache, , to write and run test cases for your smart contract. These test cases can help you verify that your smart contract is functioning as intended.

Truffle is a popular development framework for Ethereum that helps developers build, test, and deploy smart contracts. It is a comprehensive toolkit that includes a suite of libraries and tools for working with Ethereum, as well as a command-line interface (CLI) for managing your projects. Ganache is a local blockchain for Ethereum development that is included as part of the Truffle suite of tools. It is a lightweight, in-memory blockchain that is designed for testing and development purposes. Ganache allows developers to run their own Ethereum blockchain on their local machine, which can be used for testing and debugging their smart contracts.

Ganache is easy to use and can be launched from the command line or from within Truffle. It provides a user-friendly interface for managing your local blockchain, as well as a range of tools and features for testing and debugging your smart contracts.

To install Truffle and Ganache-CLI, you will need to have Node.js and npm (the Node Package Manager) installed on your computer.

To install Truffle, you can use the following npm command:

npm install -g truffle

To install Ganache-CLI, you can use the following npm command:

npm install -g ganache-cli

Once you have Truffle and Ganache-CLI installed, you can create a new project folder and named it as “test”, navigate to it in your terminal. Then, you can run the following command to create a new Truffle project:

truffle init

This will create a new project structure with the following folders:

  • contracts: This is where you will put your Solidity contract files
  • migrations: This is where you will put your migration scripts, which are used to deploy your contracts to the blockchain
  • test: This is where you will put your test files

to create a migration script

truffle migrate

Write test cases using Mocha and Chai

Truffle uses the Mocha testing framework and Chai for assertions to provide you with a solid framework from which to write your JavaScript tests. Here is an example of how you could write test cases for an auction smart contract with a minimum bidding value and an end auction function using Remix and the Mocha testing framework:


const Auction = artifacts.require("Auction.sol");

contract("Auction", (accounts) => {
let auction;
const owner = accounts[0];
const bidder1 = accounts[1];
const bidder2 = accounts[2];

beforeEach(async () => {
// Deploy a new instance of the contract before each test
auction = await Auction.new(1000,"productHash","couponCode",10, { from: owner });
});

it("should allow a bidder to place a bid that is greater than the minimum bid", async () => {
const result = await auction.bid({ from: bidder1, value: 11 });
assert.equal(result.receipt.status, true);
});

it("should not allow a bidder to place a bid that is less than the minimum bid", async () => {
try {
await auction.bid({ from: bidder1, value: 9 });
assert.fail();
} catch (error) {
assert.include(error.message, "Bid must be greater than or equal to the minimum bid");
}
});

it("should not allow a bidder to place a bid that is not greater than the current highest bid", async () => {
await auction.bid({ from: bidder1, value: 11 });
try {
await auction.bid({ from: bidder2, value: 11 });
assert.fail();
} catch (error) {
assert.include(error.message, "Bid must be greater than the current highest bid");
}
});

it("should allow the owner to end the auction and transfer the winning bid to the contract owner", async () => {
await auction.bid({ from: bidder1, value: 11 });
const result = await auction.auctionEnd({ from: owner });
assert.equal(result.receipt.status, true);
});

it("should not allow non-owners to end the auction", async () => {
try {
await auction.auctionEnd({ from: bidder1 });
assert.fail();
} catch (error) {
assert.include(error.message, "Only the owner can end the auction");
}
});

it("should return the minimum bid value", async () => {
try {
const result = await auction.getMinimumBid({ from: bidder1 });
assert.equal(result,10)
} catch (error) {
assert.include(error.message, "Only the owner can end the auction");
}
});
});

you can use the following command to run the test cases,

truffle test

Note: This is just one example of how you could write test cases for an auction smart contract. There may be other test cases that you want to include, depending on the specific functionality of your contract.

--

--