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

# Expressions and control flow

> SauceScript's expressions and statements are TypeScript's, limited to those with one fixed meaning onchain. Check here what a program can write.

SauceScript's expressions and statements are TypeScript's, restricted to those with one fixed meaning onchain. Everything on this page works on EVM chains and on Solana unless it says otherwise.

## Operators

| Operators | Operands | Result |
| - | - | - |
| `+` `-` `*` `/` `%` `**` | two integers of the same kind | that kind; checked: overflow, underflow and division by zero revert, except in an unread element of an array literal indexed at a literal position with no call or token read at or after it, which is skipped ([Arrays](https://docs.eco.com/sauce/types.md#arrays)) |
| unary `-x` | an integer | the same kind; checked: reverts for a nonzero `Uint256` and for the `Int256` minimum |
| `~x` | an integer | bitwise NOT; `~0` is the maximum `Uint256` |
| `&` `\|` `^` `<<` `>>` | integers | `>>` fills with zeros on a `Uint256` and copies the sign bit on an `Int256` |
| `<` `<=` `>` `>=` | integers | bool; compares the numeric values, signed or not |
| `==` `===` `!=` `!==` | two numbers, two bools, or two accounts | bool |
| `!x` | a number or a bool | bool |
| `a && b`, `a \|\| b` | numbers or bools | bool; short-circuit |
| `c ? a : b` | `a` and `b` of the same type | that type |

```ts operators.sauce.ts theme={null}
function main(a: Uint256, b: Uint256): Uint256 {
  const spread = a > b ? a - b : b - a;
  const mixed = ((a + b) * 3) / 2 + (a % 7) + a ** 2;
  const bits = (a & b) | (a ^ b) | (a << 1) | (b >> 1);
  if ((a > 1 && b < 10) || !(a == b)) {
    return spread + mixed + bits;
  }
  return ~a;
}
```

* On a `Uint256`, `/` rounds down. `a / 0` and `a % 0` revert.
* `&&` and `||` evaluate the right operand only when needed, and produce `true` or `false`, never one of the operands. That holds for a contract name too: `UniswapV3.Factory || 0` is `true` where the chain has the factory, not its address.
* The two arms of `? :` must have the same type. `flag ? amount : true` is refused with "the two arms of `? :` must have the same type — the `then` arm is a number, the `else` arm is a bool".
* On an `Int256`, the operators are signed. See [Signed arithmetic](#signed-arithmetic).
* An array literal indexed at a literal position (or at a `const` bound to one) does not run every element. With no helper call, contract call or token read in it, only the element taken runs: `[1 / zero, 7][1]` returns 7 for `zero = 0`. With one, every element up to and including the last call or token read runs, in order, and a write inside a call happens: `[1 / zero, pot.USDC][1]` and `[f(1 / zero), 7][1]` revert. Elements after the last call are still skipped: `[pot.USDC, 1 / zero][0]` returns the Pot's USDC. A builtin such as `Math.mulDiv` is not a call: `[Math.mulDiv(1, 1, zero), 7][1]` returns 7. Simulated on a local fork of Base at block 53166500 with the current contracts. Put a check or write that must run in its own statement ([Arrays](https://docs.eco.com/sauce/types.md#arrays)).

### Precedence

Operators bind and associate as in TypeScript. Probed with constant expressions: `2 + 3 * 4` is `14`, `1 << 2 + 1` is `8`, `10 - 4 - 3` is `3`, `2 ** 3 ** 2` is `512`, and `1 | 2 ^ 3 & 6` is `1`. As in TypeScript, `-2 ** 2` is a syntax error ("Unexpected token"). Parenthesize it, in a function that returns an `Int256`:

```ts negative-power.sauce.ts theme={null}
function main(): Int256 {
  return -(2 ** 2);
}
```

In a function that returns a `Uint256`, negating a constant is refused with "negating this constant Uint256 value would produce a negative result; convert the operand with `Int256(value)` before negating it". Parenthesize where the reading is not obvious.

### Signed arithmetic

On `Int256` values, `/` truncates toward zero, `%` takes the sign of the dividend, `>>` copies the sign bit, and the comparisons order negative values below positive ones:

```ts signed-ops.sauce.ts theme={null}
// a / b, a % b, a >> 1 and a < b, for signed a and b.
//
//   sauce simulate signed-ops.sauce.ts --chain <chain> --owner <address> --args <a> <b>
//
//   a: Int256   any signed integer: -7
//   b: Int256   nonzero: 2
function main(a: Int256, b: Int256): Int256[] {
  return [a / b, a % b, a >> 1, a < b ? 1 : 0];
}
```

Simulated on a local fork of Base at block 52965000 with the current contracts:

| `a` | `b` | `a / b` | `a % b` | `a >> 1` | `a < b` |
| - | - | - | - | - | - |
| -7 | 2 | -3 | -1 | -4 | 1 |
| 7 | -2 | -3 | 1 | 3 | 0 |
| -7 | -2 | 3 | -1 | -4 | 1 |

On Solana, the `sauce` command's local copy (LiteSVM) of the cluster at slot 460264608 gives the first row's values too.

* `/` and `%` are checked as on `Uint256`, and one more division overflows: the minimum, −2²⁵⁵, divided by `-1`. `Int256.fromBits(word) / divisor` with `word` equal to 2²⁵⁵ and `divisor = -1` reverts on the Base fork, and the engine refuses it with `Overflow` on Solana (slot 460264648). Divided by `2`, the same value gives −2²⁵⁴.
* Arithmetic takes two operands of the same kind: `amount + delta`, with `amount` a `Uint256` and `delta` an `Int256`, is refused until one operand is converted with `Int256(...)` or `Uint256(...)`.
* A comparison takes both kinds and compares the numeric values: with `b = -1`, `b < a` is `true` for `a = 5` and for `a = 2²⁵⁶ − 1` on the same fork.

[Types](https://docs.eco.com/sauce/types.md#int256) covers the conversions, `Int256.fromBits` and signed values at a contract boundary.

## Assignment

`=` assigns. The compound forms `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, `&=`, `|=`, `^=`, `<<=` and `>>=` work on plain variables. `x++`, `x--`, `++x` and `--x` work as statements only.

```ts assignment.sauce.ts theme={null}
function main(a: Uint256): Uint256 {
  let i = a;
  i++;
  i--;
  i += 10;
  i *= 2;
  i <<= 1;
  return i;
}
```

These assignment forms are not supported:

| Not supported | Compiler message | Write instead |
| - | - | - |
| `const j = i++;` | `` `++`/`--` as a value (they are statement-only here — write `i += 1n`) is not supported in SauceScript `` | `const j = i; i += 1;` (`j` keeps the value before the increment) |
| `xs[0] += 1;` | a compound assignment to an array element is not supported in SauceScript | `xs[0] = xs[0] + 1;` |
| `order.amount += 1;` | a compound assignment to a tuple field is not supported in SauceScript | `order.amount = order.amount + 1;` |
| `b \|\|= a;` | ``the `\|\|=` operator (use `=` or a supported compound assignment) is not supported in SauceScript`` | `if (b == 0) { b = a; }` |

A `const` cannot be reassigned: `k = 2` after `const k = 1` is refused with "`k` is declared `const` and cannot be reassigned". A variable keeps the type of its first value: `x = true` after `let x = 1` is refused with "`x` cannot be reassigned a value of a different type — expected a number, found a bool".

## Conditionals

`if`, `else if` and `else` take a condition that is a bool or a number; a nonzero number is true.

```ts tiers.sauce.ts theme={null}
function main(amount: Uint256): Uint256 {
  if (amount < 1000000) {
    return 30;
  } else if (amount < 100000000) {
    return 10;
  } else {
    return 5;
  }
}
```

There is no `switch`: write an `if`/`else if` chain, as `tiers.sauce.ts` does.

## Loops

| Loop | Form |
| - | - |
| `while (cond) { … }` | runs while `cond` is true or nonzero |
| `for (init; test; update) { … }` | C-style; `init` may declare several variables, `let i = 0, j = 1` |
| `for (const x of xs) { … }` | each element of an array, in order |

`break` and `continue` work in each, without labels.

```ts loops.sauce.ts theme={null}
function main(amounts: Uint256[], cap: Uint256): Uint256 {
  let total = 0;
  for (const amount of amounts) {
    total += amount;
  }
  for (let i = 0; i < amounts.length; i++) {
    if (amounts[i] == 0) {
      continue;
    }
    if (amounts[i] > cap) {
      break;
    }
    total += amounts[i];
  }
  let rounds = 0;
  while (rounds < 3) {
    rounds++;
  }
  return total + rounds;
}
```

`for...of` walks an array only; on a bytes value it is refused with "`for`...`of` can only walk an array — this is a bytes value". Index a bytes value instead:

```ts bytes-sum.sauce.ts theme={null}
function main(b: bytes): Uint256 {
  let total = 0;
  for (let i = 0; i < b.length; i++) {
    total += b[i];
  }
  return total;
}
```

| Not supported | Write instead |
| - | - |
| `do { … } while (cond)` | `while (true) { …; if (!cond) { break; } }` |
| `for (const k in xs)`, `for await` | `for (const x of xs)`, or an index loop |
| labeled `break` and `continue` | a flag variable checked by the outer loop |
| `for (i = 0, j = 1; …)` (the comma operator) | separate statements before the loop |

A `do`/`while` loop, written with `while (true)`:

```ts do-while.sauce.ts theme={null}
function main(n: Uint256): Uint256 {
  let i = 0;
  while (true) {
    i += 1;
    if (i >= n) {
      break;
    }
  }
  return i;
}
```

### What a loop costs

On an EVM chain the engine interprets the program step by step, so each iteration costs gas. On a local fork of Base at block 52965000 with the current contracts, the `for` loop below cost 50475 gas for the cook with `n = 0` and 141487 with `n = 100`: about 910 gas per iteration. The same loop that also writes `xs[i]` in each iteration costs about 480 gas more per iteration than an empty loop, and one that adds `xs[i]` to the total about 320 more than this one.

```ts loop-gas.sauce.ts theme={null}
function main(n: Uint256): Uint256 {
  let total = 0;
  for (let i = 0; i < n; i++) {
    total = total + i;
  }
  return total;
}
```

Bound every loop by its input, and read a value into a local once rather than in each iteration. [cook()](https://docs.eco.com/sauce/cook.md) covers the rest of a cook's gas.

## Destructuring

`const [a, b] = …` binds the fields of a record by position. A multi-output contract call, `abi.decode` and a record value each produce one. This example reads a Uniswap V3 pool, so it compiles for EVM chains only:

```ts destructuring.sauce.ts theme={null}
function main(pool: Address, data: bytes): Uint256 {
  const [sqrtPriceX96, tick] = UniswapV3.Pool(pool).slot0();
  const [amount, to] = abi.decode(data, ["Uint256", "Address"]);
  const order = { size: amount, fee: 30 };
  const [size, fee] = order;
  return sqrtPriceX96 + size + fee;
}
```

* A hole skips a field: `const [, tick] = UniswapV3.Pool(pool).slot0();`. Taking fewer names than fields takes the leading ones.
* `let [x, y] = …` gives variables that can be reassigned.
* An array is indexed, not destructured: `const [a, b] = xs` is refused with "a destructuring binding needs an initializer that produces a tuple — this one does not, so bind it to one name instead". Write `xs[0]` and `xs[1]`.
* Object destructuring, `const { fee } = order`, is not supported: write `const fee = order.fee;`.

## Methods and positions

A method is called on a value, and the value may be a literal:

```ts literal-methods.sauce.ts theme={null}
function main(amount: Uint256): bytes {
  return "ab".concat((5).toBytesBe(2), amount.toBytesBe(1));
}
```

On a local fork of Base at block 52965000 with the current contracts, `amount = 7` returns `0x6162000507`: `"ab"`, then 5 in two bytes, then 7 in one.

A record's field is read by name, `order.amount`, on every record. A constant position, `order[0]`, reads a record that comes from a `main` parameter, an array element, a contract call, `abi.decode` or a function's result. An object literal the function builds is read by name or destructured; a position on it is refused. [Types](https://docs.eco.com/sauce/types.md#records) shows both cases and their messages.

## Constant folding

The compiler evaluates an expression whose operands are all known when it compiles: literals, module constants, and the names the chain supplies. The result is written into the bytecode as one value.

```ts folded.sauce.ts theme={null}
const FEE_BPS = 30;

function main(amount: Uint256): Uint256 {
  return (amount * (10000 - FEE_BPS)) / 10000;
}
```

This compiles to the same bytes as `(amount * 9970) / 10000`. Folding follows the type: `0 - 1` is refused as a `Uint256` and is `-1` as an `Int256` ([Types](https://docs.eco.com/sauce/types.md#checked-arithmetic)). A constant expression that overflows, underflows or divides by zero is a compile error rather than a revert: with `const SHARES = 0`, `100 / SHARES` is refused with "constant `/` expression divides by zero".

### Testing a name

A registry name the chain lacks is zero there, and a test of it is decided when the program compiles. A branch the test rules out is removed, with the names it uses, so the program compiles for a chain that lacks the name:

```ts presence-test.sauce.ts theme={null}
function main(tokenA: Address, tokenB: Address): Address {
  if (UniswapV3.Factory) {
    return UniswapV3.Factory.getPool(tokenA, tokenB, 500);
  }
  return 0;
}
```

This compiles for Base, Ethereum and Solana. Without the `if`, it fails on Solana, where the registry records no Uniswap V3 factory, with "`UniswapV3.Factory` is not deployed on solana. …". A test is the name under `if`, `!`, `? :`, `&&`, `||`, `== 0` or `!= 0`; a decision held in a variable is not one. [Names](https://docs.eco.com/sauce/names.md#using-a-name-only-where-the-chain-has-it) covers which uses count as tests. A condition built from other known values is removed in the same way; [Functions](https://docs.eco.com/sauce/functions.md#compile-time-specialisation) shows it.

## Not supported

| Not supported | Write instead |
| - | - |
| `switch` | `if` / `else if` chains |
| `do`/`while`, `for...in`, `for await` | `while`, an index loop, `for...of` |
| template literals `` `text` ``, `` `a${b}` `` | `"text"`, or bytes built with `.concat` |
| comparing bytes with `==` | `crypto.keccak256(a) == crypto.keccak256(b)` |
| spread `[...xs]`, `f(...xs)` | explicit elements, or a loop |
| `?.`, `??` | an explicit check with `if` or `? :`; a `Uint256` is never null, so where zero means unset write `a == 0 ? 1 : a` |
| `>>>` | `>>` on a `Uint256` (it fills with zeros) |
| `typeof`, `instanceof`, `in`, `delete`, `void`, unary `+` | plain values |
| `\|\|=`, `&&=`, `??=`, `>>>=` | `x = …` |
| arrow functions, other than a `.catch` handler and a `program(...)` closure | top-level `function` declarations |
| the comma operator | separate statements |

## Next steps

* [Functions](https://docs.eco.com/sauce/functions.md): helpers, return paths and call cost.
* [Errors and reverts](https://docs.eco.com/sauce/errors.md): `require`, `throw`, `try`/`catch` and `.catch`.
* [Types](https://docs.eco.com/sauce/types.md): what each operator accepts.
* [Names](https://docs.eco.com/sauce/names.md): token symbols, namespaces, and testing whether the chain has a name.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.