Smart Contract - Auction

Posted by Bourne's Blog - A Full-stack & Web3 Developer on May 8, 2024

Smart Contract - Auction

It’s the final assignment from coursera Decentralized Applications (Dapps).

Problem Statement

Consider the problem of Chinese auction or penny social. We will refer to it as simple “Auction.” It is a conventional approach used for fundraising for a cause. The organizers collect items to be auctioned off for raising funds. Before the auction, the items for auctions are received and arranged each with a bowl to place the bid. A chairperson is a special person among the organizers. She/he heads the effort and is the only person who can determine the winner by random drawing at the end of the auction. A set of bidders buy sheets of tickets with their money. The bidder’s sheet has a stub that identifies the bidder’s number, and tokens bought. The bidders examine the items to bid, place the one or more tickets in the bowl in front of the items they desire to bid for until all the tickets are used. After the auction period ends the chairperson, collects the bowls, randomly selects a ticket from each item’s bowl to determine the winning bidder for that item. The item is transferred to the winning bidder. Total money collected is the fund raised by the penny social auction.

Contract

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
pragma solidity ^0.4.17;
contract Auction {
    
    // Data
    //Structure to hold details of the item
    struct Item {
        uint itemId; // id of the item
        uint[] itemTokens;  //tokens bid in favor of the item
       
    }

   //Structure to hold the details of a persons
    struct Person {
        uint remainingTokens; // tokens remaining with bidder
        uint personId; // it serves as tokenId as well
        address addr;//address of the bidder
    }
 
    mapping(address => Person) tokenDetails; //address to person 
    Person [4] bidders;//Array containing 4 person objects
    
    Item [3] public items;//Array containing 3 item objects
    address[3] public winners;//Array for address of winners
    address public beneficiary;//owner of the smart contract
    
    uint bidderCount=0;//counter
    
    //functions

    function Auction() public payable{    //constructor
                
        //Part 1 Task 1. Initialize beneficiary with address of smart contract’s owner
        //Hint. In the constructor,"msg.sender" is the address of the owner.
        // ** Start code here. 1 line approximately. **/
        beneficiary = msg.sender;
          //** End code here. **/
        uint[] memory emptyArray;
        items[0] = Item({itemId:0,itemTokens:emptyArray});
        
        //Part 1 Task 2. Initialize two items with at index 1 and 2. 
        // ** Start code here. 2 lines approximately. **/
        uint[] memory emptyArray1;
        items[1] = Item({itemId:0,itemTokens:emptyArray1});
        uint[] memory emptyArray2;
        items[2] = Item({itemId:0,itemTokens:emptyArray2});
        //** End code here**/
    }
    

    function register() public payable{
        
        
        bidders[bidderCount].personId = bidderCount;
        
        //Part 1 Task 3. Initialize the address of the bidder 
        /*Hint. Here the bidders[bidderCount].addr should be initialized with address of the registrant.*/

        // ** Start code here. 1 line approximately. **/
        bidders[bidderCount].addr = msg.sender;
        //** End code here. **
        
        bidders[bidderCount].remainingTokens = 5; // only 5 tokens
        tokenDetails[msg.sender]=bidders[bidderCount];
        bidderCount++;
    }
    
    function bid(uint _itemId, uint _count) public payable{
        /*
            Bids tokens to a particular item.
            Arguments:
            _itemId -- uint, id of the item
            _count -- uint, count of tokens to bid for the item
        */
        
        /*
        Part 1 Task 4. Implement the three conditions below.
            4.1 If the number of tokens remaining with the bidder is < count of tokens bidded, revert.
            4.2 If there are no tokens remaining with the bidder, revert.
            4.3 If the id of the item for which bid is placed, is greater than 2, revert.

        Hint: "tokenDetails[msg.sender].remainingTokens" gives the details of the number of tokens remaining with the bidder.
        */
        
        // ** Start code here. 2 lines approximately. **/
        require(tokenDetails[msg.sender].remainingTokens > 0);
        require(tokenDetails[msg.sender].remainingTokens >= _count);
        require(_itemId < 3);
        //** End code here. **
        
        /*Part 1 Task 5. Decrement the remainingTokens by the number of tokens bid and store the value in balance variable.
        Hint. "tokenDetails[msg.sender].remainingTokens" should be decremented by "_count". */
 
        // ** Start code here. 1 line approximately. **
        uint balance = tokenDetails[msg.sender].remainingTokens - _count;
        //** End code here. **
        
        tokenDetails[msg.sender].remainingTokens=balance;
        bidders[tokenDetails[msg.sender].personId].remainingTokens=balance;//updating the same balance in bidders map.
        
        Item storage bidItem = items[_itemId];
        for(uint i=0; i<_count;i++) {
            bidItem.itemTokens.push(tokenDetails[msg.sender].personId);    
        }
    }
    
    // Part 2 Task 1. Create a modifier named "onlyOwner" to ensure that only owner is allowed to reveal winners
    //Hint : Use require to validate if "msg.sender" is equal to the "beneficiary".
    modifier onlyOwner {
        // ** Start code here. 2 lines approximately. **
        require(msg.sender == beneficiary);
        _;
        //** End code here. **
    }
    
    
    function revealWinners() public onlyOwner{
        
        /* 
            Iterate over all the items present in the auction.
            If at least on person has placed a bid, randomly select          the winner */

        for (uint id = 0; id < 3; id++) {
            Item storage currentItem=items[id];
            if(currentItem.itemTokens.length != 0){
            // generate random# from block number 
            uint randomIndex = (block.number / currentItem.itemTokens.length)% currentItem.itemTokens.length; 
            // Obtain the winning tokenId

            uint winnerId = currentItem.itemTokens[randomIndex];
                
            /* Part 1 Task 6. Assign the winners.
            Hint." bidders[winnerId] " will give you the person object with the winnerId.
            you need to assign the address of the person obtained above to winners[id] */

            // ** Start coding here *** 1 line approximately.
            winners[id] = bidders[winnerId].addr;
                    
            //** end code here*
                
            }
        }
    } 

  //Miscellaneous methods: Below methods are used to assist Grading. Please DONOT CHANGE THEM.
    function getPersonDetails(uint id) public constant returns(uint,uint,address){
        return (bidders[id].remainingTokens,bidders[id].personId,bidders[id].addr);
    }

}

Test Case

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
let Auction = artifacts.require("./Auction.sol");

let auctionInstance;

contract('AuctionContract', function (accounts) {
  //accounts[0] is the default account
  //Test case 1
  it("Contract deployment", function() {
    //Fetching the contract instance of our smart contract
    return Auction.deployed().then(function (instance) {
      //We save the instance in a gDlobal variable and all smart contract functions are called using this
      auctionInstance = instance;
      assert(auctionInstance !== undefined, 'Auction contract should be defined');
    });
  });

  //Sample Test Case
  it("Should set bidders", function() {
    return auctionInstance.register({from:accounts[1]}).then(function(result) {
        return auctionInstance.getPersonDetails(0);
    }).then(function(result) {
      assert.equal(result[2], accounts[1], 'bidder address set');
    })
  });

  //Test Case for checking if the bid is more than the token amount
  it("Should NOT allow to bid more than remaining tokens", function() {
    /**********
    TASK 1:   Call bid method from accounts[1] of Auction.sol using auctionInstance and
    pass itemId=0, count=6 as arguments
    HINT:     To make a function call from account 1 use {from: accounts[1]} as an extra argument
    ***********/
    return auctionInstance.bid(0, 6, {from: accounts[1]})
    .then(function (result) {
      /*
      We are testing for a negative condition and hence this particular block will not have executed if our test case was correct. If this part is executed then we throw an error and catch the error to assert false
      */
      throw("Failed to check remaining tokens less than count");
    }).catch(function (e) {
      var a = e.toString();
      // console.log(a)
      if(e === "Failed to check remaining tokens less than count") {
        /**********
        TASK 2: This is the error which we had thrown. Should you assert true or false?
        HINT:   Use assert(false) to assert false
                Use assert(true) to assert true
        ***********/
        assert(false)
      } else {
        /**********
        TASK 3: assert the opposite here
        ***********/
        assert(true)
      }
    })
  });

  //Modifier Checking
  it("Should NOT allow non owner to reveal winners", function() {
    /**********
    TASK 4: Call revealWinners from account 1
    ***********/
     return auctionInstance.revealWinners({from: accounts[1]})
     .then(function (instance) {
       /*
       We are testing for a negative condition and hence this particular block will not have executed if our test case was correct. If this part is executed then we throw an error and catch the error to assert false
       */
       throw("Failed to check owner in reveal winners");
     }).catch(function (e) {
       if(e === "Failed to check owner in reveal winners") {
         /**********
         TASK 5: This is the error which we had thrown. Should you assert true or false?
         HINT:   Use assert(false) to assert false
                 Use assert(true) to assert true
         ***********/
         assert(false)
       } else {
         /**********
         TASK 6: assert the opposite here
         ***********/
         assert(true)
       }
     })
   })


  it("Should set winners", function() {
    /**********
    TASK 7: Call register function from account 2
    ***********/
    return auctionInstance.register({from: accounts[2]})
    .then(function(result) {
      /**********
      TASK 8: Call register function from account 3
      ***********/
        return auctionInstance.register({from: accounts[3]})
    }).then(function() {
      /**********
      TASK 9: Call register function from account 4
      ***********/
        return auctionInstance.register({from: accounts[4]})
    }).then(function() {
      /**********
      TASK 10: Call bid method from accounts[2] of Auction.sol using auctionInstance and
      pass itemId=0, count=5 as arguments
      ***********/
        return auctionInstance.bid(0, 5, {from: accounts[2]})
    }).then(function() {
      /**********
      TASK 11: Call bid method from accounts[3] of Auction.sol using auctionInstance and
      pass itemId=1, count=5 as arguments
      ***********/
        return auctionInstance.bid(1, 5, {from: accounts[3]})
    }).then(function() {
      /**********
      TASK 12: Call bid method from accounts[4] of Auction.sol using auctionInstance and
      pass itemId=2, count=5 as arguments
      ***********/
      return auctionInstance.bid(2, 5, {from: accounts[4]})
    }).then(function() {
      /**********
      TASK 13: Call revealWinners function from accounts[0]
      ***********/
        return auctionInstance.revealWinners({from: accounts[0]})
    }).then(function() {
      /**********
      TASK 14: call winners function from accounts[0] to get the winner of item id 0
      ***********/
        return auctionInstance.winners(0, {from: accounts[0]})
    }).then(function(result) {
      /**********
      TASK 15:  assert to see if the winner address is not the default address
      HINT:     Default address is '0x0000000000000000000000000000000000000000'
                Use notEqual method of assert
                Parameters for notEqual : (result, default address , message);
      ***********/
      assert(result.toString() != '0x0000000000000000000000000000000000000000')
      /**********
      TASK 16: call winners function from accounts[0] to get the winner of item id 1
      ***********/
      return auctionInstance.winners(1, {from: accounts[0]})
    }).then(function(result) {
      /**********
      TASK 17:  assert to see if the winner address is not the default address
      HINT:     Default address is '0x0000000000000000000000000000000000000000'
                Use notEqual method of assert
                Parameters for notEqual : (result, default address , message);
      ***********/
      /*<CODE HERE>*/
      assert(result.toString() != "0x0000000000000000000000000000000000000000")
      /**********
      TASK 18: Call winners function from account 3 to get the winner of item id 2
      ***********/
      return auctionInstance.winners(2, {from: accounts[3]})
    }).then(function(result) {
      /**********
      TASK 19:  assert to see if the winner address is not the default address
      HINT:     Default address is '0x0000000000000000000000000000000000000000'
                Use notEqual method of assert
                Parameters for notEqual : (result, default address , message);
      ***********/
      return assert.notEqual(result.toString(), '0x0000000000000000000000000000000000000000')
    })
  });
});

Let’s run the test command(it’s not passed all the test case at frist time):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  auction truffle test                     
bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)
Warning: Both truffle-config.js and truffle.js were found. Using truffle-config.js.
Using network 'test'.


Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Warning: Both truffle-config.js and truffle.js were found. Using truffle-config.js.


  Contract: AuctionContract
    ✔ Contract deployment
    ✔ Should set bidders
    ✔ Should NOT allow to bid more than remaining tokens (157ms)
    ✔ Should NOT allow non owner to reveal winners
    ✔ Should set winners (226ms)


  5 passing (455ms)

Great, it passed all the 5 test cases!