Skip to content

Commit 3036093

Browse files
committed
Added maps and arrays, needed to upgrade compiler too
1 parent 10b2822 commit 3036093

File tree

11 files changed

+334
-18
lines changed

11 files changed

+334
-18
lines changed

package-lock.json

Lines changed: 28 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@sveltejs/adapter-auto": "^2.0.0",
1717
"@sveltejs/adapter-static": "^2.0.0",
1818
"@sveltejs/kit": "^1.5.0",
19-
"@tact-lang/compiler": "1.1.0-beta.0",
19+
"@tact-lang/compiler": "1.1.0-beta.8",
2020
"@typescript-eslint/eslint-plugin": "^5.27.0",
2121
"@typescript-eslint/parser": "^5.27.0",
2222
"autoprefixer": "^10.4.12",

src/lib/helpers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Address, Slice, Cell } from "ton-core";
1+
import { Address, Slice, Cell, Dictionary } from "ton-core";
22
import examples from "../routes/(examples)/examples.json";
33

44
export function getExample(page: string) {
@@ -29,6 +29,12 @@ export function convertToText(obj: any): string {
2929
if (obj instanceof Address) return obj.toString();
3030
if (obj instanceof Slice) return obj.toString();
3131
if (obj instanceof Cell) return obj.toString();
32+
if (obj instanceof Dictionary) {
33+
const items = [];
34+
for (const key of obj.keys()) items.push(`${convertToText(key)}: ${convertToText(obj.get(key))}`);
35+
const itemsStr = items.join(", ");
36+
return itemsStr ? `{ ${itemsStr} }` : `{}`;
37+
}
3238

3339
for (const prop in obj) {
3440
if (obj.hasOwnProperty(prop)) string.push(prop + ": " + convertToText(obj[prop]));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script lang="ts">
2+
import { Dictionary, toNano, type Sender, Address } from "ton-core";
3+
import { Blockchain, type SandboxContract } from "@ton-community/sandbox";
4+
import { getExample } from "$lib/helpers";
5+
import store from "$lib/store";
6+
7+
import markdown from "./content.md?raw";
8+
import tactCode from "./contract.tact?raw";
9+
import { Arrays } from "./Arrays";
10+
11+
let sender: Sender;
12+
let contract: SandboxContract<Arrays>;
13+
14+
$store = {
15+
markdown,
16+
tactCode,
17+
deploy: async () => {
18+
const blockchain = await Blockchain.create();
19+
const deployer = await blockchain.treasury("deployer");
20+
sender = deployer.getSender();
21+
contract = blockchain.openContract(await Arrays.fromInit());
22+
const addresses = {
23+
[deployer.address.toString()]: "deployer",
24+
[contract.address.toString()]: "contract",
25+
};
26+
return [[contract], addresses, [await contract.send(deployer.getSender(), { value: toNano(1) }, { $$type: "Deploy", queryId: 0n })]];
27+
},
28+
messages: {
29+
"timer": async () => {
30+
return [await contract.send(sender, { value: toNano(1) }, "timer")];
31+
},
32+
"dump": async () => {
33+
return [await contract.send(sender, { value: toNano(1) }, "dump")];
34+
},
35+
},
36+
getters: {
37+
length: async () => {
38+
return await contract.getLength();
39+
},
40+
map: async () => {
41+
return await contract.getMap();
42+
},
43+
},
44+
prev: getExample(import.meta.url).prev,
45+
next: getExample(import.meta.url).next,
46+
};
47+
</script>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Arrays
2+
3+
You can implement simple arrays in Tact by relying on the map type.
4+
5+
To create an array, define a map with `Int` type as key. The key will hold the index in the array. Add another length variable to rememebr how many items are in the array.
6+
7+
The example contract records the last 5 timestamps of when the `timer` message was received. The timestamps are held in a cyclical array implemented as a map.
8+
9+
## Limit the number of items
10+
11+
Maps are designed to hold a limited number of items. Only use a map if you know the upper bound of items that it may hold. It's also a good idea to [write a test](https://github.com/tact-lang/tact-emulator) to add the maximum number of elements to the map and see how gas behaves under stress.
12+
13+
If the number of items is unbounded and can potentially grow to billions, you'll need to architect your contract differently. We will discuss unbounded arrays later on under the topic of contract sharding.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import "@stdlib/deploy";
2+
3+
// this contract records the last 5 timestamps of when "timer" message was received
4+
contract Arrays with Deployable {
5+
6+
const MaxSize: Int = 5;
7+
arr: map<Int, Int>; // this is our array implemented with a map
8+
arrLength: Int as uint8 = 0;
9+
arrStart: Int as uint8 = 0; // our array is cyclic
10+
11+
init() {}
12+
13+
// push an item to the end of the array
14+
fun arrPush(item: Int) {
15+
if (self.arrLength < self.MaxSize) {
16+
self.arr.set(self.arrLength, item);
17+
self.arrLength = self.arrLength + 1;
18+
} else {
19+
self.arr.set(self.arrStart, item);
20+
self.arrStart = (self.arrStart + 1) % self.MaxSize;
21+
}
22+
}
23+
24+
// iterate over all items in the array and dump them
25+
fun arrPrint() {
26+
let i: Int = self.arrStart;
27+
repeat (self.arrLength) {
28+
dump(self.arr.get(i)!!); // !! tells the compiler this can't be null
29+
i = (i + 1) % self.MaxSize;
30+
}
31+
}
32+
33+
// record the timestamp when each "timer" message is received
34+
receive("timer") {
35+
let timestamp: Int = now();
36+
self.arrPush(timestamp);
37+
}
38+
39+
receive("dump") {
40+
self.arrPrint();
41+
}
42+
43+
get fun length(): Int {
44+
return self.arrLength;
45+
}
46+
47+
get fun map(): map<Int, Int> {
48+
return self.arr;
49+
}
50+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<script lang="ts">
2+
import { Dictionary, toNano, type Sender, Address } from "ton-core";
3+
import { Blockchain, type SandboxContract } from "@ton-community/sandbox";
4+
import { getExample } from "$lib/helpers";
5+
import store from "$lib/store";
6+
7+
import markdown from "./content.md?raw";
8+
import tactCode from "./contract.tact?raw";
9+
import { Maps } from "./Maps";
10+
11+
let sender: Sender;
12+
let contract: SandboxContract<Maps>;
13+
14+
$store = {
15+
markdown,
16+
tactCode,
17+
deploy: async () => {
18+
const blockchain = await Blockchain.create();
19+
const deployer = await blockchain.treasury("deployer");
20+
sender = deployer.getSender();
21+
contract = blockchain.openContract(await Maps.fromInit(Dictionary.empty<bigint, boolean>()));
22+
const addresses = {
23+
[deployer.address.toString()]: "deployer",
24+
[contract.address.toString()]: "contract",
25+
};
26+
return [[contract], addresses, [await contract.send(deployer.getSender(), { value: toNano(1) }, { $$type: "Deploy", queryId: 0n })]];
27+
},
28+
messages: {
29+
"set keys": async () => {
30+
return [await contract.send(sender, { value: toNano(1) }, "set keys")];
31+
},
32+
"delete keys": async () => {
33+
return [await contract.send(sender, { value: toNano(1) }, "delete keys")];
34+
},
35+
"clear": async () => {
36+
return [await contract.send(sender, { value: toNano(1) }, "clear")];
37+
},
38+
"Replace{{-900:my}}": async () => {
39+
const dict = Dictionary.empty<bigint, Address>().set(-900n, sender.address!!);
40+
return [await contract.send(sender, { value: toNano(1) }, { $$type: "Replace", items: dict })];
41+
},
42+
},
43+
getters: {
44+
"oneItem(-900)": async () => {
45+
return await contract.getOneItem(-900n);
46+
},
47+
itemCheck: async () => {
48+
return await contract.getItemCheck();
49+
},
50+
allItems: async () => {
51+
return await contract.getAllItems();
52+
},
53+
},
54+
prev: getExample(import.meta.url).prev,
55+
next: getExample(import.meta.url).next,
56+
};
57+
</script>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Maps
2+
3+
Maps are a dictionary type that can hold an arbitrary number of items, each under a different key.
4+
5+
The keys in maps can either be an `Int` type or an `Address` type.
6+
7+
You can check if a key is found in the map by calling the `get()` method. This will return `null` if the key is missing or the value if the key is found. Replace the value under a key by calling the `set()` method.
8+
9+
Integers in maps stored in state currently use the largest integer size (257-bit). Future versions of Tact will let you optimize the encoding size.
10+
11+
## Limit the number of items
12+
13+
Maps are designed to hold a limited number of items. Only use a map if you know the upper bound of items that it may hold. It's also a good idea to [write a test](https://github.com/tact-lang/tact-emulator) to add the maximum number of elements to the map and see how gas behaves under stress.
14+
15+
If the number of items is unbounded and can potentially grow to billions, you'll need to architect your contract differently. We will discuss unbounded maps later on under the topic of contract sharding.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import "@stdlib/deploy";
2+
3+
struct TokenInfo {
4+
ticker: String;
5+
decimals: Int;
6+
}
7+
8+
// messages can contain maps
9+
message Replace {
10+
items: map<Int, Address>;
11+
}
12+
13+
contract Maps with Deployable {
14+
15+
// maps with Int as key
16+
mi1: map<Int, TokenInfo>;
17+
mi2: map<Int, Bool>;
18+
mi3: map<Int, Int>;
19+
mi4: map<Int, Address>;
20+
21+
// maps with Address as key
22+
ma1: map<Address, TokenInfo>;
23+
ma2: map<Address, Bool>;
24+
ma3: map<Address, Int>;
25+
ma4: map<Address, Address>;
26+
27+
init(arg: map<Int, Bool>) {
28+
// no need to initialize maps if they're empty
29+
self.mi2 = arg;
30+
}
31+
32+
receive("set keys") {
33+
// keys are Int
34+
self.mi1.set(17, TokenInfo{ticker: "SHIB", decimals: 9});
35+
self.mi2.set(0x9377433ff21832, true);
36+
self.mi3.set(pow(2,240), pow(2,230));
37+
self.mi4.set(-900, address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"));
38+
// keys are Address
39+
self.ma1.set(address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"), TokenInfo{ticker: "DOGE", decimals: 18});
40+
self.ma2.set(address("UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI"), true);
41+
self.ma3.set(address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"), ton("1.23"));
42+
self.ma4.set(address("UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI"), myAddress());
43+
}
44+
45+
receive("delete keys") {
46+
// keys are Int
47+
self.mi1.set(17, null);
48+
self.mi2.set(0x9377433ff21832, null);
49+
self.mi3.set(pow(2,240), null);
50+
self.mi4.set(-900, null);
51+
// keys are Address
52+
self.ma1.set(address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"), null);
53+
self.ma2.set(address("UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI"), null);
54+
self.ma3.set(address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"), null);
55+
self.ma4.set(address("UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI"), null);
56+
}
57+
58+
receive("clear") {
59+
self.mi1 = emptyMap();
60+
self.mi2 = emptyMap();
61+
self.mi3 = emptyMap();
62+
self.mi4 = emptyMap();
63+
self.ma1 = emptyMap();
64+
self.ma2 = emptyMap();
65+
self.ma3 = emptyMap();
66+
self.ma4 = emptyMap();
67+
}
68+
69+
receive(msg: Replace) {
70+
// replace all items in the map with those coming in the message
71+
self.mi4 = msg.items;
72+
}
73+
74+
// if the key is not found, the get() method returns null
75+
get fun oneItem(key: Int): Address? {
76+
return self.mi4.get(key);
77+
}
78+
79+
get fun itemCheck(): String {
80+
if (self.mi1.get(17) == null) {
81+
return "not found";
82+
}
83+
let item: TokenInfo = self.mi1.get(17)!!; // the !! will tell the compiler it's not null
84+
return item.ticker;
85+
}
86+
87+
// you can return maps from getters
88+
get fun allItems(): map<Address, TokenInfo> {
89+
return self.ma1;
90+
}
91+
}

0 commit comments

Comments
 (0)