@eco-incorp/sauce/testing runs your programs from a test, as cooks on a chain only the test can see. local() starts an empty chain; fork() starts a copy of a real chain at a pinned block, recorded to a file once and replayed from it with no network. Each chain has the V12 release deployed and ten test accounts, each with a Pot, and the kit gives you calls to fund accounts, run or simulate a cook, read balances and check what moved.
The kit runs in Node only. It reads no environment variable: the endpoint, the block and the state file are arguments. Its functions are plain async functions, so any test runner works; the examples use node:test.
Install
-
EVM chains run on Foundry’s
anvil, which must be onPATHor named by theanviloption. Install Foundry as the Foundry installation guide describes: -
Solana runs in LiteSVM, inside the test’s process. Install
litesvmin your project at the version the package pins. Starting a Solana chain without it throws an error that names the install command:
Test a program that calls nothing
A program that calls no protocol and moves no token needs no copy of a real chain.local() starts an empty one with no network.
double.sauce.ts
double.test.ts
chain.run sent a cook from test account 0’s Pot and returned what main returned. chain.simulate ran the same cook without sending it; the require failed, so it threw CookReverted with the require’s message as reason. Starting a chain takes seconds, so give each test and hook a timeout: node:test has no default limit.
Test against a copy of a chain
A program that reads or moves a token needs a chain where the token exists. This test forks Base at a pinned block and runs a payment program on it:pay.sauce.ts
pay.test.ts
rpcUrl, here viem’s default Base endpoint, and stop() writes every account and storage slot the tests read to test/state/pay-base.json.gz:
anvil installed and no endpoint or secret.
base.fund(pot, { USDC: "5" })gave account 0’s Pot 5 USDC. A string is whole tokens; abigintis base units.result.delta(holder, "USDC")is how much a holder’s balance changed during the cook, in base units: 1,000,000 is 1 USDC.snapshot()before each test andrevert()after it return the chain to the same state, so the second test’s Pot holds no USDC.
fork
Starts a copy of a chain at a pinned point: on an EVM chain,anvil at options.block; on Solana, LiteSVM. The release’s engine, Router and Kitchen are ready, ten test accounts exist, and account 0’s Pot is deployed.
Import
Signature
Parameters
"auto" picks the first row that applies. Passing a mode forces it: "record" records the file again, and "replay" refuses to start without it:
Returns
ATestChain, or a SolanaTestChain when chain is Solana.
local
Starts an empty chain: the release deployed at its addresses, ten test accounts, account 0’s Pot, no Kitchen fee, and no protocols or tokens. It needs no network.Import
Signature
Parameters
local({ chain: "solana" }) starts an empty Solana in LiteSVM with the engine, the Kitchen, the runtime’s own programs and wrapped SOL.
Returns
ATestChain, or a SolanaTestChain for Solana.
TestChain
Whatfork and local return on an EVM chain.
fund writes a token balance into the token’s own storage, in the slot its balanceOf reads; the total supply does not change. It refuses a token whose balance is computed rather than stored, such as a rebasing token, and a Base B20 token, a node precompile anvil cannot run. Send such a token from a holder with impersonate.
CookOptions
A program is a
ProgramInput: a path relative to the working directory, a file: URL, or { source, path? }. It compiles with the chain’s names, and its relative imports resolve beside the file.
CookResult
run reports the transaction it mined: returned and a revert’s reason come from the node’s trace of that transaction. It sends with the pending block’s gas limit as its budget, with no gas estimate and no simulation first, so a cook that is valid only in the block it is mined in is still sent. gasUsed is the gas the cook consumed, not that budget; unused gas is not charged. A reverted cook takes a block and a nonce like any transaction. Call simulate for a result without sending.
CookReverted
The error a cook that reverts throws, on EVM chains and on Solana.Recordings
A replay boots from the state file at the pinned block’s number and timestamp. The file holds every account and storage slot the recording run read, at their values in that block, plus the block’s gas limit, the hardfork, the next block’s base fee and the test accounts’ nonces, so a replayed cook uses the same gas and is the same transaction. Each block after the pinned one isblockInterval seconds later, so no result depends on the wall clock.
A replay knows only what the recording read: an account or slot read for the first time reads as zero. After a test starts reading something new, such as another token or pool, or after you move the block, record again: delete the file or pass mode: "record". The state file never contains the endpoint’s URL; the kit refuses to write one that does.
Solana
On Solana,fork and local return a SolanaTestChain. Its calls match TestChain’s, with these differences:
fork("solana", { state, rpcUrl, mode, zeroFee })takes noblock: a Solana node serves accounts only as they are now, so a recording is of the moment you record. The file keeps that slot, its time, and the cluster’s rent and epochs, and holds each account the test’s transactions named.- Test accounts are keypairs:
account(i)hasaddress,secretKeyandpot, and each starts with 1,000 SOL. The keys are the same on every run, so a recording made with them replays with them. runandsimulatetakeaccounts,main’sAccountparameters by name, in the wordssauce run --accounttakes (pot,pot:USDC,@to:USDC,mint:USDC,program:USDC, an address). The accounts the SDK adds for token methods are derived.- The result is a
SolanaCookResult:returned,returnData,changes,delta(holder, token),logs,computeUnits,signatureafterrun, andstagedandlookupTablefor a program too large for one transaction. warp(slots)andslot()move and read the slot;getAccount(key)andsetAccount(key, account)read and write any account.
solana-pay.test.ts
Next steps
- Compiling: what
compileand the kit’s compile do. - Cook transactions: build and simulate the same cook against a live chain.
- sauce simulate: simulate a cook from a terminal.
- Pot and cook: what a test account’s Pot and a cook are.
