Truffle for Dapp development

In the previous blog, I had written a smart contract that allowed hash of an artifact to be added to the blockchain. Although the programming of the smart contract was pretty simple, the deployment of the contract on the blockchain was pretty cumbersome. A framework makes this task easy.

The two popular frameworks used for Dapp development are Truffle and Embark. Both have a lot of features and allow a lot of customizations and test-based development. In this tutorial, I will provide examples of how to program a Dapp using Truffle framework.

The purpose of this Distributed Application (Dapp) is to allow an initial set of ‘shareholders’ to fairly distribute profits from an external source among themselves.

The smart contract must be able to do the following:
  1. Accept profits sent as ETH to the contract.
  2. Allow shareholders to withdraw their fair portion of the profits based on their % ownership at the time of the profits being paid in.
  3. Allow a shareholder to transfer his or her stake in the contract to someone else.
  4. Allow all shareholders to hold a vote, which if passing 51% agreement, allows them to dilute all of their own shares and issue a % of new equity at a valuation that is equal to 100 multiplied by the profits accrued during the previous month.
  5. Allow new users to buy the new equity at the offered price following a successful vote, to become shareholders themselves.
  6. Allow all interaction with the smart contracts to occur via the browser, including letting shareholders check their balance of profits to be paid.

Before we commence with the Dapp development. We will need to install Truffle framework and ATOM editor. Atom can be installed by downloading the file from https://atom.io/. While truffle framework is available as an npm package. More details on Truffle installation is available here.

The steps to be followed are as follows:

  • Create a new folder/directory that will host all the source code of the new Dapp and then execute the command. This command will generate the boiler plate code required for Dapp development. the web pack option will generate javascript based code for UI and backend development as well. If you plan to use a different programming language for backend and frontend development. You can just execute the command truffle init, this will generate boiler plate code for smart contract(solidity) development alone.
$ truffle init webpack
Downloading project...
Installing dependencies...
Project initialized.

  Documentation: https://github.com/trufflesuite/truffle-init-webpack

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test:           truffle test
  Build Frontend: npm run build
  Run Linter:     npm run lint
  Run Dev Server: npm run dev

Hint: Run the dev server via `npm run dev` to have your changes rebuilt automatically.

Make sure you have an Ethereum client like the ethereumjs-testrpc running on http://localhost:8545.

$ 

  • Truffle Init command generates the following directory structure under the directory where the command was executed.

$ ls -l
total 52
drwxrwxr-x   4 partha partha  4096 May 14 17:52 app
drwxrwxr-x   2 partha partha  4096 May 14 17:52 contracts
drwxrwxr-x   2 partha partha  4096 May 14 17:52 migrations
drwxrwxr-x 606 partha partha 20480 May 14 17:53 node_modules
-rw-rw-r--   1 partha partha  1067 May 14 17:52 package.json
-rw-rw-r--   1 partha partha  1060 May 14 17:52 README.md
drwxrwxr-x   2 partha partha  4096 May 14 17:52 test
-rw-rw-r--   1 partha partha   233 May 14 17:52 truffle.js
-rw-rw-r--   1 partha partha   801 May 14 17:52 webpack.config.js
$ 


  • The contracts folder contains all the solidity smart contract code. There are already three solidity smart contract programs in this folder. You will need to retain Migrations.sol alone from this. The other two can be used as a reference. This folder will contain your new solidity smart contracts.
  • The app folder contains, the javascript backend and front-end code.  The migrations folder contains code for deployment of smart contract onto the Ethereum blockchain. By default, all source code is deployed on the Ethereum development environment. Truffle internally uses testrpc ethereum client, so ensure to download and install this as well. However, if you want to use geth or other clients you can still continue to do so (by changing the configuration in truffle.js).  
  • The test folder contains test scripts, which can be written in both solidity or javascript (Mocha based test scripts).
  • Create a new file called EquityShare.sol under the contracts folder. Delete the Metacoin.sol and ConvertLib.sol from that folder. Also remove references of these two programs from migrations folder as well. The code for EquityShare.sol is given below:

pragma solidity ^0.4.5;
/**
* EquityShare smart contract holds the equity holding pattern of a company
*/
contract EquityShare {
  address owner;
  uint[] profits;
  struct ShareHolder {
  address person;
  uint equity;
    uint profitShare;
    /*
    * votingStatus = 1, Approve Dilution
    * votingStatus = 2, Disapprove Dilution
    * votingStatus = 0, no voting.
    */
  uint votingStatus;
 }
 ShareHolder[] shareHolders;
  mapping(address => uint) balances;
  /*
  * status = 1 means that the EquityShare contract is still valid
  * status = 2 means voting in progress
  * status = 3 means dilution approved
  */
  uint status;

  event aborted(string msg);
  event voted(string msg);

  //List of functions used for functionality
  /*
  function EquityShare() {
    owner = msg.sender;
    shareHolders.push(ShareHolder(msg.sender,100,0));
    status = 1;
  }
  */
  function EquityShare(address person,uint equity) payable {
    owner = msg.sender;
    uint ownerEquity = 100 - equity;
    shareHolders.push(ShareHolder(msg.sender,ownerEquity,0,0));
    shareHolders.push(ShareHolder(person,equity,0,0));
    status = 1;
  }

  function toggleStatus() payable returns (bool){
    if(msg.sender != owner) return false;
    if(status == 1) {
      status = 2;
    }
    else {
      status = 1;
    }
    return true;
  }

  //add a new shareholder
  function addShareHolder(address person,uint equity) payable returns (bool) {
    if(msg.sender != owner) {
      return false;
    }
    if(status == 1) {
      uint diluted = 100 - equity;
    for(uint i=0;i<shareHolders.length;i++) {
     shareHolders[i].equity -= (diluted)/shareHolders.length;
    }
    shareHolders.push(ShareHolder(person,equity,0,0));
      return true;
    }
    return false;
  }

  //function to transfer stake to another.
  function transferStake(address target,uint stake) payable returns (bool){
    if(status == 1) {
      for(uint i=0;i<shareHolders.length;i++) {
        if(shareHolders[i].person == msg.sender) {
            for(uint j=0;j<shareHolders.length;j++) {
              shareHolders[i].equity -= stake;
              if(shareHolders[j].person == target) {
                shareHolders[j].equity += stake;
                return true;
              }
            }
            shareHolders.push(ShareHolder(target,stake,0,0));
            return true;
        }
      }
      return false;
    }
    return false;
  }

  //method of splitting the profit amongst the share holders
  function addProfit(uint profit) payable returns (uint){
    profits.push(profit);
    uint share = 0;
    for(uint i=0; i< shareHolders.length ; i++ ) {
       share = (profit * shareHolders[i].equity)/100;
       //balances[shareHolders[i].person] += share;
       shareHolders[i].profitShare += share;
    }
    return profits.length;
  }

  //method to withdraw balance from an account
  function withdrawBalance(address person,uint amount) payable returns (bool) {
    for(uint i=0;i<shareHolders.length;i++) {
      if(person == shareHolders[i].person) {
        if(shareHolders[i].profitShare >= amount) {
          shareHolders[i].profitShare -= amount;
          return person.send(amount);
        }
      }
    }
    return false;
  }

  //method to fetch balance from an account
  function getBalance(address person) returns (uint) {
    for(uint i=0; i< shareHolders.length ; i++ ) {
      if(shareHolders[i].person == person) {
        return shareHolders[i].profitShare;
      }
    }
    return 0;
  }

  //method that returns the current equity for a person
  function getEquity(address person) returns (uint) {
    for(uint i=0;i<shareHolders.length;i++) {
      if(person == shareHolders[i].person) {
        return shareHolders[i].equity;
      }
    }
    return 0;
  }

  function vote(uint voted) payable returns (bool) {
    if(status != 2)
      return false;

    for(uint i=0;i<shareHolders.length;i++) {
      if(shareHolders[i].person == msg.sender) {
        shareHolders[i].votingStatus = voted;
        return true;
      }
    }
    return false;
  }

  function votingResult() payable returns (uint) {
    if(status == 2) {
      uint approved = 0;
      uint disapproved = 0;
      for(uint i=0;i<shareHolders.length;i++) {
        if(shareHolders[i].votingStatus == 1) {
          approved++;
        }
        else {
          disapproved++;
        }
        shareHolders[i].votingStatus = 0;
      }
      uint percent = (approved*100/shareHolders.length);
      if(percent > 50) {
        status = 3;
      }
      else {
        status = 1;
      }
    }
    return status;
  }

  function purchaseStake(uint amount) payable returns (bool) {
    if(status == 3) {
      uint valuation = 0;
      for(uint i=0;i<profits.length;i++) {
        valuation += profits[i];
      }
      valuation *= 100;
      uint equity = (amount*100)/valuation;
      uint dilution = 100 - equity;
      for(i=0;i<shareHolders.length;i++) {
        shareHolders[i].equity -= (dilution/shareHolders.length);
      }
      shareHolders.push(ShareHolder(msg.sender,equity,0,0));
    }
    return false;
  }
}

  • To compile the solidity contract, run the command truffle compile. If all runs fine, you will notice that a folder called build/contracts is created and this folder will have two json files containing the ABI data corresponding to the two solidity files.
$ truffle compile
Compiling ./contracts/EquityShare.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

$ 
  • The migrations folder has two scripts that are essential for the deployment of the smart contracts onto the ethereum blockchain. 1_initial_migration.js is a script that is used to deploy the Migrations. sol smart contract, which is Truffle's smart contract that manages the deployment. The second is 2_deploy_contract.js. This is the script that is used to deploy our programs. Open the file migrations/2_deploy_contracts.js and replace all instances of Metacoin.sol with EquityShare.sol and add the following line
deployer.deploy(EquityShare,"0xc75204fb8e513e42d7c17d4c5071f43da7dd5d16",30);
  • Our smart contract's constructor accepts two parameters, first an address and then the equity share. We pass on these parameters in the deployer.deploy statement. If our smart contract's constructor did not accept any parameters, the deployer.deploy statement will accept only the smart contract name as the parameter.
  • To deploy the contract we have to execute the command truffle migrate. However for this command to run succesfully you will need to have a ethereum client running. Bring up testrpc and then execute the command.

$ truffle migrate
Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  Migrations: 0x44a2f173bf34e263ff2577856e9108086f71d969
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying EquityShare...
  EquityShare: 0xfce379684854cf9618a056138a615ccf0c40681e
Saving successful migration to network...
Saving artifacts...
$ 

  • The command deploys the smart contract on the ethereum blockchain and also reports the address corresponding to the smart contract deployment. The instance of the deployment is stored within the Truffle framework and this can be referenced using the deployed method.
  •  The UI part of the code is self explanatory. Most of the web3 code to access the deployed contract us available in app/javascripts/app.js, while the simple UI is available under app/index.html.
  • The app can be executed using the command npm run dev, this will build the application and launch the web app in the url http://localhost:8080/

$ npm run dev

> truffle-init-webpack@0.0.1 dev solidity-exercise
> webpack-dev-server

Project is running at http://localhost:8080/
webpack output is served from /
Hash: 41b0cd3a3d9825a0033c
Version: webpack 2.5.1
Time: 2331ms
     Asset     Size  Chunks                    Chunk Names
    app.js  1.37 MB       0  [emitted]  [big]  main
index.html  3.56 kB          [emitted]         
chunk    {0} app.js (main) 1.34 MB [entry] [rendered]
   [82] ./~/web3/index.js 193 bytes {0} [built]
   [86] ./app/javascripts/app.js 12.9 kB {0} [built]
   [87] (webpack)-dev-server/client?http://localhost:8080 5.68 kB {0} [built]
   [88] ./build/contracts/EquityShare.json 10.9 kB {0} [built]
   [90] ./~/ansi-regex/index.js 135 bytes {0} [built]
  [129] ./~/punycode/punycode.js 14.7 kB {0} [built]
  [132] ./~/querystring-es3/index.js 127 bytes {0} [built]
  [160] ./~/strip-ansi/index.js 161 bytes {0} [built]
  [163] ./app/stylesheets/app.css 905 bytes {0} [built]
  [170] ./~/truffle-contract/index.js 2.64 kB {0} [built]
  [205] ./~/url/url.js 23.3 kB {0} [built]
  [240] (webpack)-dev-server/client/overlay.js 3.73 kB {0} [built]
  [241] (webpack)-dev-server/client/socket.js 897 bytes {0} [built]
  [243] (webpack)/hot/emitter.js 77 bytes {0} [built]
  [245] multi (webpack)-dev-server/client?http://localhost:8080 ./app/javascripts/app.js 40 bytes {0} [built]
     + 231 hidden modules
webpack: Compiled successfully.

The complete source code for this tutorial is available at https://github.com/zincoshine/solidity-exercise . You can take a copy of this code and test it out.

PREV                                                                                                                                        NEXT

Popular posts from this blog

Ethereum and Blockchain - 2

Setting up Private Ethereum TestNet