
Ethereum, tokens & smart contracts.
Notes on getting started Part 4. Smart Contracts
Previous notes in case you are just joining us:
Part 1. Setting up.
Part 2. Web3.js/node.
Part 3. Solidity.Our hello world contract from the previous notes while awesome, is not particularly smart, we now need to start adding complexity and features in order to really create a smart contract, let’s start with with giving it some memory in the form of basic storage.
Basic Storage:
FILE: basicStorage.sol/* Stolen from the docs */pragma solidity ^0.4.0;contract basicStorage {
uint storedData;function set(uint x) {
storedData = x;
}function get() constant returns (uint) {
return storedData;
}
}There is not much happening here, a variable declaration and 2 contract methods in the form of a getter and a setter which will allow us to save and modify storedData. For comparisons sake this is what a similar javascript class would look like:
// JAVASCRIPT ES6
// Source : dVvoWrclass basicStorage {
constructor(Data) {
this.storedData = Data;
}
set(x) {
this.storedData = x;
}
get() {
return this.storedData;
}
}
var BS = new basicStorage(0);
BS.set(10);console.log(BS.get()); // 10Compile and Deploy :
A slightly more verbose compiler:
FILE: Compiler.jsconsole.log('Setting up...');
const fs = require('fs');
const solc = require('solc');
const Web3 = require ('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Reading contract Source...');
const input = fs.readFileSync('basicStorage.sol');console.log('Compiling...');
const output = solc.compile(input.toString(), 1);if (output.errors) {
console.log('Compiling failed with errors:' + output.errors);
process.exit();
}
const bytecode = output.contracts[':basicStorage'].bytecode;
const abi = output.contracts[':basicStorage'].interface;console.log('saving ABI');
fs.writeFile("./basicStorage.json", abi, function(err) {
if (err) {
return console.log(err);
}
console.log("ABI Saved");
});const basicStorage = web3.eth.contract(JSON.parse(abi));console.log('unlocking Coinbase account');const password = "yourPasswordHere";
try {
web3.personal.unlockAccount(web3.eth.coinbase, password);
} catch(e) {
return console.log(e);
}console.log("Deploying contract...");
const basicStorageInstance = basicStorage.new({
data: '0x' + bytecode,
from: web3.eth.coinbase,
gas: 400000
}, function(err, res){
if (err) {
console.log('there were errors:' + err);
}
if (res.address) {
console.log('txHash:' + res.transactionHash);
console.log('Succesfully deployed Contract with address: ' + res.address);
}
});Readout:
Setting up...
Reading contract Source...
Compiling...
saving ABI
unlocking Coinbase account
Deploying contract...
ABI Saved
txHash:0x2d053628cea870c20d4757e706c4312ea3db14c74887131b7d4f5560ad0d04ee
Successfully deployed Contract with address: 0x20ed8cc5c767b100c6a5b018e533e38b44b36326So far nothing new, what we will now do is set ( via a transaction ) and read via a call the storage on our contract:
1.set()
// FILE: setTransaction.jsconsole.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const contractABI = require("./basicStorage.json");console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Creating contract instance');
const basicStorage = web3.eth.contract(contractABI);
var basicStorageInstance = basicStorage.at("0x20ed8cc5c767b100c6a5b018e533e38b44b36326");console.log('unlocking Coinbase account');
const password = "your_password";
try {
web3.personal.unlockAccount(web3.eth.coinbase, password);
} catch(e) {
console.log(e);
return;
}console.log ('sending Transaction to the contract');basicStorageInstance.set.sendTransaction(42, {from:web3.eth.coinbase}, function(err, txHash) {
if (err != null) {
console.error("Error while sending transaction: " + err);
}
else{
console.log("Transaction Sent here's you txHash: " + txHash);
}
});// If you are not interested in the callback/want a shorter version you can use:
// basicStorageInstance.set(42, {from:web3.eth.coinbase});And our readout :
Setting up...
Reading abi
Connecting
Creating contract instance
unlocking Coinbase account
sending Transaction to the contract
Transaction Sent here's you txHash: 0x05e71b56c95c66d6fe59984d9af0bee00cac7c62cd210571a5c0c96009f28d83This will in theory set our contracts storedData variable to 42 ( and it did cost ether btw) , in order to read this value ( there are timing issues, but for now we’ll skip them ). we will have to call our get function:
2.call()
// FILE: getCall.jsconsole.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const contractABI = require("./basicStorage.json");console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Creating contract instance');
const basicStorage = web3.eth.contract(contractABI);
var basicStorageI = basicStorage.at("0x20ed8cc5c767b100c6a5b018e533e38b44b36326");console.log ('calling contract');var returner = parseInt(basicStorageI.get.call());
console.log(returner + 10);
// 52We can now retrieve our previously stored variable (storedData). Notice we are casting it again as an Int in order to use it in an operation ( 42 +10 = 52 ) and as noted previously this does not incurr in gas/ether costs.
Note: web3 is in active development, calling methods might be slightly different in the next version or 1.0.0.Watching contracts:
One of the quirks of having a virtual machine run on a blockchain, is that state computations on contracts are not immediate, a transaction has to be sent, and then mined.As previously mentioned sent transactions that affect a contract state do not return any values, so we need another way to check when and if a transaction and contracts method returned something.
One solution is to watch the blockchain for our contract and then call our contract method when our transaction gets accepted.
This would involve to somehow watch the blockchain from within web3.js/node, luckily this is somehow trivial :
// FILE: filterWatch.js// Watches the blockchain:
// filter can be 'latest' or 'pending' check :
// https://ethereum.stackexchange.com/questions/3400/filters-whats-latest-and-pendingconsole.log('Setting up...');
const solc = require('solc');
const Web3 = require('web3');console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));filter = web3.eth.filter('latest'); console.log('Watching');
filter.watch(function(error, result) {
console.log(result);
});Readout:
Setting up...
Connecting
Watching
0x3296600010cd439485a254e71cc5b88fcdcf3752858df9161b55eac1586d5e3d
0x2715f22c8b5bc5ebea25d2b76f0ad789eba692591d86d0d786120b2aa8d4b49c
0x0e35d29ed8dcf93c0b7b514b6cc4d9dd181372015346f85d4b680f650a181969
.....We would need to get information from each of the block hashes and query them for transactions until we got to ours, and then call the get() method, while possible, the preferred way is via events, which we will briefly cover next. ( you also now know how to watch the blockchain ).
Contract Events
In order to use contract events, the first step is to modify our basicStorage contract to log events :
// FILE : basicStorageEv.sol
// Thanks to GregJeanmart for the snippet.pragma solidity ^0.4.0;contract basicStorage {
uint storedData; event SetEvent(
address indexed _from,
uint _x
); function set(uint x) {
storedData = x; // Log event inside set
SetEvent(msg.sender, x);
}function get() constant returns (uint) {
return storedData;
}
}Compile and Deploy (using compiler.js):
console.log('Setting up...');
const fs = require('fs');
const solc = require('solc');
const Web3 = require ('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Reading contract Source...');
const input = fs.readFileSync('basicStorageEv.sol');console.log('Compiling...');
const output = solc.compile(input.toString(), 1);if (output.errors) {
console.log('Compiling failed with errors:' + output.errors);
process.exit();
}
const bytecode = output.contracts[':basicStorageEv'].bytecode;
const abi = output.contracts[':basicStorageEv'].interface;console.log('saving ABI');
fs.writeFile("./basicStorageEv.json", abi, function(err) {
if (err) {
return console.log(err);
}
console.log("ABI Saved");
});const basicStorageEv = web3.eth.contract(JSON.parse(abi));console.log('unlocking Coinbase account');const password = "your_password";
try {
web3.personal.unlockAccount(web3.eth.coinbase, password);
} catch(e) {
return console.log(e);
}console.log("Deploying contract...");
const basicStorageEvInstance = basicStorageEv.new({
data: '0x' + bytecode,
from: web3.eth.coinbase
// gasPrice: '4000000',
// gas: 1000000 /
}, function(err, res){
if (err) {
console.log('there were errors:' + err);
}
if (res.address) {
console.log('txHash:' + res.transactionHash);
console.log('Succesfully deployed Contract with address: ' + res.address);
}
});Readout:
Setting up...
Reading contract Source...
Compiling...
saving ABI
unlocking Coinbase account
Deploying contract...
ABI Saved
txHash:0x344020880f594d9c776b750d84f9d1bec9f1c1d6ff6e66663e99cf3dde73c098
Succesfully deployed Contract with address: 0xac88d694726e84ae46e2450cc8e30ea882a0cc43And now in order to watch the event we need to subscribe to it like this:
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const contractABI = require("./basicStorageEv.json");console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Creating contract instance');
const basicStorageEv = web3.eth.contract(contractABI);
var basicStorageEvI = basicStorageEv.at("0xac88d694726e84ae46e2450cc8e30ea882a0cc43");var myEvent = basicStorageEvI.SetEvent({}, {fromBlock: 0, toBlock: 'latest'});console.log("Start watching events");
myEvent.watch(function(error, result){
if (!error) {
console.log(result);
} else {
console.log(error);
}
});I previously called set 3 times: set(10), so I would get this redout:
FILE: watchEvents.jsSetting up...
Reading abi
Connecting
Creating contract instance
Start watching events
{ address: '0xac88d694726e84ae46e2450cc8e30ea882a0cc43',
blockHash: '0x69ee11dec3121715a8ec082ca386d4b4d547aa184739228307164a105dbb7c24',
blockNumber: 4061192,
logIndex: 1,
transactionHash: '0x4e75edab76bd83911caf003e6de05324f3f10cc7f90886e3d0112139956bc640',
transactionIndex: 1,
transactionLogIndex: '0x0',
type: 'mined',
event: 'SetEvent',
args:
{ _from: '0x001301ad1556fd419cf8970b174fe9af34267eb8',
_x: { [String: '10'] s: 1, e: 1, c: [Array] } } }
{ address: '0xac88d694726e84ae46e2450cc8e30ea882a0cc43',
blockHash: '0x9a09b2920072fd0eaf4bbc0fe5f38c431b80e747e8443b4eb1050114acbc5ddf',
blockNumber: 4061200,
logIndex: 3,
transactionHash: '0xd3eb8d1dd11e6db6140bb8a3741889462d7c4102c2c08eed405664fa5b30e91b',
transactionIndex: 1,
transactionLogIndex: '0x0',
type: 'mined',
event: 'SetEvent',
args:
{ _from: '0x001301ad1556fd419cf8970b174fe9af34267eb8',
_x: { [String: '10'] s: 1, e: 1, c: [Array] } } }
{ address: '0xac88d694726e84ae46e2450cc8e30ea882a0cc43',
blockHash: '0xad9d8038f8e0334046fe530306c980f773698eae1873e224899dce1fe3fbc276',
blockNumber: 4061224,
logIndex: 3,
transactionHash: '0x5c0fa62c3c3b7015fda363b8d973708d7526c998b4fb8ae53cfe7adbd76123fe',
transactionIndex: 1,
transactionLogIndex: '0x0',
type: 'mined',
event: 'SetEvent',
args:
{ _from: '0x001301ad1556fd419cf8970b174fe9af34267eb8',
_x: { [String: '10'] s: 1, e: 1, c: [Array] } } }You can watch for specific events via topics ( you might need to modify your event emmiter in the contract) and you can get specific information back via the args variable: (result.args._x in our case):
GAS :
So far I’ve been glossing over gas costs in transactions since gas in testnets is basically free, but an important part of deploying contracts succesfuly as well as estimating costs in real money hinges on understanding gas, so let’s see how gas works and how to estimate and adjust it.
- Gas is complex. In short it is the cost of making operations on ethereums Virtual Machine (VM), and has a price that is somehow independent of Ether,it is set in a sliding scale by miners and varies by operation, there are a thousand analogies out there, but the critical bit is if you don’t get the price and the amount right when deploying and interacting with your contracts, your transactions will fail and you might even loose some ether in the process.
Let’s first fail a transaction:
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const contractABI = require("./basicStorage.json");console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Creating contract instance');
const basicStorage = web3.eth.contract(contractABI);
var basicStorageInstance = basicStorage.at("0x20ed8cc5c767b100c6a5b018e533e38b44b36326");console.log('unlocking Coinbase account');
const password = "your_password";
try {
web3.personal.unlockAccount(web3.eth.coinbase, password);
} catch(e) {
console.log(e);
return;
}console.log ('sending Transaction to the contract');const transaction = {
from: web3.eth.coinbase,
gas: 0,
gasPrice: 0
}basicStorageInstance.set.sendTransaction(220, transaction, function(err, txHash) {
if (err != null) {
console.error("Error while sending transaction: " + err);
}
else{
console.log("Transaction Sent here's you txHash: " + txHash);
}
});Readout:
Setting up...
Reading abi
Connecting
Creating contract instance
unlocking Coinbase account
sending Transaction to the contract
Error while sending transaction: Error: Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: 21464, got: 0). Try increasing supplied gas.Estimating gas prices:
The first thing we need to do is estimate gas prices :
File: estimateGas.jsconsole.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const contractABI = require("./basicStorage.json");console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Creating contract instance');
const basicStorage = web3.eth.contract(contractABI);
var basicStorageInstance = basicStorage.at("0x20ed8cc5c767b100c6a5b018e533e38b44b36326");var estimatedTransactionGas = basicStorageInstance.set.estimateGas(220);console.log('Estimated Transaction Gas:' + estimatedTransactionGas);Readout:
Setting up...
Reading abi
Connecting
Creating contract instance
Estimated Transaction Gas:26586As mentioned, the gas price is set by miners resolving blocks; we can get the median gas price ( in wei ) like so:
var gasPrice = web3.eth.gasPrice;
console.log('Current Gas price:' + gasPrice.toString(10));// Current Gas price:20000000000// See: https://ethereum.stackexchange.com/questions/27452/how-to-estimate-gas-costGoing back to our failed transaction, we can now better estimate our gas requirements and submit them as part of the transaction:
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const contractABI = require("./basicStorage.json");console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));console.log('Creating contract instance');
const basicStorage = web3.eth.contract(contractABI);
var basicStorageInstance = basicStorage.at("0x20ed8cc5c767b100c6a5b018e533e38b44b36326");var estimatedTransactionGas = basicStorageInstance.set.estimateGas(220);
var gasPrice = web3.eth.gasPrice;console.log('Cost estimates: ' );
console.log('Estimated Transaction gas: ' + estimatedTransactionGas );
console.log('gas Price: ' + gasPrice);console.log('unlocking Coinbase account');
const password = "your_password";
try {
web3.personal.unlockAccount(web3.eth.coinbase, password);
} catch(e) {
console.log(e);
return;
}console.log ('sending Transaction to the contract');const transaction = {
from: web3.eth.coinbase,
gas: estimatedTransactionGas,
gasPrice: gasPrice
}basicStorageInstance.set.sendTransaction(220, transaction, function(err, txHash) {
if (err != null) {
console.error("Error while sending transaction: " + err);
}
else{
console.log("Transaction Sent here's you txHash: " + txHash);
}
});And out readout:
Setting up...
Reading abi
Connecting
Creating contract instance
Cost estimates:
Estimated Transaction gas: 26586
gas Price: 20000000000 ( or 20 Gwei or 0.00000002 ether )
unlocking Coinbase account
sending Transaction to the contract
Transaction Sent here's you txHash: 0x9617338ce298b2da4d4610aeca6324c1c5aa05b21347aac6a572b3b7b9253d3bIf we now inspect our txHash, we can get the real costs and compare them:

Note: Gas estimation is not a perfect science, at least to me,your transaction might still fail due to sudden gas price changes or other network variables, so you might have to increase by a factor both gas price and the gas used if you want to insure your transactions succeed.I really wanted to make a token for our next part, but I think it is better to first cover a few other gotchas and features of smart contracts we will be using as to have a better foundation. Recap time:
- Notes Part 1 : Setting up : Getting a wallet/client , connecting to a test ethereum blockchain and getting some test ether.
- Notes Part 2: web3.js/node : Interacting with the blockchain through web3.js and node for more convenience and ease of development.
- Notes Part 3: Solidity : Installing solidity (Ethereums contract writing language ) , Creating a very basic contract, compiling it, deploying it to the blockchain ( the test one) and interacting with it.
- Notes Part 4: Smart Contracts ( this post ) We modified our simple contract so we could store and retrieve information (making it smarter) , in the process we also covered how to watch the blockchain and contracts and finally we took a look at gas and how to estimate it.

Want to take these notes offline ?If you are looking for an introduction to Ethereum, Solidity and Smart Contracts these notes are now available in a paper and ebook form! ❤️📖❤️https://www.amazon.com/dp/B078CQ8L7V
Cheers !
Keno
About the Author :
Born Eugenio Noyola Leon (Keno) I am a Designer,Web Developer/programmer, Artist and Inventor, currently living in Mexico City, you can find me at www.k3no.com
