“”
Ethereum Pet Shop Tutorial from a programming perspective

Behind the scenes of Ethereum Pet Shop tutorial

View more techs

Behind the scenes of Ethereum Pet Shop tutorial

 

Darío Macchi

Darío Macchi

March 30, 2020

 

If you are wondering how you can get started with blockchain and you have made the decision to embrace Ethereum, then you can start withEthereum Pet Shop -- Your First Dapp tutorial. It’s a very comprehensive and well-written tutorial that covers everything you need to start playing around with Ethereum. Instead of repeating the concepts, we will work on that same example and dive into some tools/topics beyond the coverage of the tutorial. We will be explaining things from a programmer point of view without worrying about underlying technology and concepts like mining hashing, elliptic-curve cryptography, peer-to-peer networks, etcetera. They are not necessary to use blockchain and you will have to accept them as given by the platform.

First of all, you need to install Truffle. But what is Truffle? It is a “development environment, testing framework and asset pipeline for Ethereum, aiming to make life as an Ethereum developer easier.” Basically it lets you compile & migrate smart contracts, do automatic testing with Mocha and Chai, rebuilding of assets during development, and much more. It also lets you create boilerplates called Truffle Boxes containing modules, Solidity contracts & libraries, frontend views and more. You can see and “unbox” every public box from here.

Ethereum Pet Shop tutorial

Then the tutorial asks us to create our first smart contract. A contract is a collection of code (its function) and data (its state) written in Solidity, an object-oriented programming language used in various blockchain platforms besides Ethereum.

The first contract is called Adoption. It starts with the line address[16] public adopters; which declares a state variable called adopters of type address[n], an array of Ethereum addresses. You can think of it as a single slot in a database that you can query and alter by calling functions (adopt and getAdopters in the tutorial example). Each address is a 20 byte (160 bit) value used to identify an account that can also be used to send and receive Ether between accounts.

So, you can see a blockchain as a globally shared transactional database, where everyone can read entries just by being in the network. If you want to change something you can create a transaction and, if it’s accepted by everyone, it will be completely applied; otherwise, it will not be done at all.

Ethereum Pet Shop tutorial

The diagram above shows what happens when an account (identified by its address) wants to adopt a pet using it’s ID. The first transaction is valid (accepted) so Block14 shows its result. You can always see the previous block to ask for its state and see the difference. Then, if for any reason, you want to adopt a dog that does not exist, the require of the adopt function will throw an exception (unless you pass a second parameter to the require to add a custom message). This will force a rollback (due to the nature of the transaction) so Block15 will never be created and the Gas used will be returned (well, maybe not all of it - we’ll explain more about this later).

Now, what happens if two transactions contradict each other in terms of block creation? Since blocks form a linear sequence (blockchain), the transaction that ends in first place will become part of the block. That first one will be selected for you, in a globally accepted order (during the mining) to solve the problem of what is “first” in terms of peer-to-peer network. So, nobody can assure you that your transaction will be included in the next block or any specific block in the future. It's up to the miners to determine in which block a transaction will be included.

Back to the pet shop example, we can see that the adopt function uses the msg.sender. There are special variables and functions like msg, tx and block that always exist in the global namespace and provide information about the blockchain. Here you can find a list of them. But, what if I want (for any reason) to permanently store the address of the person creating the contract? In that case you can use a constructor, that is a special function that runs during the contract creation and cannot be called afterwards. This is how the pet shop tutorial contract looks if you refactor it to use a constructor.

 // The keyword "public" makes variables
 // accessible from other contracts
 address public adopter;
 
 // Constructor code is only run when the contract
 // is created
 constructor() public {
     adopter = msg.sender;
 }
 
 // Adopting a pet
 function adopt(uint petId) public returns (uint) {
   require(petId >= 0 && petId <= 15);
 
   adopters[petId] = adopter;
 
   return petId;
 }


Estimating Gas in Ethereum

 

Something that bothered me from the very beginning was the Gas consumption. Remember that Gas is a unit that measures the amount of computational effort that will take to execute a given operation. Each created contract and every transaction executed will consume a certain amount of gas. So the first question (at least for me) is, how do I know how much Gas is needed to create a contract or to execute a transaction? The answer wasn’t straightforward, at least at the beginning.

If you did the pet shop tutorial, you saw that web3 Javascript library is used to interact with the Ethereum blockchain, retrieving users, sending transactions, interacting with smart contracts and more. Digging in the web3 documentation I found a function called estimateGas but it's in two places: web3.eth and web3.eth.Contract. The first option receives a transaction object and a callback function, where the transaction object has all the necessary information to do the call simulation and return the estimated Gas. The second (which I found clearer, at least for my level of knowledge) goes against the function of the contract you want to measure. For example, if you want to know how much Gas is consumed by the adopt function call with specific parameters you can do:

App.contracts.Adoption.deployed().then(function(instance) {
 adoptionInstance = instance;
 
 adoptionInstance.adopt.estimateGas(petId, {from: account}).then(function(gas) {
   // Here the gas parameter of this call is the estimated Gas
   // the adopt call will consume with petId param.
 });
}).catch(function(err) {
 console.log(err.message);
});


adoptionInstance is an instance of the deployed contract where you can call the adopt function as stated in the tutorial. However, if instead of calling the adoptionInstance.adopt(petId, {from: account}); you call the estimateGas with the same params, you will receive the estimated Gas consumption after the transaction simulation.

Gas cost in Ether

Remember that Ether is the currency inside Ethereum. So, what is the cost, in terms of Ether, of the previous Gas consumption calculation? To know it, we must know the Gas price. The Gas price is a value representing how much Ether a user is willing to pay per Gas. On every transaction you can set the max amount of Gas you are willing to consume and the price you are willing to pay for each unit of Gas. I say “can” because those parameters are optional. If you don’t set them, the network will provide some value determined by the last few blocks' median gas price. This price can be obtained asking the network with web3.eth.getGasPrice and it would be returned in Wei (the smallest denomination of Ether, 1 Ether = 1018 wei). Here is the complete example asking for confirmation to spend x amount of Ether in each adoption transaction:

App.contracts.Adoption.deployed().then(function(instance) {
       adoptionInstance = instance;
 
       adoptionInstance.adopt.estimateGas(petId, {from: account}).then(function(gas){
         web3.eth.getGasPrice(function(err, res){
           var gasPrice = res.toNumber();
           var costInEther = web3.fromWei(gas * gasPrice);
           if (confirm(`The adoption will cost ${costInEther} Ether. Continue?`)) {
             // Execute adopt as a transaction by sending account
             return adoptionInstance.adopt(petId, {
               from: account,
               gasPrice: gasPrice,
               gas: gas
             });
           }
           else {
             throw Error('Adoption cancelled');
           }
         });
       });
     }).then(function(result) {
       return App.markAdopted();
     }).catch(function(err) {
       console.log(err.message);
     });
   });


There are too many topics to be covered in just one article, such as Storage, Memory and the Stack, Events triggered from contracts and heard from client side via web3,Message Calls to call contract from other contracts, etc. I will try to share more about them next time!

 

Guillermo Osores

Darío Macchi

Darío has fifteen years of experience working in software development and possesses in-depth technical knowledge, ranging from defining high-performance architectures to the use of frontend and backend technologies, databases, and web frameworks. He has also led projects as Project Manager or Scrum Master following Agile methodologies. Darío is a professor and final-year project tutor at Universidad ORT Uruguay, where he got his Master’s Degree in Engineering.

Contact us

Ready to get started? Use the form below or give us a call to meet our team and discuss your project and business goals.
We can’t wait to meet you!


Follow Us
See our client reviews