Handling Time in Solidity

Handling Time in Solidity

An overview and step-by-step guide on how to deal with time in solidity.

·

3 min read

Timestamps in Solidity

Time in solidity can be handled by two ways. These options are block.number and block.timestamp. For this tutorial, we will deal with the time using block.timestamp .This way may seem easier to understand and implement than the block.number.

BUT, you should know that, block.timestamp has vulnerabilities that can be used to manipulate the smartcontracts time and get some calls ahead of the real time by seconds. If you are planning to build time sensitive operations that even seconds are important, please don't use block.timestamp!!!

We will be working with the ballot contract. You can find the 3_Ballot.sol contract on remix’s file explorer, it's one of the builtin contracts.

Here if you cant find it Ballot.sol

The ballot contract basically implements voting process along with vote delegation.

What we want to do is, level up this contract by adding a deadline on voting period so the voters will have limited time on voting.

Variables

First thing we will have to do is, record the voting start time by adding a state variable startTime

pragma solidity >=0.7.0 <0.9.0;

contract Ballot {
    struct Voter {
        // ...
    }
    struct Proposal {
        // ...
    }

    uint startTime;

Constructor

Now we can declare the startTime in our construct to say; hey, the startTime is the block.timestamp when this contract is deployed.

    constructor(bytes32[] memory proposalNames) {
        // this part is unchanged...

        startTime = block.timestamp;

        // this part is unchanged...
            }));
        }
    }

Modifier

I love modifiers, using modifiers makes life easier! So what we are going to do here is, we will write our voteEnded modifier just after the constructor. And whenever our vote function is called, this modifier will check if the voting period is over or not.

    modifier voteEnded() {
        require(block.timestamp <= (startTime + 5 minutes));
        _;
    }

As you can see, this modifier checks if current time is 5 minutes away from the start time. You can use seconds, minutes, hours, days, weeks, months as time units.

Function

Its almost done! Now we just need to add our modifier to the vote function.

    function vote(uint proposal) external voteEnded {
        // Whole function is same..
    }

Just easy as that!

Now we can relax and eat some pizza.

Here is our full ballot contract with 5 minutes voting period.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Ballot {

    struct Voter {
        uint weight;
        bool voted;
        address delegate;
        uint vote;
    }

    struct Proposal {
        bytes32 name;
        uint voteCount;
    }

    uint startTime;

    address public chairperson;

    mapping(address => Voter) public voters;

    Proposal[] public proposals;

    constructor(bytes32[] memory proposalNames) {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;
        startTime = block.timestamp;

        for (uint i = 0; i < proposalNames.length; i++) {
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    modifier voteEnded() {
        require(block.timestamp <= (startTime + 5 minutes));
        _;
    }

    function giveRightToVote(address voter) external {
        require(
            msg.sender == chairperson,
            "Only chairperson can give right to vote."
        );
        require(
            !voters[voter].voted,
            "The voter already voted."
        );
        require(voters[voter].weight == 0);
        voters[voter].weight = 1;
    }

    function delegate(address to) external {

        Voter storage sender = voters[msg.sender];
        require(!sender.voted, "You already voted.");

        require(to != msg.sender, "Self-delegation is disallowed.");

        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;
            require(to != msg.sender, "Found loop in delegation.");
        }

        sender.voted = true;
        sender.delegate = to;
        Voter storage delegate_ = voters[to];
        if (delegate_.voted) {
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
            delegate_.weight += sender.weight;
        }
    }

    function vote(uint proposal) external voteEnded {
        Voter storage sender = voters[msg.sender];
        require(sender.weight != 0, "Has no right to vote");
        require(!sender.voted, "Already voted.");
        sender.voted = true;
        sender.vote = proposal;
        proposals[proposal].voteCount += sender.weight;
    }

    function winningProposal() public view
            returns (uint winningProposal_)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }

    function winnerName() external view
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
}