Skip to main content
Compiling a program produces bytecode for the V12 engine on one chain. The compiler checks the program, resolves each name to its value on that chain and folds it into the bytes. The result is one reusable program: the values of main’s arguments are not in it, and are appended to it for each cook.

What compiling produces

pay.sauce.ts
The compiler package’s own compile() returns bytecode, isaRevision, entryEncoding, entryParams, entrySignature, programs, the chain it was compiled for when one was named, and, on Solana, manifest:
  • entryParams lists main’s parameters as ABI parameters. A top-level parameter that is a tuple of fixed size carries indirect: true: encode it as a dynamic parameter, behind an offset word. Encoded inline, the cook reverts with no data.
  • entrySignature is main’s signature as the program declares it: each parameter’s type in SauceScript’s names, and the return type. A host encodes main’s arguments and decodes what it returns from it without reading the source.
  • programs lists each closure the compile built as a separate program with program(closure, ...args): its bytecode, the chain and target it was compiled for, and the values it captures.
The compiler reference documents each field.

Names are part of the bytes

USDC above compiles to Base’s USDC address, so the same source gives different bytes on another chain. sauce inspect reports a compiled program’s size and keccak256:
A literal is part of the bytes too. Compiled for Base, USDC.transfer(to, 5000000) and USDC.transfer(to, 6000000) give programs of the same size with different hashes (0x33b1…2737 and 0x1cb2…7109). A value that changes from one cook to the next belongs in a main parameter. add.sauce.ts below uses no chain-specific name, and compiles to the same 45 bytes, with the same hash, on Base and on Ethereum.

Entry arguments

main’s arguments are encoded after the bytecode, in declaration order. Together they are the program argument of cook(). One compiled program serves every argument value, and its bytes and hash stay the same. The encoding is ABI-v1, the only one: the values are ABI-encoded with no function selector, because entry arguments are not a contract call. Each parameter takes one 32-byte head word, so a record parameter sits behind an offset word even when all its fields have a fixed size; a fixed-size record inside an array or another record is written inline, as abi.encode writes it.
add.sauce.ts
Compiled for Base with --args 7 35, the program is 45 bytes and the arguments 64, two words: payload reports 109 bytes in all. On Solana, Account parameters are not arguments: they are the accounts in the manifest, attached to the transaction. An Address argument on Solana is one 32-byte word, the whole public key. The encoding covers only what reaches main: the calls the program makes use each contract’s own ABI.

Size

On an EVM chain the program with its arguments is the program argument of the cook, in the transaction’s calldata. sauce compile --json reports its size. On Solana a transaction holds at most 1,232 bytes. sauce compile --json measures the cook transaction against that limit, as the Kitchen’s cook or, with --via engine, the engine’s direct one. For pay.sauce.ts (abridged):
Through the Kitchen, that cook is 1,266 bytes and does not fit. --via engine refuses this program, because it names pot and a direct cook on the engine has no Pot; a program that never names pot derives its accounts from authority (authority:USDC) and sizes either way. A cook that does not fit is run in one of two ways:
  • Staging. The program is first written into a code buffer of the Kitchen, in several transactions, and the cook then runs it from the buffer. The Kitchen runs only a finalized buffer, and finalizing checks the SHA-256 digest of the bytes written. A cook directly on the engine carries the digest itself, and the engine runs exactly the bytes it hashed. After the cook the buffer is closed and its rent returned. In a simulation in the sauce command’s local copy (LiteSVM) of the cluster at slot 460261665, pay.sauce.ts was staged in 3 transactions, and the buffer’s rent was 5,618,480 lamports, returned when the buffer is closed.
  • Lookup tables. Each account a transaction lists takes 32 bytes. An address lookup table is a list of addresses kept on chain: a transaction points into it and spends one byte per account. A table holds rent until its owner closes it.
sauce run stages a program and creates a lookup table when a cook needs them, and sauce tx prints the plan of transactions. A code buffer holds at most 1,048,576 bytes.

Reproducible builds

The same source, compiled for the same chain by the same compiler, gives the same bytes. Compiling pay.sauce.ts for Base twice gives the keccak256 0x2de5…9846 both times; compiling it for Ethereum gives 0x820b…8d82. A different compiler release can emit different bytes for an unchanged source. Anything that pins bytes, such as a stored program hash, a cached program or a program staged on Solana, has to be derived again when the compiler changes. To rebuild a program you keep, keep with it the compiler’s release, the chain, the source and every module it imports, the ABIs, and the arguments. sauce compile --json reports the compiler in compiler. The settlement program the SDK ships, which sweeps a Pot’s tokens to one recipient with a minimum, shows it. The current compiler builds it into 410 bytes with the keccak256 0x65e8…449b, and that pair is the pin the SDK’s verifier checks by default. A payload built by another compiler build carries its own hash and length. The verifier accepts it only when you pass that build’s { hash, bytes } pin explicitly, beside the current one, or --pin <hash:bytes> to sauce verify.

Checking what a cook runs

sauce inspect takes a cook apart. Given the cook’s calldata and the source file, it recompiles the source for the chain, compares the program, and decodes the arguments against main:
The 287 bytes are the 223-byte program and two 32-byte arguments. When the program does not match, the line says so, here for the same cook checked against a compile for Ethereum:
The command exits 0 in both cases: read the line. A transaction hash with --chain is fetched first, so a sent cook can be checked the same way (sauce inspect <tx hash> --chain base --source pay.sauce.ts). Inspect reports a program by its size and hash; it prints no instruction listing.

Next steps