Skip to content

Commit

Permalink
refactor(stdlib): rewrite contractAddressExt, newAddress and `Add…
Browse files Browse the repository at this point in the history
…ress.asSlice` functions to Tact (#1766)

* add `asAddressUnsafe` and `contractHash`
* optimize for gas
  • Loading branch information
i582 authored Feb 17, 2025
1 parent d397f20 commit 84c033d
Show file tree
Hide file tree
Showing 13 changed files with 614 additions and 387 deletions.
1 change: 1 addition & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `loadVarInt16`, `loadVarUint16`, `loadVarInt32`, `loadVarUint32` methods for the `Slice` type: PR [#1667](https://github.com/tact-lang/tact/pull/1667)
- New functions in stdlib from `stdlib.fc` and `math.fc`: `Builder.depth`, `Slice.skipLastBits`, `Slice.firstBits`, `Slice.lastBits`, `Slice.depth`, `Cell.computeDataSize`, `Slice.computeDataSize`, `Cell.depth`, `curLt`, `blockLt`, `setGasLimit`, `getSeed`, `setSeed`, `myCode`, `sign`, `divc`, `muldivc`, `mulShiftRight`, `mulShiftRightRound`, `mulShiftRightCeil`, `sqrt`: PR [#986](https://github.com/tact-lang/tact/pull/986)
- The `--output` CLI flag for specifying custom output directory in single-contract compilation: PR [#1793](https://github.com/tact-lang/tact/pull/1793)
- New functions `Slice.asAddressUnsafe` and `contractHash` in stdlib: PR [#1766](https://github.com/tact-lang/tact/pull/1766)

### Changed

Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/book/expressions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ The `StateInit{:tact}` is a built-in [Struct][s], that consists of:

Field | Type | Description
:----- | :-------------------- | :----------
`code` | [`Cell{:tact}`][cell] | initial code of the [contract](/book/contracts) (the compiled bytecode)
`data` | [`Cell{:tact}`][cell] | initial data of the [contract](/book/contracts) (arguments of `init(){:tact}` function of the contract)
`code` | [`Cell{:tact}`][cell] | initial code of the [contract](/book/contracts) (compiled bitcode)
`data` | [`Cell{:tact}`][cell] | initial data of the [contract](/book/contracts) (parameters of [`init(){:tact}`](/book/contracts#init-function) function)

:::note

Expand Down
53 changes: 50 additions & 3 deletions docs/src/content/docs/ref/core-cells.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,53 @@ let buzz: Cell = beginCell().storeSlice(s).endCell();
fizz == buzz; // true
```

### Slice.asAddressUnsafe

<Badge text="Available since Tact 1.6 (not released yet)" variant="tip" size="medium"/><p/>

```tact
extends fun asAddressUnsafe(self: Slice): Address;
```

Extension function for the [`Slice{:tact}`][slice].

Unsafely casts the [`Slice{:tact}`][slice] to an [`Address{:tact}`][p] and returns it. The inverse of [`Address.asSlice(){:tact}`](#addressasslice).

This function does **not** perform any checks on the contents of the [`Slice{:tact}`][slice].

Usage example:

```tact
let a: Address = myAddress();
let a2: Address = a.asSlice().asAddressUnsafe();
a == a2; // true
```

:::caution

Use it only if you want to optimize the code for gas and can guarantee in advance that the [`Slice{:tact}`][slice] contains the data of an [`Address{:tact}`][p].

Otherwise, it is better to do similar checks before calling this function:

```tact
// The `s` is a Slice you've obtained elsewhere
// Checking correct length
let rightLength = 267; // 11 bits for the prefix,
// 256 bits for the address itself
nativeThrowUnless(136, s.bits() == rightLength);
// Checking correct prefix
let basechainAddrPrefix = 1024; // use 1279 for the masterchain prefix check
nativeThrowUnless(136, s.preloadUint(11) == basechainAddrPrefix);
// Now we can safely perform the cast
let addr = s.asAddressUnsafe();
```

:::

## Address.asSlice

```tact
Expand All @@ -1307,14 +1354,14 @@ extends fun asSlice(self: Address): Slice;

Extension function for the [`Address{:tact}`][p].

Converts the [`Address{:tact}`][p] to a [`Slice{:tact}`][slice] and returns it. Alias to `beginCell().storeAddress(self).asSlice(){:tact}`.
Casts the [`Address{:tact}`][p] back to the underlying [`Slice{:tact}`][slice] and returns it. The inverse of [`Slice.asAddressUnsafe(){:tact}`](#sliceasaddressunsafe).

Usage example:

```tact
let a: Address = myAddress();
let fizz: Slice = a.asSlice();
let buzz: Slice = beginCell().storeAddress(a).asSlice();
let fizz: Slice = beginCell().storeAddress(a).asSlice();
let buzz: Slice = a.asSlice(); // cheap, unlike the previous statement
fizz == buzz; // true
```
Expand Down
60 changes: 49 additions & 11 deletions docs/src/content/docs/ref/core-common.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ let timeOffset: Int = now() + 1000; // thousand seconds from now()
fun myBalance(): Int;
```

Returns the [nanoToncoin](/book/integers#nanotoncoin) balance of the smart contract as it was at the start of the [compute phase](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase) of the current transaction.
Returns the [nanoToncoin](/book/integers#nanotoncoin) [`Int{:tact}`][int] balance of the smart contract as it was at the start of the [compute phase](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase) of the current transaction.

Usage example:

Expand All @@ -39,7 +39,7 @@ let iNeedADolla: Int = myBalance();

:::caution

Beware, that [all message-sending functions](/book/send#message-sending-functions) of Tact can change the _actual_ contract's balance, but they _won't_ update the value returned by this function.
Beware, that [all message-sending functions](/book/send#message-sending-functions) of Tact can change the _actual_ contract's balance, but they **won't** update the value returned by this function.

:::

Expand Down Expand Up @@ -125,7 +125,7 @@ require(ctx.value != 68 + 1, "Invalid amount of nanoToncoins, bye!");
fun newAddress(chain: Int, hash: Int): Address;
```

Creates a new [`Address{:tact}`][p] based on the [`chain` id](https://ton-blockchain.github.io/docs/#/overviews/TON_blockchain_overview) and the [SHA-256](/ref/core-math#sha256) encoded [`hash` value](https://docs.ton.org/learn/overviews/addresses#account-id).
Creates a new [`Address{:tact}`][p] based on the [`chain` ID](https://ton-blockchain.github.io/docs/#/overviews/TON_blockchain_overview) and the [SHA-256](/ref/core-math#sha256) encoded [`hash` value (account ID)][account-id].

This function tries to resolve constant values in [compile-time](/ref/core-comptime) whenever possible.

Expand All @@ -136,22 +136,21 @@ Usage example:
```tact
let oldTonFoundationAddr: Address =
newAddress(0, 0x83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8);
// ↑ ------------------------------------------------------------------
// | ↑
// ↑ ↑
// | sha-256 hash of contract's init package (StateInit)
// chain id: 0 is a workchain, -1 is a masterchain
```

:::caution

Make sure your specify only supported chain IDs: $0$ for the basechain and $-1$ for the masterchain.
Make sure your specify only supported workchain IDs: $0$ for the basechain and $-1$ for the masterchain.

:::

:::note[Useful links:]

[`chain` (Workchain ID) in TON Docs](https://docs.ton.org/learn/overviews/addresses#workchain-id)\
[`hash` (Account ID) in TON Docs](https://docs.ton.org/learn/overviews/addresses#account-id)\
[`chain` (Workchain ID) in TON Docs][workchain-id]\
[`hash` (Account ID) in TON Docs][account-id]\
[Contract's init package (`StateInit{:tact}`)](/book/expressions#initof)

:::
Expand All @@ -164,12 +163,16 @@ let oldTonFoundationAddr: Address =
fun contractAddress(s: StateInit): Address;
```

Computes smart contract's [`Address{:tact}`][p] in a workchain $0$ based on its [`StateInit{:tact}`](/book/expressions#initof).
Computes smart contract's [`Address{:tact}`][p] in the workchain ID $0$ (basechain) using the [`StateInit{:tact}`](/book/expressions#initof) `s` of the contract. Alias to `contractAddressExt(0, s.code, s.data){:tact}`.

Usage example:

```tact
let foundMeSome: Address = contractAddress(initOf SomeContract());
let s: StateInit = initOf SomeContract();
let foundMeSome: Address = contractAddress(s);
let andSomeMore: Address = contractAddressExt(0, s.code, s.data);
foundMeSome == andSomeMore; // true
```

### contractAddressExt
Expand All @@ -180,7 +183,7 @@ let foundMeSome: Address = contractAddress(initOf SomeContract());
fun contractAddressExt(chain: Int, code: Cell, data: Cell): Address;
```

Computes smart contract's [`Address{:tact}`][p] based on the `chain` id, contract's `code` and contract's initial state `data`. Use [`initOf{:tact}`](/book/expressions#initof) expression to obtain initial `code` and initial `data` of a given contract.
Computes smart contract's [`Address{:tact}`][p] in the `chain` ID using the contract's `code` and the contract's initial state `data`. Use the [`initOf{:tact}`](/book/expressions#initof) expression to obtain the initial `code` and initial `data` of a given contract.

This function tries to resolve constant values in [compile-time](/ref/core-comptime) whenever possible.

Expand All @@ -193,6 +196,38 @@ let initPkg: StateInit = initOf SomeContract();
let hereBeDragons: Address = contractAddressExt(0, initPkg.code, initPkg.data);
```

### contractHash

<Badge text="Available since Tact 1.6 (not released yet)" variant="tip" size="medium"/><p/>

```tact
fun contractHash(code: Cell, data: Cell): Int;
```

Computes and returns an [`Int{:tact}`][int] value of the [SHA-256](https://en.wikipedia.org/wiki/SHA-2#Hash_standard) hash of the `code` and `data` of the given contract. To assemble the `code` and `data` cells together for hashing, the [standard `Cell{:tact}` representation](/book/cells#cells-representation) is used.

This hash is commonly called [account ID][account-id]. Together with the [workchain ID][workchain-id] it deterministically forms the address of the contract on TON Blockchain.

Usage example:

```tact
let initPkg: StateInit = initOf SomeContract();
let accountId: Int = contractHash(initPkg.code, initPkg.data);
let basechainAddr: Address = newAddress(0, accountId);
let basechainAddr2: Address = contractAddressExt(0, initPkg.code, initPkg.data);
basechainAddr == basechainAddr2; // true
```

:::note[Useful links:]

[`chain` (Workchain ID) in TON Docs][workchain-id]\
[`hash` (Account ID) in TON Docs][account-id]\
[Contract's init package (`StateInit{:tact}`)](/book/expressions#initof)\
[Standard `Cell{:tact}` representation](/book/cells#cells-representation)

:::

## Communication

### send
Expand Down Expand Up @@ -257,3 +292,6 @@ emit("Catch me if you can, Mr. Holmes".asComment()); // asComment() converts a S
[bool]: /book/types#booleans
[int]: /book/integers
[slice]: /book/cells#slices

[workchain-id]: https://docs.ton.org/learn/overviews/addresses#workchain-id
[account-id]: https://docs.ton.org/learn/overviews/addresses#account-id
Loading

0 comments on commit 84c033d

Please sign in to comment.