(image)

Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka

4.2 The @FromContract and @Payable annotations

(See the io-hotmoka-tutorial-examples-ponzi_annotations project in https://github.com/Hotmoka/hotmoka)

The previous code of SimplePonzi.java is unsatisfactory, for at least two reasons, that we will overcome in this section:

  • 1. Any contract can call invest() and let another investor contract invest in the game. This is against our intuition that each investor decides when and how much he (himself) decides to invest.

  • 2. There is no money transfer. Anybody can call invest(), with an arbitrary amount of coins. The previous investor does not get the investment back when a new investor arrives since, well, he never really invested anything.

Let us rewrite SimplePonzi.java in the following way:

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.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 Contract currentInvestor;
    private BigInteger currentInvestment = BigInteger.ZERO;


    public @FromContract 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
        currentInvestor = caller();
        currentInvestment = amount;
    }
}

The difference with the previous version is that the investor argument of invest() has disappeared. At its place, invest() has been annotated as @FromContract. This annotation restricts the possible uses of method invest(). Namely, it can only be called from a contract object c or from an external wallet, with a paying contract c, that pays for a transaction that runs invest(). It cannot, instead, be called from the code of a class that is not a contract. The instance of contract c is available, inside invest(), as caller(). This is, indeed, saved, in the above code, into currentInvestor.

The annotation @FromContract can be applied to both methods and constructors. If a @FromContract method is redefined, the redefinitions must also be annotated as @FromContract.

Method caller() can only be used inside a @FromContract method or constructor and refers to the contract that called that method or constructor or to the contract that pays for a call, from a wallet, to the method or constructor. Hence, it will never yield null. If a @FromContract method or constructor calls another method m, then the caller() of the former is not available inside m, unless the call occurs, syntactically, on this, in which case the caller() is preserved. By syntactically, we mean through expressions such as this.m(...) or super.m(...).

The use of @FromContract solves the first problem: if a contract invests in the game, then it is the caller of invest(). But there is still no money transfer in this version of SimplePonzi.java. What we still miss is to require the caller of invest() to actually pay for the amount units of coin. Since @FromContract guarantees that the caller of invest() is a contract and since contracts hold money, this means that the caller contract of invest() can be charged amount coins at the moment of calling invest(). This can be achieved with the @Payable annotation, that we apply to invest():

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.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 Contract currentInvestor;
    private BigInteger currentInvestment = BigInteger.ZERO;


    public @Payable @FromContract 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
        currentInvestor = caller();
        currentInvestment = amount;
    }
}

When a contract calls invest() now, that contract will be charged amount coins, automatically. This means that these coins will be automatically transferred to the balance of the instance of SimplePonzi that receives the call. If the balance of the calling contract is too little for that, the call will be automatically rejected with an insufficient funds exception. The caller must be able to pay for both amount and the gas needed to run invest(). Hence, he must hold a bit more than amount coins at the moment of calling invest().

The @Payable annotation can only be applied to a method or constructor that is also annotated as @FromContract. If a @Payable method is redefined, the redefinitions must also be annotated as @Payable. A @Payable method or constructor must have a first argument of type int, long or java.math.BigInteger, depending on the amount of coins that the programmer allows one to transfer at call time. The name of that argument is irrelevant, but we will keep using amount for it in this book.