Blockchain and cryptocurrencies are the hottest topics right now, as the value of cryptocurrencies, especially Bitcoin, is record-breakingly high. Central banks are now even starting to look closely at how to treat digital money, since the popularity of blockchain technology and cryptos is skyrocketing. As a tech company, we are interested in the technology behind cryptocurrencies - the blockchain. One of the most interesting subjects in the crypto space are Smart Contracts on the Ethereum blockchain.
A blockchain is so much more than just the backbone of any cryptocurrency. It’s a way to provide safe, secure, and verifiable interactions between two players (based on a set of rules), as verified by a distributed, peer-to-peer network. The Ethereum blockchain allows us to create our own definitions of interactions between players, via Smart Contracts, and deploy them over the network. Always wanted to create your own cryptocurrency? How about an escrow system? Learn the basics here as we walk through a coded example.
What is a blockchain?
A blockchain is a list of records (blocks) which are connected together in a chain. Each block in this chain points to the previous block through a hash pointer. By design, this chain and its blocks are incorruptible and resistant to modification of the data. Each block is secured using a cryptographic function. The adding of a new block to the chain is verified by a peer-to-peer network, with each node keeping a copy of the full ledger (the blockchain) for comparison. Adding a new block is called “mining” a block, and it involves a machine solving a complex cryptographic puzzle.
Cryptocurrencies, like Bitcoin, are one of the most popular applications of blockchain technology. Ethereum is a ledger technology used to build decentralized applications (`dapps`) using Smart Contracts. It also has its own cryptocurrency (called Ether) for exchanging assets (i.e. for performing contract operations). It also has a faster blockchain write time (matter of seconds instead of minutes).
Similar to Bitcoin, you manage your accounts and contracts on the blockchain using wallets. These are your vaults, accessible via `mnemonic` or `keystore` files. `mnemoni``cs` (a pattern of letters and numbers in human readable format) allow you to recover your wallet’s data when you loose your `keystore` file; this is provided to you when you sign up for a new wallet. If you loose both your `mnemonic` and `keystore` files you will loose all your data (including any cryptocurrency you hold in your account) inside your wallet forever.
Wallets are useless without a connection to the blockchain network, so you either need a client or a service that allows you to participate in blockchain operations using your wallet. These days it’s easier to rely on 3rd party services for connecting to the network as blockchain sizes are getting bigger and bigger and running the client locally will require continuous sync (which can be time consuming).
Smart Contracts
Smart Contracts are contracts that operate on blockchain technology that are designed to instantly verify certain things that stand in the way of money movement and do away with the services of middlemen. Use cases might be third party escrow services, verification of identification documents for loans, and tracking the movement of items through a supply chain before paying suppliers and transport services. For instance, imagine that you are crowdfunding some new project. If you use a 3rd party service you can’t be 100% sure that all raised money will be paid to the team or refunded to backers if the project doesn’t get funded. Basically you are relaying on external service that it will comply with its own rules (centralized network), or, in the case of some catastrophe (i.e. bankruptcy) some other institution will run after the owners. If you developed your own Smart Contracts for crowdfunding, you can be 100% sure you’ll see the funds if you reach the right levels, and your backers can be 100% sure they’ll have their money returned if you *don’t* quite get there.
Smart Contracts are pieces of software written with a set of inbuilt parameters to ensure security and compliance to some rule set. Smart Contracts run on each machine that runs the blockchain itself, and for the contract to be proven/approved all machines must reach the same result when processing the software. This peer-to-peer agreement makes the outcome of tests of the contract always correct - provided it is coded correctly.
Using Ethereum, anyone can develop their own Smart Contracts. Each little contract confirmation that a Smart Contract requires the Ethereum blockchain network of computers to process attracts a Gas fee - calculated by how complex the confirmation is to calculate. Gas fees are converted to Ethereum, so you, as the person who invokes the Smart Contract, have a way to pay them.
In the Blockchain beyond Bitcoin talk, Stephan Tual (the former CCO of Ethereum) describes the vision where each object (AirBnB door, autonomous car, gas distributor, you name it) is connected to the global network powered by blockchain and you can interact with it directly using your smartphone through Smart Contracts.
Writing your own Smart Contracts
If it still sounds interesting to you and you would like to start writing your own Smart Contracts we will show you how to do it.
We will use the Ethereum blockchain with the Truffle framework (for developing and deploying the contracts) to the network. We will also use Genache for running a personal blockchain on your local machine so you can execute contracts in a sandboxed environment (a test blockchain) to ensure they are working correctly. For contract implementation, we will use the Solidity language which is the most popular language for writing Smart Contracts for Ethereum.
Preparing a development environment
Let’s install all the necessary tools (assuming you are using macOS and have homebrew installed):
1. `brew install node`
2. `npm install -g truffle ganache-cli`
Demo
Let’s use the MetaCoin example to familiarize ourselves with `truffle`:
mkdir my-contracts && cd $_
truffle unbox metacoin
It will create an initial structure like the one below:
.
├── contracts
│ ├── ConvertLib.sol
│ ├── MetaCoin.sol
│ └── Migrations.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
│ ├── TestMetacoin.sol
│ └── metacoin.js
├── truffle-config.js
└── truffle.js
Let’s try to deploy this sample contract to our local blockchain:
1. First we need to start our personal blockchain using `ganache-cli`:
ganache-cli
2. We need to update our configuration to specify the network for `development`:
// ./truffle.js
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
}
}
};
3. Now we can compile our contract:
truffle compile
4. Then migrate it and deploy to our test node:
truffle migrate
Since our contract is deployed, it would be shame if we didn’t play with it:
Let’s open up the console:
truffle console
and take one of the addresses that `ganache` created for us and check the `MetaCoin` balance:
truffle(development)> var account = web3.eth.accounts[0]
truffle(development)> var meta
truffle(development)> MetaCoin.deployed().then(function(inst) { meta = inst; });
truffle(development)> meta.getBalance(account).then(function(balance) { console.log(balance.toNumber()); });
=> 10000
So far so good. Let’s try to send some coins now.
Grab another account’s address from `ganache` and call `sendCoin` function on the `MetaCoin` contract:
truffle(development)> another_account = web3.eth.accounts[1]
truffle(development)> meta.sendCoin(another_account, 10, { from: account }).then(function(result) { console.log(result); });
=> { tx: '0x1cd15bfc00d0bf867e227501b85f0928d5de2019a814049183239937b7f53584',
...
truffle(development)> meta.getBalance(account).then(function(balance) { console.log(balance.toNumber()); });
=> BigNumber { s: 1, e: 3, c: [ 9990 ] }
truffle(development)> meta.getBalance(another_account).then(function(balance) { console.log(balance.toNumber()); })
truffle(development)> meta.getBalance(another_account).then(function(balance) { console.log(balance.toNumber()); })
=> 10
As you can see, we successfully transferred funds from the first account to a second one.
The example also contains tests which can be executed:
$ truffle test
Using network 'development'.
Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./test/TestMetacoin.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...
TestMetacoin
✓ testInitialBalanceUsingDeployedContract (52ms)
✓ testInitialBalanceWithNewMetaCoin (42ms)
Contract: MetaCoin
✓ should put 10000 MetaCoin in the first account
✓ should call a function that depends on a linked library
✓ should send coin correctly (86ms)
5 passing (656ms)
Your first Smart Contract
Since we already played with the demo, it’s time to write our very own first Smart Contract.
For our contract, we will use a round robin algorithm to specify a person in the company responsible for performing customer support tasks for a given day :).
Let’s create the `CustomerSupport` contract:
truffle create contract CustomerSupport
with the simplest possible implementation:
// contracts/CustomerSupport.sol
contract CustomerSupport {
bytes32[] people;
uint8 index;
function CustomerSupport() public {
index = 0;
}
function getPerson() public view returns(bytes32) {
if (people.length <= index) {
return 0x0;
}
return people[index];
}
}
Because Solidity does not support dynamic nested arrays, it does not support dynamic arrays of strings (strings themselves are a dynamic array anyway). Therefore, we decided to use a fixed array of 32 bytes, which means we can store a person’s name up to 32 bytes - keep in mind we mean bytes, not characters.
You might be thinking of trying to make things simpler by returning a string or dynamic `byte` array from the `getPerson` function, but sadly it’s another limitation of Solidity - it does not support a dynamically sized array as a return value from a function. Instead, we will convert the bytes to text in a higher level.
Now we need to update the `2_deploy_contract.js` migration to include our contract:
// migrations/2_deploy_contracts.js
var ConvertLib = artifacts.require("./ConvertLib.sol");
var MetaCoin = artifacts.require("./MetaCoin.sol");
var CustomerSupport = artifacts.require("./CustomerSupport.sol");
module.exports = function(deployer) {
deployer.deploy(ConvertLib);
deployer.link(ConvertLib, MetaCoin);
deployer.deploy(MetaCoin);
deployer.deploy(CustomerSupport);
};
Let’s try to write a simple test for our contract:
// test/customer_support.js
var CustomerSupport = artifacts.require('./CustomerSupport.sol');
contract('CustomerSupport', function() {
it("should not return a person when people are not provided", function() {
var customerSupport;
return CustomerSupport.deployed().then(function(instance) {
customerSupport = instance;
return customerSupport.getPerson.call();
}).then(function(person) {
var customerSupportPerson = web3.toUtf8(person);
assert.equal(customerSupportPerson, "");
});
});
});
Our code always returns empty bytes from the `getPerson` method for now. We are also using the `toUtf8` function from web3.js to correctly convert our bytes32 variable to string.
Let’s modify our contract to support inputting people:
// contracts/CustomerSupport.sol
pragma solidity ^0.4.4;
contract CustomerSupport {
bytes32[] people;
uint8 index;
function CustomerSupport() public {
index = 0;
}
function addPeople(bytes32[] _people) public {
index = 0;
people = _people;
}
function getPerson() public view returns(bytes32) {
if (people.length <= index) {
return 0x0;
}
return people[index];
}
}
We need to reset our index to 0 anytime we update people in the contract. This is because we’ll be using it as a counter later on in our code, which we’ll need to update when new people come on board.
And here is a corresponding test:
// test/customer_support.js
var CustomerSupport = artifacts.require('./CustomerSupport.sol');
contract('CustomerSupport', function() {
...
it("should return first person when people are assigned", function() {
var customerSupport;
var customerSupportPeople = ['Alice', 'Bob'].map(function(person) {
return web3.toHex(person);
});
return CustomerSupport.deployed().then(function(instance) {
customerSupport = instance;
return customerSupport.addPeople(customerSupportPeople);
}).then(function() {
return customerSupport.getPerson.call();
}).then(function(person) {
var customerSupportPerson = web3.toUtf8(person);
var expectedPerson = web3.toUtf8(customerSupportPeople[0]);
assert.equal(customerSupportPerson, expectedPerson);
});
});
});
We are using the `toHex` function to convert our string to bytes32 before passing `people` to the `addPeople` function.
If you have some failing tests you can try to remove the `build` directory (`rm -rf build`) as sometimes it gets stuck in limbo.
We are making good progress here! The only missing piece is ability to rotate people in the list, so let’s add it:
// contracts/CustomerSupport.sol
pragma solidity ^0.4.4;
contract CustomerSupport {
bytes32[] people;
uint8 index;
function CustomerSupport() public {
index = 0;
}
function addPeople(bytes32[] _people) public {
index = 0;
people = _people;
}
function switchPerson() public {
index++;
}
function getPerson() public view returns(bytes32) {
if (people.length <= index) {
return 0x0;
}
return people[index];
}
}
And here are our tests:
// test/customer_support.js
var CustomerSupport = artifacts.require('./CustomerSupport.sol');
contract('CustomerSupport', function() {
...
it("should allow to return next person", function() {
var customerSupport;
var customerSupportPeople = ['Alice', 'Bob'].map(function(person) {
return web3.toHex(person);
});
return CustomerSupport.deployed().then(function(instance) {
customerSupport = instance;
return customerSupport.addPeople(customerSupportPeople);
}).then(function() {
return customerSupport.switchPerson();
}).then(function() {
return customerSupport.getPerson.call();
}).then(function(person) {
var customerSupportPerson = web3.toUtf8(person);
var expectedPerson = web3.toUtf8(customerSupportPeople[1]);
assert.equal(customerSupportPerson, expectedPerson);
});
});
});
You might wonder about the difference between regular function invocation (`customerSupport.switchPerson()`) and the `call` invocation (`customerSupport.getPerson.call()`).
First, you need to understand that there are 2 types of method invocations for contracts:
- `call` - a local invocation of a contract function that does not broadcast or publish anything on the blockchain (it also does not consume any Ether)
- `transaction` - broadcast to the network, processed by miners, and if valid, is published on the blockchain (and by design it will consume Ether as a transaction cost unless the miner does us a favor and process it with a Gas price of zero)
If you invoke the method directly (i.e. `switchPerson()`) - it will automatically determine whether `call` or `sendTransaction` should be used, based on the method type.
Going back to our example - if you invoke `switchPerson` with a `call` (`switchPerson.call()`) and then call `getPerson`, you will receive the same person as before (as the person change hasn’t been written to the blockchain).
We need to handle one more case: if we are at the last element of array we need to reset our index counter to zero (you might think about increasing it indefinitely and using the modulo operator work - however at some point this will overflow `uint` range).
Here is an updated version of `switchPerson` function setting our index counter:
function switchPerson() public {
if(index < people.length - 1) {
index++;
} else {
index = 0;
}
}
and an additional test:
it("should return first person after the last one", function() {
var customerSupport;
var customerSupportPeople = ['Alice', 'Bob'].map(function(person) {
return web3.toHex(person);
});
return CustomerSupport.deployed().then(function(instance) {
customerSupport = instance;
return customerSupport.addPeople(customerSupportPeople);
}).then(function() {
return customerSupport.switchPerson();
}).then(function() {
return customerSupport.switchPerson();
}).then(function() {
return customerSupport.getPerson.call();
}).then(function(person) {
var customerSupportPerson = web3.toUtf8(person);
var expectedPerson = web3.toUtf8(customerSupportPeople[0]);
assert.equal(customerSupportPerson, expectedPerson);
});
});
Gas estimation
Let’s try to estimate the Gas usage of our Smart Contracts. We can do it using the truffle console (`truffle console`).
Let get the contract instance first:
> var contract;
> CustomerSupport.deployed().then(function(instance) { contract = instance; });
Now we can estimate Gas usage by our functions:
> var people = ['Alice', 'Bob'].map(function(p) { return web3.toHex(p); });
> contract.addPeople.estimateGas(people).then(function(result) { gasEstimate = Number(result) });
=> 88678
> var gasEstimate;
> contract.getPerson.estimateGas().then(function(result) { gasEstimate = Number(result) });
=> 22004
Since we know how much Gas our functions use, we need to know the Gas price to get the cost of the function (in `wei` units):
> var gasPrice = CustomerSupport.web3.eth.gasPrice;
=> BigNumber { s: 1, e: 10, c: [ 20000000000 ] }
> var cost = gasPrice * gasEstimate;
=> 440080000000000
Because `web3.eth.gasPrice` returns cost in `wei`, we might want to convert it to `eth`:
> var ethCost = CustomerSupport.web3.fromWei(cost, 'ether');
=> '0.00044008'
Optimizing Gas usage
Since any function that is not read-only requires Gas to run, it’s in your best interests to write code as efficiently as possible.
We will show you 2 contracts implementing the same functionality, each using a different algorithm, and compare the cost of running them.
Imagine that you have a pool of workers (identified by some arbitrary company ID) and their wallet address (to pay their wage). Now you need a function that returns their address based on the ID.
We will compare 2 implementations:
// contracts/Worker1.sol
pragma solidity ^0.4.4;
contract Worker1 {
uint[] ids;
address[] addrs;
function Worker1() public {
ids = [1, 2, 3];
addrs = [
0x0001c971d1e2d6b1605b2d73581d66199d21f44641,
0x009a17df74b15b300e592a13a614c7e61f8d3443e7,
0x00bf9369967fb0b2ffd2e9c689a2f50a7df77b0353
];
}
function getAddress(uint id) public view returns (address) {
for(uint i = 0; i < ids.length; ++i) {
if(ids[i] == id) {
return addrs[i];
}
}
}
}
and:
pragma solidity ^0.4.4;
contract Worker2 {
mapping(uint => address) addrs;
function Worker2() public {
addrs[1] = 0x0001c971d1e2d6b1605b2d73581d66199d21f44641;
addrs[2] = 0x009a17df74b15b300e592a13a614c7e61f8d3443e7;
addrs[3] = 0x00bf9369967fb0b2ffd2e9c689a2f50a7df77b0353;
}
function getAddress(uint id) public view returns (address) {
return addrs[id];
}
}
Let’s compile and migrate them (remember to include those 2 workers in `2_deploy_contracts` migration) before taking them for a spin:
truffle compile
truffle migrate --reset
Now we can compare the Gas estimate for each:
> var contract, gasEstimate;
> Worker1.deployed().then(function(instance) { contract = instance; });
> contract.getAddress.estimateGas('0x00bf9369967fb0b2ffd2e9c689a2f50a7df77b0353').then(function(result) { gasEstimate = Number(result) });
> gasEstimate
=> 25488
> Worker2.deployed().then(function(instance) { contract = instance; });
> contract.getAddress.estimateGas('0x00bf9369967fb0b2ffd2e9c689a2f50a7df77b0353').then(function(result) { gasEstimate = Number(result) });
> gasEstimate
=> 23239
As you probably suspected, the more optimized version - the second version - uses less Gas.
Deploying and verifying Smart Contracts
If you deploy a Smart Contract it will live on the blockchain in an Ethereum-specific binary format (EVM bytecode). In order to build trust between your Smart Contract and its users it’s good idea to verify it (it means it’s source code will be available to read). There is a dedicated Etherscan page which can display transactions, token transfers, and source code for each Smart Contract. You can also search contracts, transactions, etc. by their hashes.
However, before the contract can be verified, it needs to be placed on the EVM - not just in our test environment.
Let’s deploy our contracts to the Ethereum network:
1. First remove all contracts from the `contracts/` directory except `CustomerSupport.sol` and `Migrations.sol`.
2. Remove tests from the `test/` directory for previously removed contracts.
3. Update `migrations/2_deploy_contracts.js` deleting previously removed contracts.
4. Recompile everything.
Stay tuned for another blog post about verifying Smart Contracts.
Smart Contracts are the future of doing financial transactions. If you have an ambitious project that could benefit from using your very own Smart Contracts then let us help you realize it. We can build an implement Ethereum Smart Contracts that will help set your business apart. Contact iRonin for more about this exciting technology.