A smart contract is a computer program that, given certain conditions, can enforce the execution of a set of instructions without third party interference. The concept, introduced by Nick Szabo in 1994, can be seen as the cryptographic equivalent of a vending machine.
On Ethereum, smart contracts are accessed via an Ethereum address. They can receive and transfer funds, call other contracts, store variables and do any internal computation using a Turing complete virtual machine (the EVM). It is a key feature of the Ethereum protocol, extending the smart contract capabilities of the Bitcoin Script language.
Once compiled, a smart contract is deployed via an initial transaction. Each of its functions can then be triggered by an EOA, directly or via another smart contract. The complete transaction, including direct and indirect calls, will either run completely or fail. Computation is constrained by the transaction gas limit. A failed transaction is still stored on the blockchain but has no effects on funds and variables.
A deployed smart contract and each of its calls are traced on the blockchain and therefore can’t be deleted. It is yet possible to plan in the code the alteration of its functionalities upon certain conditions, for example to end a sale after a certain date or to give an administrator the ability to “kill” the contract.
The Ethereum Virtual Machine (EVM) is an emulated computer that runs a special form of machine code called bytecode, just like your computer’s CPU, which also runs machine code. While it is possible to program the EVM directly in bytecode, it’s unwieldy and very difficult for programmers to read, understand and collaborate in a low-level language like bytecode. Instead, we use a high-level symbolic language to write our program and a compiler to convert it into bytecode.
Ethereum has several high-level languages, with the compilers needed to produce EVM-executable bytecode, including (ordered by approximate age):
- LLL
-
A functional (declarative) programming language, with Lisp-like syntax. It was the first high-level language for Ethereum smart contracts, but it is rarely used.
- Serpent
-
A procedural (imperative) programming language with a syntax similar to Python. Can also be used to write functional (declarative) code, though it is not entirely free of side effects. Used sparsely. First created by Vitalik Buterin.
- Solidity
-
A procedural (imperative) programming language with a syntax similar to JavaScript, C++ or Java. The most popular and most frequently used language for Ethereum smart contracts. First created by Gavin Wood (co-author of this book).
- Vyper
-
A more recently developed language, similar to Serpent and with Python-like syntax. Intended to get closer to a pure-functional Python-like language than Serpent, but not to replace Serpent. First created by Vitalik Buterin.
- Bamboo
-
A newly developed language, influenced by Erlang with explicit state transitions and without iterative flows (loops). Intended to reduce side effects and increase auditability. Very new and rarely used.
As you can see, there are many languages to choose from. However, of all these Solidity is by far the most popular, to the point of being the de-facto high-level language of Ethereum and even other EVM-like blockchains. We will spend most of our time using Solidity, but will also explore some of the examples in other high-level languages, to gain an understanding of their different philosophies.
In computer software, an Application Binary Interface (ABI) is an interface between two program modules; often, one at the level of machine code, and the other at the level of a program run by a user. An ABI defines how data structures and computational routines are accessed in machine code; this is not to be confused with an API, which defines this access in high-level, often human-readable format as source code. The ABI is thus the primary way of encoding and decoding data into and out of machine code.
In Ethereum, the ABI is used to encode contract calls for the EVM and to read data out of transactions. As discussed in a previous section, an Ethereum smart contract is bytecode deployed on the blockchain under a contract address. The purpose of an ABI is to thus specify which functions in the contract to invoke and to get a guarantee that the function will return data in an expected format.
If an account or a web application wants to interact with a published contract and use one of its functions, the account would first need to hash the function’s definition through an ABI to create its EVM bytecode. Then, to call the function, the account would pass this bytecode to a transaction’s data field so that the bytecode could be interpreted with code at the contract’s address. Thus, the two necessary pieces of information for an external function call would be the ABI and the address of the contract wherein the function is written. We demonstrate the use of an ABI in a detailed example below.
The JSON format for a contract’s ABI is given by an array of function and event descriptions. A function description is a JSON object with fields for type
, name
, inputs
, outputs
, constant
, and payable
. An event description object has fields for type
, name
, inputs
, and anonymous
.
The ABI thus specifies information about functions in a smart contract, relaying information such as inputs and types. However, the ABI only contains information about functions and events, meaning it will not hold values for fields such as state variables or modifiers.
The data for a function call is a concatenation of several values of bytes. We discuss these in steps below.
First Four Bytes: The call data always begins with four bytes of the function signature. In specific, these are the first four bytes of the Keccak-256 hash of the signature of a function. In this context, the signature is simply the function name with a parenthesized list of parameter types split by a single comma.
Assume, as a running example, the brief contract Test
:
contract Test {
function foo(uint32 u, bool b) returns (bool b) { u > 2000 && b; }
}
foo(uint32 u, bool b)
:-
The signature of
foo(uint32 u, bool b)
would befoo(uint32,bool)
-
Running
foo(uint32,bool)
through Keccak-256 outputs the hashcc822237a37f9290b70dab4d640156d816bf8abdb959b5971d803a639dadef98
-
The first four bytes of the hash are
cc822237
, which brings us to a Method ID of0xcc822237
-
Executing the command
web3.sha3("foo(uint32,bool)").substring(0, 10)
in the Go Ethereum (geth) JavaScript console produces the result"0xcc822237"
5th Byte And On: We continue our data by encoding the arguments of our function. Encoding a uint32
means converting the number to hexadecimal form and padding it with 0’s until it becomes 32 bytes long, while encoding a bool
is equivalent to encoding a uint
with values 1
for true
and 0
for false
. A full list of type encoding can be found at the link attached in the Further Reading section.
foo
with parameters 2345
and true
:-
The hexadecimal representation of
2345
is0x929
, and so padded to 32 bytes the first argumentu
is encoded as0x0000000000000000000000000000000000000000000000000000000000000929
-
The hexadecimal representation of
true
is1
, and so padded to 32 bytes the second argumentb
becomes0x0000000000000000000000000000000000000000000000000000000000000001
Result: We concatenate the Method ID with our encoded arguments to reach the finished call data for our function call. In our case, the call data for function foo(uint32 u, bool b)
with arguments 2345
and true
is
0xcc82223700000000000000000000000000000000000000000000000000000000000009290000000000000000000000000000000000000000000000000000000000000001
We notice that our function returns a single
bool
value. For our choices ofu
andb
,foo
returnstrue
, which would be the single byte array0x0000000000000000000000000000000000000000000000000000000000000001
.
The Application Binary Interface (ABI) is strongly typed, known at compilation time and static. All contracts have the interface definitions of any contracts they intend to call available at compile-time.
A more rigorous and in-depth explanation of the Ethereum ABI can be found at
https://solidity.readthedocs.io/en/develop/abi-spec.html
.
The link includes details about the formal specification of encoding and various helpful examples.
After you’ve typed up your smart contract, you’ll want to deploy it to the main Ethereum network. The process is as follows:
-
Compile your Solidity source code to EVM bytecode
-
Sign the bytecode into a transaction
-
Send the code to an Ethereum node to be mined into the network
-
Then you can interact with the contract by sending it transactions
We’ll now go through the deployment process using the Go Ethereum client (geth). First, you’ll want to install the Solidity compiler (solc).
npm install -g solc
You’ll also want to have Geth (Go-ethereum) installed (with homebrew installed)
brew tap ethereum/ethereum brew install ethereum
Now compile your Solidity file into an interface (abi) and bytecode (bin)
solcjs --abi foo.sol solcjs --bin foo.sol
Now display the contents of these two files with:
more foo_sol_foo.abi more foo_sol_foo.bin
After this you’ll want to start up a geth node in a new terminal window with:
geth console
You should see something like:
Welcome to the Geth JavaScript console! instance: Geth/v1.8.1-stable/darwin-amd64/go1.10 INFO [03-14|18:34:37] Etherbase automatically configured address=0x6e6A1eFF05ba3a16c3A3E5a274B288b10490C428 coinbase: 0x6e6a1eff05ba3a16c3a3e5a274b288b10490c428 at block: 4535991 (Sat, 11 Nov 2017 21:17:37 EST) datadir: /Users/brianleffew/Library/Ethereum modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
Infura is a free to use hosted Ethereum cluster that allows users to run an application without the need to run a full Ethereum node or a wallet. Infura is the same Ethereum provider that powers Metamask.
In order to use Infura for smart contract deployment, you must first get an Infura Access Token. To do so, visit the Infura Registration page and fill out the form. Once registered, the information will be sent to your email. It is important to save this token and keep it private.
To gain some exposure to deploying contracts with Infura, we will go through the steps of deploying a smart contract to the Ethereum Ropsten testnet using Truffle. For the purposes of this example we will assume that Truffle has already been installed and that you have already created a Truffle project with your smart contract. Refer to the Development Tools section for more information on installing and using Truffle.
Because Infura does not manage your private keys, Infura cannot sign transactions on your behalf. To deal with this, we will take advantage of Truffle’s HDWalletProvider which can handle both transaction signing and connection to the Ethereum network.
npm install truffle-hdwallet-provider
After installation of the provider, we will want to edit our project’s truffle.js file. Add this line at the top:
var HDWalletProvider = require(“truffle-hdwallet-provider”);
We must then provide a reference to the mnemonic that generates your accounts:
var mnemonic = “<your mnemonic>“;
Now let’s make use of our newly acquired Infura Access Token to add a Ropsten network definition:
module.exports = {
networks: {
ropsten: {
provider: function() {
return new HDWalletProvider (mnemonic, “https://ropsten.infura.io/<INFURA_Access_Token>”)
},
network_id: 3
}
}
};
In the above code, make sure to replace <INFURA_Access_Token> with your provided Access Token. Also, although the above HDWalletProvider is being returned with Ropsten as the desired network, it can be made to work with any of the Infura-supported networks, a list of which can be found on the Infura homepage.
The account in charge of deployment will be the first one generated by the mnemonic. To specify an account, add an integer input after the network declaration string. For example, to specify the second account:
return new HDWalletProvider (mnemonic, “https://ropsten.infura.io/<INFURA_Access_Token>”, 1)
Now we are ready to actually deploy our contract. First, make sure that your account has enough ether to deploy the contract. Now compile the project:
truffle compile
Finally, deploy it to the network!
truffle migrate --network ropsten
There are several commonly-used test frameworks (no particular order)
- Truffle Test
-
Part of the Truffle framework, Truffle allows for unit tests to be written in Javascript (Mocha based) or Solidity. These tests are run against TestRPC/Ganache. More details on writing these tests are located at [truffle]
- Embark Framework Testing
-
Embark integrates with Mocha to run unit tests written in Javascript. The tests are in turn run against contracts deployed on TestRPC/Ganache. The Embark Framework automatically deploys smart contracts and will automatically redeploy the contracts when they are changed. It also keeps track of deployed contracts and deploys contracts when truly needed. Embark includes a testing library to rapidly run and test your contracts in an EVM, with functions like
.assert.equal()
will run any test files under directory test/.embark test
- DApp
-
DApp uses native Solidity code (a library called ds-test) and a Parity built Rust library called Ethrun to execute Ethereum bytecode and then assert correctness. The ds-test library provides assertion functions for validating correctness and events for logging data in the console.
Assertions Functions includes
assert(bool condition) assertEq(address a, address b) assertEq(bytes32 a, bytes32 b) assertEq(int a, int b) assertEq(uint a, uint b) assertEq0(bytes a, bytes b) expectEventsExact(address target)
Logging Events will log information to the console, making them useful for debugging.
logs(bytes) log_bytes32(bytes32) log_named_bytes32(bytes32 key, bytes32 val) log_named_address(bytes32 key, address val) log_named_int(bytes32 key, int val) log_named_uint(bytes32 key, uint val) log_named_decimal_int(bytes32 key, int val, uint decimals) log_named_decimal_uint(bytes32 key, uint val, uint decimals)
- Populus
-
Populus uses python and its own chain emulator to run contracts written in solidity. Unit tests are written in Python with the pytest library. Populus supports writing contracts that are specifically for testing. These contract filenames should match the glob pattern
and be located anywhere under the project tests directoryTest*.sol
../tests/
Framework |
Test Language(s) |
Testing Framework |
Chain Emulator |
Website |
Truffle |
Javascript/Solidity |
Mocha |
TestRPC/Ganache |
truffleframework.com |
Embark |
Javascript |
Mocha |
TestRPC/Ganache |
embark.readthedocs.io |
DApp |
Solidity |
ds-test (custom) |
Ethrun (Parity) |
dapp.readthedocs.io |
Populus |
Python |
Pytes |
Python chain emulator |
populus.readthedocs.io |
If you this is your first time using geth, it might take a while to sync up to the network. Then set up your variables with:
> var foo = eth(<CONTENTS_OF_ABI_FILE>) > var byteCode = '0x<CONTENTS_OF_BIN_FILE>)
Fill in the parameters with the outputs from the more commands above. Then finally deploy your contract with:
> var deploy = {from eth.coinbase, data:byteCode, gas:2000000} > var fooInstance = foo(bar, baz)
Although most testing shouldn’t occur on deployed contracts, a contract’s behavior can be checked via Ethereum clients. The following commands can be used to assess a smart contract’s state. These commands should be typed at the 'geth' terminal, although any web3 calls will also support these commands.
eth.getTransactionReceipt(txhash);
Can be used to get the address of a contract at txhash
.
eth.getCode(contractaddress)
Gets the code of a contract deployed at contractaddress
. This can be used to verify proper deployment.
eth.getPastLogs(options)
Gets the full logs of the contract located at address, specified in options. This is helpful for viewing the history of a contract’s calls.
eth.getStorageAt(address, position)
Gets the storage located at address
with an offset of position
shows the data stored in that contract.
Two of the most important concepts to consider during smart contract creation are gas and security.
Gas is described in more in detail in the [gas] section but is an incredibly important consideration in smart contract programming. Gas is a resource dictating the amount of computation power that a user will allot to a transaction. If the gas limit is exceeded during computation, the following series of events occurs:
-
An exception is thrown
-
The state of the contract prior to the function’s execution is restored
-
The entire amount of the gas is given to the miner as a transaction fee, it is not refunded
Because gas is paid by the user who creates that transaction, users are discouraged from calling functions that have a high gas cost. It is thus in the programmer’s best interest to minimize the gas cost of a contract’s functions. To this end, there are certain practices that are recommended when constructing smart contracts, so as to minimize the gas costs surrounding a function call.
Avoid dynamically-sized Arrays
-
Any loop through a dynamically sized array wherein a function performs operations on each element or searches for a particular element is at the risk of gas overflow. The contract may run out of gas before finding the desired result, or before acting on every element.
How do I estimate gas for a contract method?
In case that you need to estimate the gas necessary to execute a certain method of a contract considering its call arguments, you can use for instance the following procedure;
var contract = web3.eth.contract(abi).at(address);
var gasEstimate = contract.myAweSomeMethod.estimateGas(arg1, arg2, {from: account});
gasEstimate will tell us the number of gas units needed for its execution.
To obtain the gas price from the network you can use;
var gasPrice = web3.eth.getGasPrice();
And from there, estimate de gas cost;
var gasCostInEther = web3.fromWei((gasEstimate * gasPrice), 'ether');
In Truffle this can be achieved as follows,
var METokenContract = artifacts.require("./METoken.sol");
METokenContract.web3.eth.getGasPrice(function(error, result) {
var gasPrice = Number(result);
console.log("Gas Price is " + gasPrice + " wei"); // "10000000000000"
// Get the contract instance
METokenContract.deployed().then(function(METokenContractInstance) {
// Use the keyword 'estimateGas' after the function name to get the gas estimation for this particular function (aprove)
return METokenContractInstance.aprove.estimateGas(_address, 100);
}).then(function(result) {
var gas = Number(result);
console.log("gas estimation = " + gas + " units");
console.log("gas cost estimation = " + (gas * gasPrice) + " wei");
console.log("gas cost estimation = " + METokenContract.web3.fromWei((gas * gasPrice), 'ether') + " ether");
});
});
Which could have an output similar to;
Gas Price is 20000000000 wei gas estimation = 26794 units gas cost estimation = 535880000000000 wei gas cost estimation = 0.00053588 ether
With blockchain being in its early stages, security is one of the most important considerations when writing smart contracts. As with other programs, a smart contract will execute exactly what is written, which is not always equivalent to the intentions of the programmer. To this end, a programmer must understand common security exploits and proper ways to safeguard against these exploits. Here is a list of some of the security issues that have arisen in the past.
Re-entrancy
Re-entrancy is a phenomenon in programming in which a function or program is interrupted and then called again before its previous invocations have finished. In the context of smart contract programming, re-entrancy can occur when contract A calls a function in contract B, which in turn calls the same function in contract A, leading to a recursive execution. This can be particularly dangerous in a situation where the state of the contract is not updated until after the critical call is finished.
To understand this, imagine a withdrawal by a user calling a bank contract. User A calls the withdraw function in bank B, which executes the following actions:
-
Checks if A has the available balance
-
Calls A’s default function, paying A in Ether
-
Updates user A’s balance within the contract
As a side note, the reason that the default function of A is called during a payout is that contract B allows A to execute code during this payout. For instance, if contract A kept count of the money it was being paid, it might need to change a variable called "balance," setting "balance" equal to its previous amount, plus what it was just paid.
However, malicious attackers can take advantage of this execution. Imagine that in A’s default function, user A calls bank B’s withdraw function once again. B will first check if A has the available balance, but since step 3 (which updates A’s balance) has yet to be executed, it will appear to bank B that user A still has the available funds to withdraw, no matter how many times this function is re-invoked. Thus, "withdraw" can be called as long as there is gas available for execution.
This exploit is particularly famous because of its relevance in the DAO attack. A user took advantage of the fact that the balance in a contract was changed after a call to transfer funds was made and withdrew millions of dollars worth of ether.
To guard against re-entrancy, Solidity recommends that a programmer adheres to the Checks-Effects-Interactions pattern, wherein the effects of a function call (such as decreasing the balance) occur before making the call. In our example, this would mean switching steps 3 and 2: updating a user’s balance before paying them out. In ethereum, this is perfectly okay, because all effects of a transaction are atomic, meaning it is impossible for the balance to update without the user also being paid out. Either both occur, or an exception is thrown and neither occurs. This guards against re-entrancy attacks because all subsequent calls into the original contract will encounter the correct modified state.
- Delegate Call
-
//todo
Software developers of any programming paradigm generally experience reoccurring design challenges centered around the topics of behavior, structure, interaction, and creation. Often these problems can be generalized and re-applied to future problems of a similar nature. When given a formal structure, these generalizations are called Design Patterns. Smart contracts have their own set of reoccurring design problems that can be solved using some of the patterns described below.
There is an endless number of design problems in the development of smart contracts, making it impossible to discuss all of them here. For that reason, this section will focus on three of the most pervasive problem classifications in smart contract design: access control, state flow, and fund disbursement.
Throughout this section, we will be working on a contract that will ultimately incorporate all three of these design patterns. This contract will run a voting system that allows users to vote on "truth". The contract will suggest a claim such as "The Cubs won the World Series." or "It is raining in New York City" and then users will have the opportunity to vote either true or false. The contract will consider the proposition as true if the majority of participants voted for true and likewise if the majority of participants voted for false. To incentivize truthfulness, every vote must send 100 ether to the contract and the funds contributed by the losing minority will be split up amongst the majority. Every participant in the majority will receive their portion of winnings from the minority as well as their initial investment.
This "truth voting" system is actually the foundation of Gnosis, a forecasting tool built on top of Ethereum. More information about Gnosis can be found here: https://gnosis.pm/
Access control restricts which users may call contract functions. For the example, the owner of the truth voting contract may decide to limit those who can participate in the vote. To accomplish this the contract must impose two access restrictions:
-
Only an owner of the contract may add new users to the list of "allowed voters"
-
Only allowed voters may cast a vote
Solidity function modifiers offer a concise way to implement these restrictions.
Note: The following example uses an underscore semicolon within the modifier bodies. This is a Solidity feature used to tell the compiler when to run the modified function’s body. A developer can act as if the modified function’s body will be copied to the position of the underscore.
pragma solidity ^0.4.21;
contract TruthVote {
address public owner = msg.sender;
address[] true_votes;
address[] false_votes;
mapping (address => bool) voters;
mapping (address => bool) hasVoted;
uint VOTE_COST = 100;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier onlyVoter() {
require(voters[msg.sender] != false);
_;
}
modifier hasNotVoted() {
require(hasVoted[msg.sender] == false);
_;
}
function addVoter(address voter)
public
onlyOwner()
{
voters[voter] = true;
}
function vote(bool val)
public
payable
onlyVoter()
hasNotVoted()
{
if (msg.value >= VOTE_COST) {
if (val) {
true_votes.push(msg.sender);
} else {
false_votes.push(msg.sender);
}
hasVoted[msg.sender] = true;
}
}
}
Description of Modifiers and Functions:
-
onlyOwner: this modifier can decorate a function such that the function will then only be callable by a sender with an address that matches that of owner.
-
onlyVoter: this modifier can decorate a function such that the function will then only be callable by a registered voter.
-
addVoter(voter): this function is used to add a voter to the list of voters. This function uses the onlyOwner modifier so only the owner of this contract may call it.
-
vote(val): this function is used by a voter to vote either true or false to the presented proposition. It is decorated with the onlyVoter modifier so only registered voters may call it.
Many contracts will require some notion of operation state. The state of a contract will determine how the contract will behave and what operations it offers at a given point in time. Let’s return to our truth voting system for a more concrete example.
The operation of our voting system can be broken down into 3 distinct states.
-
Register: The service has been created and the owner can now add voters.
-
Vote: All voters cast their votes.
-
Disperse: Vote payments are divided and sent to the majority participants.
The following code continues to build on the access control code, but further restricts functionality to specific states. In Solidity, it is commonplace to use enumerated values to represent states.
pragma solidity ^0.4.21;
contract TruthVote {
enum States {
REGISTER,
VOTE,
DISPERSE
}
address public owner = msg.sender;
uint voteCost;
address[] trueVotes;
address[] falseVotes;
mapping (address => bool) voters;
mapping (address => bool) hasVoted;
uint VOTE_COST = 100;
States state;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier onlyVoter() {
require(voters[msg.sender] != false);
_;
}
modifier isCurrentState(States _stage) {
require(state == _stage);
_;
}
modifier hasNotVoted() {
require(hasVoted[msg.sender] == false);
_;
}
function startVote()
public
onlyOwner()
isCurrentState(States.REGISTER)
{
goToNextState();
}
function goToNextState() internal {
state = States(uint(state) + 1);
}
modifier pretransition() {
goToNextState();
_;
}
function addVoter(address voter)
public
onlyOwner()
isCurrentState(States.REGISTER)
{
voters[voter] = true;
}
function vote(bool val)
public
payable
isCurrentState(States.VOTE)
onlyVoter()
hasNotVoted()
{
if (msg.value >= VOTE_COST) {
if (val) {
trueVotes.push(msg.sender);
} else {
falseVotes.push(msg.sender);
}
hasVoted[msg.sender] = true;
}
}
function disperse(bool val)
public
onlyOwner()
isCurrentState(States.VOTE)
pretransition()
{
address[] memory winningGroup;
uint winningCompensation;
if (trueVotes.length > falseVotes.length) {
winningGroup = trueVotes;
winningCompensation = VOTE_COST + (VOTE_COST*falseVotes.length) / trueVotes.length;
} else if (trueVotes.length < falseVotes.length) {
winningGroup = falseVotes;
winningCompensation = VOTE_COST + (VOTE_COST*trueVotes.length) / falseVotes.length;
} else {
winningGroup = trueVotes;
winningCompensation = VOTE_COST;
for (uint i = 0; i < falseVotes.length; i++) {
falseVotes[i].transfer(winningCompensation);
}
}
for (uint j = 0; j < winningGroup.length; j++) {
winningGroup[j].transfer(winningCompensation);
}
}
}
Description of Modifiers and Functions:
-
isCurrentState: this modifier will require that the contract is in a specified state before continuing execution of the decorated function.
-
pretransition: this modifier will transition to the next state before executing the rest of the decorated function
-
goToNextState: function that transitions the contract to the next state
-
disperse: function that calculates the majority and disperses winnings accordingly. Only the owner may call this function to officially close voting.
-
startVote: function that the owner can use to start a vote.
It may be important to note that allowing the owner to close the voting process at will opens this contract up to abuse. In a more genuine implementation, the voting period should close after a publicly understood period of time. For the sake of this example, this is fine.
The additions made now ensure that voting is only allowed when the owner decides to start the voting period, users can only be registered by the owner before the vote happens, and funds are only dispersed after the vote closes.
Many contracts will offer some way for a user to retrieve money from it. In our working example, users of the majority are sent money directly when the contract begins dispersing funds. Although this appears to work, it is an under-thought solution. The receiving address of the addr.send() call in disperse could be a contract that has a fallback function which fails and consequently breaks disperse. This effectively stops all further majority participants from receiving their earning. A better solution is to provide a withdraw function that a user can call to collect their earnings.
...
enum States {
REGISTER,
VOTE,
DETERMINE,
WITHDRAW
}
mapping (address => bool) votes;
uint trueCount;
uint falseCount;
bool winner;
uint winningCompensation;
modifier posttransition() {
_;
goToNextState();
}
function vote(bool val)
public
onlyVoter()
isCurrentStage(State.VOTE)
{
if (votes[msg.sender] == address(0) && msg.value >= VOTE_COST) {
votes[msg.sender] = val;
if (val) {
trueCount++;
} else {
falseCount++;
}
}
}
function determine(bool val)
public
onlyOwner()
isCurrentState(State.VOTE)
pretransition()
posttransition()
{
if (trueCount > falseCount) {
winner = true;
winningCompensation = VOTE_COST + (VOTE_COST*false_votes.length) / true_votes.length;
} else if (falseCount > trueCount) {
winner = false;
winningCompensation = VOTE_COST + (VOTE_COST*true_votes.length) / false_votes.length;
} else {
winningCompensation = VOTE_COST;
}
}
function withdraw()
public
onlyVoter()
isCurrentState(State.WITHDRAW)
{
if (votes[msg.sender] != address(0)) {
if (votes[msg.sender] == winner) {
msg.sender.transfer(winningCompensation);
}
}
}
...
Description of Modifiers and (Updated) Functions:
-
posttransition: transitions to the next state after the function call
-
determine: this function is very similar to the previous disperse function except it now just calculates the winner and winning compensation and does not actually send any funds.
-
vote: votes are now added to the votes mapping and true/false counters are incremented.
-
withdraw: allows a voter to collect winnings (if any).
This way, if the send fails, it will only fail on the specific caller’s case and not hinder all other user’s ability to collect their winnings.
Github link: https://github.com/ethpm
Repository link: https://www.ethpm.com/registry
Website: https://www.ethpm.com/
Documentation: https://www.ethpm.com/docs/integration-guide
Perhaps the most fundamental software security principle consists in maximizing reuse of trusted code. In blockchain technologies, this is even condensed in an adage: "Do not roll your own crypto". In the case of smart contracts, this amounts to profiting as much as possible from freely available libraries that have been thoroughly vetted by the community.
In Ethereum, the most widely used solution is the OpenZeppelin suite, an ample library of contracts ranging from implementations of ERC20
and ERC721
tokens, to many flavors of crowdsale models, to simple behaviors commonly found in contracts such as Ownable
, Pausable
or LimitBalance
. The contracts in this repository have been extensively tested and in some cases even function as de facto standard implementations. They are free to use, and are built and mantained by Zeppelin together with an ever growing list of external contributors.
Also from Zeppelin is zeppelin_os, an open source platform of services and tools to develop and manage smart contract applications securely. zeppelin_os provides a layer on top of the EVM that makes it easy for developers to launch upgradeable DApps linked to an on-chain library of well tested contracts that are themselves upgradeable. Different versions of these libraries can coexist in the blockchain, and a vouching system allows users to propose or push improvements in different directions. A set of off-chain tools to debug, test, deploy and monitor decentralized applications is also provided by the platform.