Skip to main content
V12 is the Sauce engine, the interpreter that executes the bytecode the Sauce compiler emits. It is deployed as a contract on EVM chains and as a program on Solana. The bytecode is V12’s own instruction set: it is not EVM code and not a Solana program, and only a V12 engine runs it. On an EVM chain you never call the engine: a cook names it, and your Pot runs it. On Solana a cook goes through the Kitchen, which calls the engine, or straight to the engine’s own instruction. The sauce command and the SDK name the released engine in every cook they build.

How a program executes

  • The engine runs the program’s steps in order, inside the transaction that carries it: the cook on an EVM chain, the cook instruction on Solana.
  • Every contract call the program makes is a call inside that transaction. On an EVM chain it comes from the Pot.
  • An uncaught failure reverts the whole transaction: a failed require, a call that fails, an arithmetic error. On EVM chains a try/catch block rolls back its own calls, writes and assignments and runs its handler instead; work before the block remains. Solana has no catch.
  • Arithmetic on numbers is checked. Overflow, underflow and division by zero revert as the program runs; with constant operands they are compile errors. The exception is a call-free array literal indexed at a literal position, where only the element taken is evaluated (Arrays).
  • With a helper call, a contract call or a token read (pot.USDC, USDC.balanceOf(pot)) in an array literal, that call and the elements ahead of it run. [1 / zero, f()][1] and [f(1 / zero), 7][1] revert for zero = 0; a write inside f happens when execution reaches it.
  • What main returns is the engine’s output. On an EVM chain it is the return data of cook().
checked.sauce.ts
Simulated with --args 7 5 and with --args 5 7, on a local fork of Base at block 52965000 with the current contracts:
The underflow carries no reason string. A require(condition, "message") before the subtraction gives the revert a message.

Where the engine runs

On EVM chains

The engine runs only inside a Pot that its Kitchen created, with the Pot code the engine was built for. The release records both anchors: the Kitchen’s address and the Pot init-code hash. Called directly, the engine refuses. A cook on a Pot is the only way to run a program on an EVM chain. A Pot holds no engine. Each cook names the engine it runs on, as the first argument of cook(engine, program). While the cook runs, the Pot’s inFlightEngine() returns that engine; outside a cook it returns the zero address. A Pot’s address follows from the Kitchen, the owner and the salt, so a Kitchen whose Pot code differs gives every owner different Pot addresses. An engine runs only the Pots of the Kitchen it is paired with and refuses any other (Pot). A compiler-only release that keeps the Kitchen keeps every Pot address.

On Solana

Two entry points run the same bytecode with the same account indices: Both read the fee from the Kitchen’s fee account and pay it into that account. cook() on Solana covers the instructions, compute and call depth.

Instruction set label

The compiler labels each artifact with isaRevision. In the compiler’s typings the only value is "stack-v3", an opaque label. The label is not a compatibility check. Two engine releases can report the same label while only one of them runs a given compiler’s output, and the compiler does not read a chain to find what is deployed there.

Compiler and engine pairing

Each SDK pins an approved compiler, engine and Kitchen release set. A compiler’s programs run only on the engine releases recorded for that compiler: a new compiler release can emit different bytecode for the same source, which an engine not recorded for it can misread, and an older compiler’s programs can fail on a newer engine. More than one compiler version can share the same engine and Pot family: two release sets can name the same engines and Kitchens, and V12_RELEASE_SETS lists them. Use the installed SDK’s release set rather than assuming that every compiler upgrade changes the engine or Pot address. The SDK pins one compiler. For each compiler release it records a release set: the EVM engine, the EVM Kitchen, the Solana engine and the Solana Kitchen that the compiler’s output runs on, the digest of each released asset, and each engine’s address or program id. The engines the SDK names and the engine artifacts it ships are those of the installed compiler’s set. When you send a cook:
  • Compile with the version that sends the cook. Bytecode from another compiler runs only on the engines its own release set records. The sauce command compiles the file you give it each time.
  • Build unsigned transactions with the installed SDK: a cook that sauce tx printed names the engine of the release set that built it.
  • sauce pot list shows the release each recorded Pot was created under.
Every SDK builder that names an engine calls assertEngineRunsCompiler(target, engine) first: v12Pot.encodeCook on EVM chains, and on Solana the Kitchen’s cook instructions and the engine’s own. It throws for an engine that the release sets record only for other compilers, and its message names the engine the installed compiler’s output needs. An engine that no release set records, such as one you deployed yourself, passes: the SDK has nothing to check it against. sauce compile --json names the compiler that produced the program and the engine release that runs it (abridged):
For Solana the engine is named by its program id:

Addresses

The engine has the same address on every EVM chain it is deployed on, and the same program id on every Solana cluster. Deployments lists them. A recorded address is not proof that code is there. sauce pot reads whether the engine and the Kitchen are deployed on a chain:
That read was on a local fork of Base at block 52965000 with the current contracts.

In TypeScript

@eco-incorp/sauce/deployments exports the release’s identities. It reads no chain. The execution Router is content-addressed: its address follows from its code, so a Router with other code has another address. Compare Routers by address with v12RouterAddress().
The package reference documents each export.

Next steps

  • Kitchen: the contract and program that create Pots and charge the fee.
  • Pot: the account a program runs in.
  • Bytecode: what the compiler emits, and how to check that a cook carries it.
  • cook(): the call that runs a program.