> ## 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.

# Functions

> How to split a SauceScript program into helper functions, for anyone writing more than a main.

A program's functions are `main` and any helpers it declares. A helper is a top-level `function` declaration with annotated parameters and, where it returns something, an annotated return type.

```ts helpers.sauce.ts theme={null}
// Pays `amount` of the chain's USDC to `to`, less a fee of `feeBps` basis points kept in the Pot.
//
//   sauce run helpers.sauce.ts --chain <chain> --wallet <name> --args <to> <amount> <feeBps>
//
//   to: Address        the recipient
//   amount: Uint256    in USDC's smallest unit (6 decimals): 5000000 is 5 USDC
//   feeBps: Uint256    the fee in basis points, at most 10000: 30 is 0.30%
function afterFee(amount: Uint256, feeBps: Uint256): Uint256 {
  require(feeBps <= 10000, "fee above 100%");
  return amount - (amount * feeBps) / 10000;
}

function main(to: Address, amount: Uint256, feeBps: Uint256) {
  USDC.transfer(to, afterFee(amount, feeBps));
}
```

## Declarations

* Functions are declared at the top level, never inside another function or a block.
* Functions may call each other in any order, and may recurse.
* Arrow functions and function expressions are rejected, except as the handler of `.catch(...)` (see [Errors and reverts](https://docs.eco.com/sauce/errors.md)) and as the closure of [`program(...)`](#programs-built-from-a-closure).
* Default parameters, rest parameters and destructured parameters are rejected.
* `require`, `revert`, `log` and `eval` are reserved names, and so are the type names `Uint256` and `Int256` ([Types](https://docs.eco.com/sauce/types.md#reserved-names)).
* A call names a declared function and passes one argument per parameter.

Breaking one of these rules is refused:

| Mistake | Compiler message | Write instead |
| - | - | - |
| `function half(x: Uint256): Uint256 { … }` inside `main` | a nested function declaration is not supported in SauceScript | Declare `half` at the top level |
| `function fee(amount: Uint256, bps: Uint256 = 30)` | a default parameter value is not supported in SauceScript | Take `bps` as a parameter and pass `30` |
| `function log(amount: Uint256): void {}` | `` `log` is a reserved builtin and cannot be a function name `` | Pick another name |
| `return helper(amount);` with no `helper` declared | `` `helper` is called but never declared `` | Declare `helper` |
| `return f(1, 2);` where `f` takes one parameter | `` `f` takes 1 argument(s), but this call passes 2 `` | Pass one argument per parameter |

```ts recursion.sauce.ts theme={null}
function factorial(n: Uint256): Uint256 {
  if (n <= 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

function main(n: Uint256): Uint256 {
  return factorial(n);
}
```

`factorial(n)` reverts once the product passes 2²⁵⁶ − 1, because `*` is checked.

## `main`

`main` is the entry point. Its parameters are the program's inputs and its return value is what the cook returns; see [Layout of a program](https://docs.eco.com/sauce/program-layout.md). Code cannot call it: `return main(a - 1);` fails with "the entry point (`main`) cannot be called". Put the recursive logic in a helper, as `factorial` above does.

## Parameters and annotations

A parameter without an annotation is a `Uint256`. Passing a bool to one, as in `pick(useAmount, amount)` with `function pick(flag, amount: Uint256)`, fails at the call site with "a function argument: expected a number, found a bool". Annotate the parameter: `flag: bool`.

A function other than `main` has a fixed budget for its parameters and the values its body holds at once: about sixteen. A helper with sixteen parameters that reads each once compiles. Seventeen parameters with the first read twice do not: the compile fails with "`sum` keeps too many values in use at once — split it into smaller functions, take fewer parameters, or build a large literal from fewer variables at a time". Pass a record or an array instead of a long list of numbers. `main`'s parameters are not limited this way.

## Return types

| Declared return | Returns |
| - | - |
| `: Uint256`, `: Address` | a number |
| `: Int256` | a signed number; required for one in a function other than `main` |
| `: bool` | a bool |
| `: bytes`, `: T[]`, a record type | that value; required for these in a function other than `main` |
| `: void` | nothing; a bare `return;` is allowed |
| none | a `Uint256`, even when the body returns a comparison or `true` |

A comparison is a bool, but a function with no return annotation returns a number, 1 or 0. `if`, `while` and `!` accept that number, but a `: bool` function cannot return it: `return isLarge(amount);` in a `: bool` `main`, where `isLarge` has no annotation, fails with "a return value: expected a bool, found a number". Give a helper that answers yes or no a `: bool` return type:

```ts annotated-bool.sauce.ts theme={null}
// Selling token0 (the lower address) pushes a pool's price down, so a higher price is better.
function isBetter(price: Uint256, than: Uint256, sellsToken0: bool): bool {
  if (sellsToken0) {
    return price > than;
  }
  return price < than;
}

function main(price: Uint256, than: Uint256, tokenIn: Address, tokenOut: Address): bool {
  return isBetter(price, than, tokenIn < tokenOut);
}
```

A helper that returns nothing is declared `: void`. Without the annotation it is expected to return a number, and the compile fails with "`pay` does not return on every path". The result of a `: void` call is not a value: `const result = check(amount);` fails with "a call to a `: void` function produces no value, so its result can't be used as one — call it as a statement instead".

```ts void-helpers.sauce.ts theme={null}
function check(amount: Uint256): void {
  require(amount > 0, "zero");
}

function pay(to: Address, amount: Uint256): void {
  check(amount);
  USDC.transfer(to, amount);
}

function main(to: Address, amount: Uint256) {
  pay(to, amount);
}
```

A helper that returns bytes, an array or a record needs its return type. Without it, `return amount.toBytesBe(32);` fails with "returning a bytes value from a function other than `main` needs a return-type annotation". Write `: bytes`:

```ts bytes-return.sauce.ts theme={null}
function encodeAmount(amount: Uint256): bytes {
  return amount.toBytesBe(32);
}

function main(amount: Uint256): bytes {
  return encodeAmount(amount);
}
```

A helper that returns an `Int256` needs `: Int256`. Without it, the compiler says "a helper returning a signed integer needs an explicit `: Int256` return annotation". With the annotation:

```ts signed-helper-fixed.sauce.ts theme={null}
// How far `after` is above `before`: negative when it is below.
function change(before: Uint256, after: Uint256): Int256 {
  return Int256(after) - Int256(before);
}

function main(before: Uint256, after: Uint256): Int256 {
  return change(before, after);
}
```

On a local fork of Base at block 52965000 with the current contracts, `before = 10` and `after = 7` return `-3`.

### Return on every path

A helper that returns a value returns it on every path. An `if` with a `return` and no `else`, or a loop that can end without one, is refused: `larger` with only `if (a > b) { return a; }` fails with "`larger` does not return on every path". End the helper with a `return`:

```ts with-return.sauce.ts theme={null}
function larger(a: Uint256, b: Uint256): Uint256 {
  if (a > b) {
    return a;
  }
  return b;
}

function main(a: Uint256, b: Uint256): Uint256 {
  return larger(a, b);
}
```

## Arguments: by reference and by copy

Numbers, bools, addresses and bytes are copied into a helper. Arrays and records are passed by reference: a helper that writes `xs[i] = v` or `r.field = v` changes the caller's value.

```ts by-reference.sauce.ts theme={null}
function bump(xs: Uint256[], order: { amount: Uint256 }, n: Uint256): void {
  xs[0] = xs[0] + 1;
  order.amount = order.amount + 1;
  n = n + 1;
}

function main(): Uint256 {
  let xs = new Array(2);
  let order = { amount: 5 };
  let n = 7;
  bump(xs, order, n);
  return xs[0] * 100 + order.amount * 10 + n;
}
```

On a local fork of Base at block 52965000 with the current contracts, this returns `167`: `xs[0]` became 1 and `order.amount` 6, while `n` stayed 7.

A helper fills an array it is given, but cannot replace it with a new array. `xs = [1, 2];` in a helper with `xs: Uint256[]` fails with "`xs` cannot be reassigned a value of a different type — expected an element count known only at run time, found 2 element(s)". Write the elements instead: `xs[0] = 1;`.

## A script's constants are not visible to helpers

In a top-level script, a top-level `const` is a local of the script's entry. A helper cannot read it: `return x > limit;` in a helper, with `const limit = 5;` at the top of the script, fails with "`limit` is not a declared variable". Pass the value as an argument, or declare `main` so that `const limit = 5;` becomes a module constant every function reads:

```ts script-scope.sauce.ts theme={null}
const limit = 5;

function over(x: Uint256): bool {
  return x > limit;
}

function main(x: Uint256) {
  require(over(x), "no");
}
```

## Compile-time specialisation

The compiler evaluates what it knows when it compiles, through records and helper calls as well as constants:

* A record built from an object literal keeps its known fields as constants, and a condition on them is decided when compiling.
* A helper called with a known argument is compiled for that argument: a branch the argument rules out is removed.

Both programs below compile, for Base, Ethereum and Solana, to the same bytes as a `main` written without the record or the helper:

```ts specialise-record.sauce.ts theme={null}
// The same bytes as `return amount;`.
function main(amount: Uint256): Uint256 {
  const order = { amount: amount, fee: 30 };
  if (order.fee == 30) {
    return order.amount;
  }
  return 0;
}
```

```ts specialise-helper.sauce.ts theme={null}
// The same bytes as `return (amount * 5) / 10000;`.
function feeFor(tier: Uint256, amount: Uint256): Uint256 {
  if (tier == 0) {
    return (amount * 5) / 10000;
  }
  return (amount * 30) / 10000;
}

function main(amount: Uint256): Uint256 {
  return feeFor(0, amount);
}
```

With `tier` a `main` parameter instead, `feeFor(tier, amount)` keeps both branches and the call: the program is 83 bytes on Base against 43. [Expressions and control flow](https://docs.eco.com/sauce/expressions-and-control-flow.md#constant-folding) covers constants and tests of a registry name such as `if (UniswapV3.Factory)`. A helper is checked as if it ran: a registry name the chain lacks, used in a helper, must be tested in that helper, or the program is refused on that chain.

## Programs built from a closure

`program(closure, ...args)` compiles an arrow function as a separate program, with one argument after the closure per closure parameter. It returns a record `{ code, args }`: `code` is that program's bytecode, and `args` the encoded run-time values it takes. `eval(code.concat(args))` runs the program read-only and returns what it returns, as bytes.

```ts program.sauce.ts theme={null}
// Divides `delta` by 2 in a program built from a closure, and runs it.
//
//   sauce simulate program.sauce.ts --chain <chain> --owner <address> --args <delta>
//
//   delta: Int256   any signed integer: -10
function main(delta: Int256): bytes {
  const nested = program((divisor: Int256): Int256 => delta / divisor, 2);
  return eval(nested.code.concat(nested.args));
}
```

It compiles for Base, Ethereum and Solana. With `delta = -10`, it returns the 32-byte word for `-5`, `0xff…fb`: on a local fork of Base at block 52965000 with the current contracts, and in the `sauce` command's local copy (LiteSVM) of Solana at slot 460264641.

The closure reads `delta`, a variable of `main`. That is a capture: the value is passed to the new program, which can read it and cannot change it. Here `nested.args` is the 32-byte word of `delta`, and the `2`, known when compiling, is in `nested.code`. Without the capture (`7 / divisor`), `nested.args` is empty. Both were read on the same fork.

* A constant the closure reads is compiled into its code: with `const FEE = 30;` at module level, `(x: Uint256): Uint256 => x * FEE` compiles to the same bytes as `x * 30`, for Base, Ethereum and Solana.
* A capture is read-only. Assigning to it in the closure (`amount = amount + fee;`) is refused with "`amount` belongs to the program around this closure: the closure's program reads it, and can neither assign it nor change what it holds". Compute the new value in the closure and return it.
* A run-time `bytes` value cannot be captured. Reading `data.length` in a closure, where `data` is a `bytes` parameter of `main`, is refused with "`data` is a run-time `bytes` of the program around this closure, which the closure's program cannot read: it reads a number, an address or a bool, and the values the compiler knows". Pass what the closure needs, such as `data.length`, as an argument after the closure.

## What a call costs

On an EVM chain, a helper is either compiled into its caller or called in a call frame of its own. A helper whose body is straight-line arithmetic, assignments and one `return`, with no `if`, loop or contract call, is compiled into its caller where that keeps the program small. Every other helper keeps its frame. Simulated on Base at block 52997759, each program ran a loop of 100 iterations over `main`'s run-time arguments, and each helper is measured against the same code written inline:

| Helper body | Compiled into the caller | Gas per call, helper minus inline |
| - | - | - |
| `return x + y;` | yes | 53 |
| `return Math.mulDiv(x, y, 10000);` | yes | 53 |
| `return x / 2;` | yes | 110 |
| `return (x * y) / 10000;` | yes | 113 |
| `const f = (x * y) / 10000; return x - f;` | yes | −82 (the helper is cheaper) |
| a `let`, an `if` and a `return` | no | 405 |
| the same, called twice an iteration | no | 402 |
| a `for` loop and a `return` | no | 342 |
| `return USDC.balanceOf(who);` | no | 405 |

A helper compiled into its caller cost at most 113 gas a call more than the code written out, and one cost less. A helper with its own frame cost 342 to 405 gas a call more. A helper called with known arguments can be compiled away ([above](#compile-time-specialisation)). `@sauce/router` keeps each exported function self-contained: its functions branch and call contracts, so a helper inside one would keep its frame.

Call what already exists directly: `USDC.transfer(to, amount)`, `UniswapV3.Factory.getPool(WETH, USDC, 500)`, a `@sauce/router` function. A helper that wraps a single contract call keeps its frame, 405 gas a call in the table, and adds nothing else. Keep a helper for logic of its own that the program uses more than once.

## Library functions

`@sauce/token` and `@sauce/router` export functions that a program imports by name and calls like its own:

```ts library-calls.sauce.ts theme={null}
import { balanceOf, transfer } from "@sauce/token";

// Sends the Pot's whole balance of `token` to `to`.
function main(token: Address, to: Address) {
  const held = balanceOf(token, pot);
  require(held > 0, "nothing to send");
  transfer(token, to, held);
}
```

`USDC.transfer(to, amount)` and `Token(address).transfer(to, amount)` are calls to the same `@sauce/token` function, so a token write has one implementation however it is spelled. [`@sauce/token`](https://docs.eco.com/sauce-libraries/token) lists its functions.

## Next steps

* [Errors and reverts](https://docs.eco.com/sauce/errors.md): `require`, `throw`, `try`/`catch`, `.catch` and compile errors.
* [@sauce libraries](https://docs.eco.com/sauce-libraries/overview.md): `@sauce/token` and `@sauce/router`.
* [Layout of a program](https://docs.eco.com/sauce/program-layout.md): `main`, scripts and module constants.
* [cook()](https://docs.eco.com/sauce/cook.md): the gas of a whole cook.


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