In this series we will create a simple Dapp, based on Ethereum blockchain using Solidity smart contracts. In the later parts we will see how to test and deploy the contracts. We will also create React Web App and use MetaMask for blockchain transactions, to get a fully functional Dapp.

Demo of the Dapp is available on this address.

All source code is freely available in this GitHub repository.

Join us at: Solidity development community to learn more

Goal of the Dapp

I named this Dapp “Shark of The Pool”, but what exactly does it do? Goal of the Dapp is pretty simple, I want to be able to create a pool in which people can contribute their precious Ether. Collection period is limited with a deadline, after which deposits to the pool aren’t possible anymore.

When the set deadline expires, we want to see who is the Shark of the pool, or the biggest contributor. Owner of that address, we will call him Shark, is the winner and can withdraw all the funds collected.

Smart contracts

Let’s go right to the business. I will assume reader is not new to the Solidity, and can follow me through the development. Otherwise, Solidity documentation is your friend. Also I highly recommend using Truffle framework and a walk through their tutorials.

Goal seems pretty simple, but when you start to think about it in terms of development, it may not be that straightforward. What we need is to separate logic into a few modules.

Pool Contract

Pool will do exactly what we described earlier. Pool contract accepts deposits and safely stores them for the time of the collection period. After the deadline it rejects any further deposits and enables withdraw of funds for the Shark of the pool.

Let’s first define interface. Interfaces are important part of Solidity development. To learn more about why you should use them, I recommend this excellent post by Elena Dimitrova.

pragma solidity ^0.4.21;



contract iPool {

function () public payable;



function withdraw() public returns (bool success);

}

But didn’t we forget deposit function you may ask? Deposit is a direct transaction to the contract. It’s processed in the no name function. Don’t forget the payable keyword, function modifier that allows functions to receive Ether. More on the topic by Sophie Wong.

We want Deposit and Withdraw functions to have time constraints. For that we will create modifiers to the functions in the Timed contract. They get executed before the function they are modifying does, and are a great place to put function execution constraints.

pragma solidity ^0.4.21;



contract Timed {



uint256 public deadline;



modifier onlyWhileOpen {

require(block.timestamp <= deadline);

_;

}



modifier onlyWhileClosed {

require(block.timestamp > deadline);

_;

}

}

We are using some fancy stuff here, so let’s explain:

block.timestamp provides current block time in epoch seconds

provides current block time in epoch seconds modifier creates modifier to be used with functions

creates modifier to be used with functions _; after this statement, workflow of modified function continues

Now let’s go to the Pool implementation part. First let’s create constructor

contract Pool is iPool, Timed {

using SafeMath for uint256;



string public name;

address public token;

uint256 public rate;



function Pool(string _name, uint256 _rate, uint256 _deadline) public {

require(_rate > 0);

require(_deadline > block.timestamp);

name = _name;

rate = _rate;

deadline = _deadline;

token = new FishToken(_deadline);

}

Constructor creates FishToken instance, which will be associated with this pool. FishToken will be used for deposit tracking and Shark selection.

We have to make some checks upon calling constructor. Deadline has to be higher than current time and rate for award distribution has to be over 0.

So let’s see deposit function

function () public payable onlyWhileOpen {

require(msg.value > 0);

uint256 rewardTokens = rate.mul(msg.value);

require(iFishToken(token).issueTokens(msg.sender, rewardTokens));

}

So simple right? Literally all it does is calculate received tokens based on the set rate and triggers issue function on the FishToken contract. Now we can see how to apply function modifier. onlyWhileOpen modifier will be executed before function does, and will throw an error if time passes the deadline.

And our Withdraw function

function withdraw() public onlyWhileClosed returns (bool success) {

if(iFishToken(token).isShark(msg.sender)) {

msg.sender.transfer(address(this).balance);

return true;

}

return false;

}

Withdraw function is modified by onlyWhileClosed modifier. It also performs check if sender wanting to withdraw is the real Shark. If yes, he gets all the Ether on the Pool contract. Lucky guy!

That’s it for the pool contract, it has more than enough logic and we don’t want to further complicate things. Let’s move on to the most complicated part, our FishToken contract.

Fish Token Contract

Now, we want to keep records of Ether contributions and determine the Shark. This is the most complicated part of the Dapp, so don’t despair if logic is not clear right from the look at it. I’ve used a standard token contract to begin with and made a lot of modifications to suit the needs of this Dapp.

I named contract FishToken, since it keeps track of fishes in the pool and determines Shark amongst them.

So let’s define an interface for this contract

contract iFishToken {



function balanceOf(address _owner) public view returns (uint256 balance);



function transfer(address _to, uint256 _amount) public returns (bool success);



function issueTokens(address _beneficiary, uint256 _amount) public returns (bool success);



function getShark() public view returns (address sharkAddress, uint256 sharkBalance);



function isShark(address _address) public view returns (bool success);



/// @notice Event propagated on every executed transaction

event LogTransfer(address indexed _from, address indexed _to, uint256 _value);



/// @notice Event propagated when new deposit is made to the pool

event LogIssue(address indexed _member, uint256 _value);



/// @notice Event propagated when new address has the most tokens

event LogNewShark(address indexed _shark, uint256 _value);

}

I know, it’s a lot. Let’s go through it. First implementation of constructor

contract FishToken is iFishToken, Ownable, Timed {

using SafeMath for uint256;



uint8 public decimals; //How many decimals to show

address public currentShark;

uint256 public totalSupply;

mapping(address => uint256) public balances;



mapping(address => bool) public participantsMap;

address[] public participantsArray;



function FishToken(uint256 _deadline) public {

deadline = _deadline;

totalSupply = 0;

currentShark = msg.sender;

owner = msg.sender;

}

Functions balanceOf() and transfer() are similar to the classic token functions.

function transfer(address _to, uint256 _value) public onlyWhileOpen returns (bool success) {

if (balances[msg.sender] < _value || balances[_to] + _value <= balances[_to]) {

return false;

}

addToParticipants(_to);

balances[msg.sender] = balances[msg.sender].sub(_value);

balances[_to] = balances[_to].add(_value);



emit LogTransfer(msg.sender, _to, _value);



determineNewShark();



return true;

} function balanceOf(address _owner) public view returns (uint256 balance) {

return balances[_owner];

}

What they do is enable reward token transfers among participants and checking current balances. Transfer is modified with onlyWhileOpen modifier, meaning transfer is only possible for the collection period. That way users can aggregate received tokens if contributing from multiple addresses. Or give tokens to another person.

We are using SafeMath library from zeppelin-solidity repository for math operations. Also we are emitting Log events when transaction occurs. Events are a great way to interact with your client side application. You can create listeners and listen for the emitted events on the contract. That way you get push event when something happens and don’t need periodic queries on the contract.

Functions addToParticipants() and determineNewShark(), are used for shark selection. We need to find new shark every time there is a change in token distribution.

Next is the function issueTokens()

function issueTokens(address _beneficiary, uint256 _amount) public onlyOwner onlyWhileOpen returns (bool success) {

if(balances[_beneficiary] + _amount <= balances[_beneficiary]) {

return false;

}

addToParticipants(_beneficiary);

balances[_beneficiary] = _amount.add(balances[_beneficiary]);

totalSupply = _amount.add(totalSupply);



emit LogIssue(_beneficiary, _amount);



determineNewShark();



return true;

}

Function issueToken() is triggered from the Pool contract. Since we create FishToken contract with the Pool contract, that contract becomes the owner. Modifying issueTokens() with onlyOwner modifier, makes is executed only when being called from the Pool contract, that created FishToken contract. That means only parent Pool contract can issue new tokens.

For Ownable contract I used the one from zeppelin-solidity repository. They have a lot of contracts you can use in your own Dapps.

Issuing tokens is pretty straightforward. We increase the balance of the address and totalSupply. Again we have to call functions addToParticipants() and determineNewShark(), since state of token distribution changed and we have to check if Shark changed.

We also emit event LogIssue() which will help us with the client side application.

Determine Shark

This functions reside in the FishToken contract. Basic idea for the algorithm is, to keep record of all addresses that could have tokens. This is done in participantsArray. To avoid double inserts into participantsArray, we also have participantsMap, which is set on true for the addresses, that are added to the array.

To determine Shark, we simply loop through the participantsArray and set the one with most tokens as a Shark.

mapping(address => bool) public participantsMap;

address[] public participantsArray; function determineNewShark() internal {

address shark = participantsArray[0];

uint arrayLength = participantsArray.length;

for (uint i=1; i < arrayLength; i++) {

if (balances[shark] < balances[participantsArray[i]]) {

shark = participantsArray[i];

}

}



if(currentShark != shark) {

currentShark = shark;

emit LogNewShark(shark, balances[shark]);

}

}



function addToParticipants(address _address) internal returns (bool success) {

if(participantsMap[_address]) {

return false;

}

participantsMap[_address] = true;

participantsArray.push(_address);

return true;

}

These functions are set as internal, meaning they can only be triggered from another function within the contract, and not as a transaction on the contract.

We have to determine new Shark whenever state of token distribution changes. Meaning we have to do recalculation for each transaction or issuing of tokens.

Conclusion

Here we go, we have created a fully functional Dapp, which tracks Ether contributions to the pool and determines Shark, the biggest contributor among them. We have enabled transfer of reward tokens between addresses, meaning you can participate with a friend, or using multiple addresses.

Do you find any errors in my code? Do you see a way to optimize it? Please let me know, I would love to hear your feedback!

Demo of the Dapp is available on this address.

All source code is freely available in this GitHub repository.

Parts of the series: