The Friendzone 2.0: Permissions and ownership

In the last episode of The Friendzone we covered the basics of smart contracts. We talked about smart contract structure, syntax and inter-contract communication. Check it out here if you want to catch up to where we are now.

Since that article I have simplified the contract structure to two layers instead of three. Getting rid of the Friendzone.sol entry point in favor of letting users create plans through Plan.sol directly. This will allow us to keep permissioning much simpler by adding the bulk of the security measures to Plan.sol’s methods.

By creating a controlled interface in Plan.sol we will be able to propagate a Plan members’ ownership to initiatives as well as to initiative creation and vote casting through just one contract and delegate the rest to the child contracts.

Let’s start with Plan.sol. I modified the contract structure defined in the previous article by adding methods that control initiatives.

The constructor for Plan.sol will look like this:

function Plan(bytes32 _name, address[] _members) {
owner = msg.sender;
name = _name;
members = _members;
    for (uint i = 0; i < members.length; i++) {
membersMap[members[i]] = true;
}
    // add owner as member even if he hasn't been part of the initial members collection
if (!membersMap[owner]) {
members.push(owner);
membersMap[owner] = true;
}
}

We are setting the creator of the instance of the contract as the owner of it, and setting the initial name and members for the plan. Remember! The constructor will be run just once when the contract is instantiated, so only the initial msg.sender will be set as owner.

Then we are adding all the members to the membersMap so that we can check in constant time if a user (an address) is a member of the plan.

Finally, we are adding the owner to the members list if he/she is not a member yet. We want the creator of the plan to be able to participate in the plan!

As a plan organizer, you are probably going to convince people after creating the contract that they should join your crazy endeavors. Therefore, we will need a method to add new members.

function addMember(address newMember) onlyOwner {
// make sure the new member is not an existing one
require(!membersMap[newMember]);
    // add member
members.push(newMember);
membersMap[newMember] = true;
    // communicate that a new member has been added to the plan
NewMemberAdded(newMember);
}

We are marking the method with the onlyOwner modifier so that the owner of the plan is the only one who can add new members to it. We don’t want our friend Jimmy to invite his entire family to a plan so that they can pass an initiative the actual friend group hates. Right? Damn Jimmy… not again!

We also want to make sure that the member is part of the members’ list. We accomplish that with the require statement.

After adding the member to the members’ array and map, we broadcast an event saying that a new member has been added to the plan.

The other main method we need to add is the addInitiative method:

function addInitiative(
uint256 votesNeededToPass,
bytes32 name,
bytes32 description
) onlyMembers returns (Initiative newInitiativeAddress) {
// Instantiate the new initiative
Initiative newInitiative = new Initiative(
votesNeededToPass,
name,
description
);

// add it to the state
initiatives.push(newInitiative);

// broadcast the newly created initiative
NewInitiativeAdded(newInitiative);

// return the address of the new initiative
return newInitiative;
}

The method takes a name, a description and a minimum amount of votes needed to pass the initiative.

With the onlyMembers modifier, we make sure that only this plan’s members can create new initiatives. The method returns the address of the new initiative created, and broadcasts as an event to any consumers out there.

At this point, the “only” thing left for this first version of the Friendzone is adding the methods to interact with each initiative. We are going to want to open and close initiatives for voting, vote on an initiative, and get results. We will also want to get the consumer’s vote in a given initiative if he or she is a member of the plan.

function openVoting(address initiative) onlyOwner {
initiativesMap[initiative].openVoting();
}
function closeVoting(address initiative) onlyOwner {
initiativesMap[initiative].closeVoting();
}
function voteOnInitiative(
address initiative,
bool value
) onlyMembers {
initiativesMap[initiative].vote(value);
}
function getVoteForInitiative(address initiative) onlyMembers constant returns (bool) {
return initiativesMap[initiative].voterValue(msg.sender);
}
function getInitiativeVotes(address initiative) onlyMembers constant returns (uint) {
return initiativesMap[initiative].getPositiveVotes();
}

We will get into the details of the initiatives’ API in the next article, but we can already see that methods are “marked” with the onlyMembers and onlyOwner modifier to prevent any crazy punk from voting. (Yeah Jimmy… you again…)

We are also marking some of the methods as constant which acts as a promise to not mutate the state of the blockchain.

For all these methods we use the initiativesMap to access a specific instance of an initiative and act on it in constant time. Wohoo!

Initiative.sol will cover how each initiative is implemented. They will be using the member permission infrastructure from the parent Plan.sol contract instance to manage who access each Initiative.

If you want to check the current state of the codebase check out the Github repo here. Please leave a comment below with improvements and ideas, this is a living project! And we both want Jimmy to keep from messing up our plans. Right?