Welcome to the 4th part of PizzaCoin the Series. In the previous article, you have learned how PizzaCoinStaff and PizzaCoinPlayer contracts were implemented. In this article, you will learn the implementation of another child of PizzaCoin contract called PizzaCoinTeam. To better understand the contents of this article, you may be required to understand the contents of the previous article. So if you come across this article, we recommend you to read the previous article first.
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.
PizzaCoinTeam – one of the three PizzaCoin’s children contracts responsible for managing team-related tasks such as creating teams, registering a player to a specific team, revoking teams, revoking a specific player from a particular team, handling team voting, and providing team information as well as voting results.
pragma solidity^0.4.23;import"./SafeMath.sol";import"./BasicStringUtils.sol";import"./Owned.sol";// ------------------------------------------------------------------------
// Interface for exporting external functions of PizzaCoinTeam contract
// ------------------------------------------------------------------------
interfaceITeamContract{functionlockRegistration()external;functionstartVoting()external;functionstopVoting()external;functioncreateTeam(string_teamName)external;functionregisterPlayerToTeam(address_player,string_teamName)external;functionkickTeam(string_teamName)external;functionkickPlayerOutOfTeam(address_player,string_teamName)external;functiondoesTeamExist(string_teamName)externalviewreturns(boolbTeamExist);functiongetTotalPlayersInTeam(string_teamName)externalviewreturns(uint256_total);functiongetPlayerInTeamAtIndex(string_teamName,uint256_playerIndex)externalviewreturns(bool_endOfList,address_player);functiongetTotalTeams()externalviewreturns(uint256_total);functiongetTeamInfoAtIndex(uint256_teamIndex)externalviewreturns(bool_endOfList,string_teamName,uint256_totalVoted);functiongetVotingPointsOfTeam(string_teamName)externalviewreturns(uint256_totalVoted);functiongetTotalVotersToTeam(string_teamName)externalviewreturns(uint256_total);functiongetVotingResultToTeamAtIndex(string_teamName,uint256_voterIndex)externalviewreturns(bool_endOfList,address_voter,uint256_voteWeight);functionvoteToTeam(address_voter,string_teamName,uint256_votingWeight)external;functiongetMaxTeamVotingPoints()externalviewreturns(uint256_maxTeamVotingPoints);functiongetTotalWinningTeams()externalviewreturns(uint256_total);functiongetFirstFoundWinningTeam(uint256_startSearchingIndex)externalviewreturns(bool_endOfList,uint256_nextStartSearchingIndex,string_teamName,uint256_totalVoted);}// ----------------------------------------------------------------------------
// Pizza Coin Team Contract
// ----------------------------------------------------------------------------
contractPizzaCoinTeamisITeamContract,Owned{/*
* Owner of the contract is PizzaCoin contract,
* not a project deployer who is PizzaCoin owner
*/usingSafeMathforuint256;usingBasicStringUtilsforstring;.........}
Code Snippet 1. Excerpt from PizzaCoinTeam contract’s source code
The code structure of PizzaCoinTeam contract is organized the same as PizzaCoinStaff and PizzaCoinPlayer. That is, PizzaCoinTeam consists of two integral parts as shown in the code snippet 1 including ITeamContract interface in the line no. 11 and PizzaCoinTeam contract in the line no. 61. ITeamContract contains function prototypes of PizzaCoinTeam that expose to PizzaCoin mother contract. PizzaCoinTeam contract implements ITeamContract interface and inherits from the contract named Owned similar to what we have discussed in the previous article.
// Team with players
structTeamInfo{// This is used to reduce potential gas cost consumption when kicking a team
uint256index;// A pointing index to a particular team on the 'teams' array
boolwasCreated;// Check if a team is being created
address[]players;// A list of team members (the first member is the one who creates a team)
// mapping(player => playerIndex)
mapping(address=>uint256)playerIndexMap;// This is used to reduce potential gas cost consumption when kicking a player in a team
address[]voters;// A list of staffs and other teams' members who have ever voted to a team
// mapping(voter => votingWeight)
mapping(address=>uint256)votesWeight;// Voting weight from each voter
uint256totalVoted;// Total voting weight from all voters
}string[]privateteams;mapping(string=>TeamInfo)privateteamsInfo;//mapping(team=>TeamInfo)
Code Snippet 2. Data structures and state variables for storing teams and voting information
There are two state variables used for storing teams and voting information under PizzaCoinTeam contract namely teams and teamsInfo as respectively defined in the line no’s. 20 and 21 of the code snippet 2. teams is an array collecting names of all registered teams. Whereas teamsInfo is a mapping variable which gathers information of each particular team in terms of struct TeamInfo as defined in the line no. 2. Name of any specific team is used as a mapping key for accessing corresponding team information stored on teamsInfo mapping.
The struct TeamInfo gathers a group of state variables including index, wasCreated, players, playerIndexMap, voters, votesWeight and totalVoted. index points to an element on the array teams for a particular team. wasCreated is a boolean variable used for indicating a registration status of a particular team. The use of wasCreated variable is similar to what we have discussed about the use of wasRegistered under PizzaCoinStaff contract in the previous article. wasCreated plays an important role to significantly reduce gas consumption for verifying a registration status of a specific team. players is an array containing addresses of the members in a team. playerIndexMap is a mapping which maps a player address to an index pointing to a corresponding element on players array. voters is an array containing addresses of all voters who commit a vote to the team. votesWeight is a mapping variable, using an address of a specific voter as a mapping key, containing a voting weight that any particular voter has ever voted to the team. totalVoted represents a sum of voting weights from all voters.
// ------------------------------------------------------------------------
// Player creates a new team
// ------------------------------------------------------------------------
functioncreateTeam(string_teamName)externalonlyRegistrationStateonlyPizzaCoin{require(_teamName.isNotEmpty(),"'_teamName' might not be empty.");require(teamsInfo[_teamName].wasCreated==false,"The given team was created already.");// Create a new team
teams.push(_teamName);teamsInfo[_teamName]=TeamInfo({wasCreated:true,players:newaddress[](0),voters:newaddress[](0),totalVoted:0,/*
Omit 'votesWeight'
*/index:teams.length-1/*
Omit 'playerIndexMap'
*/});}
Code Snippet 3. The implementation of createTeam()
The implementation of createTeam function is shown in the code snippet 3. The function has a single input parameter named _teamName which indicates a name of a specific team needed to be registered. On receiving a transaction request, createTeam function first checks the existence of the specified team in the line no’s. 10 - 13. If the specified team is already registered, the function reverts a transaction in the line no. 12, unless the function registers the specified team to the system in the line no’s. 16 - 29. Technically, _teamName is pushed into teams array in the line no. 16. After that, the function initializes team information with initial values as shown in the line no’s. 18 - 28 and then records the initialized information into teamsInfo mapping in the line no. 17.
// ------------------------------------------------------------------------
// Register a player to a specific team
// ------------------------------------------------------------------------
functionregisterPlayerToTeam(address_player,string_teamName)externalonlyRegistrationStateonlyPizzaCoin{require(_player!=address(0),"'_player' contains an invalid address.");require(_teamName.isNotEmpty(),"'_teamName' might not be empty.");require(teamsInfo[_teamName].wasCreated,"The given team does not exist.");// Add a player to the specified team
teamsInfo[_teamName].players.push(_player);teamsInfo[_teamName].playerIndexMap[_player]=teamsInfo[_teamName].players.length-1;}
Code Snippet 4. The implementation of registerPlayerToTeam()
The implementation of registerPlayerToTeam function is shown in the code snippet 4. This function is invoked when a user wants to join an existing team. The function has two input parameters _player and _teamName. First, the function verifies that _teamName indicates an existing team in the line no’s. 17 - 20. If the verification proccess succeeds then the function registers the specified address _player as a new member to the team _teamName in the line no’s. 23 and 24 else the function reverts a transaction in the line no. 19.
You may wonder why registerPlayerToTeam function does not authenticate the specified address _player. Let’s recap to understand this. According to the conceptual design of PizzaCoin voting system, we separate several functional subsystems into multiple contracts. Each contract manages distinct tasks. PizzaCoinPlayer manages functions related to tasks such as registering players, revoking players as well as authenticating players whereas PizzaCoinTeam manages functions related to tasks such as registering teams, revoking teams, authenticating teams as well as registering players to existing teams. However, PizzaCoinTeam contract is not responsible for authenticating an identity of any player because the contract does not hold any player information itself. To verify the authenticity of a player, therefore, we delegate PizzaCoin mother contract to do this task instead. More specifically, the mother contract would authenticate the specified address _player by consulting with PizzaCoinPlayer contract before invoking registerPlayerToTeam function. Certainly, you will better understand this point after reading the later articles. So keep staying with us.
// ------------------------------------------------------------------------
// Remove a specific team (the team must be empty of players)
// ------------------------------------------------------------------------
functionkickTeam(string_teamName)externalonlyRegistrationStateonlyPizzaCoin{require(_teamName.isNotEmpty(),"'_teamName' might not be empty.");require(teamsInfo[_teamName].wasCreated,"Cannot find the specified team.");uint256totalPlayers=__getTotalPlayersInTeam(_teamName);// The team can be removed if and only if it has 0 player left
if(totalPlayers!=0){revert("The specified team is not empty.");}uint256teamIndex=getTeamIndex(_teamName);// Remove the specified team from an array by moving
// the last array element to the element pointed by teamIndex
teams[teamIndex]=teams[teams.length-1];// Since we have just moved the last array element to
// the element pointed by teamIndex, we have to update
// the newly moved team's index to teamIndex too
teamsInfo[teams[teamIndex]].index=teamIndex;// Remove the last element
teams.length--;// Remove the specified team from a mapping
deleteteamsInfo[_teamName];}// ------------------------------------------------------------------------
// Get a total number of players in the specified team (internal)
// ------------------------------------------------------------------------
function__getTotalPlayersInTeam(string_teamName)internalviewreturns(uint256_total){require(_teamName.isNotEmpty(),"'_teamName' might not be empty.");require(teamsInfo[_teamName].wasCreated,"Cannot find the specified team.");returnteamsInfo[_teamName].players.length;}// ------------------------------------------------------------------------
// Get an index pointing to the specified team on the array 'teams'
// ------------------------------------------------------------------------
functiongetTeamIndex(string_teamName)internalviewreturns(uint256_teamIndex){assert(_teamName.isNotEmpty());assert(teamsInfo[_teamName].wasCreated);returnteamsInfo[_teamName].index;}
Code Snippet 5. The implementation of kickTeam(), __getTotalPlayersInTeam() and getTeamIndex()
Code snippet 5 describes the implementation of kickTeam function. With this function, the staff has right to revoke some team if necessary. The function requires one input parameter _teamName which indicates a name of the team to be revoked. As the function is defined with onlyRegistrationState modifier in the line no. 4, this function can be invoked only if PizzaCoinTeam contract is in Registration state. Moreover, as discussed above we delegate PizzaCoin contract to authenticate the staff who is invoking the function instead.
Once kickTeam function is invoked, the function first verifies the existence of the specified team in the line no’s. 10 - 13. If the specified team exists, the function proceeds to verify if the team is empty of players in the line no’s. 15 - 20. In more detail, kickTeam function invokes __getTotalPlayersInTeam( _teamName) function in the line no. 15 to get a number of current players joining in the specified team. If the team is not empty then the function reverts a transaction in line no’s. 18 - 20 else the function asks for an index of the specified team by calling to getTeamIndex(_teamName) function in the line no. 22. Finally, the function removes the specified team pointed by the obtained index from the array teams in the line no’s. 26, 31 and 34. The function then removes the revoked team information from the mapping teamsInfo in the line no. 37.
// ------------------------------------------------------------------------
// Remove a specific player from a particular team
// ------------------------------------------------------------------------
functionkickPlayerOutOfTeam(address_player,string_teamName)externalonlyRegistrationStateonlyPizzaCoin{require(_player!=address(0),"'_player' contains an invalid address.");require(_teamName.isNotEmpty(),"'_teamName' might not be empty.");require(teamsInfo[_teamName].wasCreated,"Cannot find the specified team.");boolfound;uint256playerIndex;(found,playerIndex)=getPlayerIndexInTeam(_player,_teamName);if(!found){revert("Cannot find the specified player in a given team.");}// Remove the specified player from an array by moving
// the last array element to the element pointed by playerIndex
teamsInfo[_teamName].players[playerIndex]=teamsInfo[_teamName].players[teamsInfo[_teamName].players.length-1];// Since we have just moved the last array element to
// the element pointed by playerIndex, we have to update
// the newly moved player's index to playerIndex too
teamsInfo[_teamName].playerIndexMap[teamsInfo[_teamName].players[playerIndex]]=playerIndex;// Remove the last element
teamsInfo[_teamName].players.length--;// Remove the specified player from a mapping
deleteteamsInfo[_teamName].playerIndexMap[_player];}// ------------------------------------------------------------------------
// Get an index pointing to a specific player on the array 'players' of a given team
// ------------------------------------------------------------------------
functiongetPlayerIndexInTeam(address_player,string_teamName)internalviewreturns(bool_found,uint256_playerIndex){assert(_player!=address(0));assert(_teamName.isNotEmpty());assert(teamsInfo[_teamName].wasCreated);_playerIndex=teamsInfo[_teamName].playerIndexMap[_player];_found=teamsInfo[_teamName].players[_playerIndex]==_player;}
Code Snippet 6. The implementation of kickPlayerOutOfTeam() and getPlayerIndexInTeam()
Code snippet 6 shows the implementation of kickPlayerOutOfTeam function. Staff would invoke this function in order to revoke a specific player from a particular team. The function requires two input parameters _player and _teamName. _player denotes an address of the player to be revoked. _teamName indicates a name of the team in which the player associates with. Once the function is invoked, it verifies the existence of the team _teamName in line no’s. 17 - 20. Later, the function makes a call to getPlayerIndexInTeam( _player, _teamName) function in order to get a player index in the line no. 25. If everything went well, the function removes the player address pointed by the obtained index from the array players in the line no’s. 32, 37 and 40. Eventually, the function removes the player from the mapping playerIndexMap in the line no. 43.
// ------------------------------------------------------------------------
// Allow the staff or player to give a vote to the specified team
// ------------------------------------------------------------------------
functionvoteToTeam(address_voter,string_teamName,uint256_votingWeight)externalonlyVotingStateonlyPizzaCoin{require(_voter!=address(0),"'_voter' contains an invalid address.");require(_teamName.isNotEmpty(),"'_teamName' might not be empty.");require(_votingWeight>0,"'_votingWeight' must be larger than 0.");require(teamsInfo[_teamName].wasCreated,"Cannot find the specified team.");// If teamsInfo[_teamName].votesWeight[_voter] > 0 is true, this implies that
// the voter used to give a vote to the specified team previously
if(teamsInfo[_teamName].votesWeight[_voter]==0){// The voter has never given a vote to the specified team before
// We, therefore, have to add a new voter to the 'voters' array
// of the specified team
teamsInfo[_teamName].voters.push(_voter);}teamsInfo[_teamName].votesWeight[_voter]=teamsInfo[_teamName].votesWeight[_voter].add(_votingWeight);teamsInfo[_teamName].totalVoted=teamsInfo[_teamName].totalVoted.add(_votingWeight);}
Code Snippet 7. The implementation of voteToTeam()
The last function to be discussed in this article is voteToTeam function as described in the code snippet 7. This function allows both the staff and player commit a vote to a favourite team. The function requires three input parameters _voter, _teamName and _votingWeight. _voter denotes an address of the one who submits a voting transaction. _teamName indicates a name of the team to be voted. _votingWeight specifies a number of voting tokens given to the team. Note that again, we delegate PizzaCoin mother contract to authenticate a voter as well as verifying the voter’s token balance.
Upon receiving a voting transaction, voteToTeam function verifies the existence of the team to be voted in the line no’s. 22 - 25. Then, the function checks if the voter used to vote to the specified team or not in the line no. 29. That is, if the statement teamsInfo[_teamName].votesWeight[_voter] == 0 is true, this implies that the voter never used to vote to the specified team before. If so, the function registers the voter address into the array voters of the specified team which is stored on the mapping teamsInfo in the line no. 33. At last, the function securely transfers the given token _votingWeight to the specified team in the line no. 36 and then securely updates totalVoted variable of the specified team in the line no. 37. voteToTeam function leverages add function of SafeMath library in order to perform those secure update operations.
Summary
Let’s summarize. In this article, you have learned how PizzaCoinTeam contract was implemented. You have also learned that we delegated PizzaCoin mother contract as a coordinator for authenticating and verifying staffs and players instead, since PizzaCoinTeam contract does not hold any user information itself. In the next article, you will learn how to deploy PizzaCoin’s children contracts with contract factories.
PizzaCoin the series consists of 6 articles as follows.