Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka
5.2 Storage arrays
Arrays are an ordered sequence of elements, with constant-time access to such elements, both for reading and for writing. The size of the arrays is typically fixed, although there are programming languages with limited forms of dynamic arrays.
Java has native arrays, of type E[], where E is the type of the elements of the array. They can be used in Takamaka, but not as fields of storage classes. For that, Takamaka provides class
io.takamaka.code.util.StorageTreeArray
We refer to the JavaDoc of StorageTreeArray
Next section shows an example of use for StorageTreeArray
5.2.1 A tic-tac-toe contract
(See the io-hotmoka-tutorial-examples-tictactoe project in https://github.com/Hotmoka/hotmoka)
Tic-tac-toe is a game where two players place, alternately, a cross and a circle on a board, initially empty. The winner is the player who places three crosses or three circles on the same row, column or diagonal. For instance, in Fig. 5.3a the
player of the cross wins.
Some games that end up in a draw, when the board is full but nobody wins, as in Fig. 5.3b.
A natural representation of the tic-tac-toe board is a two-dimensional array where indexes are distributed as shown in Fig. 5.4a,
implemented as a StorageTreeArray
Create hence in Eclipse a new Maven Java 21 (or later) project. Use for this project the name io-hotmoka-tutorial-examples-tictactoe. You can do this by duplicating the 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-tictactoe</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 tictactoe {
requires io.takamaka.code;
}
Create package io.hotmoka.tutorial.examples.tictactoe inside src/main/java and add the following TicTacToe.java source inside that package:
package io.hotmoka.tutorial.examples.tictactoe;
import static io.takamaka.code.lang.Takamaka.require;
import java.math.BigInteger;
import io.takamaka.code.lang.Contract;
import io.takamaka.code.lang.Exported;
import io.takamaka.code.lang.FromContract;
import io.takamaka.code.lang.Payable;
import io.takamaka.code.lang.PayableContract;
import io.takamaka.code.lang.Storage;
import io.takamaka.code.lang.StringSupport;
import io.takamaka.code.lang.View;
import io.takamaka.code.math.BigIntegerSupport;
import io.takamaka.code.util.StorageTreeArray;
public class TicTacToe extends Contract {
@Exported
public class Tile extends Storage {
private final char c;
private Tile(char c) {
this.c = c;
}
@Override
public String toString() {
return String.valueOf(c);
}
private Tile nextTurn() {
return this == CROSS ? CIRCLE : CROSS;
}
}
private final Tile EMPTY = new Tile(' ');
private final Tile CROSS = new Tile('X');
private final Tile CIRCLE = new Tile('O');
private final StorageTreeArray<Tile> board = new StorageTreeArray<>(9, EMPTY);
private PayableContract crossPlayer;
private PayableContract circlePlayer;
private Tile turn = CROSS; // cross plays first
private boolean gameOver;
public @View Tile at(int x, int y) {
require(1 <= x && x <= 3 && 1 <= y && y <= 3, "coordinates must be between 1 and 3");
return board.get((y - 1) * 3 + x - 1);
}
private void set(int x, int y, Tile tile) {
board.set((y - 1) * 3 + x - 1, tile);
}
public @Payable @FromContract(PayableContract.class) void play(long amount, int x, int y) {
require(!gameOver, "the game is over");
require(1 <= x && x <= 3 && 1 <= y && y <= 3, "coordinates must be between 1 and 3");
require(at(x, y) == EMPTY, "the selected tile is not empty");
PayableContract player = (PayableContract) caller();
if (turn == CROSS)
if (crossPlayer == null)
crossPlayer = player;
else
require(player == crossPlayer, "it's not your turn");
else
if (circlePlayer == null) {
require(crossPlayer != player, "you cannot play against yourself");
long previousBet = BigIntegerSupport.subtract
(balance(), BigInteger.valueOf(amount)).longValue();
require(amount >= previousBet,
() -> StringSupport.concat("you must bet at least ", previousBet, " coins"));
circlePlayer = player;
}
else
require(player == circlePlayer, "it's not your turn");
set(x, y, turn);
if (isGameOver(x, y))
player.receive(balance());
else
turn = turn.nextTurn();
}
private boolean isGameOver(int x, int y) {
if (at(x, 1) == turn && at(x, 2) == turn && at(x, 3) == turn) // column x
return gameOver = true;
if (at(1, y) == turn && at(2, y) == turn && at(3, y) == turn) // row y
return gameOver = true;
if (x == y && at(1, 1) == turn && at (2, 2) == turn && at(3, 3) == turn) // first diagonal
return gameOver = true;
if (x + y == 4 && at(1, 3) == turn && at(2, 2) == turn && at(3, 1) == turn) // second diagonal
return gameOver = true;
return gameOver = false;
}
@Override
public @View String toString() {
return StringSupport.concat(at(1, 1), "|", at(2, 1), "|", at(3, 1),
"\n-----\n", at(1, 2), "|", at(2, 2), "|", at(3, 2),
"\n-----\n", at(1, 3), "|", at(2, 3), "|", at(3, 3));
}
}
The internal class Tile represents the three alternatives that can be put in the tic-tac-toe board. It overrides the default toString() implementation, to yield the usual representation for such alternatives; its nextTurn() method alternates between cross and circle.
The board of the game is represented as a new StorageTreeArray<>(9, EMPTY), whose elements are indexed from zero to eight (inclusive) and are initialized to EMPTY. It is also possible to construct the array as new StorageTreeArray<>(9), but then its elements would hold the default value null and the array would need to be initialized inside a constructor for TicTacToe.
Methods at() and set() read and set the board element at indexes , respectively. They transform the two-dimensional conceptual representation of the board into its internal one-dimensional representation. Since at() is public, we defensively check the validity of the indexes
there.
Method play() is the heart of the contract. Being called by the accounts that play the game, it is annotated as @FromContract. It is also annotated as @Payable(PayableContract.class) since players must bet money for taking part in the game, at least for the first two moves, and receive money if they win. The first contract that plays is registered as crossPlayer. The second contract that plays is registered as circlePlayer. Subsequent moves must come, alternately, from crossPlayer and circlePlayer. The contract uses a turn variable to keep track of the current turn.
Note the extensive use of require() to check all error situations:
-
1. It is possible to play only if the game is not over yet.
-
2. A move must be inside the board and identify an empty tile.
-
3. Players must alternate correctly.
-
4. The second player must bet at least as much as the first player.
-
5. It is not allowed to play against oneself.
The play() method ends with a call to gameOver() that checks if the game is over, that is, if the current player won. In that case, the winner receives the full jackpot. Note that the gameOver() method receives the coordinates where the current player has moved. This allows it to restrict the check for game over: the game is over only if the row or column where the player moved contain the same tile; if the current player played on a diagonal, the method checks the diagonals as well. It is of course possible to check all rows, columns and diagonals, always, but our solution is gas-thriftier.
The toString() method yields a string representation of the current board, such as
X|O| ----- |X|O ----- |X|
5.2.2 A more realistic tic-tac-toe contract
(See the io-hotmoka-tutorial-examples-tictactoe_revised project in https://github.com/Hotmoka/hotmoka)
The TicTacToe.java code implements the rules of a tic-tac-toe game, but has a couple of drawbacks that make it still incomplete. Namely:
-
1. The creator of the game must spend gas to call its constructor, but has no direct incentive in doing so. He must be a benefactor, or hope to take part in the game after creation, if he is faster than any other potential player.
-
2. If the game ends in a draw, money gets stuck in the TicTacToe contract instance, for ever and ever.
Replace hence the previous version of TicTacToe.java with the following revised version. This new version solves both problems at once. The policy is very simple: it imposes a minimum bet, in order to avoid free
games; if a winner emerges, then the game forwards him only of the jackpot; the remaining
goes to the creator of the TicTacToe contract itself. If, instead, the game ends in a draw, it forwards the whole jackpot to the creator. Note that we added a @FromContract constructor, that
takes note of the creator of the game:
package io.hotmoka.tutorial.examples.tictactoe;
import static io.takamaka.code.lang.Takamaka.require;
import java.math.BigInteger;
import io.takamaka.code.lang.Contract;
import io.takamaka.code.lang.Exported;
import io.takamaka.code.lang.FromContract;
import io.takamaka.code.lang.Payable;
import io.takamaka.code.lang.PayableContract;
import io.takamaka.code.lang.Storage;
import io.takamaka.code.lang.StringSupport;
import io.takamaka.code.lang.View;
import io.takamaka.code.math.BigIntegerSupport;
import io.takamaka.code.util.StorageTreeArray;
public class TicTacToe extends Contract {
@Exported
public class Tile extends Storage {
private final char c;
private Tile(char c) {
this.c = c;
}
@Override
public String toString() {
return String.valueOf(c);
}
private Tile nextTurn() {
return this == CROSS ? CIRCLE : CROSS;
}
}
private final Tile EMPTY = new Tile(' ');
private final Tile CROSS = new Tile('X');
private final Tile CIRCLE = new Tile('O');
private static final long MINIMUM_BET = 100L;
private final StorageTreeArray<Tile> board = new StorageTreeArray<>(9, EMPTY);
private final PayableContract creator;
private PayableContract crossPlayer;
private PayableContract circlePlayer;
private Tile turn = CROSS; // cross plays first
private boolean gameOver;
public @FromContract(PayableContract.class) TicTacToe() {
creator = (PayableContract) caller();
}
public @View Tile at(int x, int y) {
require(1 <= x && x <= 3 && 1 <= y && y <= 3, "coordinates must be between 1 and 3");
return board.get((y - 1) * 3 + x - 1);
}
private void set(int x, int y, Tile tile) {
board.set((y - 1) * 3 + x - 1, tile);
}
public @Payable @FromContract(PayableContract.class) void play(long amount, int x, int y) {
require(!gameOver, "the game is over");
require(1 <= x && x <= 3 && 1 <= y && y <= 3, "coordinates must be between 1 and 3");
require(at(x, y) == EMPTY, "the selected tile is not empty");
PayableContract player = (PayableContract) caller();
if (turn == CROSS)
if (crossPlayer == null) {
require(amount >= MINIMUM_BET, () -> "you must invest at least " + MINIMUM_BET + " coins");
crossPlayer = player;
}
else
require(player == crossPlayer, "it's not your turn");
else
if (circlePlayer == null) {
require(crossPlayer != player, "you cannot play against yourself");
long previousBet = BigIntegerSupport.subtract
(balance(), BigInteger.valueOf(amount)).longValue();
require(amount >= previousBet,
() -> StringSupport.concat("you must bet at least ", previousBet, " coins"));
circlePlayer = player;
}
else
require(player == circlePlayer, "it's not your turn");
set(x, y, turn);
if (isGameOver(x, y)) {
// 90% goes to the winner
player.receive(BigIntegerSupport.divide
(BigIntegerSupport.multiply(balance(), BigInteger.valueOf(9L)), BigInteger.valueOf(10L)));
// the rest to the creator of the game
creator.receive(balance());
}
else if (isDraw())
// everything goes to the creator of the game
creator.receive(balance());
else
turn = turn.nextTurn();
}
private boolean isGameOver(int x, int y) {
if (at(x, 1) == turn && at(x, 2) == turn && at(x, 3) == turn) // column x
return gameOver = true;
if (at(1, y) == turn && at(2, y) == turn && at(3, y) == turn) // row y
return gameOver = true;
if (x == y && at(1, 1) == turn && at (2, 2) == turn && at(3, 3) == turn) // first diagonal
return gameOver = true;
if (x + y == 4 && at(1, 3) == turn && at(2, 2) == turn && at(3, 1) == turn) // second diagonal
return gameOver = true;
return gameOver = false;
}
private boolean isDraw() {
for (var tile: board)
if (tile == EMPTY)
return false;
return true;
}
@Override
public @View String toString() {
return StringSupport.concat(at(1, 1), "|", at(2, 1), "|", at(3, 1),
"\n-----\n", at(1, 2), "|", at(2, 2), "|", at(3, 2),
"\n-----\n", at(1, 3), "|", at(2, 3), "|", at(3, 3));
}
}
5.2.3 Running the tic-tac-toe contract
Let us play with the TicTacToe contract. Go in the io-hotmoka-tutorial-examples-tictactoe project, compile it with Maven and store it in the Hotmoka node:
cd io-takamaka-code-examples-tictactoe mvn clean install cd .. moka jars install 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io-hotmoka-tutorial-examples-tictactoe/target/io-hotmoka-tutorial-examples-tictactoe-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 5d591c09e2ef6154874b3c5be65afbd31817f57030447bcb6f20143ad8a4d3a9. Gas consumption: * total: 1052032 * for CPU: 15715 * for RAM: 5357 * for storage: 1030960 * for penalty: 0 * price per unit: 1 pana * total price: 1052032 panas
Then we create an instance of the contract in the node:
moka objects create 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe --classpath=5d591c09e2ef6154874b3c5be65afbd31817f57030447bcb6f20143ad8a4d3a9 --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 Do you really want to call constructor public ...TicTacToe() spending up to 1000000 gas units at the price of 1 pana per unit (that is, up to 1000000 panas) [Y/N] Y A new object dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0 has been created. Gas consumption: * total: 120654 * for CPU: 20481 * for RAM: 23853 * for storage: 76320 * for penalty: 0 * price per unit: 1 pana * total price: 120654 panas
We use two of our accounts now, that we have already created in the previous section, to interact with the contract: they will play, alternately, until the first player wins. We will report the resulting of calling toString() on the contract, after each move.
The first player starts, by playing at (1,1), and bets 100:
moka objects call 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe play 100 1 1 --uri=ws://panarea.hotmoka.io:8001 --password-of-payer --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate Adding transaction cad9c743264e5f9f43970ad79e2661b8aea15ed5f310004b5e865350ac8ffbaf... done. Gas consumption: * total: 72810 * for CPU: 18177 * for RAM: 14233 * for storage: 40400 * for penalty: 0 * price per unit: 1 pana * total price: 72810 panas
moka objects call 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe toString --uri=ws://panarea.hotmoka.io:8001 --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Running transaction 2ee634298b4a548e4fb9eaa97eeecc02f6e5fec4b722b7c0a6b937f3b5bfc9a5... done. The method returned: X| | ----- | | ----- | |
Note that the call to toString() does not require to provide the password of the key pair of the caller account, since that method is a @View method: this means that moka runs a transaction to call it, it does not add a transaction.
The second player plays now, at (2,1), betting 100:
moka objects call 5d418519bccfeb6df00c35dc4445f1029a80a96735927b42139856f6bbc0a8c3#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe play 100 2 1 --uri=ws://panarea.hotmoka.io:8001 --password-of-payer --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Enter value for --password-of-payer (the password of the key pair of the payer account): orange Adding transaction b1fb2323c76f342742fa8f18a51ca06b5691e33e73653159036d547ed6557550... done. Gas consumption: * total: 71135 * for CPU: 17722 * for RAM: 12933 * for storage: 40480 * for penalty: 0 * price per unit: 1 pana * total price: 71135 panas
moka objects call 5d418519bccfeb6df00c35dc4445f1029a80a96735927b42139856f6bbc0a8c3#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe toString --uri=ws://panarea.hotmoka.io:8001 --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Running transaction da8e011cce156d727908f4f01e9453887a85ecda77719629012966bce06add94... done. The method returned: X|O| ----- | | ----- | |
The first player replies, by playing at (1,2):
moka objects call 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe play 0 1 2 --uri=ws://panarea.hotmoka.io:8001 --password-of-payer --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate Adding transaction a9d7328360699179c59859a9f431528f8c3e3f53802687acaf00867e2bf76c8d... done. Gas consumption: * total: 69023 * for CPU: 17716 * for RAM: 12667 * for storage: 38640 * for penalty: 0 * price per unit: 1 pana * total price: 69023 panas
moka objects call 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe toString --uri=ws://panarea.hotmoka.io:8001 --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Running transaction 2ee634298b4a548e4fb9eaa97eeecc02f6e5fec4b722b7c0a6b937f3b5bfc9a5... done. The method returned: X|O| ----- X| | ----- | |
Then the second player plays at (2,2):
moka objects call 5d418519bccfeb6df00c35dc4445f1029a80a96735927b42139856f6bbc0a8c3#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe play 0 2 2 --uri=ws://panarea.hotmoka.io:8001 --password-of-payer --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Enter value for --password-of-payer (the password of the key pair of the payer account): orange Adding transaction ba96e2c5ff81aad7b9dddccdafa6f0ea3e92ade7a18701e88be80ae4f8820047... done. Gas consumption: * total: 72270 * for CPU: 18485 * for RAM: 15145 * for storage: 38640 * for penalty: 0 * price per unit: 1 pana * total price: 72270 panas
moka objects call 5d418519bccfeb6df00c35dc4445f1029a80a96735927b42139856f6bbc0a8c3#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe toString --uri=ws://panarea.hotmoka.io:8001 --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Running transaction da8e011cce156d727908f4f01e9453887a85ecda77719629012966bce06add94... done. The method returned: X|O| ----- X|O| ----- | |
The first player wins by playing at (1,3):
moka objects call 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe play 0 1 3 --uri=ws://panarea.hotmoka.io:8001 --password-of-payer --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate Adding transaction 5045aabd87b349e90da32217d5a9a5bdfe9311188f590158da7690374316505e... done. Gas consumption: * total: 68511 * for CPU: 18338 * for RAM: 15693 * for storage: 34480 * for penalty: 0 * price per unit: 1 pana * total price: 68511 panas
moka objects call 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe toString --uri=ws://panarea.hotmoka.io:8001 --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Running transaction 2ee634298b4a548e4fb9eaa97eeecc02f6e5fec4b722b7c0a6b937f3b5bfc9a5... done. The method returned: X|O| ----- X|O| ----- X| |
We can verify that the game is over now:
moka objects show dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0 --uri ws://panarea.hotmoka.io:8001
class io.hotmoka.tutorial.examples.tictactoe.TicTacToe (from jar installed at 5d591c09e2ef6154874b3c5be65afbd31817f57030447bcb6f20143ad8a4d3a9) CIRCLE:io.hotmoka.tutorial.examples.tictactoe.TicTacToe$Tile = dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#3 CROSS:io.hotmoka.tutorial.examples.tictactoe.TicTacToe$Tile = dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#2 EMPTY:io.hotmoka.tutorial.examples.tictactoe.TicTacToe$Tile = dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#1 board:io.takamaka.code.util.StorageTreeArray = dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#4 circlePlayer:io.takamaka.code.lang.PayableContract = 5d418519bccfeb6df00c35dc4445f1029a80a96735927b42139856f6bbc0a8c3#0 creator:io.takamaka.code.lang.PayableContract = 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 crossPlayer:io.takamaka.code.lang.PayableContract = 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 gameOver:boolean = true turn:io.hotmoka.tutorial.examples.tictactoe.TicTacToe$Tile = dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#2 io.takamaka.code.lang.Contract.balance:java.math.BigInteger = 0
As you can see, the gameOver field holds true. Moreover, the balance of the contract is zero since it has been distributed to the winner and to the creator of the game (that actually coincide to our first account, in this specific run).
If the second player attempts to play now, the transaction will be rejected, since the game is over:
moka objects call 5d418519bccfeb6df00c35dc4445f1029a80a96735927b42139856f6bbc0a8c3#0 io.hotmoka.tutorial.examples.tictactoe.TicTacToe play 0 2 3 --uri=ws://panarea.hotmoka.io:8001 --password-of-payer --receiver= dafb03fba8b47781930f732587d97933517e20012a9d4e1d7e5024609eeb4321#0
Enter value for --password-of-payer (the password of the key pair of the payer account): orange Adding transaction eeee12d14e3cbc708750bab8a775d7cc37ceeddaed0f4aa8725245c3f04265a1... failed. The transaction failed with message io.takamaka.code.lang.RequirementViolationException: the game is over@TicTacToe.java:111 Gas consumption: * total: 1000000 * for CPU: 15519 * for RAM: 6645 * for storage: 20480 * for penalty: 957356 * price per unit: 1 pana * total price: 1000000 panas
5.2.4 Specialized storage array classes
The StorageTreeArray
Fig. 5.5 shows the hierarchy of the specialized classes for arrays of bytes, available in Takamaka. The interface StorageByteArrayView defines the methods that read data from an array of bytes, while the interface StorageByteArray defines the modification methods. Class
StorageTreeByteArray allows one to create byte arrays of any length, specified at construction time. Classes Bytes32 and
Bytes32Snapshot have, instead, fixed length of bytes; their constructors include one that allows one to specify such
bytes, which is useful for calling the constructor from outside the node, since byte is a storage type. While a Bytes32 is modifiable, instances of class Bytes32Snapshot are not
modifiable after being created and are @Exported. There are sibling classes for different, fixed sizes, such as Bytes64 and Bytes8Snaphot. For a full description of the methods of these classes
and interfaces, we refer to their JavaDoc.





We have chosen to allow a long amount in the @Payable method play() since it is unlikely that users will want to invest huge quantities of money in this game. This gives us the opportunity to discuss why the computation of the previous bet has been written as
long previousBet = BigIntegerSupport.subtract (balance(), BigInteger.valueOf(amount)).longValue()instead of the simpler long previousBet = balance().longValue() - amount. The reason is that, when that line is executed, both players have already paid their bet, that accumulates in the balance of the TicTacToe contract. Each single bet is a long, but their sum could overflow the size of a long. Hence, we have to deal with a computation on BigInteger. The same situation occurs later, when we have to compute the
that goes to the winner: the jackpot might be larger than a long and we have to compute over BigInteger. As a final remark, note that in the line:
BigIntegerSupport.divide(BigIntegerSupport.multiply (balance(), BigInteger.valueOf(9L)), BigInteger.valueOf(10L))we first multiply by
and then divide by
. This reduces the approximation inherent to integer division. For instance, if the jackpot (balance()) were 209, we have (with Java’s left-to-right evaluation)
while
.