Introduction to Ethereum - Part 2
Bhaskar S | 12/22/2017 |
Overview
In the previous article Part 1 of this series, we introduced some basic concepts in and got our hands dirty with Ethereum by setting up a private network with 4 accounts and sending a transaction between accounts.
In this article, we will explore how to build, deploy, and trigger Smart Contracts in Ethereum using Solidity, a high-level, contract-oriented, statically typed, compiled to bytecode, programming language.
So, what are Smart Contracts really ??? Lets us refer back to our first article, Introduction to Blockchain, where we indicated a Smart Contract is a digital legal contract that consists of some state (data representing the conditions of the contract), along with some code (logic that implements the contract process), and resides in the Blockchain.
When some event (another transaction) satisfying the terms and conditions of the contract occurs, the code associated with the contract self-executes, legalizing and enforcing the terms of the contract, and potentially updating the state (recording the conditions of the contract) in the Blockchain, as well as possibly generating additional transaction(s), without the involvement of any third party agents (notaries, lawyers, etc).
So, whats the big deal ??? Efficiency, Immutability, and Transparency. Imagine a complex multi-step contract process involving multiple third party agents. Using Blockchain and Smart Contracts, the entire contract process can be automated and simplified, thereby making the whole process efficient, in addition to increasing transparency of the transactions, and establishing trust/credibility amongst the involved parties.
Installation
The installation will be on a Ubuntu 16.04 LTS (and above) based Linux desktop.
We will assume a hypothetical user called polarsparc with the home directory located at /home/polarsparc.
Assuming that we are logged in as polarsparc and the current working directory is the home directory /home/polarsparc, we will create two additional sub-directories by executing the following commands:
$ mkdir -p Ethereum/src
$ mkdir -p Ethereum/bin
Now, change the current working directory to the directory /home/polarsparc/Ethereum.
To install the Solidity compiler, execute the following commands:
$ sudo apt-get update
$ sudo apt-get install solc
Once the installation complete, execute the following command to check everything was ok:
$ solc --version
The following would be the typical output:
solc, the solidity compiler commandline interface Version: 0.4.19+commit.c4cbbb05.Linux.g++
NOTE: The Solidity compiler is being continuously improved and enhanced, so the version is likely to change.
Hands-on with Smart Contracts using Solidity
We will assume that we have started the single Ethereum node in our private network, and have the geth Javascript shell prompt ready.
In order to get started with Smart Contracts in Ethereum, we need to write the contract logic using Solidity.
Referring to our first article, Introduction to Blockchain, we will implement the simple usecase of the transfer of a vehicle from the dealer to the buyer. We will start simple and evolve as we go along.
The following is the Solidity source code named Vehicle.sol located in the directory /home/polarsparc/Ethereum/src:
Let us explain some of the important keywords used in the Vehicle.sol file shown above.
A Solidity source file must always begin with a version pragma. In our case it is the first line pragma solidity ^0.4.15. This indicates that the source code will not compile with a Solidity compiler with version earlier than 0.4.15 or starting from 0.5.0.
The keyword contract is similar to the keyword class in many popular object-oriented programming languages like C++, Java, etc. It encapsulates state variables, functions, etc
The keyword uint is a built-in data-type and indicates an unsigned integer that is 256 bits long.
The keyword address is a built-in data-type and indicates the 20 byte hexadecimal account address.
The variables vin_no and owner represent the contract's state information and will be permanently stored in a block in Ethereum (often referred to as the contract storage).
The keyword public in the function definition indicates that the function can either be called internally or via messages.
The keyword view in the function definition indicates that the function will not modify any state variables.
The global variable msg.sender captures the account address of the caller or sender of the transaction message.
To compile Vehicle.sol using the Solidity compiler, execute the following command:
$ solc -o ./bin --abi --bin --gas ./src/Vehicle.sol
The following would be the typical output:
======= ./src/Vehicle.sol:Vehicle ======= Gas estimation: construction: 40527 + 105000 = 145527 external: getOwner(): 486 getVinNo(): 416 soldTo(address): 20710
From the Output.2 above, one can infer the amount of gas that will be needed to deploy (construction) and/or invoke the different contract functions (once deployed)
There will be two generated files, namely, Vehicle.abi and Vehicle.bin located in the directory /home/polarsparc/Ethereum/bin.
The option --gas passed to the Solidity compiler enables one to get an estimate of the gas required to create/deploy a contract and to interact with its functions.
The option --abi passed to the Solidity compiler enables the creation of the Vehicle.abi file. ABI stands for Application Binary Interface and is a language neutral contract specification, which details all of the publicly accessible contract methods and their associated parameters. For a contract function written in Solidity to call another contract function written in another language, say, Serpent, they both need to implement the same approach to serialize and deserialize data - hence the need for a common ABI specification. In other words, ABI is intended to serve as the standard for encoding and decoding of data in to and out of transactions.
The following is a pretty version of the contents of the file Vehicle.abi located in the directory /home/polarsparc/Ethereum/bin:
The option --bin passed to the Solidity compiler enables the creation of the Vehicle.bin binary file containing the hex-encoded binary. This file contains the bytecode for the specified contract source file Vehicle.sol. This bytecode will be executed by the Ethereum Virtual Machine (EVM), which serves as a runtime environment for smart contracts.
The following is a pretty version of the contents of the file Vehicle.bin located in the directory /home/polarsparc/Ethereum/bin:
Unfortunately, there is no easy way to load these two files Vehicle.abi and Vehicle.bin via the geth Javascript shell. Found an excellent article written by Gustavo (Gus) Guimaraes (link included in the References) addressing this issue.
To compile Vehicle.sol using the Solidity compiler to generate a Javascript file (with .js file extension) that will contain both the ABI as well as the bytecode, execute the following command:
$ echo "var vehicle=`solc --optimize --combined-json abi,bin,interface src/Vehicle.sol`" > bin/Vehicle.js
The above command will generate a Javascript file called Vehicle.js located in directory /home/polarsparc/Ethereum/bin with both the ABI and bytecode for the specified contract Vehicle.sol encoded as JSON.
To load the above generated Javascript file Vehicle.js, execute the following command in the geth Javascript shell prompt:
> loadScript("./bin/Vehicle.js")
The following would be the typical output:
true
To check the value stored in the variable vehicle, execute the following command in the Javascript shell prompt:
> vehicle
The following would be the typical output:
{ contracts: { src/Vehicle.sol:Vehicle: { abi: "[{\"constant\":false,\"inputs\":[{\"name\":\"buyer\",\"type\":\"address\"}],\"name\":\"soldTo\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getVinNo\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"vin\",\"type\":\"uint256\"},{\"name\":\"dealer\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]", bin: "6060604052341561000f57600080fd5b6040516040806101f48339810160405280805191906020018051600093845560018054600160a060020a031916600160a060020a03909216919091179055506101959182915061005f90396000f3006060604052600436106100565763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663683c8074811461005b5780636c41597a14610089578063893d20e8146100ae575b600080fd5b341561006657600080fd5b61008773ffffffffffffffffffffffffffffffffffffffff600435166100ea565b005b341561009457600080fd5b61009c610147565b60405190815260200160405180910390f35b34156100b957600080fd5b6100c161014d565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6001543373ffffffffffffffffffffffffffffffffffffffff90811691161415610144576001805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b60005490565b60015473ffffffffffffffffffffffffffffffffffffffff16905600a165627a7a72305820ead3ff4e0d30a244745dab2015896c6d3ddcbd2ba949323e6386ca18620d7a960029" } }, version: "0.4.19+commit.c4cbbb05.Linux.g++" }
To retrieve the abi value from the variable vehicle, execute the following command in the Javascript shell prompt:
> vehicle.contracts['src/Vehicle.sol:Vehicle'].abi
The following would be the typical output:
"[{\"constant\":false,\"inputs\":[{\"name\":\"buyer\",\"type\":\"address\"}],\"name\":\"soldTo\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getVinNo\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"vin\",\"type\":\"uint256\"},{\"name\":\"dealer\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]"
Similarly, to retrieve the bin value from the variable vehicle, execute the following command in the Javascript shell prompt:
> vehicle.contracts['src/Vehicle.sol:Vehicle'].bin
The following would be the typical output:
"6060604052341561000f57600080fd5b6040516040806101f48339810160405280805191906020018051600093845560018054600160a060020a031916600160a060020a03909216919091179055506101959182915061005f90396000f3006060604052600436106100565763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663683c8074811461005b5780636c41597a14610089578063893d20e8146100ae575b600080fd5b341561006657600080fd5b61008773ffffffffffffffffffffffffffffffffffffffff600435166100ea565b005b341561009457600080fd5b61009c610147565b60405190815260200160405180910390f35b34156100b957600080fd5b6100c161014d565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6001543373ffffffffffffffffffffffffffffffffffffffff90811691161415610144576001805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b60005490565b60015473ffffffffffffffffffffffffffffffffffffffff16905600a165627a7a72305820ead3ff4e0d30a244745dab2015896c6d3ddcbd2ba949323e6386ca18620d7a960029"
For convenience and ease of use, let us assign the addresses of the 2nd and 3rd accounts to variables named buyer and dealer using the Javascript shell as shown below:
> buyer = "0x35ddac855e0029676f489dafca9ab405a348317c"
> dealer = "0x46bf8994c8702919434271d89bcad8abec915612"
Now we are at a point ready to deploy our first Smart Contract called Vehicle into our private Ethereum network.
The first step is to create a contract object. To create a contract object called vehicle_contract, execute the following command in the Javascript shell prompt:
> var vehicle_contract = eth.contract(JSON.parse(vehicle.contracts['src/Vehicle.sol:Vehicle'].abi))
The following would be the typical output:
undefined
To check the value of the contract object variable vehicle_contract, execute the following command in the Javascript shell prompt:
> vehicle_contract
The following would be the typical output:
{ abi: [{ constant: false, inputs: [{...}], name: "soldTo", outputs: [], payable: false, stateMutability: "nonpayable", type: "function" }, { constant: true, inputs: [], name: "getVinNo", outputs: [{...}], payable: false, stateMutability: "view", type: "function" }, { constant: true, inputs: [], name: "getOwner", outputs: [{...}], payable: false, stateMutability: "view", type: "function" }, { inputs: [{...}, {...}], payable: false, stateMutability: "nonpayable", type: "constructor" }], eth: { accounts: ["0xef17ec5df3116dea3b7abaf1ff3045639453cc76", "0x35ddac855e0029676f489dafca9ab405a348317c", "0x46bf8994c8702919434271d89bcad8abec915612", "0x518a6377a3863201aef3310da41f3448d959a310"], blockNumber: 83, coinbase: "0xef17ec5df3116dea3b7abaf1ff3045639453cc76", compile: { lll: function(), serpent: function(), solidity: function() }, defaultAccount: undefined, defaultBlock: "latest", gasPrice: 18000000000, hashrate: 0, mining: false, pendingTransactions: [], protocolVersion: "0x3f", syncing: false, call: function(), contract: function(abi), estimateGas: function(), filter: function(options, callback, filterCreationErrorCallback), getAccounts: function(callback), getBalance: function(), getBlock: function(), getBlockNumber: function(callback), getBlockTransactionCount: function(), getBlockUncleCount: function(), getCode: function(), getCoinbase: function(callback), getCompilers: function(), getGasPrice: function(callback), getHashrate: function(callback), getMining: function(callback), getPendingTransactions: function(callback), getProtocolVersion: function(callback), getRawTransaction: function(), getRawTransactionFromBlock: function(), getStorageAt: function(), getSyncing: function(callback), getTransaction: function(), getTransactionCount: function(), getTransactionFromBlock: function(), getTransactionReceipt: function(), getUncle: function(), getWork: function(), iban: function(iban), icapNamereg: function(), isSyncing: function(callback), namereg: function(), resend: function(), sendIBANTransaction: function(), sendRawTransaction: function(), sendTransaction: function(), sign: function(), signTransaction: function(), submitTransaction: function(), submitWork: function() }, at: function(address, callback), getData: function(), new: function() }
Next, we will define 3 variables, namely, vin, gas_price, and contract_transaction with values representing the Vehicle Identification Number (VIN), the gas price paid for the contract creation, and the contract transaction object that encapsulates the address of the account creating the contract, the bytecode of the contract, and the gas price. Execute the following statements in the Javascript shell as shown below:
> var vin = 1234567890
> var gas_price = 1000000
> var contract_transaction = { from: dealer, data: '0x'+vehicle.contracts['src/Vehicle.sol:Vehicle'].bin, gas: gas_price }
To check the value of the contract transaction object variable contract_transaction, execute the following command in the Javascript shell prompt:
> contract_transaction
The following would be the typical output:
{ data: "0x6060604052341561000f57600080fd5b6040516040806101f48339810160405280805191906020018051600093845560018054600160a060020a031916600160a060020a03909216919091179055506101959182915061005f90396000f3006060604052600436106100565763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663683c8074811461005b5780636c41597a14610089578063893d20e8146100ae575b600080fd5b341561006657600080fd5b61008773ffffffffffffffffffffffffffffffffffffffff600435166100ea565b005b341561009457600080fd5b61009c610147565b60405190815260200160405180910390f35b34156100b957600080fd5b6100c161014d565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6001543373ffffffffffffffffffffffffffffffffffffffff90811691161415610144576001805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b60005490565b60015473ffffffffffffffffffffffffffffffffffffffff16905600a165627a7a72305820ead3ff4e0d30a244745dab2015896c6d3ddcbd2ba949323e6386ca18620d7a96002900000000000000000000000000000000000000000000000000000000499602d200000000000000000000000046bf8994c8702919434271d89bcad8abec915612", from: "0x46bf8994c8702919434271d89bcad8abec915612", gas: "0xf4240" }
Since the contract is deployed by the dealer, we need to unlock the dealer account before proceeding further.
To unlock the dealer account, execute the following command in the Javascript shell prompt:
> personal.unlockAccount(dealer)
This will prompt us for the password for the dealer account. The following would be the typical output:
Unlock account 0x46bf8994c8702919434271d89bcad8abec915612 Passphrase: dealer true
The second step is to create an instance of the contract object by calling the new method on the contract object vehicle_contract, passing in the required arguments. Execute the following command in the Javascript shell prompt:
> var vehicle_contract_inst = vehicle_contract.new(vin, dealer, contract_transaction)
The following would be the typical output:
undefined
To check the value of the contract instance variable vehicle_contract_inst, execute the following command in the Javascript shell prompt:
> vehicle_contract_inst
The following would be the typical output:
{ abi: [{ constant: false, inputs: [{...}], name: "soldTo", outputs: [], payable: false, stateMutability: "nonpayable", type: "function" }, { constant: true, inputs: [], name: "getVinNo", outputs: [{...}], payable: false, stateMutability: "view", type: "function" }, { constant: true, inputs: [], name: "getOwner", outputs: [{...}], payable: false, stateMutability: "view", type: "function" }, { inputs: [{...}, {...}], payable: false, stateMutability: "nonpayable", type: "constructor" }], address: undefined, transactionHash: "0x56eb85db9905d2b6f33a832f1bae745b59dabc34c54db31346cffe453dec2910" }
The above contract instance creation command will create an Ethereum transaction that needs to be mined for the contract to be deployed on the private Blockchain network.
From the Output.12, we see the transactionHash for the contract deployment transaction.
To start the miner with one mining thread, execute the following command in the Javascript shell prompt:
> miner.start(1)
In a few seconds (about 5 to 10), the contract deployment transaction will be mined and the contract will be deployed to our private Blockchain network.
To stop the miner, execute the following command in the Javascript shell prompt:
> miner.stop()
To retrieve the transaction details of the just sent transaction 0x56eb85db9905d2b6f33a832f1bae745b59dabc34c54db31346cffe453dec2910, execute the following command in the Javascript shell prompt:
> eth.getTransactionReceipt(vehicle_contract_inst.transactionHash)
The following would be the typical output:
{ blockHash: "0x2a4cd01691866665382342f80d8714ffa125691c65128ef8c06e41be65468499", blockNumber: 84, contractAddress: "0xfa332003301955dd8526f0ef3aa7c670bda5f4fa", cumulativeGasUsed: 206925, from: "0x46bf8994c8702919434271d89bcad8abec915612", gasUsed: 206925, logs: [], logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", root: "0x2d7fa49d64b631e3818ecad7813305f63cc9785ce99b3afe1d0120b02a9ad664", to: null, transactionHash: "0x56eb85db9905d2b6f33a832f1bae745b59dabc34c54db31346cffe453dec2910", transactionIndex: 0 }
The presence of a value for the contractAddress indicates that the contract has been successfully deployed to our private Blockchain network.
To check the bytecode of the just deployed contract 0xfa332003301955dd8526f0ef3aa7c670bda5f4fa, execute the following command in the Javascript shell prompt:
> eth.getCode(eth.getTransactionReceipt(vehicle_contract_inst.transactionHash).contractAddress)
The following would be the typical output:
"0x6060604052600436106100565763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663683c8074811461005b5780636c41597a14610089578063893d20e8146100ae575b600080fd5b341561006657600080fd5b61008773ffffffffffffffffffffffffffffffffffffffff600435166100ea565b005b341561009457600080fd5b61009c610147565b60405190815260200160405180910390f35b34156100b957600080fd5b6100c161014d565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6001543373ffffffffffffffffffffffffffffffffffffffff90811691161415610144576001805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b60005490565b60015473ffffffffffffffffffffffffffffffffffffffff16905600a165627a7a72305820ead3ff4e0d30a244745dab2015896c6d3ddcbd2ba949323e6386ca18620d7a960029"
The last step is to create an instance of the Vehicle contract that was just deployed to our private Blockchain, so we can invoke the contract function(s). To create a instance of the Vehicle contract, we need to invoke the at method on the contract object vehicle_contract, passing in the value of contractAddress as the argument. Execute the following command in the Javascript shell prompt:
> var vehicle_inst = vehicle_contract.at(eth.getTransactionReceipt(vehicle_contract_inst.transactionHash).contractAddress)
The following would be the typical output:
undefined
To check the value of the contract instance variable vehicle_inst, execute the following command in the Javascript shell prompt:
> vehicle_inst
The following would be the typical output:
{ abi: [{ constant: false, inputs: [{...}], name: "soldTo", outputs: [], payable: false, stateMutability: "nonpayable", type: "function" }, { constant: true, inputs: [], name: "getVinNo", outputs: [{...}], payable: false, stateMutability: "view", type: "function" }, { constant: true, inputs: [], name: "getOwner", outputs: [{...}], payable: false, stateMutability: "view", type: "function" }, { inputs: [{...}, {...}], payable: false, stateMutability: "nonpayable", type: "constructor" }], address: "0xfa332003301955dd8526f0ef3aa7c670bda5f4fa", transactionHash: null, allEvents: function(), getOwner: function(), getVinNo: function(), soldTo: function() }
To invoke the getOwner method on the contract instance vehicle_inst, execute the following command in the Javascript shell prompt:
> vehicle_inst.getOwner.call()
The following would be the typical output:
"0x46bf8994c8702919434271d89bcad8abec915612"
Similarly, to invoke the getVinNo method, execute the following command in the Javascript shell prompt:
> vehicle_inst.getVinNo.call()
The following would be the typical output:
1234567890
By using the call() method, the contract methods are executed locally on the connected Ethereum node without costing any gas. This also means that any changes made to the contract state (such as calling the soldTo method) will not be persisted or stored in our private Blockchain.
References