EthBnb: building a simple Solidity smart contract

I’ve always been fascinated with Ethereum, and especially with smart contracts. The idea of self-executing, immutable and trustless contracts is a game-changer. After watching from the sidelines for years, I’ve finally decided to get my hands dirty.

Now, I am not a software developer. I am a senior marketer, and I’ve never had any formal CS education. I’ve always been working at the crossroads of marketing and technology, and I have some experience writing code as a hobbyist, but learning a whole new language is far from trivial for me.

Luckily, Solidity is simple. Its syntax is so clear it almost reads like plain English. It took me only a day to write EthBnb (this includes watching a few hours of tutorial videos), and if you had any exposure to computer code in the past, you’ll be fine understanding how it works.

There are a few things I won’t talk about in this article. I won’t cover the basics of Ethereum (blockchain, transactions, contracts, gas, etc) or Solidity syntax; but if you want to start coding your own contract, I can point you to the right direction. BlockGeeks and Eat The Blocks are both excellent resources, and I happen to know the good people behind both. I won’t talk about how to use the Remix IDE either; luckily, Julien made a great video about it:

Specifications

I wanted to create a simple contract replicating the core features of AirBnb, but for a single landlord:

  • Track if a flat is available for rent, and let the landlord change the availability.
  • See who is the current occupant of a flat (if occupied).
  • Let a guest rent an available flat, and pay the landlord in Ether.

That’s it, no more, no less.

EthBnb doesn’t come with a frontend. The purpose of this exercise is to build the smart contract itself, and — with all honesty — I am horrible at building frontends. Still, you’ll be able to play with the contract in Remix, or even on the mainnet, if you fancy.

Let’s start coding!

I’ll break down the contract almost line-by-line, but if you are impatient and want to see the whole code, check the GitHub repo.

Let’s start with creating a new file in Remix, and name it EthBnb.sol

Initializing the contract

First, let’s specify the Solidity compiler version, and declare our contract:

pragma solidity ^0.5.0;
// Most tutorials use version 0.4.something but we go big here :)
contract EthBnb {

Then let’s declare our global variables.

First, the addresses:

 address payable landlordAddress;
address payable tenantAddress;

We need to define the addresses as payable, as we need to send funds (to the landlord, but also to the tenant, if we do a refund).

Let’s define a Flat struct, representing an apartment. We’ll store the price (in wei, the smallest denomination of Ether), the Ethereum address of the current occupant, and if the flat is available for rent:

struct Flat {
uint256 priceInWei;
address currentOccupant;
bool flatIsAvailable;
}

We will assume the landlord using this contract has eight flats to rent. I know, this is a pretty lazy assumption. I would be better to make the contract flexible, let the landlord add and remove flats, and so on — but this will do for this tutorial. Let’s declare an array of eight flats:

Flat[8] flatDB;

Next, we will introduce a modifier. A modifier is an elegant solution in Solidity to add code to a function on the fly. Some of the functions in EthBnb should only be available to the landlord; so we check if the request came from the landlord’s address (using require), and if yes, we continue the execution:

modifier landlordOnly() {
require(msg.sender == landlordAddress);
_;
}

Lastly, we need to add a constructor. This only runs once, when the contract is deployed. Let’s store the address deploying the contract as “landlord”, and fill our array with some test data (we set all flats as “available”, and set the price of flats to 0.1 or 0.2 ether). This is not necessary, but gives us something to play with:

constructor() public {
landlordAddress = msg.sender;
for (uint i=0; i<8; i++) {
flatDB[i].flatIsAvailable = true;
if (i % 2 == 0) {
flatDB[i].priceInWei = 0.1 ether;
} else {
flatDB[i].priceInWei = 0.2 ether;
}
}
}

Writing the getters

We need three getters in this contract: one to check if a flat is available, one to check the price of a flat, and one to check who is renting the flat at the moment:

function getFlatAvailability(uint _flat) view public returns(bool) {
return flatDB[_flat].flatIsAvailable;
}
function getPriceOfFlat(uint _flat) view public returns(uint256) {
return flatDB[_flat].priceInWei;
}
function getCurrentOccupant(uint _flat) view public returns(address) {
return flatDB[_flat].currentOccupant;
}

The last one will return the zero address (0x0) if there is no one renting the place.

Writing the setters

First, let’s quickly write the two trivial setters, the one changing the availability of a flat, and the other changing the price:

function setFlatAvailability(uint8 _flat, bool _newAvailability) landlordOnly public
{
flatDB[_flat].flatIsAvailable = _newAvailability;
if (_newAvailability) {
flatDB[_flat].currentOccupant = address(0);
}
}

function setPriceOfFlat(uint8 _flat, uint256 _priceInWei) landlordOnly public
{
flatDB[_flat].priceInWei = _priceInWei;
}

Note that we added the landlordOnly modifier to these functions, so only the landlord can call them.

Let’s not forget to remove the current occupant if the flat is being set as “available”. Note that there is no “null” in Solidity, and an address can’t be set to 0 (it’s a type mismatch), so we need to use address(0).

Prices need to be set in wei. It’s not easy to work with wei, but fortunately there are multiple Ethereum unit conversion tools available online.

We are almost there! Except we need to write the function to actually rent a place. I’ll embed this from GitHub, as some of the lines are really long:

Let’s break this down line by line!

First, we define the function as payable, as it will receive transfers. It will return an integer, the number of nights booked:

function rentAFlat(uint8 _flat) public payable returns(uint256) {

We store the address calling the function in our tenantAddress variable:

tenantAddress = msg.sender;

If the landlord wants to rent the place, that’s fine, too! Life can be strange :)

Then we check a few conditions:

  • If the tenant is sending the right amount. We don’t want to rent the place for 1.75 nights, so we use the modulo % operator.
  • But the % operator will evaluate as true if the amount is 0, so we need to check that, too.
  • If the place is indeed available.

This is how our if statement looks like:

if (msg.value % flatDB[_flat].priceInWei == 0 && msg.value > 0 && flatDB[_flat].flatIsAvailable == true) {

We check how many nights the guest has just paid (and now we know it’s an integer):

uint256 numberOfNightsPaid = msg.value / flatDB[_flat].priceInWei;

We set the flat availability to false:

flatDB[_flat].flatIsAvailable = false;

Store the Ethereum address of the guest:

flatDB[_flat].currentOccupant = tenantAddress;

Transfer the rental fee to the landlord:

landlordAddress.transfer(msg.value);

And finally, return the number of nights paid. You can use this data in the application itself:

return numberOfNightsPaid;

Phew! Now let’s see what happens if something is not right, the guest sent the wrong amount, or the flat is not available. First we send the Ether back (minus gas):

} else {
tenantAddress.transfer(msg.value);

Then we return zero, so the application knows the rental didn’t go through:

return 0;

And… that’s it! We have a working smart contract!

It is missing a few parts for sure. It would be nice to track the number of days, and “evict” the tenant automatically, but this is impractical to do in Solidity. The application itself (coded in NodeJS, Python or Go) should take care of the logic, and call the setFlatAvailability function when it’s time, maybe using something like the Ethereum Alarm Clock.

I hope you’ve found this tutorial helpful. The contract is online and deployed on the Rinkeby testnet if you want to play with it.

You can find the whole code on my GitHub. It is commented to death, so it’s hopefully easy to follow. Feel free to send a pull request, if you think something should be done differently.

Happy coding!