Ethereum Smart Contracts - A Quick Example

in #ethereum7 years ago (edited)

Ethereum-homestead-background-10.jpg
image source: Ethereum Foundation (https://www.ethereum.org)

Bitcoin is the #1 crypto-currency and usually dominates the headlines but there's a #2 contender called Ethereum that does everything #1 does and more.

In this post we will explain what Ethereum is using an analogy from computing history. Then we will use a business scenario and sample code to illustrate how Ethereum can help the business be more effective. We will also mention the fees for Ethereum's services.

The Analogy

Let's go back in time forty to fifty years. Before personal computers arrived, most businesses that could afford one had a mainframe computer. This was shared by all programmers in the organisation.

A programmer of yore would submit a job at a teletype and retrieve the output from the printer. Along with the output came a bill for the job's use of CPU, memory and disk resources.

The problem with sharing one mainframe was that all business stopped whenever the mainframe stopped. So manufacturers created combinations of two (or more) computers designed to provide continuous service in case one had a fault. This service appeared to the business as the work of one virtual computer.

Forward through time to now. Ethereum is a modern global massively fault-tolerant virtual mainframe. It is made up of many thousands of computers operating peer-to-peer and managing a blockchain over the internet. This is the same kind of blockchain used by Bitcoin and gives Ethereum the ability to synchronize peers and do crypto-currency transfers.

So Ethereum is a distributed and decentralized virtual machine with the built-in ability to receive and send money. A program running on this machine can operate like a virtual vending machine. Accepting tokens in cyberspace can be thought of as placing a coin in a slot.

The service is virtual, but the real computers behind Ethereum get some wear-and-tear and incur real costs for use of space, electricity and network. Ethereum collects a fee from the user to run a program. This enables Ethereum to pay the computer operators called miners for providing service.

The Scenario

Let's consider a fictitious charitable organisation directed by a council of five trustees. The traditional approach is to designate one trustee as Treasurer and trust them with exclusive control of the organisation's bank accounts.

Our charity requires:

  • a way to receive and hold donations,
  • a process to help councillors approve each expenditure and ensure no
    payment is made without at least three approvals, and
  • a way to send money to the payee once the expenditure is approved.

Ethereum provides a platform to build an automated system that does all this in a consistent and transparent manner. Our system removes the need for the Treasurer role and frees the organization from needing a bank account.

The Solution

Our solution includes one Ethereum program known as a smart contract.

Let's introduce the smart contract in pieces. The complete version is included at the end. We should mention that the code presented is immature and not ready for real operation.

Donations

We'll begin with donations. This part is quite simple as any smart contract can be built to receive Ether. Here we go:

contract OurCharity {
    function OurCharity() {} // constructor
    function() payable {}    // fallback
}

We introduce two functions. The one with the same name as the contract is the constructor. It will be called by Ethereum when our contract is created.

The second function has no name. It is referred to as the fallback function because it is called whenever the contract receives a message and no other function matches the request. Such is the case whenever the contract receives an Ether transfer. Any function that is marked payable is allowed to receive Ether.

Someone donating to OurCharity first buys Ether and sends some from their wallet to our contract. They need to know our contract's address so the organization publishes that on the web and by other means.

As an added benefit, all money transactions will be publicly viewable on the blockchain, so it should no longer be necessary to issue receipts and audits will be easier - software does most of it.

Disbursements

Ether can come in to the smart contract but there is no way to spend yet. Let's look into the disbursement side now.

Our approach requires that someone initate a "spend proposal". This includes the amount to spend and the payee's Ethereum address to receive the funds. We will also store the approvals within the same structure.

struct SpendProposal {
    address payee;
    uint amount;         // wei
    address[] approvers; // councillors who approved
}

SpendProposal[] proposals;

Ethereum doesn't do floating point (yet) so we store the amount in units of wei, the smallest possible units of Ether.

Our organisation has a council of five and requires at least three councillors to approve a spend. Our contract knows each councillor by an Ethereum address and includes a function to add a councillor:

address[] councillors;

function addCouncillor(address councillor) {

    // prevent councillor from being added again
    for (uint ix = 0; ix < councillors.length; ix++)
        if (councillors[ix] == msg.sender) return;

    councillors.push(councillor);
}

Anyone can submit a SpendProposal by calling this function:

function addProposal(address _payee, uint _wei) {
    uint ix = proposals.length++;
    proposals[ix].payee = _payee;
    proposals[ix].amount = _wei;
}

A councillor may approve at most once. The approve() function will send the payment at most once, and only if it the contract has enough funds. The logic zeroes the payment amount after the send() so the payment will be sent only once.

function approve( uint propIndex ) {
    if (propIndex >= proposals.length) throw;

    var prop = proposals[propIndex];

    // ensure caller is a councillor
    for (uint ix = 0; ix < councillors.length; ix++) {

        if (councillors[ix] == msg.sender) {

          // ensure caller has not already approved
          for (uint ap = 0; ap < prop.approvers.length; ap++) {
              if (msg.sender == prop.approvers[ap]) return;
          }

          prop.approvers.push(msg.sender);
        }
    }

    // detect spend trigger condition and send payment
    if (    prop.approvers.length >= 3
         && prop.amount > 0
         && this.balance > prop.amount) {

        if (!prop.payee.send(prop.amount)) throw;

        prop.amount = 0; // ensure happens once
    }
}

Running our Machine

Every program deployed in Ethereum has a SCA (Smart Contract Address). Anyone can use their wallet software or the Ethereum node software to send a donation. Here is a command-line example of donating one Ether:

> var sender = eth.accounts[0];
> var receiver = // our contract's SCA in hex notation
> var amount = web3.toWei(1.0, "ether");
> eth.sendTransaction({from:sender, to:receiver, value:amount});

The receipt for the donation is publicly listed on the blockchain: anyone can use an Ethereum blockchain explorer such as Etherscan to see all payments to and from our contract.

We can also call contract functions from the command-line though any complexity can make that difficult. A better approach is to run our node in the background and use simple javascript programs. Here is some code we might use to submit a
spending proposal:

const Web3 = require('web3');

var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
var sca = // our smart contract address in hex
var abi = // data from the compiled smart contract code
var ourcontract = web3.eth.contract(abi).at(sca);
var payee = // Ethereum address to receive funds in hex

ourcontract.addProposal(payee, web3.toWei(1.0,"ether"));

Costs

We mentioned the user pays to run the machine. Let's review the costs.

Our contract is deployed from an Ethereum node which automatically includes one Ethereum account. Ethereum deducts some Ether from this account when we run the script to create the contract.

var contract = ourcontract.new(
    {from: web.eth.accounts[0],
     data: (hex code from compiled contract),
     gas: 500000},

     // callback when contract is created, prints SCA
     function(err,inst) {
         if (err) console.log( "Error: ", err );
         if (!err && inst.address) console.log( "SCA: ", inst.address );
     });

The amount of Ether required to run the machine is called gas. To convert from gas to Ether we multiply by the network's gasprice. When we compiled our contract, the compiler estimated our contract would require 416047 gas to construct so we provided 500000 just to be certain. Any unused gas is automatically returned.

To determine the dollar cost we have to do some math. Assume we burned the full 500000 gas. Today's gas price is 20000000000 or 2 * 10^10 wei. There are 1000000000000000000 or 10^18 wei in one Ether, and assume today's price for Ether is $100. So our calculation goes as follows:

  • 500000 gas * 20000000000 wei/gas = 10^16 wei
  • 10^16 wei / 10^18 wei/Ether = 0.01 Ether
  • 0.01 Ether * 100 $/Ether = $1

We spent one dollar to deploy our contract. The cost of calling the addProposal() method was merely a few cents. It cost us nothing to receive Ether because the sender pays the mining fees.

Despite the fees it seems that the cost of doing business through our contract is much less than the cost of employing a Treasurer and paying bank fees. The other benefit is no waiting; Ethereum transactions complete within a few seconds whereas traditional business transactions could take days or even weeks.

Room for Improvement

Before we declare a complete victory we have to admit our design has a big drawback. Users pay Ethereum for storage as well as execution. The cost for storage increases significantly as it passes certain levels. This is an Ethereum feature that encourages programmers to minimize use of disk space.

The initial marginal cost of storing a SpendProposal is very small but will grow exponentially as more are added. A better design, perhaps for a future post, might store the proposals off of the blockchain but remain available
to our contract.

The Full Code

Here is the smart contract. As stated previously, the code is illustrative and not suitable for production use.

pragma solidity ^0.4.4;

contract OurCharity {

  function OurCharity() {} // constructor
  function() payable {}    // fallback

  struct SpendProposal {
    address payee;
    uint amount;
    address[] approvers;
  }

  SpendProposal[] proposals;

  address[] councillors;

  function addCouncillor(address councillor) {
    // not if we already got 'em
    for (uint ix = 0; ix < councillors.length; ix++)
      if (councillors[ix] == msg.sender) return;

    councillors.push(councillor);
  }

  function addProposal(address _payee, uint _wei) {
    uint ix = proposals.length++;
    proposals[ix].payee = _payee;
    proposals[ix].amount = _wei;
  }

  function getProposalCount() constant returns (uint) {
      return proposals.length;
  }

  function getProposal(uint ix) constant returns (address, uint) {
    if (ix >= proposals.length) throw;
    return (proposals[ix].payee, proposals[ix].amount);
  }

  function getApprovalCountForProposal(uint ix) constant returns (uint) {
    if (ix >= proposals.length) throw;

    return proposals[ix].approvers.length;
  }

  function approve( uint propIndex ) {
    if (propIndex >= proposals.length) throw;

    var prop = proposals[propIndex];

    for (uint ix = 0; ix < councillors.length; ix++) {
      if (councillors[ix] == msg.sender) {
        for (uint ap = 0; ap < prop.approvers.length; ap++) {
          if (msg.sender == prop.approvers[ap]) return;
        }

        prop.approvers.push(msg.sender);
      }
    }

    if (    prop.approvers.length >= 3
         && prop.amount > 0
         && this.balance > prop.amount) {
      if (!prop.payee.send(prop.amount)) throw;
      prop.amount = 0;
    }
  }
}

Coin Marketplace

STEEM 0.29
TRX 0.12
JST 0.032
BTC 63423.66
ETH 3077.69
USDT 1.00
SBD 3.87