Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka
4.3 Payable contracts
(See the io-hotmoka-tutorial-examples-ponzi_payable project in https://github.com/Hotmoka/hotmoka)
The SimplePonzi.java class is not ready yet. Namely, the code of that class specifies that investors have to pay an always increasing amount of money to replace the current investor. However, in the current version of the code, the replaced investor never gets his previous investment back, plus the 10(at least): money keeps flowing inside the SimplePonzi contract and remains stuck there, forever. The code needs an apparently simple change: just add a single statement before the update of the new current investor. That statement should send amount units of coin back to currentInvestor, before it gets replaced:
// document new investor if (currentInvestor != null) currentInvestor.receive(amount); currentInvestor = caller(); currentInvestment = amount;
In other words, a new investor calls invest() and pays amount coins to the SimplePonzi contract (since invest() is @Payable); then this SimplePonzi contract transfers the same amount of coins to pay back the previous investor. Money flows through the SimplePonzi contract but does not stay there for long.
The problem with this simple line of code is that it does not compile. There is no receive() method in class io.takamaka.code.lang.Contract: a contract can receive money only through calls to its @Payable constructors and methods. Since currentInvestor is, very generically, an instance of Contract, that has no @Payable methods, there is no method that we can call here for sending money back to currentInvestor. This limitation is a deliberate design choice of Takamaka.
So how do we send money back to currentInvestor? The solution is to restrict the kind of contracts that can participate to the Ponzi scheme. Namely, we limit the game to contracts that implement the libary class io.takamaka.code.lang.PayableContract, a subclass of io.takamaka.code.lang.Contract that, yes, does have a payable receive() method. This is not really a restriction, since the typical players of our Ponzi contract are externally owned accounts, that are instances of PayableContract.
Let us hence apply the following small changes to our SimplePonzi.java class:
-
1. The type of currentInvestment must be restricted to PayableContract.
-
2. The invest() method must be callable by PayableContracts only.
-
3. The return value of caller() must be cast to PayableContract, which is safe because of point 2 above.
The result is the following:
package io.hotmoka.tutorial.examples.ponzi;
import static io.takamaka.code.lang.Takamaka.require;
import java.math.BigInteger;
import io.takamaka.code.lang.Contract;
import io.takamaka.code.lang.FromContract;
import io.takamaka.code.lang.Payable;
import io.takamaka.code.lang.PayableContract;
import io.takamaka.code.lang.StringSupport;
import io.takamaka.code.math.BigIntegerSupport;
public class SimplePonzi extends Contract {
private final BigInteger _10 = BigInteger.valueOf(10L);
private final BigInteger _11 = BigInteger.valueOf(11L);
private PayableContract currentInvestor;
private BigInteger currentInvestment = BigInteger.ZERO;
public @Payable @FromContract(PayableContract.class) void invest(BigInteger amount) {
// new investments must be at least 10% greater than current
BigInteger minimumInvestment = BigIntegerSupport.divide
(BigIntegerSupport.multiply(currentInvestment, _11), _10);
require(BigIntegerSupport.compareTo(amount, minimumInvestment) >0,
() -> StringSupport.concat("you must invest more than ", minimumInvestment));
// document new investor
if (currentInvestor != null)
currentInvestor.receive(amount);
currentInvestor = (PayableContract) caller();
currentInvestment = amount;
}
}
Note the use of @FromContract(PayableContract.class) in the code above: a method or constructor annotated as @FromContract(C.class) can only be called by a contract whose class is C or a subclass of C. Otherwise, a run-time exception will occur.

Solidity programmers will find this very different from what happens in Solidity contracts. Namely, these always have a fallback function that can be called for sending money to a contract. A problem with Solidity’s approach is that the balance of a contract is not fully controlled by its payable methods, since money can always flow in through the fallback function (and also in other, more surprising ways). This led to software bugs, when a contract found itself richer then expected, which violated some (wrong) invariants about its state. For more information, see unexpected Ether in [2].