Vegas is a domain-specific language for specifying strategic games. It emphasizes distribution transparency, allowing developers to write game logic as a sequential specification while the compiler handles the complexities of distributed, adversarial execution.
Unlike traditional imperative languages that execute line-by-line, or round-based systems that execute in lock-step, Vegas compiles your program into a Directed Acyclic Graph (DAG) of actions.
The compiler analyzes data flow to determine dependencies. An action
-
$B$ reads a variable written by$A$ . -
$B$ is arevealof a commitment made in$A$ . -
$B$ uses a variable in awhereclause that was written by$A$ .
Actions that do not depend on each other are considered concurrent.
- In Analysis (Gambit): Concurrent actions are modeled as simultaneous moves (information sets), meaning players move without knowing the other's choice.
- In Execution (Solidity): Concurrent actions can be submitted to the blockchain in any order.
The compiler identifies "Risk Partners"—actions that are both public and concurrent. To prevent one player from seeing the other's move in the mempool and reacting (front-running), the compiler automatically rewrites these actions into a Commit-Reveal pattern.
Original Code:
yield Alice(x: int);
yield Bob(y: int);
// If x and y are independent, Alice could wait to see y before sending x.
Compiled DAG Behavior:
- Alice commits
hash(x). - Bob commits
hash(y). - Alice reveals
x. - Bob reveals
y.
Vegas supports hygienic macros to encapsulate reusable logic. Macros are inlined before the intermediate representation (IR) is generated.
// Define a macro
macro isWinner(guess: int, target: int): bool = guess == target;
// Use the macro
yield Player(g: int);
withdraw isWinner(Player.g, 5) ? ...
Players enter the game. This is implicitly the root of the DAG.
join Alice() $ 100; // Join with 100 wei deposit
A player provides information.
yield Alice(x: int); // Public move
yield Bob(y: hidden int); // Hidden move (generates commitment)
A player opens a previously hidden value.
reveal Bob(y: int);
Note: Constraints (where clauses) on hidden values are checked during the reveal phase, not the yield phase.
Specifies the terminal payouts. This runs once all necessary actions are complete or players have timed out.
int: Unbounded integer (mapped toint256).bool: Boolean value.address: Ethereum address.type T = {1, 2, 3}: Enumerated subset.type T = {1..10}: Range.
The abstract DAG is compiled into a Solidity contract that enforces the game rules.
The generated contract does not use a global "step" counter. Instead, every action is gated by a depends modifier:
modifier depends(Role r, uint id) { ... }An action can be performed only if all its ancestors in the DAG are marked as done.
Vegas implements a non-blocking timeout mechanism to prevent griefing (where one player stops moving to freeze the funds).
- Global Timeout: If a player fails to act within
TIMEOUTseconds of their dependencies being met, any other player can trigger a check. - Bail State: The non-responsive player is marked as
bailed. - Fall-Through: The
dependsmodifier is relaxed. If an action depends on a player who hasbailed, that dependency is ignored. The game proceeds as if the value werenull.
Because any player might bail, all variable reads are potentially nullable (Opt[T]). The withdraw clause must explicitly handle these cases:
withdraw (Alice.x != null)
? { Alice -> 10 }
: { Alice -> -10 } // Penalty for bailing
- Parsing & Type Checking: Validates types and macro expansions.
- Macro Inlining: Desugars macros into raw expressions.
- DAG Construction:
- Builds the dependency graph.
- Identifies "Risk Partners" (concurrent public moves).
- Rewrites DAG: Inserts Commit and Reveal nodes for risk partners.
- Backend Generation:
- Solidity: Generates functions with
dependsmodifiers and bail logic. - Gambit: Generates a game tree where concurrent DAG nodes share information sets.
- Solidity: Generates functions with