Let’s face it. For a smart contract, implementing an addition of two integers is not very "smart". In this post, I’ll try to go beyond this former trivial example, and propose a simple but real use-case.
This is the 4th post in the Starting Ethereum focus series.Other posts include:
- Starting with Ethereum - Setup
- Starting with Ethereum - Writing a contract
- Starting with Ethereum - Deploying and running a contract
- Starting with Ethereum - Smart contracts (this post)
- Starting with Ethereum - Industrialization
As an example, let’s consider the business domain of a referendum.
A referendum use-case
A referendum is a direct vote in which an entire electorate is invited to vote on a particular proposal.
https://en.wikipedia.org/wiki/Referendum
To keep things simple for a blog post, there shouldn’t be too many requirements. Here are some very minimal ones:
- There are x votes per referendum. Let’s name such a vote a "voting token".
- An account can submit multiple voting token. In other words, there’s no check whether an account submitted one or more tokens.
- Votes are public.
Third-party can check which account submitted which tokens.
In other words, anyone can check transactions and who created them - which is the benefit of the blockchain.
However, it’s not trivial to bind an account to a specific identity. - Answers can be either "yes", "no", or "blank".
- The account that creates the referendum also gets credited with all initial voting tokens. It’s up to the creating account to dispatch the tokens to other accounts so they can submit them.
- At anytime, the state of the submitted voting tokens can be queried.
- When all voting tokens have been cast, the result should be computed.
- Any account can subscribe to the final result.
The model
Here’s a proposal for the conceptual model:
- There’s a limited amount of available voting tokens associated with a referendum.
- A vote is cast with a specific answer.
And the sequence diagram:
How do features, the conceptual model and the sequence translate into a Solidity implementation model?
The first step is to design the voting token object itself. There’s a need for a registry, one that references which account owns which amount. Ethereum is made by design to create custom crypto-currencies. They offer a solid documentation and some code templates for that. This translates very well into a voting token. I just updated the code a bit to better fit the requirements:
- Removal of the name, a symbol is more than enough
- Transferring voting tokens can only originate from the token owner - the function caller. This will be used after contract creating, to give them to voter accounts. It can also be used afterwards, to pass the voting token to another account.
- On the other hand, burning a token i.e. using it, will be called from the referendum contract and needs a reference to the original caller (see below).
The code
The full source code is available here.
Let’s highlight some interesting functions.
At the hear of the matter is the castVotes()
function.
function castVotes(Answer _answer, uint256 _count) public {
require(referendum.open); (1)
VotingToken tokenContract = VotingToken(tokenAddress); (2)
tokenContract.burn(msg.sender, _count); (3)
handleVotes(_answer, _count); (4)
Vote(msg.sender, _answer, _count); (5)
if (hasAllVotes()) {
close();
}
}
1 | require is a Solidity keyword to manage pre-conditions in Design by Contract Programming.
A failed require will rollback any state passed during the current call.
Here, we disallow voting on closed contract instances. |
2 | Creates a reference to an existing contract! This is one of the most important feature of Ethereum: a contract instance can call the method of another contract instance, thus chaining method calls across contracts. |
3 | Effectively calls the burn() method of the contract instance.
Notice the caller account is passed as an argument - in order to remove the voting toking from the account. |
4 | Increment the vote count. |
5 | Send an event. Events are essentially blockchain logs, but they also allow to be subscribed to from JavaScript clients. |
Another interesting bit is the following function:
function getResults() public view returns (uint256 yesCount, uint256 noCount, uint256 blankCount) {
return (referendum.votes.yesCount, referendum.votes.noCount, referendum.votes.blankCount);
}
For Java developers, it might be a surprise, but Solidity can not only return multiple values but also allows to name them for better maintainability.
Also, note the view
modifier.
In writing the first contract, we used the pure
modifier.
pure
means the return value only depends on the input parameters - hence, there’s no write to nor read from the blockchain.
The view
modifier means there’s no write, but there might be a read.
If there’s no read, then the modifier shouldn’t be view but pure
|
Usage
Let’s deploy both contracts on the Remix network:
- The VotingToken
- The ReferendumContract
Then start interacting with the deployed contract:
var referendumContract = web3.eth.contract(<content/of/ABI>);
var referendumContractInstance = referendumContract.at('0x48dbcdd695fb6a7728dc656abd8baa460d7ff081');
referendumContractInstance.getQuestion();
This yields the question I initialized the vote with - check if you want.
At this point, voting tokens should be given to other accounts. However, it’s easier to directly cast the votes from the main account.
referendumContractInstance.castVote(0);
referendumContractInstance.getResults();
Interestingly enough, I get an exception when calling castVote() from the console, but it works from the Wallet.
Probably because I’m not signed in in the console.
|
As stated above, Solidity events write into the blockchain.
But, they can also be listened to from web3.js, via events.
The following code will trigger every time a Finish
event is written into the blockchain (from the close()
method).
var finishEvent = referendumContractInstance.Finish();
var consoleLog = function(error, result) {
if (error) {
console.log(error);
} else {
console.log("Referendum " + result.args._referendum + " finished");
}
}
finishEvent.watch(consoleLog);
Conclusion
At this point, we really got into smart contracts.
The contracts designed have some degree of business logic.
We saw some of Solidity features: events, require
, view
and how to call a contract from another one.
On your side, you can re-use the Solidity source, build upon it and deploy your own referendum to the blockchain.