Operators
operators.sauce.ts
- On a
Uint256,/rounds down.a / 0anda % 0revert. &&and||evaluate the right operand only when needed, and producetrueorfalse, never one of the operands. That holds for a contract name too:UniswapV3.Factory || 0istruewhere the chain has the factory, not its address.- The two arms of
? :must have the same type.flag ? amount : trueis refused with “the two arms of? :must have the same type — thethenarm is a number, theelsearm is a bool”. - On an
Int256, the operators are signed. See Signed arithmetic. - An array literal indexed at a literal position (or at a
constbound 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 forzero = 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 asMath.mulDivis 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).
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:
negative-power.sauce.ts
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
OnInt256 values, / truncates toward zero, % takes the sign of the dividend, >> copies the sign bit, and the comparisons order negative values below positive ones:
signed-ops.sauce.ts
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 onUint256, and one more division overflows: the minimum, −2²⁵⁵, divided by-1.Int256.fromBits(word) / divisorwithwordequal to 2²⁵⁵ anddivisor = -1reverts on the Base fork, and the engine refuses it withOverflowon Solana (slot 460264648). Divided by2, the same value gives −2²⁵⁴.- Arithmetic takes two operands of the same kind:
amount + delta, withamountaUint256anddeltaanInt256, is refused until one operand is converted withInt256(...)orUint256(...). - A comparison takes both kinds and compares the numeric values: with
b = -1,b < aistruefora = 5and fora = 2²⁵⁶ − 1on the same fork.
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.
assignment.sauce.ts
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.
tiers.sauce.ts
switch: write an if/else if chain, as tiers.sauce.ts does.
Loops
break and continue work in each, without labels.
loops.sauce.ts
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:
bytes-sum.sauce.ts
A
do/while loop, written with while (true):
do-while.sauce.ts
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, thefor 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.
loop-gas.sauce.ts
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:
destructuring.sauce.ts
- 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] = xsis refused with “a destructuring binding needs an initializer that produces a tuple — this one does not, so bind it to one name instead”. Writexs[0]andxs[1]. - Object destructuring,
const { fee } = order, is not supported: writeconst fee = order.fee;.
Methods and positions
A method is called on a value, and the value may be a literal:literal-methods.sauce.ts
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 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.folded.sauce.ts
(amount * 9970) / 10000. Folding follows the type: 0 - 1 is refused as a Uint256 and is -1 as an Int256 (Types). 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:presence-test.sauce.ts
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 covers which uses count as tests. A condition built from other known values is removed in the same way; Functions shows it.
Not supported
Next steps
- Functions: helpers, return paths and call cost.
- Errors and reverts:
require,throw,try/catchand.catch. - Types: what each operator accepts.
- Names: token symbols, namespaces, and testing whether the chain has a name.
