Ethereum Tutorial: Writing Real Estate Smart-contracts in Solidity | by Piotr Rotyński | Feb, 2022

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


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.

  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.

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.

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

  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

↘️ 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.

↘️ 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.

↘️ 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!

↘️ See this on github

This lesson is kind of the beginning of several commits about withdrawing from 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.

↘️ 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

↘️ 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 tests that some specific call is rejected. I have already written about transaction testing rejections here.

↘️ 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.

↘️ 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 ie 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 registration

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.

↘️ 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.

↘️ 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.

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 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 not need 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:

Leave a Comment