paint-brush
A Beginner's Guide to Raffle Draws on the Ethereum Blockchainby@daltonic
219 reads

A Beginner's Guide to Raffle Draws on the Ethereum Blockchain

by Darlington Gospel January 23rd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Raffle draws are a popular way to randomly select a winner or winners from a group of participants. In this tutorial, we'll take a look at how to create a smart contract that can be used to conduct a raffle draw. We'll discuss the functions and state variables that make up the smart contract and look at the sample code for each function.
featured image - A Beginner's Guide to Raffle Draws on the Ethereum Blockchain
Darlington Gospel  HackerNoon profile picture


Introduction

Raffle draws are a popular way to randomly select a winner or winners from a group of participants. In this tutorial, we'll take a look at how to create a smart contract that can be used to conduct a raffle draw on the Ethereum blockchain. We'll discuss the functions and state variables that make up the smart contract and look at the sample code for each function.


If you enjoy working on Web3 projects, subscribe to my channel for all sorts of videos for learning web3 development. You can check out my services here if you want to connect with me.


Now, let’s jump into the tutorial…

Creating a Raffle Draw Smart Contract

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract RaffleDraw {
    address public owner;
    mapping(address => bool) public players;
    mapping(uint256 => address) public playerNumber;
    mapping(address => bool) erasePlayers;
    mapping(uint256 => address) eraseNo;
    uint256 public ticketPrice;
    uint256 public totalPlayers;
    uint256 public totalPot;
    address[] winners;

    constructor() {
        owner = msg.sender;
        ticketPrice = 1 ether;
    }

    function enter() public payable {
        require(msg.value == ticketPrice, "Not upto the ticket price");
        require(!players[msg.sender], "Player already entered");
        players[msg.sender] = true;
        playerNumber[totalPlayers] = msg.sender;
        totalPlayers++;
        totalPot += msg.value;
    }

    function draw() public {
        require(msg.sender == owner, "Caller must be the owner");
        require(totalPlayers > 0, "Total players must be more than zero");
        address\[] memory eligible = new address[\](totalPlayers);

        for (uint256 i = 0; i < totalPlayers; i++) {
            address player = playerNumber[i];
            eligible[i] = player;
        }

        uint256 winnerCount = thirtyPercent(totalPlayers) > 0
            ? thirtyPercent(totalPlayers)
            : 1;

        for (uint256 i = 0; i < winnerCount; i++) {
            uint256 randIndex = uint256(
                keccak256(abi.encodePacked(block.timestamp, block.difficulty))
            ) % totalPlayers;

            winners.push(eligible[randIndex]);
            players[winners[i]] = true;
            eligible = removeAddress(eligible, eligible[randIndex]);
        }
    }

    function shareMoney() public {
        require(msg.sender == owner, "Caller must be the owner");
        for (uint256 i = 0; i < winners.length; i++) {
            payable(winners[i]).transfer(ticketPrice * 2);
        }
    }

    function getWinners() public view returns (address[] memory) {
        return winners;
    }

    function resetRaffle() public returns (bool) {
        require(msg.sender == owner, "Caller must be the owner");
        for (uint256 i = 0; i < winners.length; i++) {
            delete winners[i];
        }

        for (uint256 i = 0; i < totalPlayers; i++) {
            delete players[playerNumber[i]];
            delete playerNumber[i];
        }

        totalPlayers = 0;
        totalPot = 0;
        return true;
    }

    function thirtyPercent(uint256 x) public pure returns (uint256) {
        return (x * 3) / 10;
    }

    function removeAddress(address[] memory addresses, address toRemove)
        public
        pure
        returns (address[] memory)
    {
        address\[] memory newAddresses = new address[\](addresses.length - 1);
        uint256 index = 0;

        for (uint256 i = 0; i < addresses.length; i++) {
            if (addresses[i] != toRemove) {
                newAddresses[index] = addresses[i];
                index++;
            }
        }

        return newAddresses;
    }
}


Check out one of my videos on how to code an NFT Marketplace below, you can use it to accelerate your web3 development skill.


The smart contract above is called RaffleDraw. It starts with several state variables that are used to keep track of the players, the ticket price, and the total pot of ether collected. See the code block below:


address public owner;
mapping(address => bool) public players;
mapping(uint256 => address) public playerNumber;
mapping(address => bool) erasePlayers;
mapping(uint256 => address) eraseNo;
uint256 public ticketPrice;
uint256 public totalPlayers;
uint256 public totalPot;
address[] winners;

constructor() {
    owner = msg.sender;
    ticketPrice = 1 ether;
}


The first function we'll look at is the enter() function. Players use this function to enter the raffle by sending a certain amount of ether to the smart contract. The function checks that the amount of ether sent is equal to the ticket price and that the player has not already entered the raffle. If the checks pass, the player is added to the players mapping and the totalPlayers and totalPot variables are incremented. See the snippet below:


function enter() public payable {
    require(msg.value == ticketPrice, "Not upto the ticket price");
    require(!players[msg.sender], "Player already entered");

    players[msg.sender] = true;
    playerNumber[totalPlayers] = msg.sender;

    totalPlayers++;
    totalPot += msg.value;
}


The next function is the draw() function. This function is used by the contract owner to conduct the raffle draw. It first checks that the caller is the owner of the contract and that there are more than zero players. It then creates a new address array called eligible that is the same size as the totalPlayers variable. This array is used to store all the eligible players for the draw. The draw is then conducted by selecting a random index from the eligible array and adding the player at that index to the winners array. The selected player is also removed from the eligible array using the removeAddress() function to prevent repeat winners. Observe the code below:


function draw() public {
    require(msg.sender == owner, "Caller must be the owner");
    require(totalPlayers > 0, "Total players must be more than zero");

    address\[] memory eligible = new address[\](totalPlayers);

    for (uint256 i = 0; i < totalPlayers; i++) {
        address player = playerNumber[i];
        eligible[i] = player;
    }

    uint256 winnerCount = thirtyPercent(totalPlayers) > 0
        ? thirtyPercent(totalPlayers)
        : 1;

    for (uint256 i = 0; i < winnerCount; i++) {
        uint256 randIndex = uint256(
            keccak256(abi.encodePacked(block.timestamp, block.difficulty))
        ) % totalPlayers;

        winners.push(eligible[randIndex]);
        players[winners[i]] = true;
        eligible = removeAddress(eligible, eligible[randIndex]);
    }
}


The shareMoney() function is used by the owner of the contract to distribute the winnings to the winners. This function iterates through the winners array and sends each winner 2 times the ticket price in ether. See the code below:


function shareMoney() public {
    require(msg.sender == owner, "Caller must be the owner");
    for (uint256 i = 0; i < winners.length; i++) {
        payable(winners[i]).transfer(ticketPrice * 2);
    }
}


The getWinners() function is a view function that returns the winners array. This function is useful for displaying the winners to users after the raffle has been conducted. See the snippet below:


function getWinners() public view returns (address[] memory) {
    return winners;
}


The resetRaffle() function is used by the contract owner to reset the raffle. This function deletes all the players, winners, and resets the totalPlayers and totalPot variables to zero.


function resetRaffle() public returns (bool) {
    require(msg.sender == owner, "Caller must be the owner");
    for (uint256 i = 0; i < winners.length; i++) {
        delete winners[i];
    }

    for (uint256 i = 0; i < totalPlayers; i++) {
        delete players[playerNumber[i]];
        delete playerNumber[i];
    }

    totalPlayers = 0;
    totalPot = 0;
    return true;
}

I

f you want to understand the nitty-gritty of smart contract development, check out the book below to become an in-demand smart contract developer.


Capturing Smart Contract Development


Grab a copy of my book titled, “capturing smart contract development to become an in-demand smart contract developer.


The last two functions are helper functions. The thirtyPercent() function is a pure function that takes in an argument and returns 30% of that argument. The removeAddress() function is used to remove an address from an address array. See the code below:


function thirtyPercent(uint256 x) public pure returns (uint256) {
    return (x * 3) / 10;
}

function removeAddress(address[] memory addresses, address toRemove)
    public
    pure
    returns (address[] memory)
{
    address\[] memory newAddresses = new address[\](addresses.length - 1);
    uint256 index = 0;

    for (uint256 i = 0; i < addresses.length; i++) {
        if (addresses[i] != toRemove) {
            newAddresses[index] = addresses[i];
            index++;
        }
    }

    return newAddresses;
}

Conclusion

In this tutorial, we looked at how to create a smart contract for conducting a raffle draw on the Ethereum blockchain. We discussed the various functions and state variables that make up the contract and looked at sample codes for each function.


With this solid understanding of the smart contract, you can now use it as a starting point for your own raffle draw projects or customize it to suit your specific needs. Keep in mind that this is just a basic example, and it is always important to thoroughly test and audit smart contracts before deploying to a live chain.


Don’t forget to subscribe to my channel for all sorts of web3 development videos. Till next time, all the best.

About the Author

Gospel Darlington is a full-stack blockchain developer with 6+ years of experience in the software development industry.


By combining Software Development, writing, and teaching, he demonstrates how to build decentralized applications on EVM-compatible blockchain networks.


For more information about him, kindly visit and follow his page on Twitter, Github, LinkedIn, or on his website.


Also Published Here