(image)

Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka

Chapter 4 Smart contracts

A contract is a legal agreement among two or more parties. A good contract should be unambiguous, since otherwise its interpretation could be questioned or misunderstood. A legal system normally enforces the validity of a contract. In the context of software development, a smart contract is a piece of software with deterministic behavior, whose semantics should be clear and enforced by a consensus system. Blockchains provide the perfect environment where smart contracts can be deployed and executed, since their (typically) non-centralized nature reduces the risk that a single party overthrows the rules of consensus, by providing for instance a non-standard semantics for the code of the smart contract.

Contracts are allowed to hold and transfer money to other contracts. Hence, traditionally, smart contracts are divided into those that hold money but have no code (externally owned accounts), and those that, instead, contain code (actual smart contracts). The formers are typically controlled by an external agent (a wallet, a human or a software application, on his behalf) while the latters are typically controlled by their code. Takamaka implements both alternatives as instances of the abstract library class io.takamaka.code.lang.Contract (inside the jar io-takamaka-code). That class extends io.takamaka.code.lang.Storage, hence its instances can be kept in the store of the node. Moreover, that class is annotated as @Exported, hence nodes can receive references to contract instances from the outside world. The Takamaka library defines subclasses of io.takamaka.code.lang.Contract, that we will investigate later. Programmers can define their own subclasses as well.

This chapter presents a simple smart contract, whose goal is to enforce a Ponzi investment scheme: each investor pays back the previous investor, with at least a 10% reward; as long as new investors keep coming, each investor gets at least a 10% reward; the last investor, instead, will never see his/her investment back. The contract has been inspired by a similar Ethereum contract, shown in [11].

We will develop the contract in successive versions, in order to highlight the meaning of different language features of Takamaka.

4.1 A simple Ponzi scheme contract

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

Create a new Maven Java 21 (or later) project in Eclipse. Use, as name for this project, the string io-hotmoka-tutorial-examples-ponzi. You can do this by duplicating the previous project io-hotmoka-tutorial-examples-family. Use the following pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">


  <modelVersion>4.0.0</modelVersion>
  <groupId>io.hotmoka</groupId>
  <artifactId>io-hotmoka-tutorial-examples-ponzi</artifactId>
  <version>|\hotmokaVersion{}|</version>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>21</maven.compiler.release>
  </properties>


  <dependencies>
    <dependency>
      <groupId>io.hotmoka</groupId>
      <artifactId>io-takamaka-code</artifactId>
      <version>|\takamakaVersion{}|</version>
    </dependency>
  </dependencies>


  <build>
    <plugins>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>3.11.0</version>
      </plugin>
    </plugins>
  </build>


</project>

and the following module-info.java:

module ponzi {
    requires io.takamaka.code;
}

Create package io.hotmoka-tutorial.examples.ponzi inside src/main/java and add the following SimplePonzi.java source inside that package:

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.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 void invest(Contract investor, 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 = investor;
        currentInvestment = amount;
    }
}

This code is only the starting point of our discussion and is not functional yet. The real final version of this contract will appear at the end of this section.

Look at SimplePonzi.java above. The contract has a single method, named invest. This method lets a new investor invest a given amount of coins. This amount must be at least 10% higher than the current investment. The expression BigIntegerSupport.compareTo(amount, minimumInvestment) > 0 is a comparison between two Java BigIntegers and should be read as the more familiar amount >= minimumInvestment: the latter cannot be written in this form, since Java does not allow comparison operators to work on reference types. Class BigIntegerSupport is needed in order to charge gas proportionally to the size of the BigIntegers involved in the operation. The static method io.takamaka.code.lang.Takamaka.require() is used to require some precondition to hold. The require(condition, message) call throws an exception if condition does not hold, with the given message. If the new investment is at least 10% higher than the current one, it will be saved in the state of the contract, together with the new investor.

You might wonder why we used require(..., () -> StringSupport.concat("you must invest more than ", minimumInvestment) instead of the simpler require(..., StringSupport.concat("you must invest more than ", minimumInvestment). Both are possible and semantically almost identical. However, the former uses a lambda expression that computes the string concatenation lazily, only if the message is needed; the latter always computes the string concatenation, instead. Hence, the first version consumes less gas, in general, and is consequently preferred. This technique simulates lazy evaluation in a language, like Java, that has only eager evaluation for actual arguments. This technique has been used since years, for instance in JUnit assertions.