[ PizzaCoin the Series #3 ] Detailed Implementation of Staff and Player Contracts
Welcome to the 3rd part of PizzaCoin the Series. In the previous article, you have been walked through the workflow design of PizzaCoin voting system. You have seen that the system composes of nine components including PizzaCoin (mother) contract, PizzaCoinStaff contract, PizzaCoinPlayer contract, PizzaCoinTeam contract, PizzaCoinStaffDeployer library, PizzaCoinPlayerDeployer library, PizzaCoinTeamDeployer library, PizzaCoinCodeLib library and PizzaCoinCodeLib2 library.
Although each component is responsible for specific tasks and independent from each other, PizzaCoin mother contract would be a coordinator which collaborates with all other components. In this article we will be describing the more technical details on implementation of PizzaCoinStaff and PizzaCoinPlayer contracts.
Terms used in this article
PizzaCoin – the mother contract of PizzaCoinStaff, PizzaCoinPlayer and PizzaCoinTeam contracts.
PizzaCoinStaff – one of the three PizzaCoin’s children contracts responsible for managing staff-related tasks such as registering staffs, revoking staffs, providing staff information, and managing token balance as well as voting action for the staff.
PizzaCoinPlayer – one of the three PizzaCoin’s children contracts responsible for managing player-related tasks such as registering players, revoking players, providing player information, and managing token balance as well as voting action for a player.
Project Deployer – a user who deploys PizzaCoin contract which is considered as one of staffs.
Source files refered to in this article
The implementation of PizzaCoinStaff contract
|
|
PizzaCoinStaff contract consists of two integral parts as shown in the code snippet 1 including IStaffContract interface in the line no. 11 and PizzaCoinStaff contract in the line no. 45. IStaffContract interface contains function prototypes of PizzaCoinStaff that expose to PizzaCoin contract to use (we will discuss about this in the later article). In the line no. 45, PizzaCoinStaff contract implements IStaffContract interface and also inherits from the contract named Owned, which will be explained in a while.
Under PizzaCoinStaff contract, we attached SafeMath library to type uint256 in the line no. 53 and attached BasicStringUtils library to type string in the line no. 54. Refer to the following link to understand more about using-for in Solidity.
|
|
The functionality of Owned contract is just simple and straightforward as shown in the code snippet 2. The contract defines a contract creator as its owner once it is created in the line no. 15. Additionally, the contract has a single function modifier named onlyOwner. This modifier verifies whether a transaction is sent from a contract owner in the line no. 23; if a transaction is not sent from a contract owner, onlyOwner modifier will revert the transaction in the line no. 24. Since PizzaCoinStaff contract inherits from Owned contract, it derives onlyOwner modifier automatically.
|
|
Code snippet 3 shows the implementation of SafeMath. SafeMath is a well-known library developed by OpenZeppelin. This library provides secure mathematical operations i.e. addition, substraction, division and multiplication operations on Solidity’s data type uint256. We adopted SafeMath in the line no. 53 of the snippet 1 to take care of secure mathematical operations on staff’s token balance. Here is the original source file of SafeMath library.
|
|
PizzaCoinStaff contract also employs BasicStringUtils library as shown in the code snippet 4 to attach to type string in the line no. 54 of the snippet 1. This library consists of two functions, that is, isEqual and isNotEmpty.
isEqual function is straightforward. We compare two strings using the Solidity’s built-in hash function named keccak256. More technically, we encode each string using abi.encodePacked function and then pipe the encoded result as an input to keccak256 function. We then compare two hashed results using the statement keccak256(abi.encodePacked(self)) == keccak256(abi.encodePacked(other))
in the line no. 13; if two strings match, the comparison result would be true. The following link explains how ABI encoding and hash functions in Solidity work.
The implementation of isNotEmpty function is just simpler. We directly convert the string to bytes using the statement bytes memory selfInBytes = bytes(self);
in the line no. 20 and then compare the length of the contents stored in selfInBytes with 0 using the statement selfInBytes.length != 0
in the line no. 21. If the result is true, the string is not empty.
Alright, let’s come back to PizzaCoinStaff contract. PizzaCoinStaff contract is responsible for managing staff-related tasks such as registering staffs, revoking staffs, providing staff information and managing token balance as well as voting action for the staff. The following code snippet shows data structures and state variables used for storing personal information of each staff.
|
|
Under PizzaCoinStaff contract, there are two state variables used for collecting staff’s personal information viz. staffs and staffsInfo as defined in the line no’s. 14 and 15 respectively. staffs is an array holding ethereum addresses of all registered staffs. Whereas staffsInfo is a mapping variable which stores personal information of each particular staff in terms of struct StaffInfo as defined in the line no. 1. An address of the staff is used as a mapping key for accessing corresponding personal information stored on staffsInfo mapping.
The struct StaffInfo gathers a bunch of state variables including index, name, wasRegistered, tokenBalance, teamsVoted and votesWeight. index points to an element on the array staffs for a particular staff. name is self-explanatory. wasRegistered is a boolean variable used for indicating a registration status of a particular staff. tokenBalance represents an amount of voting tokens that a particular staff remains for voting. teamsVoted is an array containing names of the teams the staff used to vote to. votesWeight is a mapping variable, using a name of a specific team as a mapping key, containing a voting weight for any particular team that the staff has ever voted to.
|
|
|
|
Additionally, there is another bunch of state variables used by PizzaCoinStaff contract including voterInitialTokens, totalSupply and state variables as shown in the code snippet 6. voterInitialTokens represents a number of voting tokens (set by a project deployer as shown in the line no. 10 of the snippet 7) that PizzaCoinStaff contract would supply to each registered staff upon a registration process. Meanwhile, totalSupply logs a total number of voting tokens that have ever been supplied to all the registered staffs. state indicates a working context of PizzaCoinStaff contract. As described in the previous article, state comprises of four unidirectional steps inculding Registration, RegistrationLocked, Voting and VotingFinished as defined in the line no. 4 of the snippet 6.
|
|
As mentioned in the previous article, PizzaCoin contract is considered as a contract coordinator for its children contracts including PizzaCoinStaff. Let’s say the staff wants to invoke any function that makes changes to any state variable of PizzaCoinStaff, the staff has to execute that function by way of calling to the mapped function which is on PizzaCoin contract like steps 1.1 - 1.3 in Figure 2. In other words, the staff cannot execute a state-changing function on PizzaCoinStaff contract directly. To enforce this rule, we defined a function modifier named onlyPizzaCoin as defined in the line no. 4 of the code snippet 8. In fact, onlyPizzaCoin modifier is applied to every state-changing function. For instance, applying onlyPizzaCoin modifier to the state-changing function lockRegistration in the line no. 16. Consequently, lockRegistration function has to be executed through PizzaCoin contract only.
|
|
Since we expected to reduce as much gas consumption as possible when deploying PizzaCoin contract, non state-changing functions of PizzaCoinStaff contract would not be mapped with any functions on PizzaCoin contract. For example, getTotalStaffs as shown in the code snippet 9 which is a non state-changing function (defined using view keyword in the line no. 4) of PizzaCoinStaff contract would not be applied with onlyPizzaCoin modifier. Hence, any user is allowed to make a direct call to this function right away like a step 2 in Figure 2.
|
|
The implementation of registerStaff function is shown in the code snippet 10. registerStaff function is applied with two modifiers onlyRegistrationState and onlyPizzaCoin in the line no. 4. As a result, this function is executable if and only if the contract is in Registration state and the function has to be called via the mapped function on PizzaCoin contract only.
In the line no’s. 15 - 18, registerStaff function verifies whether an input address is already registered. If the specified address is already registered, a transaction would be reverted. Otherwise, the specified address would be registered as an address of the new staff. Specifically, the specified address is pushed into staffs array in the line no. 21. Personal record of the new staff in the line no’s. 23 - 30 would then be initialized and recorded into staffsInfo mapping. The specified address is used as a mapping key for accessing personal information of that particular staff in the line no. 22.
By nature, mapping data type can be seen as Hash table which is virtually initialized such that any possible key exists and each key is mapped to a value whose byte representations are all set to zeros of that value type. In case of wasRegistered which is a boolean variable, thus this variable in every possible mapping key would be automatically set to false (boolean’s default type) once staffsInfo mapping gets initialized during PizzaCoinStaff contract is being constructed. With such characteristic, wasRegistered can be employed to detect if any specified address is already registered or not as used in the line no. 16. If wasRegistered of a specific address indicates true, that means that the address is registered already. This solution enables PizzaCoinStaff contract a practical approach to verifying the registration status of any given address without the need to iterate over staffs array in order to search for being of that specific address, which consumes unreliable gas depending on a number of staffs being registered.
In the line no. 25, tokenBalance is initialized with the initial voting tokens predetermined by a project deployer. In other words, every registered staff would get equal voting tokens. In the line no. 30, index represents a pointer to a corresponding staff address stored on staffs array. The use of index will be discussed again on the implementation of kickStaff function below. In the line no. 33, totalSupply would be increased by a number of tokens supplied to the new staff (i.e., the value indicated by voterInitialTokens). SafeMath library is adopted to take care of a secure addition operation on totalSupply.
|
|
Code snippet 11 explains the implementation of kickStaff function. When kickStaff function is being called, it first checks if the specified address identifies a registered staff in the line no’s. 10 - 13. If not, a transaction would be reverted. Even though a project deployer is considered as one of staff members, the deployer is not allowed to be revoked in our system. In the line no’s. 15 - 18, kickStaff function would revert a transaction if there is any attempt to revoke a project deployer.
Since an array element in Solidity cannot be removed like other traditional programming languages, we will use the element substitution procedure to tackle this issue. That is, we will move the last element of an array to replace a target element of which we want to remove and then we will decrease the array length by 1.
To revoke the specified staff, hence, kickStaff function invokes getStaffIndex function in order to get the target staffIndex, which points to the specified address on staffs array, in the line no. 20. The implementation of getStaffIndex function is defined in the line no. 43. After obtaining the staffIndex, kickStaff function removes the specified staff’s address from staffs array by moving the last array element to replace the element pointed by the obtained staffIndex in the line no. 24. Since the last array element has just moved to the element pointed by staffIndex, the function has to update the last moved staff’s index to point to staffIndex in the line no. 29. Later, the function removes the last array element by decreasing the staffs array’s length by 1 according to the line no. 32. In the line no. 35, the function executes delete operator to remove the personal record of the revoked staff from staffsInfo mapping. Finally, SafeMath library undertakes a secure substraction operation by deducting totalSupply by a number of initial voting tokens (i.e., the value indicated by voterInitialTokens) in the line no. 37.
|
|
The last function of PizzaCoinStaff contract we would like to discuss in this article is commitToVote function as shown in the code snippet 12. This function is called when the staff wants to give a vote to any favourite team. Like any other state-changing functions, commitToVote function must be executed by the staff through its mapped function on PizzaCoin contract only. Besides, the function is applied with onlyVotingState modifier in the line no. 5. Hence, this function can be executed successfully if and only if the PizzaCoinStaff contract is in Voting state.
commitToVote function requires three input parameters including _staff, _teamName and _votingWeight. _staff indicates an account address of the staff who submits a voting transaction. Meanwhile, _teamName represents a name of the team in which the staff would want to vote to. _votingWeight indicates a number of voting tokens in which the staff would like to give to the specified team.
Upon receiving a voting transaction, commitToVote function first checks whether or not _staff represents an address of the actual registered staff in the line no’s. 22 - 25. Then, the function verifies that the staff holds enough tokens to spend in the line no’s. 27 - 30. If everything went fine then the function deducts the token balance of that staff by _votingWeight in the line no. 32. Again, SafeMath library’s sub function undertakes a secure substraction operation on staff’s token balance at this point. In the line no’s. 36 - 40, the function pushes a name of the voted team _teamName into the array teamsVoted. This action is performed only when the staff gives a vote to the specified team for the first time. Eventually, the function employs add function of SafeMath library to securely transfer the given tokens _votingWeight to the specified team in the line no. 42.
The implementation of PizzaCoinPlayer contract
|
|
Actually, the functionality between PizzaCoinStaff contract and PizzaCoinPlayer contract is quite similar. If you understand how PizzaCoinStaff contract works, you do understand how PizzaCoinPlayer contract works already. Both contracts provision almost all the same functions but they are targeting on different types of users i.e. staff and player. That is, PizzaCoinStaff contract manages staff-related tasks whereas PizzaCoinPlayer contract manages player-related tasks.
Once PizzaCoinStaff and PizzaCoinPlayer are integrated with PizzaCoin mother contract, the mother contract would redirect a transaction request to either PizzaCoinStaff or PizzaCoinPlayer in accordance with a type of a requesting user. More specifically, any transaction request sent from an address being registered as the staff would be routed to PizzaCoinStaff contract. The request would be transacted and recorded on PizzaCoinStaff contract. Meanwhile, any transaction sent from an address being registred as a player would be handed over to PizzaCoinPlayer contract to handle instead.
The code snippet 13 describes data structures and state variables used by PizzaCoinPlayer contract. As you can see, the only difference between PlayerInfo struct used by PizzaCoinPlayer and StaffInfo struct used by PizzaCoinStaff (code snippet 5) is that PlayerInfo has an additional field named teamName in the line no. 7, which indicates a team that associates with a specific player.
The code snippets 14 - 16 show the implementation of registerPlayer, kickPlayer and commitToVote functions of PizzaCoinPlayer contract. As mentioned earlier, all of them were implemented using the similar programming logic like what happens to PizzaCoinStaff contract. The following link points to the source file of PizzaCoinPlayer.
|
|
|
|
|
|
Summary
Let’s summarize. In this article, you have learned how PizzaCoinStaff and PizzaCoinPlayer contracts were implemented. In order to avoid ‘Out-of-Gas’ error when deploying the contracts as well as when invoking a contract function, several techniques were employed. In the next article, you will learn how PizzaCoinTeam, which is another child of PizzaCoin contract, was implemented in detail. See you in the next article.
PizzaCoin the series consists of 6 articles as follows.
Part 1: How Did We Develop Ethereum-based Voting System for Pizza Hackathon?
Part 2: Workflow Design for PizzaCoin Voting System
Part 3: Detailed Implementation of Staff and Player Contracts
Part 4: Detailed Implementation of Team Contract
Part 5: Deploying Children Contracts with Contract Factories
Part 6: Integrating PizzaCoin Contract with Dependencies