---
title: Gas best practices
description: "Anti-patterns, suboptimal approaches that increase gas usage, and best practices of cheap runtime execution that Tact smart contract developers should be aware of."
---

import { Badge } from '@astrojs/starlight/components';

There are several anti-patterns that unnecessarily increase gas usage or are suboptimal in most cases. Below, we discuss various trade-offs when writing gas-efficient and safe Tact smart contracts.

Suggestions are given in no particular order as a simple and quick checklist to see how your contract is doing regarding gas usage. You don't have to check all the points, but try to follow as many as possible without neglecting the [security best practices](/book/security-best-practices).

:::note

  Tact compiler is continuously evolving, so some points below may become outdated and removed or replaced. Please check this page periodically for updates.

:::

## General considerations

### Prefer contract parameters to `init()` and contract fields

If you require some on-chain deployment logic that runs just once after the contract's deployment is completed, then use the lazy initialization strategy provided by the [`init(){:tact}` function](/book/contracts#init-function). It uses an extra bit in the contract's persistent state, runs extra checks upon receiving any message, and disables storage write optimizations of Tact compiler.

However, most contracts do not require lazy initialization and define their initial data only when the contract is first deployed, with everything for it prepared off-chain. Therefore, prefer using [contract parameters syntax](/book/contracts#parameters) to define the data of the contract's persistent storage that way.

```tact
contract ContractParams(
    // Persistent state variables declared via the contract parameters syntax
    val1: Int as uint64, // `as`-annotations are supported
    val2: String,
) {
    // For deployments
    receive() { cashback(sender()) }
}
```

```ts
// TypeScript wrappers generated by Tact compiler
import { ContractParams } from '../wrappers/ContractParams';

// Various utility libraries
import { NetworkProvider } from '@ton/blueprint';
import { toNano } from '@ton/core';

// Default deployment function / script of the Blueprint
export async function run(provider: NetworkProvider) {
  const contract = provider.open(await ContractParams.fromInit(
    42,                  // `val1` in the contract
    "The time has come", // `val2` in the contract
  ));
  await playground.send(
    provider.sender(),
    { value: toNano('0.05') },
    null, // because there's a `null` message body
          // `receive()` function in the contract
  );
  await provider.waitForDeploy(playground.address);
}
```

### Do not deploy contracts with `Deployable` trait

The `Deployable{:tact}` trait is now deprecated and should only be used if you require the `queryId`, which serves as a unique identifier for tracing transactions across multiple contracts.

Instead of inheriting the `Deployable{:tact}` trait, prefer having a simple receiver for the empty message body and deploying your contracts with it.

```tact
contract Friendly {
    // This is when you DO want to send excesses back
    receive() { cashback(sender()) } // expects a `null` body
}

contract Scrooge {
    // This is when you don't want to send excesses back
    receive() {} // expects a `null` body
}
```

### Use `BasechainAddress` struct and related functions for runtime manipulations on addresses in the base workchain

<Badge text="Available since Tact 1.6.3" variant="tip" size="medium"/><p/>

Almost all contracts on TON are deployed on the basechain — a [workchain with ID 0][workchain-id]. The [`BasechainAddress{:tact}`](/ref/core-addresses#basechainaddress) [struct][struct] and related functions were introduced to allow optimal handling of basechain addresses at runtime.

A frequent use-case for basechain addresses is checking message sender addresses for validity, i.e., if a contract needs to make sure the incoming message comes from a specific contract deployed in the basechain, it can use the [`StateInit.hasSameBasechainAddress(){:tact}`](/ref/core-addresses#stateinithassamebasechainaddress) extension function.

```tact
contract Child(parentAddr: Address) {
    receive() {
        // Forward surplus to the parent address
        cashback(self.parentAddr);
    }
}

contract Parent() {
    receive() {
        // Ensure that the message came from the child contract
        let childContract = initOf Child(myAddress());
        require(
            childContract.hasSameBasechainAddress(sender()),
            "Message must come from the Child contract",
        );

        // ...subsequent logic...
    }
}
```

To ensure that a certain [`Address{:tact}`][p] is in the basechain and can be represented by the `BasechainAddress{:tact}`, you can use the [`forceBasechain(){:tact}`](/ref/core-addresses#forcebasechain) function first.

See other functions related to the `BasechainAddress{:tact}` struct:

* [`emptyBasechainAddress(){:tact}`](/ref/core-addresses#emptybasechainaddress)
* [`newBasechainAddress(){:tact}`](/ref/core-addresses#newbasechainaddress)
* [`contractBasechainAddress(){:tact}`](/ref/core-addresses#contractbasechainaddress)

### Pay attention to "500+ gas" badge

Some functions in the Tact documentation are annotated with a special badge, "500+ gas", which marks the functions that use 500 gas units or more. It is placed right under the function name heading and looks like this: <Badge text="500+ gas" title="Uses 500 gas units or more" variant="danger" size="medium"/>

If you use one of those functions, consider finding cheaper alternatives.

### Inline functions that are rarely called

Some [kinds of functions](/book/functions) allow their code to be inlined by adding an `inline` annotation. If the function is used often, this might result in a significant increase in contract code size, but generally, it allows to save gas on redundant function calls.

Furthermore, you might reach for the [experimental `inline` field](/book/config#experimental-inline) in `tact.config.json`, which enables the inlining of all functions that can be inlined.

This advice needs benchmarks to decide its usefulness on a case-by-case basis.

### Prefer manipulating strings off-chain

Strings on-chain are represented as [slices][slice], which are expensive for handling Unicode strings and quite costly even for ASCII ones. Prefer not to manipulate strings on-chain.

### Prefer arithmetic to branching operators

On average, branching uses many more instructions than equivalent arithmetic. Common examples of branching are the [`if...else{:tact}`](/book/statements#if-else) statement and the ternary operator [`?:{:tact}`](/book/operators#ternary).

Use arithmetic and standard library functions over branching or complex control flow whenever possible.

```tact
// If the forwardAmount is non-negative, this
msg.forwardAmount > 0 ? 2 : 1;

// is more expensive than doing this
1 + sign(msg.forwardAmount);
```

### Prefer specialized math functions to general ones

Specialized math functions are cheaper than general ones as they require fewer stack manipulations and often use more efficient [TVM instructions][tvm-instruction].

If you only need to obtain logarithms to the base 2, use the [`log2(){:tact}`](/ref/core-math#log2) function over the general [`log(){:tact}`](/ref/core-math#log) function.

If you only need to obtain powers of 2, use the [`pow2(){:tact}`](/ref/core-math#pow2) function over the general [`pow(){:tact}`](/ref/core-math#pow) function.

```tact
let num = 42;

// Use this instead of log(num, 2)
log2(num);

// Use this instead of pow(num, 2)
pow2(num);
```

### Asm functions

Many commonly used [TVM instructions][tvm-instruction] expect the same values but in a different order on the stack. Often, the code you write will result in instructions defined by your logic intermittent with stack-manipulation instructions, such as `SWAP` or [`ROT`](https://docs.ton.org/v3/documentation/tvm/instructions#58), which would've been unnecessary if the positioning of values on the stack was better planned before.

On the other hand, if you know the layout and boundaries of your data, the generic choice of underlying instructions might be suboptimal in terms of gas usage or code size.

In both cases, you can use assembly functions (or `asm` functions for short) to manually describe the logic by writing a series of [TVM instructions][tvm-instruction]. If you know what you're doing, `asm` functions can offer you the smallest possible gas usage and the most control over [TVM][tvm] execution.

Read more about them on their dedicated page: [Assembly functions](/book/assembly-functions).

### Disable run-time `safety` checks for well-tested contracts

:::caution

  Proceed only if you have a comprehensive contract test suite and are about to apply various other security measures, such as audits. The following is not guidance but is an illustration of what can be done to maximize gas efficiency when you are sure of the safety of your contracts.

:::

When using the unwrapping [non-null assertion `!!`](/book/operators/#unary-non-null-assert) operator, you can decrease its gas consumption at the expense of its runtime `null{:tact}` checks. Thus, you should only disable such checks when you are sure there will not be a runtime `null{:tact}` present in the given branch of your code.

```tact
fun example() {
    let val: Int? = 42;
    if (val == null) {
        return;
    }
    // At this point we know that `val` is not null,
    // so we can safely disable runtime checks of the !! operator in the config
    val!!; // 42
}
```

To disable runtime `null{:tact}` checks of the `!!` operator, set the [`nullChecks`](/book/config#safety-nullchecks) field to `false{:json}` in your [`tact.config.json`](/book/config).

```json filename="tact.config.json" {9}
{
  "projects": [
    {
      "name": "contract",
      "path": "./your-contract-file.tact",
      "output": "./output",
      "options": {
        "safety": {
          "nullChecks": false
        }
      }
    }
  ]
}
```

## Receiving messages

### Prefer binary receivers to text receivers

Tact automatically handles various message body types, including binary and string (text) ones. Both message bodies start with a 32-bit integer header (or tag) called an [opcode](/book/structs-and-messages#message-opcodes), which helps distinguish their following contents.

To prevent conflicts with binary message bodies which are usually sent on the blockchain, the string (text) receivers skip the opcode and instead route based on the hash of the message body contents — an expensive operation that requires more than 500 units of gas.

While text receivers are convenient during development in testing, when preparing your contract for production you should replace all text receivers with binary ones and create relevant [message structs][message] even if they'll be empty and only their opcodes will be used.

```tact
message(1) One {}
message(2) Two {}

contract Example() {
    // Prefer this
    receive(_: One) {
        // ...
    }
    receive(_: Two) {
        // ...
    }

    // Over this
    receive("one") {
        // ...
    }
    receive("two") {
        // ...
    }
}
```

### Prefer `inMsg()` to `Message.toSlice()`

<Badge text="Available since Tact 1.6.7" variant="tip" size="medium"/><p/>

When working with an incoming message, prefer using the [`inMsg(){:tact}`](/ref/core-contextstate#inmsg) function over the [`Message.toSlice(){:tact}`](/ref/core-cells#messagetoslice) extension function, because the `inMsg(){:tact}` function provides direct access to the incoming message body [`Slice{:tact}`][p], while `msg.toSlice(){:tact}` performs redundant operations to recreate that `Slice{:tact}`.

```tact
contract Example() {
    receive(msg: SomeMessage) {
        // Prefer this
        let body = inMsg();

        // over this
        let sameBody = msg.toSlice();
    }
}
```

### Avoid internal contract functions

The [internal functions](/book/contracts#internal-functions) of a contract (often called contract methods) are similar to global functions, except that they can access the contract's [storage variables](/book/contracts#variables) and [constants](/book/contracts#constants).

However, they push the contract's variables on the stack at the start of their execution and pop them off afterward. This creates lots of unnecessary stack-manipulation instructions and consumes gas.

If your contract method does not access any of its persistent state variables, move it outside the contract and make it a global, module-level function instead.

### Use `sender()` over `context().sender`

When you receive an internal message, you can obtain the address of the contract that has sent it. This can be done by calling the [`sender()`](/ref/core-contextstate#sender) function or by accessing the `.sender` field of the `Context{:tact}` [struct][struct] after calling the [`context()`](/ref/core-contextstate#context) function.

If you only need the sender's address and no additional context on the incoming message that is contained in the `Context{:tact}` struct, then use the [`sender()`](/ref/core-contextstate#sender) function as it is less gas-consuming.

```tact /sender\\(\\)/
message(MessageParameters {
    to: sender(),
    value: ton("0.05"),
});
```

### Use `throwUnless()` over `require()`

The [`require(){:tact}`](/ref/core-debug#require) function is convenient for stating assumptions in code, especially in debug environments. Granted, currently, it generates [exit codes](/book/exit-codes) greater than $2^{11}$, making it a bit expensive compared to alternatives.

If you're ready for production and are willing to sacrifice some convenience for gas, use [`throwUnless(){:tact}`](/ref/core-debug#throwunless) function, keep track of your exit codes by declaring them as [constants](/book/constants), and keep exit codes within the inclusive range of $256-2^{11}$. It's essential to respect the latter range because the exit code values from 0 to 255 are reserved by [TVM][tvm] and the Tact compiler.

```tact
const SOMETHING_BAD_1: Int = 700;
const SOMETHING_BAD_2: Int = 701; // it is convenient to increment by 1

fun example() {
    throwUnless(SOMETHING_BAD_1, now() > 1000);
    throwUnless(SOMETHING_BAD_2, now() > 1000000);
}
```

### Use `SignedBundle` to verify signatures in external message receivers

When handling [external messages](/book/external), the [`SignedBundle{:tact}`](/ref/core-crypto#signedbundle) [struct][struct] provides a gas-efficient way to verify message signatures. This approach is particularly useful for wallet contracts and other contracts that need secure user authorization.

The `SignedBundle{:tact}` contains both the signature and the non-serialized data of the enclosing struct or message struct that is being signed, allowing for convenient verification with the [`SignedBundle.verifySignature(){:tact}`](/ref/core-crypto#signedbundleverifysignature) extension function.

```tact
contract Wallet(publicKey: Int as uint256) {
    external(msg: SomeWalletOperation) {
        // Verifying the signature before accepting the message
        require(msg.bundle.verifySignature(self.publicKey), "Invalid signature");

        // Now it's safe to accept it
        acceptMessage();

        // ...and proceed further...
    }
}

message SomeWalletOperation {
    // You must set the SignedBundle as the first field
    bundle: SignedBundle;

    // Some data to be signed
    walletId: Int as int32;
    seqno: Int as uint32;
}
```

## Sending messages

### Prefer `message()` and `cashback()` to `self.forward()`, `self.reply()`, and `self.notify()`

Every [contract](/book/contracts) in Tact implicitly [inherits](/book/contracts#traits) the `BaseTrait{:tact}` trait, which contains a number of [internal functions](/book/contracts#internal-functions) for any contract. Those internal functions are gas-expensive for the same reasons as stated earlier.

Calls to `self.forward(){:tact}`, `self.reply(){:tact}` and `self.notify(){:tact}` can be replaced with respective calls to the [`send(){:tact}`](/ref/core-send#send) or [`message(){:tact}`](/ref/core-send#message) functions with suitable values.

Moreover, if all you want is to forward the remaining value back to the sender, it is best to use the [`cashback(){:tact}`](/ref/core-send#cashback) function instead of `self.notify(){:tact}` function.

```tact
// This
self.forward(sender(), null, false, initOf SomeContract());

// could be replaced with this
let initState = initOf SomeContract();
send(SendParameters {
    to: sender(),
    mode: SendRemainingValue | SendIgnoreErrors,
    bounce: false,
    value: 0,
    code: initState.code,
    data: initState.data,
    body: null,
})

// This
self.reply(null);

// should be replaced with this
message(MessageParameters {
    body: null,
    to: sender(),
    mode: SendRemainingValue | SendIgnoreErrors,
    bounce: true,
    value: 0,
});

// And this
self.notify(null);

// should be replaced with this
cashback(sender());
```

### Use `deploy()` function for on-chain deployments

There are many [message-sending functions](/book/send#message-sending-functions), and the [`deploy(){:tact}`](/ref/core-send#deploy) function is optimized for cheaper on-chain deployments compared to the [`send(){:tact}`](/ref/core-send#send) function.

```tact
deploy(DeployParameters {
    init: initOf SomeContract(), // with initial code and data of SomeContract
                                 // and with an empty message body
    mode: SendIgnoreErrors,      // skip the message in case of errors
    value: ton("1"),             // send 1 Toncoin (1_000_000_000 nanoToncoin)
});
```

### Use `message()` function for non-deployment messages

There are many [message-sending functions](/book/send#message-sending-functions), and the [`message(){:tact}`](/ref/core-send#message) function is optimized for cheaper non-deployment regular messages compared to the [`send(){:tact}`](/ref/core-send#send) function.

```tact
message(MessageParameters {
    to: addrOfSomeInitializedContract,
    value: ton("1"), // sending 1 Toncoin (1_000_000_000 nanoToncoin),
                     // with an empty message body
});
```

## Applied examples of best gas practices

Tact has a growing set of contracts benchmarked against their reference FunC implementations. We fine-tune each Tact contract following the gas-preserving approaches discussed on this page while staying true to the original code and without doing precompilation or excessive ASM usage.

See those examples with recommendations applied:

- [Jetton Wallet contract](https://github.com/tact-lang/jetton/blob/9db75a5a828e9093be5c425605c5f5c9903f505d/src/contracts/jetton-wallet.tact)
- [Jetton Minter contract](https://github.com/tact-lang/jetton/blob/9db75a5a828e9093be5c425605c5f5c9903f505d/src/contracts/jetton-minter.tact)
- [Escrow contract](https://github.com/tact-lang/tact/blob/73fb52b2c8b4e8b2309e0aae4dcc0f8cb35117ea/src/benchmarks/contracts/escrow.tact)

[p]: /book/types#primitive-types
[slice]: /book/cells#slices
[struct]: /book/structs-and-messages#structs
[message]: /book/structs-and-messages#messages

[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
[tvm-instruction]: https://docs.ton.org/v3/documentation/tvm/instructions
[workchain-id]: https://docs.ton.org/learn/overviews/addresses#workchain-id
[account-id]: https://docs.ton.org/learn/overviews/addresses#account-id
