Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka
Chapter 6 Tokens
A popular class of smart contracts implement a dynamic ledger of coin transfers between accounts. These coins are not native tokens, but rather new, derived tokens. In some sense, tokens are programmed money, whose rules are specified by a smart contract and enforced by the underlying blockchain.
Native and derived tokens can be categorized in many ways [20, 9, 30]. The most popular classification is between fungible and non-fungible tokens. Fungible tokens are interchangeable with each other, since they have an identical nominal value that does not depend on each specific token instance. Native tokens and traditional (fiat) currencies are both examples of fungible tokens. Their main application is in the area of crowdfunding and initial coin offers to support startups. On the contrary, non-fungible tokens have a value that depends on their specific instance. Hence, in general, they are not interchangeable. Their main application is currently in the art market, where they represent a written declaration of author’s rights concession to the holder.
A few standards have emerged for such tokens, that should guarantee correctness, accessibility, interoperability, management and security of the smart contracts that run the tokens. Among them, the Ethereum Requests for Comment #20 (ERC-20 [32]) and #721 (ERC-721 [8]) are the most popular, also outside Ethereum. They provide developers with a list of rules required for the correct integration of tokens with other smart contracts and with applications external to the blockchain, such as wallets, block explorers, decentralized finance protocols and games.
The most popular implementations of the ERC-20 and ERC-721 standards are in Solidity, by OpenZeppelin [22, 23], a team of programmers in the Ethereum community who deliver useful and secure smart contracts and libraries, and by ConsenSys, later deprecated in favor of OpenZeppelin’s. OpenZeppelin extends ERC-20 with snapshots, that is, immutable views of the state of a token contract, that show its ledger at a specific instant of time. They are useful to investigate the consequences of an attack, to create forks of the token and to implement mechanisms based on token balances such as weighted voting.
6.1 Fungible tokens (ERC20)
A fungible token ledger is a ledger that binds owners (contracts) to the numerical amount of tokens they own. With this very high-level description, it is an instance of the IERC20View interface in Fig. 6.1. The balanceOf() method tells how many tokens an account holds and the method totalSupply() provides the total number of tokens in circulation. The UnsignedBigInteger class is a Takamaka library class that wraps a BigInteger and guarantees that its value is never negative. For instance, the subtraction of two UnsignedBigIntegers throws an exception when the second is larger than the first.
The snapshot() method, as already seen for collection classes, yields a read-only, frozen view of the latest state of the token ledger. Since it is defined in the topmost interface, all token classes can be snapshotted. Snapshots are computable in constant time.
In the original ERC20 standard and implementation in Ethereum, only specific subclasses allow snapshots, since their creation adds gas costs to all operations, also for token owners that never performed any snapshot. See the discussion and comparison in [7].
An ERC20 ledger is typically modifiable. Namely, owners can sell tokens to other owners and can delegate trusted contracts to transfer tokens on their behalf. Of course, these operations must be legal, in the sense that an owner cannot sell more tokens than it owns and delegated contracts cannot transfer more tokens than the cap to their delegation. These modification operations are defined in the IERC20 interface in Fig. 6.1. They are identical to the same operations in the ERC20 standard for Ethereum, hence we refer to that standard for further detail. The view() method is used to yield a view of the ledger, that is, an object that reflects the current state of the original ledger, but without any modification operation.
The ERC20 implementation provides a standard implementation for the functions defined in the IERC20View and IERC20 interfaces. Moreover, it provides metadata information such as name, symbol and number of decimals for the specific token implementation. There are protected implementations for methods that allow one to mint or burn an amount of tokens for a given owner (account). These are protected since one does not want to allow everybody to print or burn money. Instead, subclasses can call into these methods in their constructor, to implement an initial distribution of tokens, and can also allow subsequent, controlled mint or burns. For instance, the ERC20Burnable class is an ERC20 implementation that allows a token owner to burn its tokens only, or those it has been delegated to transfer, but never those of another owner. The ERC20Capped implementation allows the specification of a maximal cap to the number of tokens in circulation. When new tokens get minted, it checks that the cap is not exceeded and throws an exception otherwise.
6.1.1 Implementing our own ERC20 token
(See the io-hotmoka-tutorial-examples-erc20 project in https://github.com/Hotmoka/hotmoka)
Let us define a token ledger class that allows only its creator the mint or burn tokens. We will call it CryptoBuddy. As Fig. 6.1 shows, we plug it below the ERC20 implementation, so that we inherit that implementation and do not need to reimplement the methods of the IERC20 interface.
Create in Eclipse a new Maven Java 21 (or later). Use, as name for this project, the string io-hotmoka-tutorial-examples-erc20. 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-erc20</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 erc20 {
requires io.takamaka.code;
}
Create package io.hotmoka.tutorial.examples.erc20 inside src/main/java and add the following CryptoBuddy.java source inside that package:
package io.hotmoka.tutorial.examples.erc20;
import static io.takamaka.code.lang.Takamaka.require;
import io.takamaka.code.lang.Contract;
import io.takamaka.code.lang.FromContract;
import io.takamaka.code.math.UnsignedBigInteger;
import io.takamaka.code.tokens.ERC20;
public class CryptoBuddy extends ERC20 {
private final Contract owner;
public @FromContract CryptoBuddy() {
super("CryptoBuddy", "CB");
owner = caller();
var initialSupply = new UnsignedBigInteger("200000");
var multiplier = new UnsignedBigInteger("10").pow(18);
_mint(caller(), initialSupply.multiply(multiplier)); // 200000 * 10 ^ 18
}
public @FromContract void mint(Contract account, UnsignedBigInteger amount) {
require(caller() == owner, "Lack of permission");
_mint(account, amount);
}
public @FromContract void burn(Contract account, UnsignedBigInteger amount) {
require(caller() == owner, "Lack of permission");
_burn(account, amount);
}
}
The constructor of CryptoBuddy initializes the total supply by minting a very large number of tokens. They are initially owned by the creator of the contract, that is saved as owner. Methods mint() and burn() check that the owner is requesting the mint or burn and call the inherited protected methods in that case.
You can generate the jar archive and install that jar in the node, by letting our first account pay:
cd io-takamaka-code-examples-erc20 mvn clean install cd .. moka jars install 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io-hotmoka-tutorial-examples-erc20/target/io-hotmoka-tutorial-examples-erc20-1.11.5.jar --yes --password-of-payer --uri=ws://panarea.hotmoka.io:8001
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate The jar has been installed at 3e05f5cebf4053554fb8501b65717dad516f179331931108c13bd81273fcbb60. Gas consumption: * total: 480705 * for CPU: 15364 * for RAM: 5181 * for storage: 460160 * for penalty: 0 * price per unit: 1 pana * total price: 480705 panas
Finally, you can create an instance of the token class, by always letting our first account pay for that:
moka objects create 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.erc20.CryptoBuddy --classpath=3e05f5cebf4053554fb8501b65717dad516f179331931108c13bd81273fcbb60 --yes --password-of-payer --uri=ws://panarea.hotmoka.io:8001
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate A new object cd503dbab1f1693713e8eb026c8e3226652bc1f5d091e03f0d5535b087cdc2ad#0 has been created. Gas consumption: * total: 114004 * for CPU: 17230 * for RAM: 13414 * for storage: 83360 * for penalty: 0 * price per unit: 1 pana * total price: 114004 panas
The new ledger instance has been installed in the storage of the node now, at the address cd503dbab1f16937…#0. It is possible to start interacting with that ledger instance, by transferring tokens between accounts. For instance, this can be done with the moka objects call command, that allows one to invoke the transfer() or transferFrom() methods of the ledger. It is possible to show the state of the ledger with the moka objects show command, although specific utilities will provide a more user-friendly view of the ledger in the future.
6.1.2 Richer than expected
Every owner of an ERC20 token can decide to send some of its tokens to another contract C, that will become an owner itself, if it was not already. This means that the ledger inside an ERC20 implementation gets modified and some tokens get registered for the new owner C. However, C is not notified in any way of this transfer. This means that our contracts could be richer than we expect, if somebody has sent tokens to them, possibly inadvertently. In theory, we could scan the whole memory of a Hotmoka node, looking for implementations of the IERC20 interface, and check if our contracts are registered inside them. Needless to say, this is computationally irrealistic. Moreover, even if we know that one of our contracts is waiting to receive some tokens, we don’t know immediately when this happens, since the contract does not get notified of any transfer of tokens.
This issue is inherent to the definition of the ERC20 standard in Ethereum and the implementation in Takamaka inherits this limitation, since it wants to stick as much as possible to the Ethereum standard. A solution to the problem would be to restrict the kind of owners that are allowed in Fig. 6.1. Namely, instead of allowing all instances of Contract, the signature of the methods could be restricted to owners of some interface type IERC20Receiver, with a single method onReceive() that gets called by the ERC20 implementation, every time tokens get transferred to an IERC20Receiver. In this way, owners of ERC20 tokens get notified when they receive new tokens. This solution has never been implemented for ERC20 tokens in Ethereum, while it has been used in the ERC721 standard for non-fungible tokens, as we will show in the next section.

In this context, the term token is used for the smart contract that tracks coin transfers, for the single coin units and for the category of similar coins. This is sometimes confusing.