Introduction
In my last two articles, I explored the Solidity language. This language is designed to work with the Ethereum Virtual machine (EVM). In this article, we will move past the language theory and use a test environment for our contract. With this, we will be able to understand better how Ethereum works.
The tools
In this first article, we will use truffle and ganache-cli. Truffle is a framework for Ethereum development. It allows you to create an test environment, write tests for your contracts and other things. In this article however, we will only use it to create a test environment.
ganache-cli will be used with truffle to create a fully functioning test environment. What we will do here won't be quite like a real production blockchain, but it will allow us to see how things work.
Installing the tools
- truffle:
[sudo] npm install -g truffle
- ganache-cli:
[sudo] npm install -g ganache-cli
Getting things ready
First, we will initialize a new truffle project. To do this, create a new directory and inside it, run: truffle init
.
You will now have several folders. First, open the truffle-config.js and paste this inside:
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
networks: {
development: {
host: '127.0.0.1',
port: 7545,
network_id: '*'
}
}
}
For Windows users, you will have to remove the truffle.js file to avoid conflicts. For others, you can keep both and put this code in truffle.js, or do like a Windows user, doesn't matter.
This file indicates that our development network will be running on localhost:7545.
Next, in the contracts folder, create a new DeveloperFactory.sol file. This is where our contract will be written. Put the following inside:
pragma solidity ^0.4.18;
contract DeveloperFactory {
// Let's create a Developer!
event NewDeveloper(uint devId, string name, uint age);
uint maxAge = 100;
uint minAge = 5;
struct Developer {
string name;
uint id;
uint age;
}
Developer[] public developers;
mapping (uint => address) public devToOwner;
mapping (address => uint) public ownerDevCount;
function _createDeveloper( string _name, uint _id, uint _age ) private{
uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
ownerDevCount[msg.sender]++;
devToOwner[id] = msg.sender;
NewDeveloper(id, _name, _age);
}
function _generateRandomId( string _str ) private pure returns (uint){
uint rand = uint(keccak256(_str));
return rand;
}
function createRandomDeveloper( string _name, uint _age ) public payable {
require(_age > minAge);
require(_age < maxAge);
require(msg.value == 5000000000000000000);
uint randId = _generateRandomId( _name );
_createDeveloper(_name, randId, _age );
}
function getAllDevelopers() public view returns (uint) {
return developers.length;
}
}
If you want to know what is happening here in more details, I covered it in my first and second article about Solidity. But, in short, this contract can be called to create a Developer struct with a name and an age. In our example, creating a new Developer will require 5 ether or ( 5000000000000000000 wei, the lowest denomination possible in Ethereum ).
Next, go inside the migrations folder and create a file called 2_deploy_contracts.js:
const DeveloperFactory = artifacts.require('./DeveloperFactory.sol')
module.exports = function(deployer){
deployer.deploy(DeveloperFactory)
}
In this file, we import our contract and deploy it to our blockchain.
Launching our test environment
Open a new terminal window and run ganache-cli -p 7545
. This will run ganache-cli on port 7545 ( the same we specified in our truffle-config.js file ) and create some accounts for us. Each account will have 100 ether by default. You should see something like this in your console:
Available Accounts
==================
(0) 0x473c0be352f997aa0b194786c27d26e29a3f75b1
(1) 0x9657290da5570b17a03198f490b0a2d7eea84ecf
(2) 0x516c0e0152d7b85facb7e3da2d30f67e42a80ca9
(3) 0xf81be8bbe99d2302b85f7cb0f60103c435ae703b
(4) 0xcfacf5ac5567cfdd70ee5a8a9fe4bf7f74d80b02
(5) 0x623e18e34b2de07933fe179862f038230cc69012
(6) 0xd7100dbc1d6f72777ae2a6f5d95c4b8d71f7ce07
(7) 0x7f40df6c6042888a37124821130910e77051b1cf
(8) 0x26a2c2be1f31571f289b7fb60e41f31f7c57a5be
(9) 0x08a945825a28166466987d5fc77b016fe3d80aa5
Of course, the accounts' addresses will be different for you, but you will get 10 accounts to play with.
Now, go back to your first terminal window. Make sure you are inside the folder you created for the truffle project and run: truffle compile
, then run truffle migrate --network development
. This will compile our code in a language that the Ethereum Virtual Machine (EVM) can understand. Here, ganache will emulate the EVM.
If everything went well, your terminal should have shown this:
truffle migrate --network development
Using network 'development'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0xc83617394674cd65f751ff9c05438e16339414ccf1e1662ba66479d79335af13
Migrations: 0xe982e78028e0dfcbdb135e7a3c1e1ed3d98e36e5
Saving successful migration to network...
... 0x78bdff98e4dac310de4650048a0856075a460bed9de0c4d4ea879ea399d142c4
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying DeveloperFactory...
... 0x0a314c5ed99c772019ea358ac98e002a1442e26903122528d705bf3ff7ed02ed
DeveloperFactory: 0xc34cc3e53850673db1dea31d267ea1738edc629f
Saving successful migration to network...
... 0x95a3cd861f067cdbe8c96f13477526eacd2a7936662f31724e1354922af49664
Saving artifacts...
Notice in the ganache-cli output that our contract has been instantiated:
Transaction: 0xc83617394674cd65f751ff9c05438e16339414ccf1e1662ba66479d79335af13
Contract created: 0xe982e78028e0dfcbdb135e7a3c1e1ed3d98e36e5
Gas usage: 269607
Block Number: 1
Block Time: Thu May 03 2018 21:04:55 GMT+0200 (CEST)
Console and playing around
Now, in the same terminal window you ran the truffle commands, run truffle console --network development
. This will launch the truffle console and allow you to interact with our blockchain. We will use the Web3 Javascript API to make it easier. First, let's take one of the accounts and put it in a variable:
account = web3.eth.accounts[4]
Next, let's run the following command:
DeveloperFactory.deployed().then(inst => {Factory = inst})
This will assign a contract's instance inside the Factory variable. Let's make sure our account has 100 ether:
truffle(development)> web3.fromWei(web3.eth.getBalance(account).toNumber())
'100'
truffle(development)>
The method getBalance will return a BigNumber type. toNumber() will give us the account's balance in Wei. We convert it in ether with fromWei().
- Creating a Developer
Alright, so now we are going to call the function createRandomDeveloper. As you can see, this function takes two parameters, a string _name and a uint _age. Because we also require 5 ether to call this function, we will need to specify this in our function call:
truffle(development)> Factory.createRandomDeveloper('Damien', 26, {from: account, value: web3.toWei(5, "ether")})
Factory is our contract instance. We give three parameters to our function. Damien is the _name, 26 is the _age. The third is an object with a key from to know which account is calling it, and a key value that specify the value sent by the account. Here, the from value is account, the variable we created earlier. We are converting 5 ether to Wei to match the required value in our contract.
Your terminal should show something like this:
{ tx: '0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e',
receipt:
{ transactionHash: '0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e',
transactionIndex: 0,
blockHash: '0x3ef1c41cbc79d65c1282a86da3a68120a5c069709a6a5bd3e206ed85d9c270c5',
blockNumber: 5,
gasUsed: 148160,
cumulativeGasUsed: 148160,
contractAddress: null,
logs: [ [Object] ],
status: '0x01',
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000200000000000000000000000000000000000000000000000000000002000000000000000000000' },
logs:
[ { logIndex: 0,
transactionIndex: 0,
transactionHash: '0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e',
blockHash: '0x3ef1c41cbc79d65c1282a86da3a68120a5c069709a6a5bd3e206ed85d9c270c5',
blockNumber: 5,
address: '0x033711f6fd408b10cc94a21a3e8c20f0e75a4615',
type: 'mined',
event: 'NewDeveloper',
args: [Object] } ] }
truffle(development)>
The transaction has been successful. There are a lot of informations here. We can see that the event 'NewDeveloper' has been fired, as expected. We have the transaction's hash, the block hash, the gas used... Let's check our account's balance now:
truffle(development)> web3.fromWei(web3.eth.getBalance(account).toNumber())
'94.985184'
Notice that it isn't 95 ether. This is because when you interact with a contract, you also have to pay additional ether to run a transaction. We have in our transaction informations the cumulativeGasUsed ( 148160 ). This means that 148160 Wei have been used to complete this transaction. We need to multiply this by the gasPrice. Each transaction has a gasPrice. We can retrieve it with the transactionHash, and multiply it by the cumulativeGasUsed to get the transaction's cost in ether:
truffle(development)> web3.eth.getTransaction('0xa3792da93311fdf60054f8a30e7624dd385ccf36cc639881eeb25308ddad5e0e').gasPrice.toNumber() * 148160
14816000000000000
truffle(development)> web3.fromWei(14816000000000000)
'0.014816'
truffle(development)> 100 - 0.014816
99.985184
And we are able to calculate our balance this way! We can also make sure that our contract's balance is 5 ether:
truffle(development)> web3.fromWei(web3.eth.getBalance('0x033711f6fd408b10cc94a21a3e8c20f0e75a4615').toNumber())
'5'
You will get your contract's address in the transaction's logs field address above. Finally, what would happen if we didn't sent 5 ether to our contract? Let's get a new account:
truffle(development)> account1 = web3.eth.accounts[9]
'0x5e273389dba808789a27cb792faaf31429c8de8c'
truffle(development)> web3.fromWei(web3.eth.getBalance(account1).toNumber())
'100'
Now, let's call our createRandomDeveloper function:
truffle(development)> Factory.createRandomDeveloper('Johnny', 43, {from: account1, value: web3.toWei(10, "ether")})
Error: VM Exception while processing transaction: revert
at Object.InvalidResponse (/usr/local/lib/node_modules/truffle/build/cli.bundled.js:41484:16)
truffle(development)> web3.fromWei(web3.eth.getBalance(account1).toNumber())
'99.9976828'
We get an error. But, the gas used to start the transaction is lost anyway! You can see that the account's balance is not 100 ether anymore.
Conclusion
In the next article, we will explore something close to a real environment, by using geth and mist. I hope this article gave you a better understanding at how you could get started with Ethereum development.
Have fun!