## Voting System Tutorial
### Create Objects/Define Data
#### Create the blockchain
<pre>
ignite scaffold chain <b>votingsystem</b> --no-module
cd votingsystem
</pre>
#### Create Module
<pre>
ignite scaffold module <b>votingsystem</b> --dep bank
</pre>
#### Create System Variables
- Election Counter
- Tokens Number Issued
- Max Tokens
<pre>
ignite scaffold single systemInfo electionCounter:uint tokensAllocated:uint tokensMax:uint --module votingsystem --no-message
</pre>
Edit proto/votingsystem/genesis.proto
<pre>
SystemInfo systemInfo = 2 <b>[(gogoproto.nullable) = false];</b>
</pre>
Run
<pre>
ignite generate proto-go
</pre>
Edit x/votingsystem/types/genesis.go
**From:**
<pre>
func DefaultGenesis() *GenesisState {
return &GenesisState{
SystemInfo: nil,
</pre>
**To**
<pre>
const DefaultIndex uint64 = 1
const NumberOfTokens uint64 = 0
const MaxTokensToIssue uint64 = 1
func DefaultGenesis() *GenesisState {
return &GenesisState{
SystemInfo: SystemInfo{
ElectionCounter: uint64(DefaultIndex),
TokensAllocated: uint64(NumberOfTokens),
TokensMax: uint64(MaxTokensToIssue),
},
},
</pre>
Edit x/votingsystem/genesis.go
**From**
<pre>
if genState.SystemInfo != nil {
k.SetSystemInfo(ctx, *genState.SystemInfo)
}
</pre>
**To**
<pre>
if genState.SystemInfo != (types.SystemInfo{}) {
k.SetSystemInfo(ctx, genState.SystemInfo)
}
</pre>
Also (around line 40)
**From**
<pre>
if found {
genesis.SystemInfo = &systemInfo
}
</pre>
**To**
<pre>
if found {
genesis.SystemInfo = systemInfo
}
</pre>
### Create Objects/Define Data
We need to define the structures that will store the data of the voting system
- Election *(A thing for people to vote on. Only yes/no votes.)*
- Vote *(A token that allows a vote. Transferrable.)*
- Ballot *(A ballot that is tied to an election and must be cast by token holder.)*
#### Commands to create data structures
<pre>
ignite scaffold map vote owner nickname --module votingsystem --no-message
ignite scaffold map election desc voter startDate endDate --module votingsystem --no-message
ignite scaffold map ballot electionid:uint vote choice:bool --module votingsystem --no-message
</pre>
#### Commands to create data messages
<pre>
ignite scaffold message createElection desc startDate endDate --module votingsystem --response electionId
ignite scaffold message castBallot electionId:uint voteToken choice:bool --module votingsystem
ignite scaffold message issueVoteToken nickname --module votingsystem
ignite scaffold message transferVoteToken tokenIndex newTokenHolder nickname --module votingsystem
</pre>
#### Commands to create queries
<pre>
ignite scaffold query electionResult electionID --module votingsystem --response votesFor,votesAgainst
ignite scaffold query myVoteList --module votingsystem --response voteList
</pre>
- **Make method to create election**
Edit election file x/votingsystem/keeper/msg\_server\_create\_election.go
<pre>
ctx := sdk.UnwrapSDKContext(goCtx)
// Get and increment election counter
systemInfo, found := k.Keeper.GetSystemInfo(ctx)
if !found {
panic("SystemInfo not found")
}
nextIndex := systemInfo.ElectionCounter
nextIndex++
newIndex := strconv.FormatUint(nextIndex, 10)
// Get vote info
// Check dates
if false {
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "Bid is not high enough")
}
startDate := msg.StartDate
endDate := msg.EndDate
thisElection := types.Election{
Index: newIndex,
Desc: msg.Desc,
Voter: msg.Creator,
StartDate: startDate,
EndDate: endDate,
}
k.SetElection(ctx, thisElection);
// Save the systemInfo number of tokens
systemInfo.ElectionCounter = nextIndex;
k.SetSystemInfo(ctx, systemInfo)
return &types.MsgCreateElectionResponse{ElectionId: newIndex}, nil
</pre>
Edit election file x/votingsystem/keeper/msg\_server\_cast\_ballot.go
<pre>
ctx := sdk.UnwrapSDKContext(goCtx)
// Try getting a election from the store
ElectionId, electionValid := k.GetElection(ctx, strconv.FormatUint(uint64(msg.ElectionID), 10))
voteToken, voteValid := k.GetVote(ctx, msg.VoteToken)
if !electionValid || !voteValid {
// Make custom error. This is just temporary
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "You've provided bad data")
}
if msg.Creator != msg.VoteToken {
// Make custom error. This is just temporary
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "You've provided bad data")
}
externalAnswer, err := callExternalService(ElectionId.String())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
fmt.Println("From External Service: " + externalAnswer)
thisBallot := types.Ballot{
Index: ElectionId.String(),
Vote: voteToken.String(),
Choice: msg.Choice,
}
k.SetBallot(ctx, thisBallot);
return &types.MsgCastBallotResponse{}, nil
</pre>
Edit election file x/votingsystem/keeper/msg\_server\_issue\_vote\_token.go
<pre>
// In the import section
"strconv"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
// In the IssueVoteToken func
ctx := sdk.UnwrapSDKContext(goCtx)
systemInfo, found := k.Keeper.GetSystemInfo(ctx)
if !found {
panic("SystemInfo not found")
}
nextIndex := systemInfo.TokensAllocated
nextIndex++
newTokenIndex := strconv.FormatUint(nextIndex, 10)
if nextIndex > systemInfo.TokensMax {
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "All vote tokens have been issued")
}
newVote := types.Vote{
Index: newTokenIndex,
Owner: msg.Creator,
Nickname: msg.Nickname,
}
k.SetVote(ctx, newVote)
// Save the systemInfo number of tokens
systemInfo.TokensAllocated = nextIndex;
k.SetSystemInfo(ctx, systemInfo)
return &types.MsgIssueVoteTokenResponse{}, nil
</pre>
Edit election file x/votingsystem/keeper/msg\_server\_transfer\_vote\_token.go
<pre>
// In import
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
// In TransferVoteToken func
ctx := sdk.UnwrapSDKContext(goCtx)
// If the message didn't come from the token holder, error out
voteToken, voteValid := k.GetVote(ctx, msg.TokenIndex)
if !voteValid {
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "Token not found")
}
if msg.Creator != voteToken.Owner {
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "Non-Owner tried to tranfer token")
}
newVote := types.Vote{
Index: voteToken.Index,
Owner: msg.NewTokenHolder,
Nickname: msg.Nickname,
}
k.SetVote(ctx, newVote)
return &types.MsgTransferVoteTokenResponse{}, nil
</pre>
#### Add Error Messages
Add some error messages (x/votingsystem/types/errors.go)
<pre>
ErrInvalidDeadline = sdkerrors.Register(ModuleName, 1109, "Deadline cannot be parsed: %s")
</pre>
#### Add Unit Tests
Make a list of test files and sample content
<pre>
Sample test code
</pre>
Add external program call to ballot casting
Edit election file x/votingsystem/keeper/msg\_server\_cast\_ballot.go
**Add**
<pre>
// At top, in import section
"strconv"
"flag"
"log"
"time"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
// Inside CastBallot Method, before "thisBallot := types.Ballot"
externalAnswer, err := callExternalService(ElectionId.String())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
fmt.Println("From external service: " + externalAnswer)
// At bottom
func callExternalService(dataToSend string) (string, error) {
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", dataToSend, "Name to greet")
)
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := types.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &types.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
return r.GetMessage(), nil
}
</pre>
Create proto file proto/votingsystem/greeter.proto
**Add**
<pre>
syntax = "proto3";
package votingsystem.votingsystem;
option go_package = "votingsystem/x/votingsystem/types";
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
</pre>
## Set up the queries
Edit the file proto/votingsystem/query.proto
**Change**
<pre>
message QueryMyVoteListResponse {
string voteList = 1;
}
</pre>
**To**
<pre>
message QueryMyVoteListResponse {
repeated string vote = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
</pre>
Run command
<pre>
ignite generate proto-go
</pre>
Edit file x/votingsystem/keeper/grpc\_query\_my\_vote\_list.go
<pre>
// Body of MyVotingList, after ctx line
// Define a variable that will store a list of posts
var votes []*types.Vote
// Get the key-value module store using the store key (in this case store key is "chain")
store := ctx.KVStore(k.storeKey)
// Get the part of the store that keeps posts (using vote key, which is "Vote-value-")
voteStore := prefix.NewStore(store, []byte(types.VoteKey))
pageRes, err := query.Paginate(voteStore, req.Pagination, func(key []byte, value []byte) error {
var vote types.Vote
if err := k.cdc.Unmarshal(value, &vote); err != nil {
return err
}
votes = append(votes, &vote)
return nil
})
return &types.QueryMyVoteListResponse{Vote: votes}, nil
</pre>
Edit file x/votingsystem/keeper/grpc\_query\_election\_result.go
<pre>
// Add to import section
"strconv"
// Body of ElectionResult, after ctx line
// Define a variable that will store a list of posts
var ballotFor int = 0
var ballotAgainst int = 0
// Get the key-value module store using the store key (in this case store key is "chain")
store := ctx.KVStore(k.storeKey)
// Get the part of the store that keeps posts (using ballot key, which is "Ballot-value-")
ballotStore := prefix.NewStore(ballot, []byte(types.BallotKey))
// Terrible way to do this. Need to find way to just query what I want instead of everything
pageRes, err := query.Paginate(ballotStore, req.Pagination, func(key []byte, value []byte) error {
var ballot types.Ballot
if err := k.cdc.Unmarshal(value, &vote); err != nil {
return err
}
if ballot.ElectionId == msg.ElectionId {
if ballot.Choice == "true" {
ballotFor++
} else {
ballotAgainst++
}
}
return nil
})
return &types.QueryElectionResultResponse{VotesFor: strconv.Itoa(ballotFor), VotesAgainst: strconv.Itoa(ballotAgainst)}, nil
</pre>
Start up your new blockchain(zone)
<pre>
ignite chain serve
</pre>