> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/contract-dev/gas",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Estimate gas usage in TON contracts

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

Contracts which receive a user's message are referred to as **receiver** contracts. Other contracts in the same system are called **internal** contracts. This terminology is local to the current article. Consider a contract system with three contracts: receiver, and internal contracts **A** and **B**. In that system the typical trace looks like this (transactions move from left to right):

```mermaid theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
flowchart LR
    User1((User)) --> Receiver((Receiver)) --> A((A)) --> B((B)) --> User2((User))

    style User1 r:50
    style Receiver r:50
    style A r:50
    style B r:50
    style User2 r:50
```

<Aside type="tip">
  This article covers abstract contracts system, not connected to any existing project. However, it's primarily applicable to contract systems that use a carry-value pattern.
</Aside>

It is important to understand that there is *no separate* message balance and contract balance. After the message is received, coins from the message are stored to the contract balance, and then the contract is executed. [Sending message modes](/foundations/messages/modes) and [reserve actions](/foundations/actions/reserve) help to properly divide contract balance in the action phase. This diagram of a possible value flow illustrates this.

```mermaid theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
flowchart LR
    User1((User)) --> A((A)) --> B((B)) --> User2((User))

    style User1 r:50
    style A r:50
    style B r:50
    style User2 r:50
```

```mermaid theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
---
config:
  sankey:
    showValues: false
---
sankey-beta
%% source,target,value
Value of message,A,100
Old balance on A,A,20
A,B,80
A,Storage fee (A),5
A,Compute fee (A),15
A,Forward fee (A->B),20
Old balance on B,B,20
B,Storage fee (B),5
B,Compute fee (B),10
B,Forward fee (B->C),15
B,Remaining value on B,70
```

Receiver contracts must verify that the attached Toncoin is sufficient to cover fees for all contracts in the subsequent trace. If an entry contract accepts a user message it must guarantee that the message will not later fail due to insufficient attached TON. "Accept" doesn't mean the call to `accept_message()`, but semantic acceptance, i.e., no throw and no asset returns.

The reason for this requirement is that reverting the contract system state is usually not possible, because the Toncoin is already spent.

When a contract system's correctness depends on successful execution of the remaining transaction trace, it must guarantee that an incoming message carries enough attached Toncoin to cover all fees. This article describes how to compute those fees.

Define variables for limits and initialize them with zero. We will set them to actual values afterwards.

Use descriptive names for the operation and the contract. It's best to store them in a dedicated file with constants.

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
const GasSwapRequest: Int = 0;
```

* Run tests covering all execution paths. Missing a path might hide the most expensive one.
* Extract resource consumption from the `send()` method's return value. The sections below describe ways to compute consumption of different kinds of resources.
* Use `expect(extractedValue).toBeLessThanOrEqual(hardcodedConstant)` to verify that the hardcoded limit was not exceeded.

```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import {findTransactionRequired} from "@ton/test-utils";

const result = await contract.send(/* params */);
const vaultTx = findTransactionRequired(result.transactions, {
    on: contract.address,
    op: 0x12345678,
});
expect(
    getComputeGasForTx(vaultTx),
).toBeLessThanOrEqual(GasSwapRequest);
```

After the first run, use the displayed error message to find the actual value for the constant.

```text theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
expect(received).toBeLessThanOrEqual(expected)

Expected: <= 0n
Received:    11578n
```

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
const GasSwapRequest: Int = 12000;
```

### Compute fees

There are two kinds of values: gas units and Toncoin. The price of contract execution is fixed in gas units. However, the price of the gas itself is determined by the blockchain configuration.

Conversion of gas to Toncoin on-chain using currently set blockchain config parameters can be performed with

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
let fee = getComputeFee(hardcodedGasValue, isAccountInMasterchain);
```

This function uses the [`GETGASFEE`](/tvm/instructions#f836-getgasfee) TVM opcode.

### Forward fees

Forward fee is calculated with this formula:

```
fwdFee = lumpPrice
       + priceForCells * (msgSizeInCells - 1)
       + priceForBits * (msgSizeInBits - bitsInRoot)
```

where

* `lumpPrice` is the fixed value [from config](/foundations/config#param-24-and-25%3A-message-price) paid once for the message.
* `msgSizeInCells` is the number of cells in the message.
* `msgSizeInBits` is the number of bits in all the cells of the message.

In Tolk, `cell.calculateSizeStrict()` can be used to compute `msgSizeInCells` and `msgSizeInBits`. In TVM, it's implemented as [`CDATASIZE`](/tvm/instructions#f941-cdatasize) instruction.

In Tolk, the formula above is implemented in `calculateForwardFee()`. In TVM, it's implemented as [`GETFORWARDFEE`](/tvm/instructions#f838-getforwardfee) instruction.

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
let sizeMsg = computeDataSize(
    msg.toCell(),
    8192
);

let fwdFee = getForwardFee(
    sizeMsg.cells - 1,
    sizeMsg.bits - msg.toCell().bits(),
    isAccountInMasterchain
);
```

<Aside type="caution">
  The `computeDataSize()` function consumes large, unpredictable amount of gas. If at all it is possible to precompute the size, it is recommended to do so.

  If required, the function has the second argument: maximum number of cells to visit. Usually it is set to 8192 since it is the [limit for message size](/foundations/limits#message-and-transaction-limits). It might be used to limit amount of spent gas.
</Aside>

In Tolk, both computations can be performed with a call to `msg.send()` with mode 1024. In TVM, it's implemented as [`SENDRAWMSG`](/tvm/instructions#fb00-sendrawmsg). It will consume approximately the same amount of gas.

### Optimized forward fee calculation

If the size of the outgoing message is bounded by the size of the incoming message, we can estimate the forward fee of an outgoing message to be no larger than the forward fee of the incoming message, that was already computed by TVM. Thus, we don't have to calculate it again. Note that this estimation is correct only for a contract system in the same workchain, because gas prices depend on the workchain.

```tolk title="Tolk" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
fun onInternalMessage(in: InMessage) {
    val fwdFee = in.originalForwardFee;
    // ...
}
```

### Complex forward fee calculation

Assume the contract receives a message with an unknown size and forwards it further adding fields with total of `a` bits and `b` cells to the message, e.g., [`StateInit`](/foundations/messages/deploy).

For this case, in Tolk, there is a function `calculateForwardFeeWithoutLumpPrice()`. In TVM, it's implemented as [`GETFORWARDFEESIMPLE`](/tvm/instructions#f83c-getforwardfeesimple). This function does not take `lumpPrice` into account.

```tolk title="Tolk" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
fun onInternalMessage(in: InMessage) {
    val origFwdFee = in.originalForwardFee;

    // "Out" message will consist of fields from
    // "in" message, and some extra fields.
    // Forward fee for "out" message is estimated
    // from a forward fee for "in" message
    val additionalFwdFee = getSimpleForwardFee(
        additionalFieldsSize.cells,
        additionalFieldsSize.bits,
        isAccountInMasterchain
    );
    val totalFwdFee = origFwdFee + additionalFwdFee;

    // Remember to multiply totalFwdFee by the number
    // of hops in the trace
}
```

### Storage fees

<Aside>
  For calculating storage fees, maximum possible size of the contract in `cells` and `bits` should be known. Calculating it manually might be hard, if the contract stores a hashmap or some other complex data structure in its state.

  It might be easier to compute it by writing a test that will occupy maximum possible size, and use the measured value. There are [helper functions](#helper-functions) for doing these measurements.
</Aside>

We cannot predict storage fees that we have to pay for sending messages because it depends on how long the target contract didn't pay storage fee. Storage fees differ in this way from forward and compute fees, as they should be handled both in receiver and internal contracts.

#### Maintain a positive reserve

Always keep a minimum balance on all contracts in the system. Storage fees get deducted from this reserve. The reserve get replenished with each user interaction. Do not hardcode Toncoin values for fees. Instead, hardcode the maximum possible contract size in cells and bits.

This approach affects code of internal contracts.

<Aside type="tip">
  It is the developer's decision for how long the storage fees should be reserved. Popular options are 5 and 10 years.
</Aside>

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
const secondsInFiveYears: Int = 5 * 365 * 24 * 60 * 60;
receive(msg: Transfer) {
    let minTonsForStorage: Int = getStorageFee(
        maxCells,
        maxBits,
        secondsInFiveYears,
        isAccountInMasterchain
    );
    nativeReserve(
        max(oldBalance, minTonsForStorage),
        ReserveAtMost
    );
    // Process operation with remaining value...
}
// Also this contract probably will require some code, that will allow owner to withdraw TONs from this contract.
```

In this approach, a receiver contract should calculate maximum possible storage fees for all contracts in trace.

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
const secondsInFiveYears: Int = 5 * 365 * 24 * 60 * 60;
receive(msg: UserIn) {
    // Suppose trace will be
    // receiver -> A -> B
    let storageForA = getStorageFee(
        maxCellsInA,
        maxBitsInA,
        secondsInFiveYears,
        isAccountInMasterchain
    );
    let storageForB = getStorageFee(
        maxCellsInB,
        maxBitsInB,
        secondsInFiveYears,
        isAccountInMasterchain
    );
    let totalStorageFees = storageForA + storageForB;
    // compute compute + forward fees here
    let otherFees = 0;
    require(
        messageValue() >= totalStorageFees + otherFees,
        "Not enough TONs"
    );
}
```

<Aside type="caution">
  Verify the hardcoded contract size in tests.
</Aside>

#### Cover storage on demand

The order of phases [depends](/foundations/phases) on the `bounce` flag of an incoming message. If all messages in the protocol are unbounceable, then the storage phase comes after the credit phase. So, the contract's storage fees are deducted from the joint balance of the contract and incoming message. In this case the pattern where the contract's balance is zero and incoming messages cover storage fees can be applied.

It is impossible to know in advance what the storage fee due will be on the contract, so a threshold must be selected depending on the network configuration. It is a good practice to use [`freeze_due_limit`](/foundations/config#param-20-and-21%3A-gas-prices) as the threshold. Otherwise, the contract likely is already frozen and a transaction chain is likely to fail anyway.

This pattern can be generalized to both bounceable and unbounceable messages with `myStorageDue()` function that returns [`storage_fees_due`](/foundations/phases#storage-phase)

This approach affects code of internal contracts.

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
receive(msg: Operation) {
    // Reserve original balance plus any storage debt
    nativeReserve(
        myStorageDue(),
        ReserveAddOriginalBalance | ReserveExact
    );

    // Send remaining value onward
    send(SendParameters{
        value: 0,
        mode: SendRemainingBalance,
        // ...
    });
}
```

If we expect that the rest of trace uses `n` unique contracts, then it won't take more than `n` freeze limits to pay their storage fees. So, in the receiver contract, the check should be:

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
receive(msg: Operation) {
    // The trace is still receiver -> A -> B
    let freezeLimit = getFreezeLimit(isAccountsInMasterchain);
    let otherFees = ...;
    // n equals 3 because receiver -> A -> B
    require(
        messageValue() >= freezeLimit * 3 + otherFees,
        "Not enough TONs"
    );
}
```

For contracts using this approach, confirm there is no excess accumulation:

```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
it("should not accumulate excess balance", async () => {
    await pool.sendSwap(amount);
    const contract = await blockchain.getContract(
        pool.address,
    );
    const balance = contract.balance;
    expect(balance).toEqual(0n);
});
```

This confirms that all incoming value was consumed or forwarded, with none left behind. It helps identify any bugs that cause accumulation of TON on any contract.

## Implement fee validation

The final code in the receiver contract could look like this:

```tact theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
receive(msg: SwapRequest) {
    let ctx = context();
    let fwdFee = ctx.readForwardFee();

    // Count all messages in the operation chain
    // IMPORTANT: We know that each of messages is
    // less or equal to `SwapRequest`.
    let messageCount = 3;  // receiver -> vault → pool → vault

    // Calculate minimum required
    let minFees =
        messageCount * fwdFee +
        // Operation in first vault
        getComputeFee(GasSwapRequest, isInMasterchain) +
        // Operation in pool
        getComputeFee(GasPoolSwap, isInMasterchain) +
        // Operation in second vault
        getComputeFee(GasVaultPayout, isInMasterchain) +
        3 * getFreezeLimit();

    require(
        ctx.value >= msg.amount + minFees,
        "Insufficient TON attached"
    );

    // Send remaining value for fees...

    // It may also be necessary to handle fees on this exact
    // contract if it is not supposed to hold users' TONs.
    // That can be done in either of the two approaches
    // described above.
}
```

## Helper functions

Getting gas for transaction in sandbox is quite easy:

```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
function getComputeGasForTx(tx: Transaction): bigint {
    if (tx.description.type !== "generic") {
        throw new Error("Expected generic transaction");
    }
    if (tx.description.computePhase.type !== "vm") {
        throw new Error("Expected VM compute phase");
    }
    return tx.description.computePhase.gasUsed;
}
```

To calculate the size of a message in cells, use this function:

```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
const calculateCellsAndBits = (
    root: Cell,
    visited: Set<string> = new Set<string>()
) => {
    const hash = root.hash().toString("hex");
    if (visited.has(hash)) {
        return { cells: 0, bits: 0 };
    }
    visited.add(hash)
    let cells = 1;
    let bits = root.bits.length;
    for (const ref of root.refs) {
        const childRes = calculateCellsAndBits(
            ref,
            visited,
        );
        cells += childRes.cells;
        bits += childRes.bits;
    }
    return { cells, bits, visited };
};
```

To extract a contract's size in tests, use this function:

```ts theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
export async function getStateSizeForAccount(
    blockchain: Blockchain,
    address: Address,
): Promise<{cells: number; bits: number}> {
    const contract = await blockchain.getContract(address);
    const accountState = contract.accountState;
    if (!accountState || accountState.type !== "active") {
        throw new Error("Account state not found");
    }
    if (!accountState.state.code || !accountState.state.data) {
        throw new Error("Account state code or data not found");
    }
    const accountCode = accountState.state.code;
    const accountData = accountState.state.data;
    const codeSize = calculateCellsAndBits(
        accountCode,
    );
    const dataSize = calculateCellsAndBits(
        accountData,
        codeSize.visited,
    );
    return {
        cells: codeSize.cells + dataSize.cells,
        bits: codeSize.bits + dataSize.bits,
    };
};
```

Message-size constants should be verified across all possible paths in tests. Otherwise, the resulting gas estimates might be wrong.
