Disclaimers


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

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.

tldr; Quick summary of the whole text on another post:

Dividend-Paying Token - 7 Key Takeaways
What is Dividend-Paying Token aka Dividend Token? An easy to imagine example would be an apartment that one cannot afford however it would be much easier if a bunch of fellows shares the effort. Apartment’s shareholders should eventually share the costs and share the income

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 any time proportionally to the number of shares.

Tools

I will be using vscode with 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 test case description as the commit message.

GitHub - piorot/solidity-tdd: This repo shows coding blockchain smart-contract in solidity with use of test driven development approach
This repo shows coding blockchain smart-contract in solidity with use of test driven development approach - GitHub - piorot/solidity-tdd: This repo shows coding blockchain smart-contract in solidit...


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


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 on 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 fulfil the first test.

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

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 or 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

The whole point of this problem is to allow investors earn money. They want earn by acquiring the apartment and renting it for money. The beauty of the smart-contract is that is has all the logic implemented inside. If contract got any funds it will manage it according to the programmed logic. There will be no need to got to bank withdraw cash and split it by any of 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 link receive keyword

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

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: Owner should be able to withdraw resources paid as rent

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 means 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 allow to withdraw the right amount of funds. Calculating the right amount of funds seems easy but will require analysing several cases and will be discussed over couple of test cases for simplicity. One detail at the 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 as losing all funds since anyone can call withdraw all funds with no math involved.

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

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

No big deal in this increment. The only thing is that we kind of 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 :)

Test case 6: Attempt to withdraw by non shareholder should be reverted

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


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 to calculate 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 next lesson.  

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

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 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 of what was the smart-contract state last time someone has been withdrawing. It may look like on 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

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

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

As we see there is 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 calsulated 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. On the image below there is Alice having 15%, not 20% as in the unit test, but is 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

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 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 old reality and from now on think only about the new one.

💡
Alternatively, to force withdrawals (that costs gas) we might create yet another register and store here info about calculated funds to be withdrawn on the next occasion. This will be a more elegant solution but for simplicity, instead of transferring funds to the register, I transfer them directly to the user.


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.

Time line explainer below:

Summary:

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

The great resources to go deeper into this matter are:
https://weka.medium.com/dividend-bearing-tokens-on-ethereum-42d01c710657
https://programtheblockchain.com/posts/2018/02/07/writing-a-simple-dividend-token-contract/

Thank you for getting that far!