Build your First DAO and deploy it to moonbeam network PART 2 : Smart Contract

Drnutsu
blocktrend
Published in
8 min readApr 2, 2022

This article is part of DAO building on moonbeam series, if you still didn’t read the first part yet, I highly recommend checking it first here

Build your First DAO and deploy it to moonbeam network PART 1 : Setup and play around

Previously, we already setup project with boilerplate and also have already tried basic functionality from boilerplate UI. In this article, we will deeply into the smart contract code, to implement all functionality we have tried previously.

Let’s check the smart contract at

packages
- hardhat
- contracts
- PowDow.sol

If you working with OOP programming before, you would think the smart contract is like class, that have many methods inside. yeah, it quite the same but it has something different, it’s like class with financial functionality enhancement included, for example all contract be able to store ETH balance like wallet, and also can send/received ETH from other wallet. So before getting start, if you still not familiar with solidity programing please check here first.

all line of code is reference from file in project, so please open the file while walking through code with this article

Getting Start

In my opinion, when we need to understand some smart contract it shouldn’t read the code from top to the bottom, because it will make you confuse the relation between function and variable, and also the smart contract developer always write the code to save gas cost as much as they can, that sometime may reduce code readability. However, the best way to figure out the mechanism for some smart contract is by trying to walk through the code follow the flow on each feature from start to the end. Alright ! Then let get start with the first feature 🚀

Add & Remove member

uint256[] public proposalQueue; 

// Create a proposal that shows the address (member to be added) as the proposer. And sets the flags to indicate the type of proposal, either add or kick.

function _SubmitMemberProposal(address entity, string memory details, uint256 action) internal {
proposalQueue.push(proposalCount);
if(action == 0) {
Proposal storage prop = proposals[proposalCount];
prop.proposer = entity;
prop.paymentRequested = 0;
prop.startingTime = now;
// [sponsored, processed, didPass, cancelled, memberAdd, memberKick]
prop.flags = [false, false, false, false, true, false]; // memberAdd
prop.details = details;
prop.exists = true;

emit SubmitProposal(prop.proposer, 0, prop.details, prop.flags, proposalCount, msg.sender);
proposalCount += 1;
}

if(action == 1) {
Proposal storage prop = proposals[proposalCount];
prop.proposer = entity;
prop.paymentRequested = 0;
prop.startingTime = now;
prop.flags = [false, false, false, false, false, true]; // memberkick
prop.details = details;
prop.exists = true;

emit SubmitProposal(prop.proposer, 0, prop.details, prop.flags, proposalCount, msg.sender);
proposalCount += 1;
}
}

Let's check on line 162 first, the function _SubmitMemberProtocol the with underscore, it’s not syntax sugar, it just the convention to differentiate private and public function. this function is the internal one, that create the new proposal on DAO. Then let’s check the code, you might wonder what is action and how this number refer from, this one is also the reason that smart contract not good for readability, actually this number is just an enum, but implementor chooses int to save the gas price. So here, 0 refer to add member action and 1 refer to kick member out. if action is 0, then the contract will create new proposal struc, and add all information that sent from frontend client. Let's move next, there’re another interesting point, you would see

// [sponsored, processed, didPass, cancelled, memberAdd, memberKick]
prop.flags = [false, false, false, false, true, false]

This one is boolean flag that indicates state of this proposal, it’s like the status indicator of the proposal that we will use to track state later. So right now skip it first, so let move next, another interesting point is

emit SubmitProposal(prop.proposer, 0, prop.details, prop.flags, proposalCount, msg.sender);

This line will emit the event, that is catchable by frontend client, and this event will trigger client to handle it to do something after execution is finished.

However, I don’t want to make this article too long, you could check another case action == 1 by yourself it very similar to the first one.

Let’s move to the upper layer, check addMember and kickMember function

function addMember(address newMemAddress, string memory details) public onlyMember {
_SubmitMemberProposal(newMemAddress, details, 0); // 0 adds a member
}

function kickMember(address memToKick, string memory details) public onlyMember {
_SubmitMemberProposal(memToKick, details, 1); // 1 kicks a member
}

Here is just a simple function that call internal _SubmitMemberProposal function, you may have a question, why do we need to create new function just for call another function, the reason is all solidity contract is internal or private by default, but these two functions have public keyword which is callable from external, and these two functions are created to be an interface, to scope down all params that user could sent. It shouldn’t be good if user could directly call _SubmitMemberProposal function, which they can send the unsupported action as the last param to make smart contract function overflow, that so hackable.

Another interesting keyword on this function is onlyMember which is the modifier that could make smart contract reduce duplicate code a lots, let check onlyMember function

modifier onlyMember {
require(members[msg.sender].shares > 0, "Your are not a member of the PowDAO.: don't have shared.");
_;
}

This modifier just guards to permit only DAO’s member to call, so any function that uses this modifier will also block non-member as well.

PROCESS THE PROPOSAL

This one is the most important function in this contract it executor, which will make proposal take action like user request, let's check the code for more understanding.

function processProposal(uint256 proposalId) public onlyMember returns (bool) {
require(proposals[proposalId].exists, "This proposal does not exist.");
require(proposals[proposalId].flags[1] == false, "This proposal has already been processed");
require(getCurrentTime() >= proposals[proposalId].startingTime, "voting period has not started");
require(hasVotingPeriodExpired(proposals[proposalId].startingTime), "proposal voting period has not expired yet");
require(proposals[proposalId].paymentRequested <= address(this).balance, "DAO balance too low to accept the proposal.");
for(uint256 i=0; i<proposalQueue.length; i++) {
if (proposalQueue[i]==proposalId) {
delete proposalQueue[i];
}
}

Proposal storage prop = proposals[proposalId];

// flags = [sponsored, processed, didPass, cancelled, memberAdd, memberKick]
if(prop.flags[4] == true) { // Member add
if(prop.yesVotes > prop.noVotes) {
members[prop.proposer] = Member(1, 0, true, 0);
prop.flags[1] = true;
prop.flags[2] = true;
}
else{
prop.flags[1] = true;
prop.flags[3] = true;
}
}
if(prop.flags[5] == true) { // Member kick
if(prop.yesVotes > prop.noVotes) {
members[prop.proposer].shares = 0;
prop.flags[1] = true;
prop.flags[2] = true;
}
else{
prop.flags[1] = true;
_cancelProposal(proposalId);
}
}
if(prop.flags[4] == false && prop.flags[5] == false) {
if(prop.yesVotes > prop.noVotes) {
prop.flags[1] = true;
prop.flags[2] = true;
_increasePayout(prop.proposer, prop.paymentRequested);
}
else{
prop.flags[1] = true;
_cancelProposal(proposalId);
}
}

emit ProcessedProposal(prop.proposer, prop.paymentRequested, prop.details, prop.flags, proposalId, prop.proposer);
return true;
}

This code is quite long, but if you consider it deeply, it’s just an if-else case for each feature that can create proposal to do it, let check part by part

require(proposals[proposalId].exists, "This proposal does not exist.");
require(proposals[proposalId].flags[1] == false, "This proposal has already been processed");
require(getCurrentTime() >= proposals[proposalId].startingTime, "voting period has not started");
require(hasVotingPeriodExpired(proposals[proposalId].startingTime), "proposal voting period has not expired yet");
require(proposals[proposalId].paymentRequested <= address(this).balance, "DAO balance too low to accept the proposal.");
for(uint256 i=0; i<proposalQueue.length; i++) {
if (proposalQueue[i]==proposalId) {
delete proposalQueue[i];
}
}

Basically, this is the guarding part, it’s very important to check authority before executing some action, for more detail you can read it directly on the code. It’s quite straightforward.

Proposal storage prop = proposals[proposalId];

// flags = [sponsored, processed, didPass, cancelled, memberAdd, memberKick]
if(prop.flags[4] == true) { // Member add
if(prop.yesVotes > prop.noVotes) {
members[prop.proposer] = Member(1, 0, true, 0);
prop.flags[1] = true;
prop.flags[2] = true;
}
else{
prop.flags[1] = true;
prop.flags[3] = true;
}
}

This is the execution part. basically, it just get the proposal info from Proposal struct and execute it following the proposal type. So I’ll pick just one action to walkthrough. the code above is “Add Member” action. as I have already talked about before, props.flags is proposal type, which indicate what action this proposal will take after got approved. So for this it just create the new Member struct and add it to members list. For others condition case, you could check it by yourself it quite similar with condition that I just walkthrough.

Submit Vote

Another important process on DAO is voting system, let check the code together.

function submitVote(uint256 proposalId, uint8 uintVote) public onlyMember {
require(members[msg.sender].exists, "Your are not a member of the PowDAO.: not existed");
require(proposals[proposalId].exists, "This proposal does not exist.");

address memberAddress = msg.sender;
Member storage member = members[memberAddress];
Proposal storage prop = proposals[proposalId];

require(uintVote < 3, "must be less than 3");
Vote vote = Vote(uintVote);

require(getCurrentTime() >= prop.startingTime, "voting period has not started");
require(!hasVotingPeriodExpired(prop.startingTime), "proposal voting period has expired");
require(prop.votesByMember[memberAddress] == Vote.Null, "member has already voted");
require(vote == Vote.Yes || vote == Vote.No, "vote must be either Yes or No");

prop.votesByMember[memberAddress] = vote;

if (vote == Vote.Yes) {
prop.yesVotes = prop.yesVotes.add(member.shares);

}
else if (vote == Vote.No) {
prop.noVotes = prop.noVotes.add(member.shares);
}

emit SubmitVote(proposalId, msg.sender, memberAddress, uintVote);
}

Same as previous feature, the first part is authorized validation part, so you can check it yourself on the code, let’s move to the interesting part. you may wonder why it has both require and traditional if what’s the exactly different between it. The main difference is require will revert all state if the condition is not passed. For example,

address memberAddress = msg.sender;
Member storage member = members[memberAddress];
Proposal storage prop = proposals[proposalId];

require(uintVote < 3, "must be less than 3");

On the first 3 lines, we assign sender address to memberAddress and also assign any others variable, if require verification is failed, it will revert all variable assignment above require line, that different from traditional if that will just skip code block inside if then continue to proceed the next line.

So we’ll add the important verification guard in require to ensure everything will be reverted if it failed.

Get Paid

The last most important feature is “Get Paid”, to pay proposer when proposal is approved by DAO member.

// Internal function
function _decreasePayout(address beneficiary, uint256 subtractedValue) internal returns (bool) {
uint256 currentAllowance = _payoutTotals[beneficiary];
require(currentAllowance >= subtractedValue, "ERC20: decreased payout below zero");
uint256 newAllowance = currentAllowance - subtractedValue;
_payoutTotals[beneficiary] = newAllowance;
return true;
}

function _increasePayout(address recipient, uint256 addedValue) internal returns (bool) {
uint256 currentBalance = 0;
if(_payoutTotals[recipient] != 0) {
currentBalance = _payoutTotals[recipient];
}
_payoutTotals[recipient] = addedValue + currentBalance;
return true;
}

// Public
function payout(address recipient) public view returns (uint256) {
return _payoutTotals[recipient];
}

// A proposer calls function and if address has an allowance, recieves ETH in return.
function getPayout(address payable addressOfProposer) public returns (bool) {
uint256 allowanceAvailable = _payoutTotals[addressOfProposer]; // Get the available allowance first amd store in uint256.
require(allowanceAvailable > 0, "You do not have any funds available.");

if (allowanceAvailable != 0 && allowanceAvailable > 0) {
addressOfProposer.call{value:allowanceAvailable}(""); // can also be: addressOfProposer.transfer(allowanceAvailable)
_decreasePayout(addressOfProposer, allowanceAvailable);
// console.log("transfer success");
emit Withdraw(addressOfProposer, allowanceAvailable);
return true;
}
}

Basically, this function will check user allowanceAvailable that indicate how much allowance that user can withdraw from DAO, this variable will increase when proposal is approved and call processProposal, then proposer will be able to withdraw the allowance with this getPayout` function.

DAO Flow

After we already understand all main functionality for DAO, you should be able to figure out overview mechanism of DAO, so we would visualize it to be like:

As you can see, actually it not much complex, but because of gas-saving code style and also developer need to include all logic into one file, make it look complex at first sight.

Congratulation 🎉, I hope you guys should already understand DAO contract code. However, we still have some functions and features that we still didn’t walkthrough yet, and I suggested you check it all, to get more understand on the DAO concept.

on the next part, we’ll try to deploy it to moonbeam and play some feature on moonbeam, to get more understanding of moonbeam's benefits for developers.

However, I don’t want to make the article too long, so I will split the next part into the next chapter.

Thank you so much for your guys interest, and see you in the next chapter 😄.

--

--

Drnutsu
blocktrend

Web3 Creator, Lifelong Learner, entrepreneur