Ethereum Tutorial: Writing Real Estate Smart-contracts in Solidity

Check out the solidity smart contract development process. Unit testing according to TDD

Piotr Rotyński
Better Programming

--

Disclaimer:

The solution presented here is not production-ready, rather an educational presentation. I am learning myself so errors leading to serious vulnerabilities may be present in the solutions shown. Be careful referring to this as various risks may be involved including losing your and your users’ money.

Remember deploying smart contracts inevitably brings in several vulnerabilities and risks and might be exploited against you and your solution.

Below I show the whole process of how I code smart-contract and how I think about the problem.

Note: To browse a quick summary of dividend-paying smart contracts, check my other post.

Problem definition

  1. The flat is too expensive for a single individual to buy. A group of friends or investors can buy, a flat together and later on share income on this investment proportionally to how each of them paid.
  2. Anyone staying at the apartment pays the rent directly to the apartment smart contract address.
  3. This rent can be withdrawn by any shareholder at any time proportionally to the number of shares.

Tools

I will be using vscode with a hardhat installed. I will be writing unit tests in chai.js delivered with a hardhat out of the box. I assume I will use openzepellin's erc20 implementation and treat each of 100 shares as a single coin.

Repository

Github repository presents a path to achieve end results such that each step (unit test) is committed separately with the test case description as the commit message.

Github repository

Contents (unit test headers):

  1. The contract creator should have 100 shares of the apartment.
  2. It should be possible to transfer some shares to another user.
  3. It should be possible to pay the rent and deposit it in ethers in the apartment contract
  4. Owner should be able to withdraw resources paid as rent
  5. Shareholder should be able to withdraw resources paid as rent
  6. Attempt to withdraw by non-shareholder should be reverted
  7. Apartment shareholder be able to withdraw resources proportional to his share
  8. It should not be possible to withdraw more than one has
  9. It should be possible to withdraw multiple times provided there were incomes in between
  10. Each withdrawal should be calculated against new income, not the total balance
  11. Transfer of shares should withdraw current funds of both parties

Test case 1: The contract creator should have 100 shares of the apartment.

↘️ See this on github

Let’s start with the test case. We assume that the apartment will only have 100 shares and that all of those will be initially in possession of the contract owner who is also the real-estate initial owner.

With ERC20 there come the _mint the function which is very handy in that case as it requires no effort to fulfill the first test.

Test case 2: It should be possible to transfer some shares to another user

↘️ See this on github

This test case states that apartment shares can simply be transferred to someone else. For instance to the second investor.

Like as previously described in the problem context a single person cannot afford the apartment for themselves so they invite others (shareholders aka co-investors) to participate in both costs and incomes.

There is no implementation required on the smart contract side because again it is all ERC20 standard.

Test case 3: It should be possible to pay the rent and deposit it in ethers in the apartment contract

↘️ See this on github

The whole point of this problem is to allow investors to earn money. They want to earn by acquiring the apartment and renting it for money. The beauty of the smart contract is that it has all the logic implemented inside. If the contract got any funds it will manage it according to the programmed logic. There will be no need to go to the bank to withdraw cash and split it among any of the investors. The smart contract will do this making the whole cash flow:

  1. convenient — no action required, automation introduced
  2. trustless — nobody has the whole cash even for a moment

This test case ensures that there is a method in the smart contract that will be called whenever a funds transfer is called. This method will be receive and is decorated with an external modifier. More about the logic behind this function under receive keyword

So let’s look at the code and the unit test case

New method ‘receive’ and unit test making sure it works properly

As we see in the test case there is another player introduced to the picture Bob . He is not an investor. He is the apartment guest and he pays for the stay directly to the smart contract address. As it happens the smart contract balance is increased and can be queried. In the next steps, those paid funds will be a matter of proportional distribution among investors. Stay tuned!

Test case 4: The owner should be able to withdraw resources paid as rent

↘️ See this on github

This lesson is kind of the beginning of several commits about withdrawing of funds from smart contract functionality. Generally speaking, the goal is to be able to allow shareholders (and shareholders only) to withdraw an applicable amount of funds from smart-contract. It is supposed to be safe in such a way that shareholders can not call this function infinitely draining the contract funds as well as always allowing to withdraw the right amount of funds. Calculating the right amount of funds seems easy but will require analyzing several cases and will be discussed over a couple of test cases for simplicity. One detail at a time.

In this lesson, there is just starting point added. There is no safety mechanism whatsoever and leaving it as is will have severe consequences such as losing all funds since anyone can call to withdraw all funds with no math involved.

Let’s take a look at the withdrawal method that is for now just a starting point.

unit test for smart-contract method, making sure the callee balance has changed

The corresponding unit test makes sure that the callee balance after the transaction is greater than before.

Test case 5: Shareholder should be able to withdraw resources paid as rent

↘️ See this on github

No big deal in this increment. We add some protections against misuse of the withdrawal function. In the previous lesson literally, everyone was allowed to call this method and take over all funds. Pretty scary, huh?. Right now we limit possibilities to call it only to those having at least one share (more than zero to be more accurate). Let’s remember that it still gives no safety as right now any shareholder can drain a smart contract anytime. It is just a little bit better than allowing to do the same to literally anyone. But not much :)

Here we test that not owner but the shareholder can call withdraw and her balance is increased

Test case 6: Attempt to withdraw by nonshareholder should be reverted

↘️ See this on github

Little to discuss here. The only new thing is a meaningful message when an unauthorized person calls this method. The most important here is the unit test that takes care of securing this condition. Only shareholders can withdraw, and any attempt to withdraw when you are not one is supposed to be rejected. Cool that hardhat allows for such unit test that explicitly testes that some specific call is rejected. I have already written about testing transaction rejections here.

Test case 7: Apartment shareholder be able to withdraw resources proportional to his share

↘️ See this on github

Alright, now here goes some math. We cannot allow anyone to withdraw all funds but only funds proportionally to the shares in the apartment. Let’s have a look at the function.

As we see the math is simple but it allows us to calculate the exact funds to be withdrawn. The math here is simple as it bases on some integer math. It would be much more complicated if those numbers require rounding. As always there is a designated unit test ensuring this thing work as expected. Now and in the future.

The unit test just makes sure that there is the right increase (estimated) on user balance and also the right amount of funds left on the contract after the transaction. The missing piece here is that although we accurately calculate the funds’ amount we do not limit the number of consecutive calls to this function. Let’s see this problem in the next lesson.

Test case 8: It should not be possible to withdraw more than one has

↘️ See this on github

Even though Alice (from the unit test above) cannot withdraw more than the value proportional to her shares, she can easily cheat and call the function multiple times. Almost draining the smart contract to zero. An example of what can happen below:

  1. Alice withdraws 20% of 1 Ether (Alice has 0.2 Ether, Contract has 0.8)
  2. Alice withdraws 20% of 0.8 Ether (Alice: 0.2 + 0.16, Contract 0.64)
  3. Alice withdraws 20% of 0.64 Ether (Alice: 0.488, Contract 0.512)
With each consecutive withdrawal Alice will get fewer funds but anyway she will be able to get almost all funds quickly

So now we know the threat left in the previous lesson let’s fix it.

  1. There is a new mapping was introduced. In this mapping of every shareholder, I save the total income earned so far on the smart contract. It is something like a pointer to what was the smart-contract state last time someone has been withdrawing. It may look like on the image below.
  2. By having the register any time someone tries to withdraw I just make sure that there is anything new earned since the last withdrawal of that user.
  3. Provided there are new funds earned the user is allowed to move on and withdraw their share of those new funds*. However current contract total income wrote down next to his name and next time on withdrawal this value will be used for verification

their share of those new funds* — in fact, this is not true. The user will get i.e 20% of all funds on the smart contract not the 20% on the new funds only. This will be taken into account in `lesson 10`

Simplified representation of withdrawal register

Let’s now take a look at the unit test. As stated here there is no way to call withdraw twice so Alice will only be able to call it once per any new income.

Test case 9: It should be possible to withdraw multiple times provided there were incomes in between

↘️ See this on github

There is no new solidity code here just kind of making sure that it is still possible to withdraw multiple times if the new funds are appearing on the smart contract in between.

Test Case 10: Each withdrawal should be calculated against new income, not the total balance

↘️ See this on github

So as mentioned in lesson 8 each withdrawal should be calculated based on new funds earned on the smart contract, not total funds available, which has been the case until now.

As we see there is a great use of the withdrawRegister. Thanks to this we not only limit unfair withdrawing approaches but also calculate withdrawal amount based on new income from the last withdrawal of that user. This makes sure we will always have funds left for those patient shareholders that are more patient and do not withdraw as often as others.

This is how new income is calculated based on the register and apartment total income

This time there is a massive unit test creating the whole history of various operations like several incomes and withdrawals.

Let’s see the history on a timeline. In the image below there is Alice who has 15%, not 20% as in the unit test but it still is helpful to understand it.

Test Case 11: Transfer of shares should withdraw current funds of both parties

In this lesson, there is too much code to present so it will not be pasted. Take a look at GitHub to see the exact source code of the change

↘️ See this on github

And now goes the most complicated case. At least in terms of the code. From a business logic perspective, it is quite easy to understand. Whenever there is the transfer of a share (an operation that shifts some shares of an apartment from one user to another) the two affected parties should have their funds withdrawn just before the action of share transfer.

Why? Just to make the whole logic easier as all funds earned and not withdrawn until this point should be treated according to the old share division and all the new funds earned after this point according to the new one. To clearly state the switching point it is best to clear things out and forget about the old reality and from now on think only about the new one.

And again yet another problem here might be that one party involved in shares transfer might in fact be unaware of this operation. The recipient will is not needed to make shares transfer and in the current solution, this user will not only get some new shares but also will possibly get unexpected ethers.

Timeline explainer below:

This is a simple presentation of the way one may think and work with a couple of simple requirements about the contracts. This is far from all the work that needs to be done until this code can be in production and serve users in secure way. Thank you for getting that far!

Resources

The great resources to go deeper into this matter are:

Originally published at https://www.rotynski.dev

--

--

Experienced JavaScript developer. Enthusiast of serverless on AWS, nodejs and angular. Recently fascinated with blockchain. rotynski.dev