Language Basics
EVMcrispr uses a custom DSL (domain-specific language) designed for writing EVM transaction scripts. This guide covers the core syntax.
Comments
Section titled “Comments”Lines starting with # are comments:
# This is a commentset $x 42 # Inline comments work tooCommands
Section titled “Commands”Commands are the primary building blocks. They produce transactions or control program flow. Each command starts with its name followed by arguments:
exec 0x44fA8E6f47987339850636F88629646662444217 "transfer(address,uint256)" @me 100e18set $greeting "hello"print "The value is" $greetingArguments are separated by spaces, not commas — this applies everywhere in EVML: command arguments, helper arguments, and array elements.
Commands from non-default modules use a prefix:
load aragonosaragonos:connect my-dao.aragonid.eth ( aragonos:grant @me voting CREATE_VOTES_ROLE)Helpers
Section titled “Helpers”Helpers are expressions that produce values. They are prefixed with @:
set $sender @me # No argumentsset $dai @token(DAI) # Single argumentset $bal @get($dai "balanceOf(address)(uint256)" @me) # Multiple arguments, space-separatedHelper arguments are space-separated: @token.balance(DAI @me) is correct,
@token.balance(DAI, @me) is a parse error.
Helpers can be nested and used as arguments to commands:
exec @token(DAI) "transfer(address,uint256)" @me @token.amount(DAI 100)Variables
Section titled “Variables”Use set to assign values and $name to reference them:
set $recipient 0x4F2083f5fBede34C2714aFfb3105539775f7FE64set $amount @token.amount(DAI 1000)exec @token(DAI) "transfer(address,uint256)" $recipient $amountThe DSL supports these value types:
| Type | Example | Description |
|---|---|---|
address |
0xAbCd...1234 |
20-byte Ethereum address |
number |
42, 100e18, 1.5e6 |
Integer or scientific notation |
string |
"hello", 'it\'s fine' |
Quoted string (single or double); supports \', \", \\, \n, \r, \t, and \u{HHHH} escapes (any other \X is left literal); may span multiple lines |
bool |
true, false |
Boolean |
bytes |
0xdeadbeef |
Hex-encoded bytes |
bytes32 |
0x00...001 |
32-byte value |
array |
[1 2 3] |
Ordered collection — elements are space-separated, never commas |
Numbers support scientific notation with e: 100e18 means 100 * 10^18.
This is useful for token amounts with 18 decimals.
Blocks
Section titled “Blocks”Some commands accept a block of sub-commands in parentheses:
batch ( exec 0x44fA8E6f47987339850636F88629646662444217 "foo()" exec 0x0102030405060708090a0b0c0d0e0f1011121314 "bar()")
loop $i of @arr(0 5) ( print $i)
set $x 10if @bool($x > 0) ( print "positive") ( print "non-positive")Note there is no else keyword: if takes a condition, a then-block, and
an optional second block that runs when the condition is false.
ABI Signatures
Section titled “ABI Signatures”Contract function signatures follow Solidity syntax. Inside a signature string the parameter types are comma-separated, exactly as in Solidity:
# Write functions (for exec): inputs onlyexec @token(DAI) "transfer(address,uint256)" @me 100e18exec @token(DAI) "approve(address,uint256)" @me 100e18
# Read functions (for @get): append (returnTypes) directly after the inputsset $bal @get(@token(DAI) "balanceOf(address)(uint256)" @me)set $name @get(@token(DAI) "name()(string)")set $reserves @get(0x44fA8E6f47987339850636F88629646662444217 "getReserves()(uint112,uint112,uint32)")The read format is "name(inputTypes)(returnTypes)" — two parenthesized
lists back to back with no colon or other separator between them
("name()(string)", not "name():(string)").
Modules
Section titled “Modules”The std module is loaded by default. Load additional modules with:
load aragonosload simload ensload httpAfter loading, use their commands and helpers with the module prefix:
load simsim:fork ( sim:set-balance @me 100e18 exec 0x44fA8E6f47987339850636F88629646662444217 "foo()")Options
Section titled “Options”Some commands accept options with --name value:
exec 0x44fA8E6f47987339850636F88629646662444217 "foo()" --value 1e18exec 0x44fA8E6f47987339850636F88629646662444217 "foo()" --from 0x4F2083f5fBede34C2714aFfb3105539775f7FE64
load aragonos --as daoA command and all of its options must be written on a single line — EVML
has no \ line continuation.
Event Captures
Section titled “Event Captures”The exec command can capture events emitted by the transaction:
exec 0x44fA8E6f47987339850636F88629646662444217 "createPool(address,uint24)" @token(DAI) 3000 -> Transfer [_ $pool]Error Captures
Section titled “Error Captures”Use -!> to catch and decode transaction reverts. After the error name you
can add a destructure ([...]), a boolean variable ($var), or nothing:
set $c 0x44fA8E6f47987339850636F88629646662444217
# Assert a specific error (no data captured)exec $c "deny()" -!> Unauthorized()
# Destructure error arguments into variablesexec $c "withdraw(uint256)" 200 -!> InsufficientBalance(uint256,uint256) [$balance $required]
# Catch a require/revert reasonexec $c "transfer(address,uint256)" @me 100e18 -!> Error(string) [$reason]
# Boolean variable — $e is "true" if error matchedexec $c "deny()" -!> Unauthorized() $e
# Generic catch-all (no error name)exec $c "doSomething()" -!> [$reason]exec $c "doSomething()" -!> $eUse -?!> if the error is optional (transaction may or may not revert).
With a boolean variable, $e is "true" on match, "false" on success or
mismatched error:
set $c 0x44fA8E6f47987339850636F88629646662444217
exec $c "maybeRevert()" -?!> Error(string) [$reason]exec $c "maybeRevert()" -?!> Unauthorized() $eSupported error types:
- Custom named errors:
revert CustomError(arg1, arg2) - Error(string):
require(cond, "msg")/revert("msg") - Panic(uint256):
assert(cond)failures - Empty reverts: pre-0.4.22
revert()with no data
Arithmetic & Boolean Expressions
Section titled “Arithmetic & Boolean Expressions”Use @num() for arithmetic and @bool() for boolean logic:
set $a 1set $b 2set $total @num($a + $b * 2)
set $x 5set $y 50set $isValid @bool($x > 0 and $y < 100)Control Flow
Section titled “Control Flow”# Conditionalset $balance @token.balance(DAI @me)if @bool($balance > 0) ( print "Has balance")
# Loop over arrayloop $item of @arr(0 10) ( print $item)
# Loop until a condition is trueset $i 0loop until @bool($i >= 5) ( print $i set $i @num($i + 1))User-Defined Commands and Helpers
Section titled “User-Defined Commands and Helpers”Use def to define reusable commands and helpers:
# Define a helper — the body is a single expression, not a blockdef @double "$x: number -> number" @num($x * 2)
# Define a command — params live in the signature string, the body is a blockdef transfer "$token: address $to: address $amount: number" ( exec $token "transfer(address,uint256)" $to $amount)
# Use themset $result @double(21)transfer @token(DAI) 0x4F2083f5fBede34C2714aFfb3105539775f7FE64 100e18