package yultracer
Install
dune-project
Dependency
Authors
Maintainers
Sources
md5=1b0f7bc0adafc459db8887a148a5ac5e
sha512=891cc5afa1cc729206bbf722760ce695e86dbbbc58744cfb1338ba14197832a7c7ff8f25de401214be834f3e7f0389bcfbdd5dd63210efd6582a68537020c838
doc/README.html
YulTracer
YulTracer is a bounded safety checker (finds assertion violations) and interpreter for Yul. YulTracer performs reachability analysis for programs written in Yul and is also able to analyse smart contracts written in Solidity via the Solidity compiler — we provide examples in this repository showing automatic tool-chains set up to analyse large code bases.
We envision YulTracer to be used in semi-automatic fashion by developers and auditors. The exploration is fully automatic, but assertions need to be added to the code to specify safety. Additionally, we expect exploration parameters need to be specified for complex projects.
Current Release (0.2.x)
DOI: 10.5281/zenodo.18280182 Report: arXiv:2512.22417
The core technical contribution of this release is the addition of a semantically grounded attacker/environment model that exhaustively enumerates all possible traces reachable by an external user interacting with the set of contracts being analysed. We use Game Semantics to model the environment and perform an on-the-fly depth-bounded reachability analysis of the Game Semantics for said set of smart contracts. The tool constructs a finite exploration tree that is an unfolding of the interaction LTS, and reports a counterexample trace when an assertion-violating configuration is reachable within depth explored.
The above makes the tool sound and complete up to the bound provided and with respect to the fidelity of our Yul interpreter and EVM model; i.e. YulTracer is precise with regards to the trace enumeration (no false positives up to). Our Yul interpreter implements a CEK machine based on our formal small-step operational semantics for Yul (see 10.1007/978-3-031-77382-2_19). Our EVM model is a line-by-line port of the Shanghai fork of the Ethereum Execution Client Specifications, extended with Symbolic Execution and abstracted for compatibility with Yul and our Games.
YulTracer currently features limited support for Symbolic Execution via Z3 BitVectors: symbolic reasoning is present for arithmetic and branching, but not for symbolic pointers. As a result, symbolic execution is not recommended at this stage and is not used in any of the smart contract use-case examples provided in this repository.
Previous Release (0.1.x)
DOI: 10.5281/zenodo.12098663 Publication: 10.1007/978-3-031-77382-2_19
For the previous interpreter-only version 0.1 of YulTracer, please see commit 57f9202 and release 0.1.1.
Testing
Provided under YulTracer/test/ are two directories:
smart_contracts: Contains directories documenting our application ofYulTracerto various real-world on-chain smart contracts exploits and benchmarks from literature. Each subdirectory contains its ownREADMEfile explaining how to compile and run the examples.yul_tests: Contains tests from the previous release written in Yul to test the interpreter.
Usage
To run YulTracer on a given program, use the -i option:
dune exec -- yult -i <target-path>.yulThis compiles the project and runs the resulting binary with option -i <path>.yul, which evaluates the target program without running any games. For games (and safety analysis), we use the -g option, which enables games, together with the -abi option, which defines the set methods to explore. For more help on the options available, use the -help or --help option.
In practice, we use previously prepared scripts in project directories (especially for large complex projects) to automatically compile Solidity sources, link the resulting Yul objects that need linking, and provide other necessary options (like ABI and exploration parameters). See the test/smart_contracts directory for examples.
Note: You need Python with the Crypto library (i.e. pycryptodome) for many of these scripts:
pip install pycryptodomeCompilation
To compile YulTracer, if you have all dependencies, run:
dune buildThe dependencies are:
- Opam package manager (+initialisation)
- OCaml 4.10+ compiler
- Dune build system for OCaml projects
- Menhir parser generator
- Z3 package for OCaml bindings
- Zarith package for arbitrary-precision integers
- Yojson and
ppx_deriving_yojson
All the above dependencies can be obtained from Opam package manager after setting up Opam. For more detailed instructions, the following were tested for Linux and macOS. Please check the official website for Windows support.
1. Installing the OCaml Package Manager opam
All dependencies are obtainable through OCaml's official package manager opam. Installation of opam is system specific so you may need to refer to their website linked above. Instructions for some common systems are listed below:
Ubuntu
add-apt-repository ppa:avsm/ppa
apt update
apt install opamFedora, CentOS and RHEL
dnf install opammacOS
# Homebrew
brew install gpatch
brew install opam
# MacPort
port install opam2. Initialising opam
opam needs to be set up after installation, which may also be system dependent. First one needs to initialise it:
opam initAfter initialisation, one has to create the switch to a specific compiler. Any version 4.10 and over works. The command below uses 4.10.1, but one can use the latest version listed.
opam switch create 4.10.1If this does not work, it could be because opam is missing a dependency. This depends on how minimal the installation of the system is. Check the error messages to know what is missing. From our experience, these are the dependencies typically missing for a fresh installation of Ubuntu:
apt install build-essential
apt install gcc
apt install bubblewrapThe above correspond to make, gcc and bwrap, which are often missing in fresh installations.
Finally, initialising opam updates your ~/.profile file, so you may have to source it on Linux:
source ~/.profile3. Installing Dependencies
With opam set up, installing dependencies becomes very easy.
opam install dune
opam install menhir
opam install zarith
opam install z3
opam install ocaml-inifiles
opam install yojson
opam install ppx_deriving_yojsonNote that Z3 takes a long time to install.
4. Compiling YulTracer
With all dependencies installed and ~/.profile sourced, call the build system:
dune buildThis produces an executable binary yult.exe usually located at _build/default/bin/.
Directory Structure:
This project has the following directory structure. This may change in the future.
YulTracer/
├── bin/ # contains main file; produces the executable
├── lib/ # contains implementation of Yul interpreter, EVM and Games
└── test/ # contains our smart contract tests and yul tests.A typical project directory in our tests has the following structure:
<project-name>/
├── abi/ # contains ABI (.json) files for the project
├── scripts/ # contains scripts for pre-/post-compilation processing
├── src/ # contains Solidity (.sol) source files
└── Makefile # stages the compilation, processing scripts, and runs YulTracerA README is provided with each example.
EVM Dialect and Instruction Set
Provided in this repository is a partial implementation of the EVM based on the Shanghai update of the EVM execution specifications. This means the tool supports EVM behaviours up to, without including, the addition of transient storage.
Following EVM opcode categories are implemented:
- Arithmetic:
add,sub,mul,div,sdiv,mod,smod,addmod,mulmod,exp,signextend - Bitwise:
and,or,xor,not,byte,shl,shr,sar - Block:
coinbase1,timestamp,chainid1,number1,gaslimit1 - Comparison:
lt,gt,slt,sgt,eq,iszero - Control-flow:
stop,pc1,gas - Data2:
datasize3,dataoffset3,datacopy3 - Environment:
address,caller,codesize,callvalue,codecopy,calldataload,calldatasize,balance,calldatacopy,returndatasize,returndatacopy,extcodesize,WAIT4,MK_SYMBOL4,EXT_FUND4 - Immutable2:
setimmutable3,loadimmutable3 - Keccak:
keccak - Linker2:
SETLINKER4,linkersymbol3 - Log:
log11,log21,log31,log41 - Memory:
mload,mstore,mstore8,msize,memoryguard3 - Stack:
pop1 - Storage:
sload,sstore - System:
revert1,return,call,staticcall,delegatecall,create,create2,START_ANALYSIS4/LAUNCH_OPPONENT4,IMPERSONATECALL4,REVEAL_UINT4,REVEAL_ADDR4 - Print2:
PRINT3,PRINT_signed3,PRINT_HEX3,PRINT_hex3,PRINT_bin3,PRINT_object3,PRINT_IDS3,PRINT_names3,PRINT_sigma3,PRINT_z33,PRINT_mem3,PRINT_ascii3
1 Instructions with simpler or dummy implementations. 2 Unofficial instruction categories needed for Yul. 3 Official Yul-only instructions that are not part of the EVM specs. 4 Custom YulTracer-only instructions in all-caps.
Data, Immutable and Linker are unofficial categories added to support official instructions specified only in the Yul documentation. These Yul-only instructions are not part of the EVM specification, but are still necessary to analyse real-world Yul objects (in particular, those produced by the Solidity compiler). Since the semantics of these instructions is not formally specified, their behaviour was inferred from the documentation. Additionally, some instructions were not compatible with Yul or not necessary for safety analysis of the contracts we checked. These were implemented as simpler or dummy versions. Lastly, a few custom opcodes were added for analysis — these are all in uppercase. These are:
WAIT(n): Increases the current timestamp bynseconds.MK_SYMBOL(): Creates a fresh symbolicuint.EXT_FUND(n): Increases the balance of the current address bynwei.SETLINKER(s,n): Sets stringsto point atuintnin the linker table used by thelinkersymbolinstruction.START_ANALYSIS/LAUNCH_OPPONENT: Passes control to the opponent.IMPERSONATECALL(c,g,t,v,i,s1,o,s2): Variant ofcallthat spoofs the calling address — acts like acall(g,t,v,i,s1,o,s2)from addresscinstead of the current address.REVEAL_UINT(n): Passes theuintwordnto the opponent as a value — the opponent remembersnand will be able to use it as a value.REVEAL_ADDR(n): Passes theuintwordnto the opponent as an address — the opponent remembersnand will be able to use it as an address.
The following print functions are provided to aid debugging:
PRINT: Print auintword as an unsigned decimal integer.PRINT_signed: Print auintword as a signed decimal integer.PRINT_HEX: Print auintword in uppercase hexadecimal.PRINT_hex: Print auintword in lowercase hexadecimal.PRINT_bin: Print auintword in binary.PRINT_object: Print the current object.PRINT_IDS: Print all discovered object IDs.PRINT_names: Print all discovered object names.PRINT_sigma: Print the symbolic environment (sigma) directly.PRINT_z3: Print the symbolic environment (sigma) via Z3.PRINT_mem: Print the current memory.PRINT_ascii: Print the ASCII string interpretation of auintword.
note: all uint words are uint256 by default.