Handling Time in Solidity
An overview and step-by-step guide on how to deal with time in solidity.
Table of contents
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;
}
}